目录
HashMap描述
hashmap底层是由数组 + 链表 + 红黑树(当链表的长度大于等于8时,链表会转换为红黑树) 实现,往hashmap中添加键值对的步骤可以分为两步:①通过取模运算得到桶数组的坐标;②往链表或者红黑树中添加节点;
对于第一步的取模运算,是通过hash & (n - 1)实现。因为hashmap的容量大小是2的幂次方,所以可以通过&运算来优化%运算。例如:(17 % 16 )等价于 (17 & (16 - 1))。但是在hashmap获取桶数组坐标的时候,有一个细节需要注意:会先执行hash(key)运算。这个细节很容易被忽视。
HashMap如何定位桶数组的位置
HashMap在通过key,get、put键值对的时候,会先对key调用hash(key)的处理,然后才会是一般的做取模运算(&(n - 1))定位桶数组的位置。最后将键值对添加到链表或者红黑树中。
hash(key)的源码如下:
/**
* 计算键的 hash 值
*/
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
在 Java 中,hashCode 方法产生的 hash 是 int 类型,32 位宽。前16位为高位,后16位为低位。hash(key)的含义就是将key的高位与低位做异或运算,其目的是让key的高位也参与到取模运算中,使得键值对分布的更加的均匀。
验证
import java.util.HashMap;
public class HashMapTest {
public static void main(String[] args) {
HashMap<Integer, String> hashMap = new HashMap<>(16);
hashMap.put(0,"");
hashMap.put(1,"");
hashMap.put(2,"");
hashMap.put(3,"");
hashMap.put(4,"");
hashMap.put(5,"");
/* 2的16次方 */
int high = (int) Math.pow(2,16);
//为了使得高位不全为0,这里将添加大于2的16次方的整数
hashMap.put(high + 0, "");
hashMap.put(high + 1, "");
hashMap.put(high + 2, "");
hashMap.put(high + 3, "");
hashMap.put(high + 4, "");
hashMap.put(high + 5, "");
for (Integer key : hashMap.keySet()) {
System.out.print(key + "->");
}
}
}
输出的结果为:
我们会发现65537( + 1)被添加到坐标为0的桶数组链表上,65536()被添加到坐标为1的桶数组链表上。这就很奇怪了,因为 % 16 应该为0;( + 1) % 16应该为1。这其中的原因就是因为先对key执行了hash运算,使得高位与低位异或,然后再取模运算导致的。
以key = 65537( + 1)为例,整个计算过程如下:
HashMap遍历顺序
hashmap一般的遍历顺序和插入顺序是不一致的,但是对同一个hashmap的遍历多次顺序会是一致的。 hashmap的遍历顺序是:坐标从小到大,依次遍历每一个桶数组上的链表或者红黑树(红黑树节点会根据插入的顺序,记录自己的前驱和后继)。
若想让map结构的插入顺序与遍历顺序一致,可以使用LinkedHashMap。LinkedHashMap通过维护一个双向链表,使得插入顺序与遍历信息一致。
JDK1.7与1.8的hash函数的代码不一样
1.7使用了hash种子且移位异或运算更加的复杂,但是核心的作用是一样的。
/**
* Retrieve object hash code and applies a supplemental hash function to the
* result hash, which defends against poor quality hash functions. This is
* critical because HashMap uses power-of-two length hash tables, that
* otherwise encounter collisions for hashCodes that do not differ
* in lower bits. Note: Null keys always map to hash 0, thus index 0.
*/
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}