HahMap的灵魂拷问

带着问题读源码:
1.HashMap是线程安全的吗?如果不是,怎么变成线程安全?
       不是线程安全的, 可以使用concurrentHashmap  或者 使用Collections类的synchronizedMap方法包装一下

2.怎么解决hash冲突的?
       用链表

3. jdk1.7 与1.8版本的实现有什么差异?做了哪些优化?
        jdk1.8版本引入了红黑树,当链表的长度大于8的时候会转化为红黑树,解决长链表效率低的问题

4. put 与get的过程是怎样的?
   jdk1.7的过程
    put的过程:
    if(key == null){
       //调用方法,将该键值对保存在table的第一位置
      return putForNullKey(value);
    }
  如果不为空,则计算该key的hash值,计算应该放在数组的哪个位置,如果该位置原本没有值,直接存放;如果有值,查找链表,equals看看有没有相同的值,如果有的话覆盖,没有的话插入。jdk1.7中是插入在表头(因为写jdk的大佬可能认为刚插入的被查找的概率比较高一点)
   get的过程:计算key的hash值,定位到数组的位置, equals判断链表中的key是否与目标key相同,相同就返回对应的value

5.什么时候需要扩容?怎么实现扩容的?
        当hashmap中的元素越来越多,发生碰撞的概率就会越来越大,造成链越来越长,势必会影响到hashmap的存取效率,所以当个数到达临界值的时候就会扩容,也就是 容量*负载因子
        扩容是怎么实现的呢?扩容实际上是一个非常耗时的过程,因为它需要重新计算这些元素在新table数组中的位置并进行复制处理,每个元素的位置都有可能变化,因为数组大小改变了,对每个元素重新哈希计算存储的位置。按当前桶数组长度的2倍进行扩容
 在开发中如果我们能够估计元素的个数,尽量初始化指定,这样可以避免扩容,提升效率

6.列举几种遍历的方法?

7.HashMap的底层数组长度为什么总是2的幂次方?
       不同的hash值发生碰撞的概率比较小,这样就会使得数据在table数组中分布较均匀,空间利用率较高,查询速度也较快;
h&(length - 1) 就相当于对length取模,而且在速度、效率上比直接取模要快得多,即二者是等价不等效的,这是HashMap在速度和效率上的一个优化。为什么相当于取模呢,其实相当于按位与运算。

如默认的hashmap的length = 16, 那么 Length-1 化为二进制就是  0000 1111,其他数字与之按位与,那么范围就会在二进制 0000 0000 到

0000 1111中。也就是0 到15,这就相当与h%16的效果了,但是对于二进制的计算,明显效率会更好

注:hashmap中没有直接使用获取到的hashcode,而是做了一些处理,比如右移,按位与等等,这些操作都是为了让扰动结果,让得到的结果的散列性更好。

8.负载因子为什么是0.75,基于怎样的考虑?
     负载因子表示的是在扩容之前,元素占满容量的程度是多少。例如默认的初始容量是16,那么达到16*0.75 = 12 时就进行扩容。
     负载因子越大,表示对空间的使用越充分,但是冲突的概率也大,由于是拉链法来解决冲突,会导致链表长度增长,搜索效率降低
     负载因子越小,那么空间的使用不充分,分布很稀疏,造成空间的浪费。
     所以0.75是一种折中的方案

扫描二维码关注公众号,回复: 8968219 查看本文章

9.什么时候会线程不安全?
    多线程同时put时,如果同时触发了rehash操作,会导致HashMap中的链表中出现循环节点,进而使得后面get的时候,会死循环

10.为什么重写equals 时候要重写 hashcode 方法?
    首先在java中的有这样的规则:两个对像如果hashcode相同 那么一定equals,反之不然。所以如果写了重写了equals,不重写hashcode,那么就会存在两个对象在重写的equals方法下相等,但是hashcode不同(因为hashcode 是object类下计算的,每个new出来的对象都是不同的,作用是利用哈希快速寻找 判断)
  那以上会有什么影响呢?会造成使用到这两个方法的集合,出现错乱,达不到效果,例如hashset无法去重之类的,我的另外博文中讲。

11.有人问既然HashMap是无序的,那为什么我打印出来的和我插入的顺序是一样的呢?

    首先从底层的原理来讲,hashmap在put的时候是用散列的方法(也就是计算hash值来确定位置的),那肯定是无序的。

public static void main(String[] args) {
        HashMap<Integer, String> map = new HashMap();
        for (int i = 150; i > 0; i--) {
            map.put(i , "name" + i);
        }
        for (Map.Entry<Integer, String> entry : map.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }

    }
//如果key的类型是Integer ,Long等这样的数值类型,由于 这样类型的值计算出来的hashcode就是它本身。
// 如 Integer a =123; 那么 a.hashcode就是为123,那么此时就是有序的。这完全是一种特殊情况。

//如果Key是String类型,那就不会有序了

    12.关于hashMap中的hash算法

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
发布了29 篇原创文章 · 获赞 34 · 访问量 8157

猜你喜欢

转载自blog.csdn.net/weixin_39634532/article/details/90675707
今日推荐