Maps

Feb 27th, 2020 - Now

HashMap

存储结构

左侧是一个数组,数组的每个成员是一个链表,每个元素都包含着一个指针,用于元素之间的连接。Hash函数根据Object的性质将Object插入到其中去,同理也从中寻找取出。

Java 7

final int hash(Object k) {
    int h = hashSeed;
    if (0 != h && k instanceof String) {
        return sun.misc.Hashing.stringHash32((String) k);
    }

    // 下面进行的是一个hash的扰乱
    // 因为在下面我们直接与 length-1 与操作
    // 如果不进行扰动会导致高位的贡献为0
    // 所以这里是将高位部分 (h>>>20) 和中间位 (h>>>12)
    // 和低位都进行了一个计算最后得到我们的数值
    h ^= k.hashCode(); 
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

// indexFor函数获取当前的Object插入的下标  
// 由于 Java 中初始化和扩容hash存储的数组都是 2的幂次
// 所以直接用 & (2^n-1) 代替 % 2^n 的操作
// 还有一个好处就是,取模操作对负数的支持不好
// 直接与操作保证了不会出现负数,无需考虑数组越界
static int indexFor(int h, int length) {
    return h & (length-1);
}

Java8

ConcurrentHashMap

Java7

Java8

Java 8 里面的求hash的方法从hash改为了spread。

HashTable (线程安全)

Java7

为什么HashTable的hash函数和取模都采用的是最简单的方式?

需要考虑到 HashTable 的构造函数和扩容函数: HashTable默认的初始大小为11,之后每次扩充为原来的2n+1。 也就是说,HashTable的链表数组的默认大小是一个素数、奇数。之后的每次扩充结果也都是奇数。 由于HashTable会尽量使用素数、奇数作为容量的大小。当哈希表的大小为素数时,简单的取模哈希的结果会更加均匀。 详细的论证:Link

Reference

Last updated

Was this helpful?