在Java8与Java7中HashMap源码实现的对比
一、HashMap的原理介绍 此乃老生常谈,不作仔细解说。 一句话概括之:HashMap是一个散列表,它存储的内容是键值对(key-value)映射。 二、Java 7 中HashMap的源码分析 首先是 //代码块1 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; threshold = initialCapacity; init(); } Java7中对于 代码块2 public V put(K key,V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash,table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash,key,value,i); return null; } //addEntry方法中会检查当前table是否需要resize void addEntry(int hash,K key,V value,int bucketIndex) { if ((size >= threshold) && (null != table[bucketIndex])) { resize(2 * table.length); //当前map中的size 如果大于threshole的阈值,则将resize将table的length扩大2倍。 hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash,table.length); } createEntry(hash,bucketIndex); } Java7 中 代码如代码块3中所示, //代码块3 --JDK7中HashMap.resize()方法 void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; transfer(newTable,initHashSeedAsNeeded(newCapacity)); table = newTable; threshold = (int)Math.min(newCapacity * loadFactor,MAXIMUM_CAPACITY + 1); } /** * 将当前table的Entry转移到新的table中 */ void transfer(Entry[] newTable,boolean rehash) { int newCapacity = newTable.length; for (Entry<K,V> e : table) { while(null != e) { Entry<K,V> next = e.next; if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } int i = indexFor(e.hash,newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } } } HashMap性能的有两个参数:初始容量( 根据源码分析可以看出:在Java7 中 HashMap的 然而这种解决方法会有一个缺点,假如 在Java8中为了优化该最坏情况下的性能,采用了平衡树来存放这些hash冲突的键值对,性能由此可以提升至 代码块4 -- JDK8中HashMap中常量定义 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; static final int TREEIFY_THRESHOLD = 8; // 是否将list转换成tree的阈值 static final int UNTREEIFY_THRESHOLD = 6; // 在resize操作中,决定是否untreeify的阈值 static final int MIN_TREEIFY_CAPACITY = 64; // 决定是否转换成tree的最小容量 static final float DEFAULT_LOAD_FACTOR = 0.75f; // default的加载因子 在Java 8 HashMap的 代码块5 --JDK8 HashMap.put方法 public V put(K key,V value) { return putVal(hash(key),false,true); } final V putVal(int hash,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; //table为空的时候,n为table的长度 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash,null); // (n - 1) & hash 与Java7中indexFor方法的实现相同,若i位置上的值为空,则新建一个Node,table[i]指向该Node。 else { // 若i位置上的值不为空,判断当前位置上的Node p 是否与要插入的key的hash和key相同 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) // 不同,且当前位置上的的node p已经是TreeNode的实例,则再该树上插入新的node。 e = ((TreeNode<K,V>)p).putTreeVal(this,tab,hash,value); else { // 在i位置上的链表中找到p.next为null的位置,binCount计算出当前链表的长度,如果继续将冲突的节点插入到该链表中,会使链表的长度大于tree化的阈值,则将链表转换成tree。 for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash,null); 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; } 再看下 代码块6 -- JDK8的resize方法 inal Node<K,V>[] resize() { Node<K,V>[] oldTab = table; 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;//如果超过最大容量,无法再扩充table return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // threshold门槛扩大至2倍 } 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); } 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];// 创建容量为newCap的newTab,并将oldTab中的Node迁移过来,这里需要考虑链表和tree两种情况。 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); // split方法会将树分割为lower 和upper tree两个树, 如果子树的节点数小于了UNTREEIFY_THRESHOLD阈值,则将树untreeify,将节点都存放在newTab中。 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; } 再看一下tree的 // 代码块7 //MIN_TREEIFY_CAPACITY 的值为64,若当前table的length不够,则resize() final void treeifyBin(Node<K,V>[] tab,int hash) { int n,index; Node<K,V> e; if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); else if ((e = tab[index = (n - 1) & hash]) != null) { TreeNode<K,V> hd = null,tl = null; do { TreeNode<K,V> p = replacementTreeNode(e,null); if (tl == null) hd = p; else { p.prev = tl; tl.next = p; } tl = p; } while ((e = e.next) != null); if ((tab[index] = hd) != null) hd.treeify(tab); } } // putVal 的tree版本 final TreeNode<K,V> putTreeVal(HashMap<K,V> map,Node<K,int h,K k,V v) { Class<?> kc = null; boolean searched = false; TreeNode<K,V> root = (parent != null) ? root() : this; for (TreeNode<K,V> p = root;;) { int dir,ph; K pk; if ((ph = p.hash) > h) dir = -1; else if (ph < h) dir = 1; else if ((pk = p.key) == k || (k != null && k.equals(pk))) return p; else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc,k,pk)) == 0) { if (!searched) { TreeNode<K,V> q,ch; searched = true; if (((ch = p.left) != null && (q = ch.find(h,kc)) != null) || ((ch = p.right) != null && (q = ch.find(h,kc)) != null)) return q; } dir = tieBreakOrder(k,pk); } TreeNode<K,V> xp = p; if ((p = (dir <= 0) ? p.left : p.right) == null) { Node<K,V> xpn = xp.next; TreeNode<K,V> x = map.newTreeNode(h,v,xpn); if (dir <= 0) xp.left = x; else xp.right = x; xp.next = x; x.parent = x.prev = xp; if (xpn != null) ((TreeNode<K,V>)xpn).prev = x; moveRootToFront(tab,balanceInsertion(root,x)); return null; } } } 看了这些源码,并一一做了比较之后,惊叹于源码之妙,收益良多。 总结 以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |