-
HashMap底层是一个数组,数组中元素是链表或者红黑树。
//debug运行,强制进入方法内部(Alt+Shift+F7): /** * The default initial capacity - MUST be a power of two. */ static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 /** * The maximum capacity, used if a higher value is implicitly specified * by either of the constructors with arguments. * MUST be a power of two <= 1<<30. */ //最大容量 2的30次方,因为最高一位为符号位 static final int MAXIMUM_CAPACITY = 1 << 30; /** * The load factor used when none specified in constructor. */ static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * The bin count threshold for using a tree rather than list for a * bin. Bins are converted to trees when adding an element to a * bin with at least this many nodes. The value must be greater * than 2 and should be at least 8 to mesh with assumptions in * tree removal about conversion back to plain bins upon * shrinkage. */ static final int TREEIFY_THRESHOLD = 8; /** * The bin count threshold for untreeifying a (split) bin during a * resize operation. Should be less than TREEIFY_THRESHOLD, and at * most 6 to mesh with shrinkage detection under removal. */ static final int UNTREEIFY_THRESHOLD = 6; /** * The smallest table capacity for which bins may be treeified. * (Otherwise the table is resized if too many nodes in a bin.) * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts * between resizing and treeification thresholds. */ static final int MIN_TREEIFY_CAPACITY = 64; //底层数组 transient Node<K,V>[] table; /** * Holds cached entrySet(). Note that AbstractMap fields are used * for keySet() and values(). */ 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.) //当元素数量大于threshold = loadFactor * capcity(数组容量)时,则会扩容 int threshold; /** * The load factor for the hash table. * * @serial */ final float loadFactor; //构造方法 public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; //返回一个 this.threshold = tableSizeFor(initialCapacity); } //将threshold转为离传入容量最近的2的次幂 //在第一次扩容时,会将threshold转为 这个threshold * loadFactor; static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; } //扩容 final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; //懒加载,第一次放数据时才会创建Node[] int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } //扩容1倍 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) //threshold也扩容一倍 newThr = oldThr << 1; // double threshold } //定义了初始容量 else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults //懒加载 newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } //定义了初始容量则根据初始容量初始化threshold if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) //如果是树节点,则进入新的逻辑 //会考虑树化和去树化 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; } //树节点扩容时 final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) { TreeNode<K,V> b = this; // Relink into lo and hi lists, preserving order TreeNode<K,V> loHead = null, loTail = null; TreeNode<K,V> hiHead = null, hiTail = null; int lc = 0, hc = 0; for (TreeNode<K,V> e = b, next; e != null; e = next) { next = (TreeNode<K,V>)e.next; e.next = null; //低位还是放原来桶坐标 //hash值 & 原来的数组容量来决定是放在高位桶还是低位桶 if ((e.hash & bit) == 0) { if ((e.prev = loTail) == null) loHead = e; else loTail.next = e; loTail = e; ++lc; } //高位就放在高位桶坐标 else { if ((e.prev = hiTail) == null) hiHead = e; else hiTail.next = e; hiTail = e; ++hc; } } if (loHead != null) { //小于等于6则去树化 if (lc <= UNTREEIFY_THRESHOLD) tab[index] = loHead.untreeify(map); else { //否则还是树化 tab[index] = loHead; if (hiHead != null) // (else is already treeified) loHead.treeify(tab); } } if (hiHead != null) { //小于6则去树化 if (hc <= UNTREEIFY_THRESHOLD) tab[index + bit] = hiHead.untreeify(map); else { //否则还是树化 tab[index + bit] = hiHead; if (loHead != null) hiHead.treeify(tab); } } } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; 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 binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); //当这个桶里的节点数大于8时,则树化 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; } //如果移除的是树节点 final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab, boolean movable) { int n; if (tab == null || (n = tab.length) == 0) return; int index = (n - 1) & hash; TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl; TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev; if (pred == null) tab[index] = first = succ; else pred.next = succ; if (succ != null) succ.prev = pred; if (first == null) return; if (root.parent != null) root = root.root(); //删除操作时,只有根节点||根节点的右节点||根节点的左子节点||根节点的左子节点的左子节点为null时才会去树化。 if (root == null || (movable && (root.right == null || (rl = root.left) == null || rl.left == null))) { tab[index] = first.untreeify(map); // too small return; } TreeNode<K,V> p = this, pl = left, pr = right, replacement; if (pl != null && pr != null) { TreeNode<K,V> s = pr, sl; while ((sl = s.left) != null) // find successor s = sl; boolean c = s.red; s.red = p.red; p.red = c; // swap colors TreeNode<K,V> sr = s.right; TreeNode<K,V> pp = p.parent; if (s == pr) { // p was s's direct parent p.parent = s; s.right = p; } else { TreeNode<K,V> sp = s.parent; if ((p.parent = sp) != null) { if (s == sp.left) sp.left = p; else sp.right = p; } if ((s.right = pr) != null) pr.parent = s; } p.left = null; if ((p.right = sr) != null) sr.parent = p; if ((s.left = pl) != null) pl.parent = s; if ((s.parent = pp) == null) root = s; else if (p == pp.left) pp.left = s; else pp.right = s; if (sr != null) replacement = sr; else replacement = p; } else if (pl != null) replacement = pl; else if (pr != null) replacement = pr; else replacement = p; if (replacement != p) { TreeNode<K,V> pp = replacement.parent = p.parent; if (pp == null) root = replacement; else if (p == pp.left) pp.left = replacement; else pp.right = replacement; p.left = p.right = p.parent = null; } TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement); if (replacement == p) { // detach TreeNode<K,V> pp = p.parent; p.parent = null; if (pp != null) { if (p == pp.left) pp.left = null; else if (p == pp.right) pp.right = null; } } if (movable) moveRootToFront(tab, r); } //自己写得增加TreeNode的put //通过比较hash值,hash值相等则equal,如果equal就覆盖,不equal则继续compareTo,无法比较则最后比较内存地址。通过这些来决定往左找还是往右找 @Override public V put(K key, V value) { resize(); int index = index(key); Node<K, V> root = table[index]; //如果添加的是第一个节点 if (root == null) { root = createNode(key,value,null); table[index] = root; size++; fixAfterPut(root); return null; } Node<K, V> node = root; Node<K, V> parent; Node<K, V> result; K k1 = key; int h1 = hash(key); int cmp = 0; boolean done = false; do { parent = node; K k2 = node.key; int h2 = node.hash; //比较hash值 //为了提高性能 if (h1 > h2) { cmp = 1; } else if (h1 < h2) { cmp = -1; //比较是否相等 } else if (Objects.equals(k1, k2)) { cmp = 0; //是否有可比较性 //为了提高性能 } else if (k1 != null && k2 != null && k1.getClass() == k2.getClass() && k1 instanceof Comparable && (cmp = ((Comparable) k1).compareTo(k2)) != 0) { //cmp这里可能等于0,要排除 //cmp = ((Comparable)k1).compareTo(k2); //排除已经进行过了 //done = true } else if (done) { //比较标识hash码,相当于比较内存地址 cmp = System.identityHashCode(k1) - System.identityHashCode(k2); //没有可比较性,排除红黑树有没有这个key } else { // 扫描; 然后再根据内存地址大小决定左右 if (node.left != null && (result = node(node.left, k1)) != null || node.right != null && (result = node(node.right, k1)) != null) { // 已经存在这个k1 cmp = 0; node = result; //不存在k1 //设置一个标记done,表示搜索过,则直接进入else if(done) } else { done = true; cmp = System.identityHashCode(k1) - System.identityHashCode(k2); } } if (cmp > 0) { node = node.right; } else if (cmp < 0) { node = node.left; } else { node.key = key; V oldValue = node.value; node.value = value; node.hash = h1; return oldValue; } } while (node != null); Node<K, V> newNode = createNode(key,value,parent); if (cmp > 0) { parent.right = newNode; } else { parent.left = newNode; } fixAfterPut(newNode); size++; return null; }
-
hashMap补充:
-
注意,自己写的hashmap的key会被覆盖(两个key的hashcode相等,且equals),而java里面的不会;value都会被覆盖
-
jdk8实现懒惰初始化,用到的时候才创建,此时threshold会变为其容量大小,数组在用到的时候才会创建
-
hashmap初始化容量都会转为2的次幂; 没定义容量则初始容量为16
-
map允许key和value为null
-
hashMap里面得node和treenode都直接继承或间接继承了Entry。node继承Map.Entry, treeNode继承LinkedHashMap.Entry, LinkedHashMap.Entry又继承hashMap.Node. 所以这样在去树化时很方便,TreeNode里面有next指针。
-
某个下标上元素大于8,且数组容量达到64后,就会考虑转为红黑树(treeify);当扩容操作时调用resize方法,当是红黑树时,(以位坐标为单位)会调用split方法将红黑树拆到两个桶(低位索引和高位索引)里,如果小于等于6则会转换为链表,否则转换为红黑树。删除节点时当根节点的左右子节点为空或者其左子节点的左子节点为空时就会转为链表(untreeify)下面是扩容时源码:
//index为原数组下标,bit为原数组大小 final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) { TreeNode<K,V> b = this; // Relink into lo and hi lists, preserving order TreeNode<K,V> loHead = null, loTail = null; TreeNode<K,V> hiHead = null, hiTail = null; int lc = 0, hc = 0; for (TreeNode<K,V> e = b, next; e != null; e = next) { next = (TreeNode<K,V>)e.next; e.next = null; //put()方法中是数组长度n - 1 & e.hash,这样就会把 //如果hash&bit == 0 则仍放到原来索引位置 if ((e.hash & bit) == 0) { if ((e.prev = loTail) == null) loHead = e; else loTail.next = e; loTail = e; ++lc; } //否则就放到index + bit的高位索引上 else { if ((e.prev = hiTail) == null) hiHead = e; else hiTail.next = e; hiTail = e; ++hc; } } //如果在该下标的节点个数小于等于6,则降为链表,否则就升为树 if (loHead != null) { if (lc <= UNTREEIFY_THRESHOLD) tab[index] = loHead.untreeify(map); else { tab[index] = loHead; if (hiHead != null) // (else is already treeified) loHead.treeify(tab); } } if (hiHead != null) { if (hc <= UNTREEIFY_THRESHOLD) // 然后只需要判断hash在新增的有效位的位置是0还是1就可以算出新的索引位置,如果是0,那么索引没有发生变化,如果是1,索引就为原索引加上扩容前的容量。 tab[index + bit] = hiHead.untreeify(map); else { tab[index + bit] = hiHead; if (loHead != null) hiHead.treeify(tab); } } }
-
-
面试题:
-
为什么一开始不用红黑树呢?
单个 TreeNode 需要占用的空间大约是普通 Node 的两倍,所以只有当包含足够多的 Nodes 时才会转成 TreeNodes,这个足够多的标准就是由 TREEIFY_THRESHOLD 的值(默认值8)决定的。而当桶中节点数由于移除或者 resize (扩容) 变少后,红黑树会转变为普通的链表,这个阈值是 UNTREEIFY_THRESHOLD(默认值6)。
-
*为什么树化标准是8个*
如果 hashCode的分布离散良好的话,那么红黑树是很少会被用到的,因为各个值都均匀分布,很少出现链表很长的情况。在理想情况下,链表长度符合泊松分布,各个长度的命中概率依次递减,注释中给我们展示了1-8长度的具体命中概率,当长度为8的时候,概率概率仅为0.00000006,这么小的概率,HashMap的红黑树转换几乎不会发生
-
-
hashMap中的hash值:
static final int hash(Object key) { int h; //hash值得计算也会加上一个扰动因子,使hash分布更均匀 //即key得hashcode ^ 这个hashcode无符号右移16位,这样运算是为了减少碰撞冲突,因为大部分元素的hashCode在低位是相同的,不做处理的话很容易造成冲突。 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } // n的值是table.length //求索引是(n - 1) & hash,因此将容量n设为2得幂次,这样可以通过位与运算得到hash值,而不是通过取模,效率可以快好多 //同时,在扩容得时候n得有效位只增加一位,然后只需要判断hash在新增的有效位的位置是0还是1就可以算出新的索引位置,如果是0,那么索引没有发生变化,如果是1,索引就为原索引加上扩容前的容量。 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null);
-
-
对象得hash值一般设置为
JAVA集合问题(2)
于 2022-06-24 16:38:35 首次发布