标签:
HashMap源码一直是众多Java程序员的必经之路,今天我也看看,大家凑热闹不?基于水平有限,有些地方理解错误、理解不了,请大家指出哦~~
查看的版本是jdk1.7.0_71
public HashMap(int initialCapacity, float loadFactor) public HashMap(int initialCapacity) public HashMap() public HashMap(Map<? extends K, ? extends V> m)
HashMap有4个构造方法,具体看下代码,可知第2、3个方法都是调用第1个方法进行操作的。那么,具体看第1个吧。
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 static final float DEFAULT_LOAD_FACTOR = 0.75f;
查看参数的全局变量,知道初始化容量是16,扩容因子(容量达到哪里时要重新构造HashMap的容器)默认为0.75。
最后具体看第1个方法的方法体,主要作了3件事:
1、如果入参异常,则抛出异常
2、对初始化容量进行饱顶
3、将入参设置为属性,这里有点注意:threshold(阀值),在HashMap刚初始化市被赋值为初始容量。
4、后面,还调用了init()供子类的开发人员扩展?(猜的)
if (table == EMPTY_TABLE) { inflateTable(threshold); }
......
/** * Inflates the table. */ private void inflateTable(int toSize) { // Find a power of 2 >= toSize int capacity = roundUpToPowerOf2(toSize); threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); table = new Entry[capacity]; initHashSeedAsNeeded(capacity); }
内部有个叫table的数组,其元素是指向链表,而这链表装载的就是实际包含key、value的元素。
刚刚初始化HashMap时,此时table为空,这时就需要根据threshold对table进行扩容。
将table扩容至threshold的上随2的n次方大小。比如,threshold为16,则扩容至16;threshold为17,则扩容至32。
注:
roundUpToPowerOf2()见下述。
if (key == null) return putForNullKey(value);
查看putForNullKey方法,将key为null的元素,放入table下表为0的链表里。而逻辑与下面要讲的放入元素的逻辑基本一致。
注:
为什么是下标为0的元素放key为null的值呢?见下述。
int hash = hash(key); int i = indexFor(hash, table.length);
对key对象进行哈希计算后,映射到table数组中一个位置,为i。
注:
indexFor(),见下述。
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; } }
上面已找到当前要插入的元素位于table数组的哪个位置了,接下来就线性遍历这个位置指向的链表,如果发现hash值相等并且key也相等的,就说明此Map已包含此元素,那么,就用新值覆盖旧值,并返回旧值吧。
modCount++; addEntry(hash, key, value, i); ... /** * Adds a new entry with the specified key, value and hash code to * the specified bucket. It is the responsibility of this * method to resize the table if appropriate. * * Subclass overrides this to alter the behavior of put method. */ void addEntry(int hash, K key, V value, int bucketIndex) { if ((size >= threshold) && (null != table[bucketIndex])) { resize(2 * table.length); hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } createEntry(hash, key, value, bucketIndex); } /** * Like addEntry except that this version is used when creating entries * as part of Map construction or "pseudo-construction" (cloning, * deserialization). This version needn‘t worry about resizing the table. * * Subclass overrides this to alter the behavior of HashMap(Map), * clone, and readObject. */ 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++; }
程序跑到这里,说明在Map中并没有找到Key值,需要作插入。
modCount是记录插入的次数,估计用作限制并发操作的。
addEntry()在插入元素前,要判断元素是否达到一个阀值,如果达到,就对table进行2倍的扩容、重新哈希。(此点内容下面讲述)
然后,重新计算元素在扩容后的位置,调用createEntry()作实际的插入操作。插入操作,就是将新插入的元素的next指向链表的第一个元素,然后将table数字的该下表指向新插入的元素。
/** * Rehashes the contents of this map into a new array with a * larger capacity. This method is called automatically when the * number of keys in this map reaches its threshold. * * If current capacity is MAXIMUM_CAPACITY, this method does not * resize the map, but sets threshold to Integer.MAX_VALUE. * This has the effect of preventing future calls. * * @param newCapacity the new capacity, MUST be a power of two; * must be greater than current capacity unless current * capacity is MAXIMUM_CAPACITY (in which case value * is irrelevant). */ 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); } /** * Transfers all entries from current table to newTable. */ 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; } } }
先根据newCapacity实例化一个新的table。
因新table的长度变更了嘛,需遍历原table所指向的链表的所有元素,一个个转到新的table(计算hash、重新定位)。(至于是否重新hash,我还没看明白)
private static int roundUpToPowerOf2(int number) { // assert number >= 0 : "number must be non-negative"; return number >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1; }
这个方法是计算number最接近的2的N次方数。
其中Integer.highestOneBit()是取最高位1对应的数,如果是正数,返回的是最接近的比它小的2的N次方;如果是负数,返回的是-2147483648,即Integer的最小值。
那为什么要先减1,再求highestOneBit()?
举几个数的二进制就知道了:
00001111 = 15 -> 00011110 = 30 -> highestOneBit(30) = 16
00010000 = 16 -> 00100000 = 32 -> highestOneBit(32) = 32
所以,为了获取number最接近的2的N次方数,就先减一。
附一个简单的分解计算:
public class Lefter { public static void main(String[] args) { for (int i = 2; i <= 17; i++) { System.out.println(i); System.out.println(i - 1); System.out.println((i - 1) << 1); System.out.println(Integer.highestOneBit((i - 1) << 1)); System.out.println("result : " + i + " -> " + Integer.highestOneBit((i - 1) << 1)); } } }
结果:
2 1 2 2 result : 2 -> 2 3 2 4 4 result : 3 -> 4 4 3 6 4 result : 4 -> 4 5 4 8 8 result : 5 -> 8 6 5 10 8 result : 6 -> 8 7 6 12 8 result : 7 -> 8 8 7 14 8 result : 8 -> 8 9 8 16 16 result : 9 -> 16 10 9 18 16 result : 10 -> 16 11 10 20 16 result : 11 -> 16 12 11 22 16 result : 12 -> 16 13 12 24 16 result : 13 -> 16 14 13 26 16 result : 14 -> 16 15 14 28 16 result : 15 -> 16 16 15 30 16 result : 16 -> 16 17 16 32 32 result : 17 -> 32
将h映射到length的范围里,效果就像求模。
return h & (length-1);
将h和length - 1和操作就可以了。
比如length为16,那么:
16 = 00010000
15 = 00001111
根据上述indexFor(int h, int length)映射的范围在1到length - 1,那么剩下的下标就是0。
我觉得这是为了迁就&运算(因为&运算效率较高嘛),而用&运算有个限制,就是2的N次方内嘛,所以嘛。(个人理解,不知对否)
1、initHashSeedAsNeeded(capacity)
2、hash()
标签:
原文地址:http://www.cnblogs.com/nick-huang/p/5193511.html