整理所学之HashMap | 第一篇

本文参考:

https://blog.csdn.net/yyyljw/article/details/80903391

https://www.jianshu.com/p/a89e9487a06c

https://blog.csdn.net/woshimaxiao1/article/details/83661464

https://blog.csdn.net/eaphyy/article/details/84386313

https://blog.csdn.net/qq_33666602/article/details/80139620

所学浅薄,抛砖引玉。

这里会涉及:

1. 哈希表

2.HashMap的最大容量为什么是2的30次方

3. HashMap 的hash算法

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

4. HashMap 查找数组下标为什么是

(n - 1) & hash

哈希表

散列表(也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

  来让我们在研究哈希表之前,先看下其他数据结构

  数组:是在内存中存储相同数据类型的物理上连续的空间。特点:查找快,插入、删除等更新操作慢。

  线性链表:在链式存储结构中,存储数据结构的存储空间可以不连续,各数据结点的存储顺序与数据元素之间的逻辑关系可以不一致,而数据元素之间的逻辑关系是由指针域来确定的。特点:查找慢,插入、删除等更新操作快。

  那么,是否可以设计一种数据结构,结合数组和链表?这就是哈希表。

  哈希表特征:

  1. 哈希表的存储是key、value形式的。

  2. 哈希表的主干就是数组,数组的下标是通过函数确定的,即:地址index=f(key),这个函数f一般称为哈希函数,这个函数的设计好坏会直接影响到哈希表的优劣。

  3. 数组的元素是链表形式

  例子:

如何存储字典里的所有单词能够让搜索、更新的效率快?

这个例子哈希表的解决方案,参见:https://www.cnblogs.com/bloodthirsty/p/12017077.html

HashMap的最大容量为什么是2的30次方

    /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;
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);
    }

可以看到,当容量达到2的30次方时,阈值设置为整形的最大值,不再扩容。

首先看下,1 << 30 含义:

  以一个字节为例:1<<2 = 4

  0000 0001(十进制1)

  向左移动两位之后变成

  0000 0100(十进制4)

可见 1<<30 等同于十进制中2的30次幂。

回到问题,为什么是2的30次幂?

因为java中的int类型为4个字节32位二进制,那么按理最大容量应该可以达到2的31次幂。但是事实上二进制中最高位即最左边的一位是符号位(1代表正,0代表负),

所以最大容量为2的30次幂。

解析HashMap 的hash算法

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

  1. key如果为0,hash值为0

  2. key不为0的情况:

  ^按位异或运算,不同为1,相同为0;

    0^0=0, 1^0=1, 0^1=1, 1^1=0,由此可见,A ^ 0 = A
  >>> 无符号右移:高位补0

  做个测试

h = key.hashCode()        1111 1101 1101 1111    1000 0101 0011 1100
^
h >>> 16                  0000 0000 0000 0000    1111 1101 1101 1111     右移16位,即保留高16位移动到低位,高16位0补齐
------------------------------------------------------------------
                          1111 1101 1101 1111    0111 1001 1110 0011     做异或,不同则为1,相同则为0

  结果可见:高位不变,然后把高16位和低16位做异或

  这样做法的意义是什么?

    我们知道,HashMap设计中,槽位的计算公式为:hash &(n-1),来继续看:

hash                    1111 1101 1101 1111    0111 1001 1110 0011
&
16-1                    0000 0000 0000 0000    0000 0000 0000 1111
------------------------------------------------------------------
                        0000 0000 0000 0000    0000 0000 0000 0011    任何数与0做'与'运算都为0

  可以看出,在容量为16的情况下,决定槽位的是低四位,hash值得高位不会起作用,所以拿key的hashcode的高16位与低16位做'异或',高位的特性也显现出来了!

  为什么是做异或?

    异或的特点:0^0=0, 1^0=1, 0^1=1, 1^1=0,覆盖数据均衡。

    与运算:0&0=0, 0&1=0, 1&0=0, 1&1=1,向0靠拢了

    或运算:0|0=0, 0|1=1, 1|0=1, 1|1=1,向1靠拢了

HashMap 数组槽位为什么是2的N次幂

  1.直观枚举

  当n为2的幂次方时,(n-1)& hash 的值是均匀分布的,我们假设n=16,hash从0开始递增:

hash    (n-1)& hash    结果
0         1111 & 0        0
1         1111 & 1        1
2         1111 & 10       2
3         1111 & 11       3
4         1111 & 100      4
5         1111 & 101      5
……         ……    ……
16        1111 & 10000    0        哈希碰撞
17        1111 & 10001    1        哈希碰撞
18        1111 & 10010    2        哈希碰撞

  当n不为2的幂次方时,(n-1)& hash 的值不是是均匀分布的,我们假设n=15,hash从0开始递增:

hash    (n-1)& hash    结果
0         1110 & 0        0        
1         1110 & 1        0        哈希碰撞
2         1110 & 10       2
3         1110 & 11       2        哈希碰撞
4         1110 & 100      4
5         1110 & 101      4        哈希碰撞
……    ……    ……
16        1110 & 10000    0        哈希碰撞
17        1110 & 10001    0        哈希碰撞
18        1110 & 10010    2        哈希碰撞

  2. 理论

  其实,当n=16时,(n-1)& hash = hash % n,这就是为什么在n=16的时候,hash为0~15根本不会发生碰撞。

   

  

每一步脚印都要扎得深一点!
原文地址:https://www.cnblogs.com/bloodthirsty/p/12015813.html