HashMap源码阅读笔记

来源:互联网 发布:mac如何切换任务 编辑:程序博客网 时间:2024/05/18 20:09

39-45行 注释:主要介绍了HashMap和HashTable的区别,即HashMap允许null作为键、值,而且HashMap不是线程安全的。并且HashMap中的元素不是有序的,特别的,也不保证随着时间推移,这个map中存储的顺序不发生改变。

解析:hashmap是线程不安全的,键、值都允许null的存在,map中的元素不保证有序,随着时间推移可能还会发生变化。


47-54行 注释:HashMap的get、put方法在能够正确将元素散列在桶中的情况下拥有常数级别的表现(即碰撞次数不过多)。而迭代器在Map中的表现情况依赖于初始化的容量与桶的数量+键值对的值的比例,即装载因子。因此,如果希望迭代器的表现良好,则不能够设置过大的初始容量。 

我们知道HashMap的方法中,默认的装载因子大小是0.75,初始容量大小是16(旁边还有一句注释,必须是2的次方)

解析:默认装载因子是0.75,默认初始容量是16


56-65行 注释:就像上面提到的,影响HashMap性能的主要有两个因素,一个是初始容量的大小,一个是装载因子的大小。当HashMap中键值对的数量超过当前容量大小*装载因子的时候,整个表会重新进行hash一次,并进行扩容(接近于原容量的两倍)。

解析:每次resize的大小是将原容量扩充接近1倍。

67-76行 注释: 介绍了采用0.75作为默认装载因子的意义。太高的装载因子会减少多余的空间,但是对查找性能很不友好。因此,最好是在初始化HashMap的时候根据它的键值对量对初始容量和装载因子进行相应的调整,确保让整个hashmap重新hash的次数最小化(即提高性能)。如果初始容量*装载因子>键值对的数量,那么重新散列的情况就不会发生。

解析:定


78-85行 注释:如果Hashmap中的大多数键值对都已经有序了,那么给与它一个充分大的容量会比小的好(肯定啊。。。小了又要rehash)。如果说map中存放了大量的重复的键,会影响map的效率。为了改善影响,那么会在键之间采用比较来改善这种情况。

解析:hashmap的散列方法采用的是拉链法,如果有大量重复主键的话,必然会导致碰撞的大量发生。


87-94行 注释:介绍了hashmap的一个特点:线程不安全。如果有多个线程对Map进行增加/删除元素的话,需要在外部对map对象加锁。这个通常是由一些对象封装了相应的映射关系来完成的。

解析:hashmap是线程不安全的,如果有并发操作需要在外部加上互斥锁。


96-100行 注释:接着上面的继续讲,如果不存在这种封装了的对象,那么hashmap需要在初始化之初使用一个包装类:Collections.synchronizedMap

操作像这样:Map m = Collections.synchronizedMap(new HashMap(...));

解析:一种使线程不安全的hashmap变得线程安全的操作。


102-109行 注释:这里介绍了一下集合类迭代器的一个共同特性:快速失败。即当迭代器创建之后,所属的集合有任何结构上的变化(比如增加/删除元素,那种改变已有键值对的不算),都会抛出一个名为:ConcurrentModificationException的异常。除非是使用迭代器本身安全的remove()方法。

解析:迭代器本身有两个元素,一个ModCount,一个ExceptModCount,每次移动迭代器指针都会检查这两个的值是否相等,如果不等就会抛出快速失败的异常,而迭代器本身的人remove()方法会同步修改这两个值,则不会抛出异常。这个是所有集合类迭代器的共同特征。

110-117行 注释:迭代器的快速失败特征是不可靠的,应该说任何依赖于不同步情况下的并发修改都是难以保证正确性的。因此,迭代器的快速失败特征应该只被用于检查bug出在哪里,而不是保证程序的正确性。


145-154行 注释:hashmap虽然在很多实现的性能表现地像哈希表元素存储在桶里,但在map中桶的数量过多时,桶节点会转化成二叉树节点(红黑树)我们知道红黑树是平衡树的一种,它在数据很大时的查找性能很好。当然我们知道,方法大多数还是为一般情况下准备的,因此这种检验桶是否变成了树节点会一定程度上影响性能。

解析:HashMap在桶(一个单链表,因为采用的是拉链法)的键簇(即链表中的元素数目)较大时(这个值是8),桶节点会变成树节点(红黑树),用于提高它的查找性能。但同时这种检验也难免会一定程度上影响平时的性能。(最好的查找性能是一个桶里只有一个元素,因为桶里存放的是发生碰撞的)


156-172行 注释:当桶里的节点全部变成树节点的时候,它们会主要通过比较hashcode变得有序。如果两个键之间都是实现了Comparable接口的(比如常用的原始数据类型+包装类+String都是实现了的),那么它们会通过compareTo()方法进行比较。虽然说这样对于本来hashcode就唯一/已经有序的键值对会比较浪费时间,但是对于hashcode()方法错误的分配以及很多键共享同一个hashcode的情况,这么做是值得的。

解析:对于树节点中的数据,hashmap主要采用比较Hashcode的方法,如果键的类型实现了Comparable接口的方法,那么就会采用compareTo()使它有序。我们知道,hashcode相等,不一定equals(),equals()不一定==。


174-186行 注释:因为树节点占用的空间比较大,近似普通节点的2倍,所以只有当空间足够的情况下才会进行转变(>=8),在小于6的时候又会转变回去。如果hashcode方法分配良好,在理想情况下,桶中转变为树节点的概率应当服从泊松分布。

解析:转化为树节点的阈值是8,退化的阈值是6.树节点占用空间较大。


199-202行 注释:根节点有时候可能不在树中(比如迭代器的remove方法),不过可以通过TreeNode.root()方法恢复。


204-209行 注释:所有适用的内部方法接收哈希码作为一个参数(通常来自公有方法),来避免对键哈希码的重新计算。许多内部方法也接收一个标签参数,通常是当前的表,在resize的时候也能是新的或旧的表。

解析:接收哈希码作为参数,避免重复计算。提高性能。

以上是hashmap的一些特性,接下来是源码的逐行分析阅读