hashmap jdk1.8
hashjdk1.7的实现看另一篇
1.实现的接口
jdk1.8的hashmap 和 1.7的一样都是实现一个map接口
包含了一些操作kv键值的一些常用的方法,get(),put(),remove()等等。
依然使用一个Set集合来保存所有的key,保证key的唯一性
2.内部类
迭代器就不介绍了。相较于jdk1.7,这里的出现了两个内部类,node和treeNode。
-
Node
点进去,这不就是jdk1.7版本中的entry嘛,这里只是修改了一下名字而已。功能也是一样的,用来保存每个kv键值对,next用于哈希冲突时,链接下一个Node。 -
TreeNode(红黑树节点)
这就是jdk1.8中引入的红黑树。分析成员, -
parent 父节点
-
left 左子节点
-
right 右子节点
-
red 判断是否时红黑树
-
pre 指向链表的前一个节点
这里比较令人好奇的是红黑树中为什么会需要pre节点?这个不是用于双向链表中吗?
进入继承类中
在进入
到这里就知道答案了,TreeNode 内部隐式的继承了Node节点,因为node是单链表,只有next,这里存在一个pre,在内部隐式的构成了以双向链表。 —— 关于有什么作用后面在分析。
3.hashmap内部的成员
transient Node<K,V>[] table;
/**
* Holds cached entrySet(). Note that AbstractMap fields are used
* for keySet() and values().
*/ kv的set集合
transient Set<Map.Entry<K,V>> entrySet;
/**
* The number of key-value mappings contained in this map.
*/
哈希表中实际的键值对的数量
transient int size;
/**
* The number of times this HashMap has been structurally modified
* Structural modifications are those that change the number of mappings in
* the HashMap or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the HashMap fail-fast. (See ConcurrentModificationException).
*/
哈希表的修改次数 —— 用于迭代器时,方式在迭代的过程中删除哈希表中的数据做判断
transient int modCount;
/**
* The next size value at which to resize (capacity * load factor).
*
* @serial
*/
// (The javadoc description is true upon serialization.
// Additionally, if the table array has not been allocated, this
// field holds the initial array capacity, or zero signifying
// DEFAULT_INITIAL_CAPACITY.)
阙值 —— 用于扩容的时的判断
int threshold;
/**
* The load factor for the hash table.
*
* @serial
*/
负载因子,用于计算阙值
final float loadFactor;
分析可以知道,jdk8的成员和jdk7的基本是一致的。
4.默认值 (注意)
默认初始容量
最大容量
负载因子
上面的三个属性没有变化
但是在jdk8中多个3个默认的属性
-
TREEIFY_THRESHOLD = 8 表示树形的阙值
就是哈希表中的某个槽位上的Node的数量大于8时,就会将其由链表变为红黑树 -
UNTREEIFY_THRESHOLD = 6
取消树形化的阙值,红黑树种的节点的个数小于6时,就会将红黑树变为链表 -
MIN_TREEIFY_CAPACITY = 64
树化时,哈希表的最小容量,因为树化完成之后,可能还会将红黑树转化为链表,转化过程种设计到槽位的重新计算,位置重新分配,保证哈希表种有足够的槽位
5.方法分析
5.1构造方法
可以看到依然是只有四个,所以这里就直接分析默认构造方法。
看到这里,忍不住笑了。jdkb做的比jdk7更绝,初始化时,就只是设置了一个默认的负载因子,jdk7好歹还设置了一下阙值,针对不同的实现,调用了一下init()方法。
然后看一下有参数的构造方法
参数值为初始化容量的大小,然后调用了另一个构成方法
这里就基本和jdk7一致,设置了一些基本的属性,包括阙值,负载因子,重点注意,这里依然没有对哈希表进行实例化(分配空间),真正的实例化过程在put()时才做。 好处就是,使用的懒加载机制,到正真使用哈希表时才初始化哈希表,提高空间利用率。
5.2put方法
不同于jdk1.7 直接在方法内部处理,这里是调用一个方法来处理put()。首先计算了一下hash值
计算的方式页没有1.7版本的复杂,直接调用了Object 对象的hashcod()函数得到哈希值
进入hashcode() 发现是一个native方法,所以可以知道计算hashcode值不是有java代码实现,而是c++ 实现。实现得原理是根据该对象在内存中得位置来计算得。 见下图
为什么不是用java 来实现,可能就是因为java操作内存比较慢,所以使用c++来实现。
计算得到得哈希值,然后向右逻辑循环右移了16位,使用高16位来作为哈希值,这种做法有个名字 —— 扰动函数
计算完哈希值,后进入方法体。 代码比较复杂,这里注释一些关键点
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
首先判断哈希表是否位null,在前面构造函数中并没有初始化哈希表,这里需要初始化哈希表
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
判断一
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
判断二
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
判断三
else {
for (int