经典面试题之HashMap(二)

简介: 我们知道int 的极限最大值 Integer.MAX_VALUE 是2的31次方减1,即2147483647,如果 1 << 30 改为 1 << 31 ,由于int是有符号数,这个值将为 -2147483648,而且hashMap的容量都是2的整数次幂,也就只能是2的30次方了。然而这并不是HashMap的最大容量。

接上文 经典面试题之HashMap(一)


三 不考虑内存限制,HashMap可以无限存储数据吗?


不可以,HashMap是有最大容量上限的。我们还是来看下源码注释:


/**
     * 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;


很明显,如果构造函数传入的值大于MAXIMUM_CAPACITY ,那么替换成该数 MAXIMUM_CAPACITY 是 1 << 30 即 2的30次方。


为什么是1 << 30?  1 <<31 不行吗?


注意看这个值是int 类型的。我们知道int 的极限最大值 Integer.MAX_VALUE 是2的31次方减1,即2147483647,如果 1 << 30 改为 1 << 31 ,由于int是有符号数,这个值将为 -2147483648,而且hashMap的容量都是2的整数次幂,也就只能是2的30次方了。然而这并不是HashMap的最大容量。


来看下HashMap的构造函数


 public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }


上面代码中有一句


if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;


如果我要存的数目大于 MAXIMUM_CAPACITY,你还把我的容量缩小成 MAXIMUM_CAPACITY?其实不是的,在resize()方法中有一句


if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }


在这里我们可以看到其实
hashmap的“最大容量“是Integer.MAX_VALUE;


threshold=capacity*loadFactor threshold 表示当HashMap的size大于threshold时会执行resize操作。size就是HashMap中实际存在的键值对数量。


思考题:如果到达了HashMap的容量上限,再继续添加元素,会怎样?


其实你可以计算一下,当HashMap到达容量上限后占用的内存大小,已经很大了,所以一般情况下是内存溢出,我们在日常使用时,一般情况下不会把那么大的数据全部放到一个HashMap中。然而如果不考虑内存溢出的情况,就是你有一个超大的内存,那这个时候会怎样?


四 如何确定哈希桶数组索引位置?


我们首先想到的就是把hash值对数组长度取模运算,这样一来,元素的分布相对来说是比较均匀的。但是,模运算的消耗还是比较大的,在HashMap中是这样做的:调用下面的代码来计算该对象应该保存在table数组的哪个索引处。


static int indexFor(int h, int length) {  
    //jdk1.7的源码,jdk1.8没有这个方法,但是实现原理一样的
     return h & (length-1);  //第三步 取模运算
}


这个方法非常巧妙,它通过h & (table.length -1)来得到该对象的保存位,而HashMap底层数组的长度总是2的n次方,这是HashMap在速度上的优化。当length总是2的n次方时,h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效率。


注意  h& (length-1) 当且仅当length(即capacity)是2的整倍数的时候才等于 h % length ,从这个度也说明了capacity为什么一定要用2的整次幂。


数组长度-1 正好相当于一个“低位掩码”。“与”操作的结果就是散列值的高位全部归零,只保留低位值,用来做数组下标访问。以初始长度16为例,16-1=15。2进制表示是00000000 00000000 00001111。和某散列值做“与”操作如下,结果就是截取了最低的四位值。


14.png


五 了解HashMap的hash函数吗?


我们先来说说hash算法的一般实现:

  • 大数变小数-->取模
  • 让结果的规律性不明显--> 异或、改变原始数据、移位
  • 碰撞是存在的,主要是看解决碰撞的方案


java中常用的hashCode算法


  • Object类的hashCode。返回对象的经过处理后的内存地址。由于每个对象的内存地址都不一样,所以哈希码也不一样,这是个native方法。取决于JVM的内部设计,一般是某种C地址的偏移。
  • String 类的hashCode,根据String类包含的字符串的内容,根据一种特殊的算法返回哈希码,只要字符串的内容相同,返回的哈希码也相同。
  • Integer 等包装类,返回的哈希码就是Integer对象里所包含的那个整数的值,例如 Integer  i1 = new Integer(100), i1.hashCode() 的值就是 100。由此可见,两个一样大小的Integer对象,返回的哈希码也一样。
  • int、char这样的基础类,它们不需要hashCode,如果需要存储时,将进行自动装箱操作,计算方法同上。


我们来看下HashMap中的hash算法是如何实现的:


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


hash(Object key)的作用就是重新计算hash值。


HashMap中为什么不直接用key.hashCode()获取哈希值,而是使用(h = key.hashCode()) ^ (h >>> 16)?


我们通过上文了解了HashMap如何计算出数组索引位置,但其实有一个问题,就是即使我的散列值分布再松散,要是只取最后几位的话,碰撞也会很严重。更要命的是如果散列本身做得不好,分布上成等差数列的漏洞,恰好使最后几个低位呈现规律性重复,就无比蛋疼。


这时候“扰动函数”的价值就体现出来了


15.png



右位移16位,正好是
32bit的一半,自己的高半区和低半区做异或,就是为了混合原始哈希码的高位和低位,以此来加大低位的随机性而且混合后的低位掺杂了高位的部分特征,这样高位的信息也被变相保留下来这么做可以在数组table的length比较小的时候,也能保证考虑到高低Bit都参与到Hash的计算中,同时不会有太大的开销。(JDK 7做了4次右移,估计是边际效应的原因,JDK8就只做了一次右移)


总结 :(h = key.hashCode()) ^ (h >>> 16)这样写有点类似重写了hashCode,确保得出的数足够的随机,因为进行hash计算的时候 确保它的数足够的分散,以便于计算数组下标的时候存放的值足够分散。

相关文章
|
1月前
|
存储 缓存 安全
面试题-HashMap底层原理与HashTable的区别
字节跳动面试题-HashMap底层原理与HashTable的区别
38 0
|
8月前
|
存储 Java
每日一道面试题之谈一谈HashMap和HashSet的区别
每日一道面试题之谈一谈HashMap和HashSet的区别
|
8月前
|
存储 索引 容器
每日一道面试题之介绍一下HashMap~
每日一道面试题之介绍一下HashMap~
|
9月前
|
存储 算法 安全
拜托,面试请不要再问我HashMap了
拜托,面试请不要再问我HashMap了
47 0
|
8月前
|
搜索推荐 Java API
一道Java集合排序题,HashMap排序,面试必备
一道Java集合排序题,HashMap排序,面试必备
|
22天前
|
消息中间件 存储 缓存
面试题--HashMap和TreeMap的区别和应用场景有啥区别?
然后底层调用key的hashCode()方法得出hash值; 过哈希表哈希算法,将hash值转换成数组的下标(注1),下标位置上如果没有任何元素,就把Node添加到这个位置上。如果说下标对应的位置上有值。此时,就会拿着key和链表上每个节点的key进行equal。如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true,那么这个节点的value将会被覆盖,如果最终长度大于8就会转成红黑树,红黑树插入;
21 3
|
3天前
|
存储 安全 Java
《ArrayList & HashMap 源码类基础面试题》面试官们最喜欢问的ArrayList & HashMap源码类初级问,你都会了?
《ArrayList & HashMap 源码类基础面试题》面试官们最喜欢问的ArrayList & HashMap源码类初级问,你都会了?
7 0
|
1月前
|
Python
2024年Python最新刷爆全网的动态条形图,原来5行Python代码就能实现!,2024年最新Python面试必问的HashMap
2024年Python最新刷爆全网的动态条形图,原来5行Python代码就能实现!,2024年最新Python面试必问的HashMap
2024年Python最新刷爆全网的动态条形图,原来5行Python代码就能实现!,2024年最新Python面试必问的HashMap
|
1月前
|
存储 算法 Java
如果面试也能这样说HashMap,那么就不会有那么多遗憾!(中)
如果面试也能这样说HashMap,那么就不会有那么多遗憾!
35 0
|
1月前
|
存储 算法 Java
耗时3天写完的HashMap万字解析,争取一篇文章讲透它,面试官看了都直点头!
耗时3天写完的HashMap万字解析,争取一篇文章讲透它,面试官看了都直点头!
65 3