JDK 源码分析(4)—— HashMap/LinkedHashMap/Hashtable
2017-02-25 15:43
423 查看
JDK 源码分析(4)—— HashMap/LinkedHashMap/Hashtable
HashMap
HashMap采用的是哈希算法+链表冲突解决,table的大小永远为2次幂,因为在初始化的时候,会保证给定的初始容量为2次幂,如下:// Find a power of 2 >= initialCapacity int capacity = 1; while (capacity < initialCapacity) capacity <<= 1;
每一次扩展都为2的倍数,这样子的好处在于,计算HashCode之后,计算bucket index时就不需要进行求余运算,直接进行&运算即可(如2^3 - 1 = 0x0111,进行&运算就保证了在该值范围内,而不是2的次幂的话,就不行了,只能求余)
static int indexFor(int h, int length) { return h & (length-1); }
具体实现
put
put操作核心就在于先计算Hash找出对应的bucket,然后再看里面是否有对应的entry,如果没有,则新增,有则替换掉原来的值。在新增的过程中,如果判断出容量达到阈值,则要进行上述所讲的扩容。public V put(K key, V value) { if (key == null) // 特殊处理,key为null的,放在bucket 0上 return putForNullKey(value); // 计算hash code int hash = hash(key); // 计算bucket index 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加1 modCount++; // 添加entry addEntry(hash, key, value, i); return null; } void addEntry(int hash, K key, V value, int bucketIndex) { if ((size >= threshold) && (null != table[bucketIndex])) { // 达到阈值,并且当前有冲突才会进行rehashing // 2倍的速度增长 resize(2 * table.length); hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } // 添加新的entry createEntry(hash, key, value, bucketIndex); } void createEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<>(hash, key, value, e); size++; }
resize
重新哈希(rehashing),条件是(size >= threshold) && (null != table[bucketIndex]),达到阈值并且产生冲突。如果新容量大于设定的Hash算法切换阈值,有可能会切换Hash算法,这时Hash Code就要进行重新计算。
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]; boolean oldAltHashing = useAltHashing; // 这里当虚拟机启动后,并且新的容量大于设定切换Hash的阈值,则切换Hash useAltHashing |= sun.misc.VM.isBooted() && (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD); /* 这里的rehash指示是否需要重新计算HashCode,只有当Hash算法进行切换的时候才需要重新计算, 如果已经切换过来了,则不需要了 */ boolean rehash = oldAltHashing ^ useAltHashing; transfer(newTable, rehash); table = newTable; threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); }
transfer
将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; // 这里就是指示是否需要重新计算HashCode 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; } } }
get
get相对比较简单,计算Hash,定位到对应的bucket,然后再从bucket的链表中查找。public V get(Object key) { if (key == null) return getForNullKey(); Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue(); } final Entry<K,V> getEntry(Object key) { // 计算Hash code int hash = (key == null) ? 0 : hash(key); // 定位到对应的bucket for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { // 循环,依次查找 Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } return null; }
LinkedHashMap
LinkedHashMap,一般用于在使用HashMap的时候,需要记录每个entry的插入顺序或者访问顺序(如LRU,FIFO)。在HashMap的基础上实现还是比较简单的,只需要自定义一个entry,继承HashMap的entry,在里面添加链表的特性,然后再覆盖对应的addEntry,使用自定义的entry即可。关键字段:
accessOrder:用于控制节点排序为访问顺序还是插入的顺序,accessOrder=false可以实现FIFO的淘汰策略,accessOrder=true实现LRU,默认为false。
具体实现
put
put操作,通过对addEntry以及createEntry的方法来进行追加链表特性。// put操作当需要添加entry时,会调用此addEntry方法 void addEntry(int hash, K key, V value, int bucketIndex) { // 调用父类的实现,不作更改 super.addEntry(hash, key, value, bucketIndex); // 当有必要的时候,会移除一些键值(通过覆盖removeEldestEntry方法来实现) Entry<K,V> eldest = header.after; if (removeEldestEntry(eldest)) { /* 移除最老的键值对,这里最老的键值对判断方式由子类实现, 但是顺序由accessOrder实现,可以为LRU或者FIFO */ removeEntryForKey(eldest.key); } } // 父类的entry会调用到此方法(因为覆盖了) void createEntry(int hash, K key, V value, int bucketIndex) { HashMap.Entry<K,V> old = table[bucketIndex]; // 覆盖父类的createEntry是为了使用当前类定义的entry,具有链表的特性 Entry<K,V> e = new Entry<>(hash, key, value, old); table[bucketIndex] = e; e.addBefore(header); size++; }
get
Get操作也是覆盖了父类的实现,方便访问后调用子类的recordAccess。public V get(Object key) { Entry<K,V> e = (Entry<K,V>)getEntry(key); if (e == null) return null; // 访问回调 e.recordAccess(this); return e.value; } private static class Entry<K,V> extends HashMap.Entry<K,V> { Entry<K,V> before, after; void recordAccess(HashMap<K,V> m) { LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m; // 这里accessOrder为false则不调整顺序 if (lm.accessOrder) { // true则将当前访问的节点移动到的链表头 lm.modCount++; remove(); addBefore(lm.header); } } }
Hashtable
Hashtable和HashMap的区别:Hashtable的基本操作采用同步实现,加了synchronized关键字
扩容策略,可以指定初始大小,后续以这个基础进行扩容,每次均为当前数值两倍(不一定是2次幂,采用求余运算)
取所有的key或者values时,返回的是Enumeration(也有keySet操作)
相关文章推荐
- jdk源码分析之LinkedHashMap
- jdk 源码分析(2)java hashtable的结构及hashMap对比
- JDK源码学习(1)-HashMap源码分析,HashMap与HashTable的差别
- java源码分析之LinkedHashMap
- LinkedList源码分析(基于JDK1.6)
- HashMap源码分析(基于JDK1.6)
- HashMap源码分析(jdk1.8)
- JDK中ArrayList、HashMap和HashSet的equals方法源码分析
- HashMap源码分析(基于JDK1.6)
- HashMap、HashTable、TreeMap 深入分析及源码解析
- JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue
- HashMap源码分析(基于JDK1.6)
- Java Collections Framework之Deque(LinkedList实现)源码分析(基于JDK1.6)(已补充)
- HashMap源码分析(基于JDK1.6)
- Java Collections Framework之LinkedList源码分析(基于JDK1.6)
- HashMap源码分析(基于JDK1.6)
- HashMap源码分析(基于JDK1.6)
- Java Collections Framework之Queue(LinkedList实现)源码分析(基于JDK1.6)
- java源码分析之LinkedHashMap
- HashMap源码分析(基于JDK1.6)