java源码分析之HashMap(三)

2014-11-24 11:24:54 · 作者: · 浏览: 39
法,暂时将这段逻辑放一边,看key不为null的情况。先调用了hash(int h)方法获取了一个hash值。
1 static int hash(int h) {
2 // This function ensures that hashCodes that differ only by
3 // constant multiples at each bit position have a bounded
4 // number of collisions (approximately 8 at default load factor).
5 h ^= (h >>> 20) ^ (h >>> 12);
6 return h ^ (h >>> 7) ^ (h >>> 4);
7 }
这个方法的主要作用是防止质量较差的哈希函数带来过多的冲突(碰撞)问题。Java中int值占4个字节,即32位。根据这32位值进行移位、异或运算得到一个值。
1 static int indexFor(int h, int length) {
2 return h & (length-1);
3 }
indexFor返回hash值和table数组长度减1的与运算结果。为什么使用的是length-1?应为这样可以保证结果的最大值是length-1,不会产生数组越界问题。
获取索引位置之后做了什么?探测table[i]所在的链表,所发现key值与传入的key值相同的对象,则替换并返回oldValue。若找不到,则通过addEntry(hash,key,value,i)添加新的对象。来看addEntry(hash,key,value,i)方法。
1 void addEntry(int hash, K key, V value, int bucketIndex) {
2 Entry e = table[bucketIndex];
3 table[bucketIndex] = new Entry(hash, key, value, e);
4 if (size++ >= threshold)
5 resize(2 * table.length);
6 }
这就是在一个链表头部插入一个节点的过程。获取table[i]的对象e,将table[i]的对象修改为新增对象,让新增对象的next指向e。之后判断size是否到达了需要扩充table数组容量的界限并让size自增1,如果达到了则调用resize(int capacity)方法将数组容量拓展为原来的两倍。
1 void resize(int newCapacity) {
2 Entry[] oldTable = table;
3 int oldCapacity = oldTable.length;
4 // 这个if块表明,如果容量已经到达允许的最大值,即MAXIMUN_CAPACITY,则不再拓展容量,而将装载拓展的界限值设为计算机允许的最大值。
5 // 不会再触发resize方法,而是不断的向map中添加内容,即table数组中的链表可以不断变长,但数组长度不再改变
6 if (oldCapacity == MAXIMUM_CAPACITY) {
7 threshold = Integer.MAX_VALUE;
8 return;
9 }
10 // 创建新数组,容量为指定的容量
11 Entry[] newTable = new Entry[newCapacity];
12 transfer(newTable);
13 table = newTable;
14 // 设置下一次需要调整数组大小的界限
15 threshold = (int)(newCapacity * loadFactor);
16 }
结合上面给出的注释,调整数组容量的内容仅剩下将原table中的内容复制到newTable中并将newTable返回给原table。即上面代码中的“transfer(newTable);table = newTable;”。来看transfer(Entry[] newTable)方法。
1 void transfer(Entry[] newTable) {
2 // 保留原数组的引用到src中,
3 Entry[] src = table;
4 // 新容量使新数组的长度
5 int newCapacity = newTable.length;
6 // 遍历原数组
7 for (int j = 0; j < src.length; j++) {
8 // 获取元素e
9 Entry e = src[j];
10 if (e != null) {
11 // 将原数组中的元素置为null
12 src[j] = null;
13 // 遍历原数组中j位置指向的链表
14 do {
15 Entry next = e.next;
16 // 根据新的容量计算e在新数组中的位置
17 int i = indexFor(e.hash, newCapacity);
18 // 将e插入到newTable[i]指向的链表的头部
19 e.next = newTable[i];
20 newTable[i] = e;
21 e = next;
22 } while (e != null);
23 }
24 }
25 }
从上面的代码可以看出,HashMap之所以不能保持元素的顺序有以下几点原因:第一,插入元素的时候对元素进行哈希处理,不同元素分配到table的不同位置;第二,容量拓展的时候又进行了hash处理;第三,复制原表内容的时候链表被倒置。
一个put方法带出了这么多内容,接着看看putAll吧。
1 public void putAll(Map< extends K, extends V> m) {
2 int numKeysToBeAdded = m.size();
3 if (numKeysToBeAdded == 0)
4 return;
5 // 为什么判断条件是numKeysToBeAdded,不是(numKeysToBeAdded+table.length)>threshold
6 if (numKeysToBeAdded > threshold) {
7 int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
8 if (targetCapacity > MAXIMUM_CAPACITY)