Java的Map.entry详解一个问题

Map接口不是Collection接口的继承。Map接口用于维护键/值对(key/value pairs)。该接口描述了从不重复的键到值的映射。

  (1) 添加、删除操作:
  Object put(Object key, Object value): 将互相关联的一个关键字与一个值放入该映像。如果该关键字已经存在,那么与此关键字相关的新值将取代旧值。方法返回关键字的旧值,如果关键字原先并不存在,则返回null
  “键和值都可以为null。但是,您不能把Map作为一个键或值添加给自身。”
  (2) 查询操作:
  Object get(Object key): 获得与关键字key相关的值,并且返回与关键字key相关的对象,如果没有在该映像中找到该关键字,则返回null
  int size(): 返回当前映像中映射的数量
  (3) 视图操作 :处理映像中键/值对组
  Set keySet(): 返回映像中所有关键字的视图集
  “因为映射中键的集合必须是唯一的,您用Set支持。你还可以从视图中删除元素,同时,关键字和它相关的值将从源映像中被删除,但是你不能添加任何元素。”
  “因为映射中值的集合不是唯一的,您用Collection支持。你还可以从视图中删除元素,同时,值和它的关键字将从源映像中被删除,但是你不能添加任何元素。”
  “因为映射是唯一的,您用Set支持。你还可以从视图中删除元素,同时,这些元素将从源映像中被删除,但是你不能添加任何元素。”

  通过这个集合的迭代器,您可以获得每一个条目(唯一获取方式)的键或值并对值进行更改。当条目通过迭代器返回后,除非是迭代器自身的remove()方法或者迭代器返回的条目的setValue()方法,其余对源Map外部的修改都会导致此条目集变得无效,同时产生条目行为未定义。

由此可以得到两种遍历Map的方法

这一章,我们对HashMap进行学习。
我们先对HashMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashMap。内容包括:

HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。

HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”。容量 是哈希表中桶的数量,初始容量 只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。
通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。

HashMap共有4个构造函数,如下:

// 指定“容量大小”的构造函数 // 指定“容量大小”和“加载因子”的构造函数 // 包含“子Map”的构造函数

  size是HashMap的大小,它是HashMap保存的键值对的数量。

为了更了解HashMap的原理,下面对HashMap源码代码作出分析。
在阅读源码时,建议参考后面的说明来建立对HashMap的整体认识,这样更容易理解HashMap。

9 // 默认的初始容量是16,必须是2的幂。 12 // 最大容量(必须是2的幂且小于2的30次方,传入容量过大将被这个值替换) 18 // 存储数据的Entry数组,长度是2的幂。 19 // HashMap是采用拉链法实现的,每一个Entry本质上是一个单向链表 28 // 加载因子实际大小 34 // 指定“容量大小”和“加载因子”的构造函数 51 // 设置“加载因子” 61 // 指定“容量大小”的构造函数 66 在“该hash值对应的链表”上查找“键值等于key”的元素 141 // 在“该hash值对应的链表”上查找“键值等于key”的元素 158 // 若“key不为null”,则计算该key的哈希值,然后将其添加到该哈希值对应的链表中。 163 // 若“该key”对应的键值对已经存在,则用新的value取代旧的value。然后退出! 188 // 这里的完全不会被执行到! 201 // 若该HashMap表中存在“键值等于key”的元素,则替换该元素的value值 216 // 该方法被内部的构造HashMap的方法所调用。 通过迭代器,将“m”中的元素逐个添加到HashMap中。 296 // 获取哈希值。若key为null,则哈希值为0;否则调用hash()进行计算 302 // 删除链表中“键为key”的元素 303 // 本质是“删除单向链表中的节点” 337 // 删除链表中的“键值对e” 338 // 本质是“删除单向链表中的节点” // 在这种情况下,我们不知道何时“HashMap的实际容量”会超过“阈值”; 503 // 例如,我们调用HashMap“带有Map”的构造函数,它绘将Map的全部元素添加到HashMap中; 504 // 但在添加之前,我们已经计算好“HashMap的容量和阈值”。也就是,可以确定“即使将Map中 511 // 设置“e”为“新Entry的下一个节点” 533 // 这里利用了index的初始值为0,从0开始依次向后遍历,直到找到不为null的元素就退出循环。 553 // 若该Entry的下一个节点不为空,就将next指向下一个节点; 554 // 否则,将next指向下一个链表(也是下一个Entry)的不为null的节点。 615 // 返回“key的集合”,实际上返回一个“KeySet对象” 649 // Values中的元素能够重复。因为不同的key可以指向相同的value。 701 // 将HashMap的“总的容量,实际容量,所有的Entry”都写入到输出流中 731 // 将HashMap的“总的容量,实际容量,所有的Entry”依次读出

在详细介绍HashMap的代码之前,我们需要了解:HashMap就是一个散列表,它是通过“拉链法”解决哈希冲突的
还需要再补充说明的一点是影响HashMap性能的有两个参数:初始容量(initialCapacity) 和加载因子(loadFactor)。容量 是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。


第3.1部分 HashMap的“拉链法”相关内容

4 // 指向下一个节点 60 // 这里不做任何处理 65 // 这里不做任何处理

clear() 的作用是清空HashMap。它是通过将所有的元素设为null来实现的。

getEntry() 的作用就是返回“键为key”的键值对,它的实现源码中已经进行了说明。

它们3个的原理类似,这里以entrySet()为例来说明。
entrySet()的作用是返回“HashMap中所有Entry的集合”,它是一个集合。实现代码如下:

HashMap是通过拉链法实现的散列表。表现在HashMap包括许多的Entry,而每一个Entry本质上又是一个单向链表。那么HashMap遍历key-value键值对的时候,是如何逐个去遍历的呢?

get() 的作用是获取key对应的value,它的实现代码如下:

若要添加到HashMap中的键值对对应的key已经存在HashMap中,则找到该键值对;然后新的value取代旧的value,并退出!

若要添加到HashMap中的键值对对应的key不在HashMap中,则将其添加到该哈希值对应的链表中,并调用addEntry()。下面看看addEntry()的代码:

那它们的区别到底是什么呢?

阅读代码,我们可以发现,它们的使用情景不同。(01) addEntry()一般用在 新增Entry可能导致“HashMap的实际容量”超过“阈值”的情况下。     

putAll() 的作用是将"m"的全部元素都添加到HashMap中,它的代码如下:

串行写入函数是writeObject(),它的作用是将HashMap的“总的容量,实际容量,所有的Entry”都写入到输出流中。而串行读取函数是readObject(),它的作用是将HashMap的“总的容量,实际容量,所有的Entry”依次读出

第二步:通过Iterator迭代器遍历“第一步”得到的集合。

第二步:通过Iterator迭代器遍历“第一步”得到的集合。

第一步:根据value()获取HashMap的“值”的集合。
第二步:通过Iterator迭代器遍历“第一步”得到的集合。

下面通过一个实例学习如何使用HashMap


列举几个关于Java Map的常见问题并给出答案。

如果map中的value不重复,可以通过反转key-value对为value-key对来用上面的3中的TreeMap方法对其排序。该方法不推荐。

5. 初始化一个不可变Map

  1. Hashtable的方法是同步的,HashMap的方法不是同步的。这是两者最主要的区别。

如果对同步性或与遗留代码的兼容性没有任何要求,建议使用HashMap。

如果希望该map为不可变的,则:

我要回帖

更多关于 Map.entry详解 的文章

 

随机推荐