Java Top.

通过。开始使用Spring 5和Spring Boot 2学习春天课程:

>>查看课程

1.概述

在本文中,我们将看到如何使用HashMap在Java中,我们将看看它内部是如何工作的。

一个非常相似的课程HashMap哈希表。请参考我们的其他几篇文章,以了解更多关于雷竞技app官网网站java.util.Hashtable班级本身和之间的差异HashMap哈希表

2.基本用法

让我们先看看这意味着什么HashMap是一张地图。映射是一个键值映射,这意味着每个键都映射到完全一个值,并且我们可以使用键从地图中检索相应的值。

人们可能会询问为什么不简单地将值添加到列表中。为什么我们需要一个HashMap吗?简单的原因是性能。如果我们想在列表中找到一个特定的元素,时间复杂度是上)如果列表是排序的,那将是O (log n)例如,使用二进制搜索。

a的优点HashMap是插入和检索值的时间复杂性是O (1)平均。稍后我们将讨论如何实现这一点。让我们先看看如何使用HashMap

2.1。设置

让我们创建一个简单的类,我们将在整篇文章中使用:

公共类产品{私有字符串名称;私有字符串描述;私有列表标签;//标准getter / setter / setters / builductors public product addtagsofotherproduct(产品产品){this.tags.addall(product.gettags());返回这个;}}

2.2。放

我们现在可以创建aHashMap用type键字符串类型的元素产品:

Map productsByName = new HashMap<>();

并添加产品到我们的HashMap:

Product eBike =新产品(“E-Bike”,“带电池的自行车”);Product roadBike =新产品(“公路自行车”,“竞赛自行车”);productsByName.put (eBike.getName (), eBike);productsByName.put (roadBike.getName (), roadBike);

2.3。得到

我们可以通过其密钥从地图中检索值:

Product NextPurchase = ProductsByName.Get(“电子自行车”);assertequals(“带电池的自行车”,nextpurchase.getdescription());

如果我们尝试找到地图中不存在的键的值,我们会得到一个值:

Product nextPurchase = productsByName.get("Car");assertNull (nextPurchase);

如果我们插入第二个具有相同键值的值,我们将只得到该键插入的最后一个值:

Product newEBike =新产品(“E-Bike”,“电池更好的自行车”);productsByName.put (newEBike.getName (), newEBike);assertEquals("一辆电池更好的自行车",productsByName.get("E-Bike").getDescription());

2.4。零作为关键

HashMap也让我们拥有作为一个关键:

Product defaultProduct = new Product("巧克力","至少买巧克力");productsByName。放(null, defaultProduct); Product nextPurchase = productsByName.get(null); assertEquals("At least buy chocolate", nextPurchase.getDescription());

2.5。具有相同键的值

此外,可以用不同的键插入同一个对象两次:

productsByName.put (defaultProduct.getName (), defaultProduct);assertSame (productsByName.get (null), productsByName.get(“巧克力”));

2.6。删除一个值

函数中可以删除键值映射HashMap:

productsByName.remove(“电动自行车”);assertNull (productsByName.get(“电动自行车”));

2.7。检查地图中是否存在密钥或值

要检查地图中是否存在密钥,我们可以使用containsKey ()方法:

productsByName.containsKey(“电动自行车”);

或者,要检查一个值是否存在于映射中,可以使用containsValue ()方法:

ProductsByName.containsValue(ebike);

两个方法调用都会返回真正的在我们的例子中。尽管它们看起来非常相似,但这两个方法调用在性能上有一个重要的区别。检查一个密钥是否存在的复杂性是O (1),而检查元素的复杂性是O (n),由于必须循环地图中的所有元素。

2.8。遍历一个HashMap

有三种基本方法可以遍历a中的所有键值对HashMap

我们可以遍历所有键的集合:

for(String键:ProductsByName.keyset()){Product Product = ProductsByName.get(key);}

或者我们可以遍历所有条目的集合:

(地图。 Entry: productsByName.entrySet()) {Product Product = Entry . getvalue ();String key = entry.getKey();//处理键和值}

这一点,我们可以迭代所有价值观:

List products = new ArrayList<>(productsByName.values());

3.关键

我们可以使用任何类作为键HashMap。然而,为了使映射正常工作,我们需要提供一个实现equals ()hashcode()。假设我们想要一个以产品为键,以价格为值的地图:

HashMap priceByProduct = new HashMap<>();priceByProduct。放(eBike, 900);

让我们实现equals ()hashCode ()方法:

@Override公共布尔等于(对象O){if(== o){return true;} if (o == null || getClass() != o.getClass()) {return false;产品产品=(产品)O;返回Objects.equals(name,product.name)&& objects.equals(描述,product.description);@override public int hashcode(){return objects.hash(姓名,说明);}

注意hashCode ()equals ()需要仅覆盖我们想要用作映射键的类,而不是仅在地图中使用的类。我们将在本文的第5节中了解为什么这是必要的。

4.Java 8中的其他方法

Java 8添加了一些函数样式的方法HashMap。在本节中,我们将研究一些方法。

对于每种方法,我们将看两个示例。第一个示例展示了如何使用新方法,第二个示例展示了如何在早期版本的Java中实现相同的方法。

由于这些方法非常简单,我们就不讨论更详细的例子了。

4.1。foreach()

forEach方法是遍历map中所有元素的函数样式方法:

ProductsByName.Foreach((key,product) - > {system.out.println(“key:”+ key +“产品:”+ product.getdescription()); //用键和值做一些事情});

在Java 8之前:

(地图。 Entry: productsByName.entrySet()) {Product Product = Entry . getvalue ();String key = entry.getKey();//处理键和值}

我们的文章Java 8指南forEach涵盖了forEach更详细地循环。

4.2。getordefault()

使用getordefault()方法时,可以从map中获取一个值,或者在给定键没有映射的情况下返回一个默认元素:

Product chocolate = new Product("chocolate", "something sweet");Product defaultProduct = productsByName。得到OrDefault("horse carriage", chocolate); Product bike = productsByName.getOrDefault("E-Bike", chocolate);

在Java 8之前:

Product bike2 = productsByName.containsKey("E-Bike") ?productsByName.get(“电动自行车”):巧克力;Product defaultProduct2 = productsByName。containsKey(“马马车”)?productsByName。得到("horse carriage") : chocolate;

4.3。putifabsent()

使用这个方法,我们可以添加一个新的映射,但前提是给定键还没有映射:

ProductsByname.putifabsent(“电子自行车”,巧克力);

在Java 8之前:

如果(productsByName.containsKey(“电动自行车”)){productsByName。把(“电动自行车”,巧克力);}

我们的文章用Java 8合并两个地图仔细看看这个方法。

4.4。合并()

合并(),如果存在映射,可以修改给定键的值,否则可以添加一个新值:

产品eBike2 =新产品(“电子自行车”,“带电池的自行车”);ebike2.gettags()。添加(“运动”);ProductsByName.Merge(“电子自行车”,Ebike2,产品:: AddTagsofotherProduct);

在Java 8之前:

if(productsByname.containskey(“电子自行车”)){ProductsByName.get(“电子自行车”)。AddtagsofotherProduct(Ebike2);} else {productsbyname.put(“e-bike”,ebike2);}

4.5。计算()

计算()方法中,我们可以计算给定键的值:

productsByName.compute("E-Bike", (k,v) -> {if(v != null) {return v. addtagsofotherproduct (eBike2);} else{返回eBike2;}});

在Java 8之前:

if(productsByname.containskey(“电子自行车”)){ProductsByName.get(“电子自行车”)。AddtagsofotherProduct(Ebike2);} else {productsbyname.put(“e-bike”,ebike2);}

值得注意的是方法合并()计算()是很相似的。计算()方法接受两个论点:关键和一个BiFunction重新映射。和合并()接受三个参数关键,一个默认值如果键还不存在,则添加到映射中BiFunction重新映射。

5.HashMap内部

在本节中,我们将看看如何HashMap在内部工作,使用的好处是什么HashMap而不是一个简单的列表。

正如我们所看到的,我们可以从a中检索一个元素HashMap它的关键。一种方法是使用列表,遍历所有元素,并在找到键匹配的元素时返回。这种方法的时间和空间复杂性将是上)

HashMap,我们可以达到平均时间复杂度为O (1)得到操作和空间的复杂性上)让我们看看它是如何工作的。

5.1。哈希码和等号

而不是迭代所有元素,HashMap尝试根据值的键计算值的位置。

简单的方法是让一个列表包含尽可能多的键。例如,我们假设我们的键是一个小写字符。然后,有一个长度为26的列表就足够了,如果我们想访问键为' c'的元素,我们就知道它是位置3的那个元素,我们可以直接检索它。

但是,如果我们有一个更大的钥匙空间,这种方法不会非常有效。例如,假设我们的密钥是整数。在这种情况下,列表的大小必须是2,147,483,647。在大多数情况下,我们也会具有较少的元素,因此分配内存的大部分将保持未使用。

HashMap将元素存储在所谓的桶中,桶的数量称为桶的数量容量

当我们在map中放入一个值时,键的值hashCode ()方法用于确定将存储该值的桶。

检索值,HashMap以相同的方式计算桶-使用hashCode ()。然后它迭代在该桶中找到的对象并使用键equals ()方法来查找精确匹配。

5.2。钥匙的不可变形

在大多数情况下,我们应该使用不可变键。或者至少,我们必须意识到使用可变键的后果。

让我们看看在使用键在map中存储值后,键发生了什么变化。

对于这个例子,我们将创建MutableKey:

public class MutableKey {private String name;//标准构造函数,getter和setter @Override public boolean equals(Object o) {if (this == o) {return true;} if (o == null || getClass() != o.getClass()) {return false;} MutableKey = (MutableKey) o;返回对象。=(名称、that.name);} @Override public int hashCode() {return Objects.hash(name);}}

在这里进行测试:

变形键=新的变形键(“初始”);地图项目= new hashmap <>();项目.put(关键,“成功”);key.setName(“已更改”);assertnull(项目.get(key));

正如我们所看到的,一旦键发生了变化,我们就不能得到相应的值,返回。这是因为HashMap找错了桶

如果我们没有很好地理解其中的原理,上面的测试用例可能会令人惊讶HashMap内部工作。

5.3。碰撞

为了正确地工作,相等的键必须具有相同的散列,但是不同的键可以具有相同的散列。如果两个不同的键具有相同的哈希,则属于它们的两个值将存储在同一桶中。在桶内,值存储在列表中,并通过循环以循环而检索。这是成本上)

正如Java 8的那样(见中180如果一个桶中有8个或8个以上的值,它的数据结构就会从列表树改变为平衡树,如果在某个点上,桶中只剩下6个值,它就会改变回列表树。这将提高性能O (log n)

5.4。容量和负载系数

为了避免多个桶有多个值,如果桶的负载系数是非空的,则容量翻倍。负载系数的默认值为75%,初始容量的默认值为16。两者都可以在构造函数中设置。

5.5。的总结得到运营

让我们总结一下得到操作工作。

当我们向地图添加一个元素时,HashMap计算了桶。如果bucket已经包含了一个值,那么这个值就会被添加到属于该bucket的列表(或树)中。如果负荷系数大于地图的最大负荷系数,则容量翻倍。

当我们想从map中获取一个值时,HashMap计算桶,并从列表(或树)中使用相同的密钥获取值。

六,结论

在本文中,我们看到了如何使用HashMap以及它内部是如何运作的。随着ArrayList,HashMap是Java中最常用的数据结构之一,因此掌握如何使用它以及它在底层如何工作的知识非常方便。我们的文章底层的Java HashMap涵盖内部HashMap更多细节。

像往常一样,完整的源代码是可用的在github上

Java底部

通过。开始使用Spring 5和Spring Boot 2学习春天课程:

>>查看课程
4评论
最老的
最新
内联反馈
查看所有评论
评论在本文上关闭!