HashMap源码学习
Posted jiachao
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HashMap源码学习相关的知识,希望对你有一定的参考价值。
HashMap
- 特性:基于数组+链表+红黑树实现,当链表长度超过8(阈值)时,链表转换为红黑树;HashMap本身是非线程安全的,允许键或值为NULL;元素无序且顺序会不定时改变(因为每次resize(),都会重新计算hash值,同一链表中的元素resize后可能就不在一个桶中了);key用set存放,需要重写key的hashcode()和equals()方法(因为Object类中的hashcode()方法返回的是对象的地址值)。
- 类成员属性
1 //最大容量 2 static final int MAXIMUM_CAPACITY = 1 << 30; 3 //加载因子 4 static final float DEFAULT_LOAD_FACTOR = 0.75f; 5 //阈值,当链表超过这个长度时,转换为红黑树 6 static final int TREEIFY_THRESHOLD = 8; 7 //resize时,当链表小于这个长度,红黑树又退化为链表 8 static final int UNTREEIFY_THRESHOLD = 6; 9 //只有哈希表的容量大于这个值时,才会树化,否则进行扩容 10 static final int MIN_TREEIFY_CAPACITY = 64;
- resize()
- 方法:在往HashMap中put()元素时,如果数组已使用长度超过阈值时,就需要扩容。
- 源码
1 final Node<K,V>[] resize() { 2 Node<K,V>[] oldTab = table;//旧数组,即当前所有元素所在的数组 3 int oldCap = (oldTab == null) ? 0 : oldTab.length;//记录旧容量是否为null或0 4 int oldThr = threshold;//记录旧阈值 5 int newCap, newThr = 0;//记录新容量,新阈值 6 //如果旧容量>0,再判断是否超过最大容量;超过最大容量则修改阈值为int的最大值,这样就不再扩容,只能碰撞 7 if (oldCap > 0) { 8 if (oldCap >= MAXIMUM_CAPACITY) { 9 threshold = Integer.MAX_VALUE; 10 return oldTab; 11 } 12 //如果旧容量的二倍<最大容量并且旧容量超过默认值16,则新阈值翻倍 13 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && 14 oldCap >= DEFAULT_INITIAL_CAPACITY) 15 newThr = oldThr << 1; // double threshold 16 } 17 //如果旧容量和旧阈值都>0,则将旧阈值赋值给新容量 18 else if (oldThr > 0) // initial capacity was placed in threshold 19 newCap = oldThr; 20 //如果旧容量=0,即刚调用无参构造进行初始化时,则设置默认值 21 else { // zero initial threshold signifies using defaults 22 newCap = DEFAULT_INITIAL_CAPACITY; 23 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 24 } 25 //如果新阈值=0,重新计算新阈值 26 //如果新容量和新容量*加载因子都<最大容量,则新阈值=新容量*加载因子,否则等于int的最大值 27 if (newThr == 0) { 28 float ft = (float)newCap * loadFactor; 29 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? 30 (int)ft : Integer.MAX_VALUE); 31 } 32 threshold = newThr;//设置阈值为新阈值 33 @SuppressWarnings({"rawtypes","unchecked"}) 34 //初始化一个容量为newCap大小的Node类型的数组(如果旧数组长度为0,那么新数组即为初始数组;如果旧数组长度不为0,那么新数组即为扩容的数组) 35 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 36 table = newTab; 37 //如果旧数组不为空,即为扩容操作,将旧数组中的数据移到新数组中 38 if (oldTab != null) { 39 for (int j = 0; j < oldCap; ++j) { 40 Node<K,V> e; 41 //遍历旧数组 42 if ((e = oldTab[j]) != null) { 43 oldTab[j] = null;//释放引用 44 //如果元素的 next引用为null,则不冲突 45 if (e.next == null) 46 //重新计算元素在新数组中的位置 47 newTab[e.hash & (newCap - 1)] = e; 48 //如果元素是一个TreeNode节点,则进行红黑树的rehash 49 else if (e instanceof TreeNode) 50 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); 51 //否则即为链表,进行链表的rehash 52 else { // preserve order 53 Node<K,V> loHead = null, loTail = null; 54 Node<K,V> hiHead = null, hiTail = null; 55 Node<K,V> next; 56 do { 57 next = e.next; 58 if ((e.hash & oldCap) == 0) { 59 if (loTail == null) 60 loHead = e; 61 else 62 loTail.next = e; 63 loTail = e; 64 } 65 else { 66 if (hiTail == null) 67 hiHead = e; 68 else 69 hiTail.next = e; 70 hiTail = e; 71 } 72 } while ((e = next) != null); 73 if (loTail != null) { 74 loTail.next = null; 75 newTab[j] = loHead; 76 } 77 if (hiTail != null) { 78 hiTail.next = null; 79 newTab[j + oldCap] = hiHead; 80 } 81 } 82 } 83 } 84 } 85 return newTab; 86 }
- put()
- 方法:可单次向HashMap中添加一个键值对,添加的元素,无顺序。
- 原理:
- 计算当前key的hash值
- 通过tab[i = (n-1) & hash]计算该hash值对应的数组位置,若该位置的桶为null,则新建一个桶,即将键值对直接插入到该数组位置上
- 若该hash值对应的桶不为null,通过key的equals()方法依次遍历桶中元素。先判断桶中的第一个元素,若hash值和key都与要插入的键值对一样,则替换value,put完成;若与桶中第一个元素不相等,再判断该节点是否是TreeNode,如果是,则以红黑树的方式插入;否则,就是链表,以链表的方式插入节点,在遍历链表时,若找到hash值和key都与要插入的键值对一样的节点,则替换value,未找到,则添加到链表的尾部,添加时,要判断链表长度是否超过阈值,超过,则转换为红黑树。
- size记录当前数组使用大小,若超过阈值,则resize()
- 源码
1 public V put(K key, V value) { 2 return putVal(hash(key), key, value, false, true); 3 } 4 5 /** 6 * Implements Map.put and related methods 7 * 8 * @param hash hash for key 9 * @param key the key 10 * @param value the value to put 11 * @param onlyIfAbsent if true, don‘t change existing value 12 * @param evict if false, the table is in creation mode. 13 * @return previous value, or null if none 14 */ 15 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, 16 boolean evict) { 17 Node<K,V>[] tab; Node<K,V> p; int n, i; 18 //如果数组为null或者数组长度=0,则进行resize 19 if ((tab = table) == null || (n = tab.length) == 0) 20 n = (tab = resize()).length;//n记录数组长度 21 //判断数组中该位置的桶是否为空,若为空,则新建一个桶,即将节点直接插在该数组位置上 22 if ((p = tab[i = (n - 1) & hash]) == null) 23 tab[i] = newNode(hash, key, value, null); 24 else {//否则,这个数组位置上有节点,即桶中有元素 25 Node<K,V> e; K k; 26 //和该数组位置上的节点(即桶中第一个元素)做比较,如果与该节点元素的hash值和key都相等,则直接覆盖value 27 if (p.hash == hash && 28 ((k = p.key) == key || (key != null && key.equals(k)))) 29 e = p; 30 //如果该节点是一个TreeNode,则以红黑树的方式插入节点 31 else if (p instanceof TreeNode) 32 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); 33 else {//否则,即为链表 34 for (int binCount = 0; ; ++binCount) { 35 //如果该节点的next引用为空,则插到链表中该节点的位置后面 36 if ((e = p.next) == null) { 37 p.next = newNode(hash, key, value, null); 38 //如果链表长度>=7,即要超过阈值,则转换为红黑树 39 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 40 treeifyBin(tab, hash); 41 break; 42 } 43 //遍历链表,如果与该节点元素的hash值和key都相等,则break 44 if (e.hash == hash && 45 ((k = e.key) == key || (key != null && key.equals(k)))) 46 break; 47 p = e; 48 } 49 } 50 //如果在桶中匹配到了要插入的节点(即与该节点元素的hash值和key都相等),则覆盖value 51 if (e != null) { // existing mapping for key 52 V oldValue = e.value; 53 if (!onlyIfAbsent || oldValue == null) 54 e.value = value; 55 afterNodeAccess(e); 56 return oldValue;//返回旧value 57 } 58 } 59 ++modCount; 60 //size记录数组当前已使用大小,如果size超过阈值,则resize 61 if (++size > threshold) 62 resize(); 63 afterNodeInsertion(evict); 64 return null; 65 }
- get()
- 方法:根据传入的key,返回value,不存在则返回null
- 原理:
- 计算当前key的hash值
- 通过tab[i = (n-1) & hash]计算该hash值对应的数组位置,和该位置桶中的第一个元素作比较,若该元素的hash值和key都与传入的key一样,则返回该节点;否则,继续遍历,若该节点是一个TreeNode,则遍历红黑树;否则,就是链表,遍历链表,若找到hash值和key都与传入的key一样的节点,则返回该节点,若遍历完链表还未找到,则返回null
- 源码
1 public V get(Object key) { 2 Node<K,V> e; 3 //根据hash值和key查询节点,若不存在则返回null,若存在则返回该节点的value 4 return (e = getNode(hash(key), key)) == null ? null : e.value; 5 } 6 7 /** 8 * Implements Map.get and related methods 9 * 10 * @param hash hash for key 11 * @param key the key 12 * @return the node, or null if none 13 */ 14 final Node<K,V> getNode(int hash, Object key) { 15 Node<K,V>[] tab; Node<K,V> first, e; int n; K k; 16 //如果数组不为null且数组长度>0且该hash值对应的数组位置上的桶不为空时 17 if ((tab = table) != null && (n = tab.length) > 0 && 18 (first = tab[(n - 1) & hash]) != null) { 19 //和该数组位置上的节点(即桶中第一个元素)做比较,如果与该节点元素的hash值和key都相等,则返回该节点 20 if (first.hash == hash && // always check first node 21 ((k = first.key) == key || (key != null && key.equals(k)))) 22 return first; 23 //如果不相等且该节点的next引用不为null,则遍历 24 if ((e = first.next) != null) { 25 //如果该节点是一个TreeNode,则遍历红黑树 26 if (first instanceof TreeNode) 27 return ((TreeNode<K,V>)first).getTreeNode(hash, key); 28 do {//否则即为链表,遍历链表 29 if (e.hash == hash && 30 ((k = e.key) == key || (key != null && key.equals(k)))) 31 return e; 32 } while ((e = e.next) != null);//遍历完链表的所有节点 33 } 34 } 35 return null; 36 }
- remove()
- 方法:根据传入的key值,查询元素,存在则删除该键值对并返回value,不存在则返回null
- 原理:
- 计算当前key的hash值
- 通过tab[i = (n-1) & hash]计算该hash值对应的数组位置,和该位置桶中的第一个元素作比较,若该元素的hash值和key都与传入的key一样,则该节点就是要移除的节点;否则,继续遍历桶中元素,如果该节点是一个TreeNode,则遍历红黑树,找到则以红黑树的方式移除该节点;否则,就是链表,遍历链表,找到则以链表的方式移除该节点
- 源码
1 public V remove(Object key) { 2 Node<K,V> e; 3 //根据hash值和key查询节点,若不存在则返回null,若存在则删除该节点并返回该节点的value 4 return (e = removeNode(hash(key), key, null, false, true)) == null ? 5 null : e.value; 6 } 7 8 /** 9 * Implements Map.remove and related methods 10 * 11 * @param hash hash for key 12 * @param key the key 13 * @param value the value to match if matchValue, else ignored 14 * @param matchValue if true only remove if value is equal 15 * @param movable if false do not move other nodes while removing 16 * @return the node, or null if none 17 */ 18 final Node<K,V> removeNode(int hash, Object key, Object value, 19 boolean matchValue, boolean movable) { 20 Node<K,V>[] tab; Node<K,V> p; int n, index; 21 //如果数组不为null且数组长度>0且该hash值对应的数组位置上的桶不为空时 22 if ((tab = table) != null && (n = tab.length) > 0 && 23 (p = tab[index = (n - 1) & hash]) != null) { 24 Node<K,V> node = null, e; K k; V v; 25 //和该数组位置上的节点(即桶中第一个元素)做比较,如果与该节点元素的hash值和key都相等,则该节点就是要remove的节点,赋值给node 26 if (p.hash == hash && 27 ((k = p.key) == key || (key != null && key.equals(k)))) 28 node = p; 29 //如果不相等且该节点的next引用不为null,则遍历 30 else if ((e = p.next) != null) { 31 //如果该节点是一个TreeNode,则遍历红黑树 32 if (p instanceof TreeNode) 33 node = ((TreeNode<K,V>)p).getTreeNode(hash, key); 34 else {//否则即为链表,遍历链表 35 do { 36 //如果与该节点元素的hash值和key都相等,则该节点就是要remove的节点,赋值给node 37 if (e.hash == hash && 38 ((k = e.key) == key || 39 (key != null && key.equals(k)))) { 40 node = e; 41 break; 42 } 43 p = e; 44 } while ((e = e.next) != null);//遍历完链表的所有节点 45 } 46 } 47 //如果node不为null,则说明已匹配到要remove的节点 48 if (node != null && (!matchValue || (v = node.value) == value || 49 (value != null && value.equals(v)))) { 50 //如果该节点是一个TreeNode,则该节点存在于红黑树中,调用红黑树的removeTreeNode方法 51 if (node instanceof TreeNode) 52 ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable); 53 //否则即为链表,以链表的方式remove该节点 54 else if (node == p) 55 tab[index] = node.next; 56 else 57 p.next = node.next; 58 ++modCount; 59 --size; 60 afterNodeRemoval(node); 61 return node; 62 } 63 } 64 return null; 65 }
以上是关于HashMap源码学习的主要内容,如果未能解决你的问题,请参考以下文章