HashMap 如何确定key值的位置

本文为个人学习笔记分享,没有任何商业化行为,对其他文章的引用都会标记。如有侵权行为,请及时提醒更正!如需转载请表明出处
部分参考资料
https://blog.csdn.net/j1231230/article/details/78072115


今天有幸去玩吧面试Android工程师一岗位,面试过程轻松愉快。在面试过程中被问到关于HashMap相关的问题。
1.HashMap的数据结构?
答:数组+链表。java8以后增加红黑二叉树,为了优化链表过长时的查找速度。(这里简单的介绍一下红黑二叉树的特点,及优点。链表转红黑树的时机就可以了)
2.HashMap是否线程安全?
答:不安全
3.如果想使用线程安全的HashMap该如何做?
答:1.Collections 工具类中 synchronizedCollection
2.采用装饰者模式实现map接口对HashMap进行封装。
3. ConcurrentHashMap 这个也是最推荐使用的线程安全的Map,也是实现方式最复杂的一个集合,每个版本的实现方式也不一样,在jdk8之前是使用分段加锁的一个方式,分成16个桶,每次只加锁其中一个桶,而在jdk8又加入了红黑树和CAS算法来实现。

4.HashMap怎么通过key判断索引位置的?
答:通过Hash算法,获取key的Hash值,通过indexFor算法计算真实索引

   // 获取key对应的value
    public V get(Object key) {
       if (key == null)
            return getForNullKey();
     // 获取key的hash值
       int hash = hash(key.hashCode());
       // 在“该hash值对应的链表”上查找“键值等于key”的元素
       for (Entry e = table[indexFor(hash, table.length)];
            e != null;
            e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
               return e.value;
            }
        return null;
    }

indexFor算法的精髓

static int indexFor(int h, int length) {
    return h & (length-1);
}

它没有对hash表的长度取余而使用了位运算来得到索引,这是为什么呢,顿生怀疑~(PS:当时老朽就答的取余)

HashMap的初始容量和扩容都是以2的次方来进行的,那么length-1换算成二进制的话肯定所有位都为1,就比如2的3次方为8,length-1的二进制表示就是111, 而按位与计算的原则是两位同时为“1”,结果才为“1”,否则为“0”。所以h& (length-1)运算从数值上来讲其实等价于对length取模,也就是h%length。

如果不满足前提条件“HashMap的初始容量和扩容都是以2的次方来进行的”,会发生什么问题呢?
假设当前table的length是15,二进制表示为1111,那么length-1就是1110,此时有两个hash值为8和9的key需要计算索引值,计算过程如下:

8的二进制表示:1000
8&(length-1)= 1000 & 1110 = 1000,索引值即为8;
9的二进制表示:1001
9&(length-1)= 1001 & 1110 = 1000,索引值也为8;

这样一来就产生了相同的索引值,也就是说两个hash值为8和9的key会定位到数组中的同一个位置上形成链表,这就产生了碰撞。
而查询的时候需要遍历这个链表,这样就降低了查询的效率。同时,我们也可以发现,当数组长度为15的时候,hash值会与length-1(1110)进行按位与,那么最后一位永远是0,而0001,0011,0101,1001,1011,0111,1101这几个位置永远都不能存放元素了,会造成严重的空间浪费,更糟的是这种情况下,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率。

因此可以看出,只有当数组长度为2的n次方时,不同的key计算得出的index索引相同的几率才会较小,数据在数组上分布也比较均匀,碰撞的几率也小,相对的,查询的时候就不用遍历某个位置上的链表,这样查询效率也就较高了。

此外,位运算快于十进制运算,hashmap扩容也是按位扩容,这样同时也提高了运算效率。

你可能感兴趣的:(HashMap 如何确定key值的位置)