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是一种折中的方案

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);
    }

 

### 网络工程师面试常见难题及解答 #### 难题一:解释OSI七层模型及其每一层的功能 在网络工程领域,理解开放系统互连(Open System Interconnection, OSI)参考模型至关重要。此模型分为七个层次,从物理层到应用层依次为: - 物理层:负责定义电气、机械、过程和功能特性以建立、维护和拆除用于比特传输的物理链路连接;描述接口硬件特征。 - 数据链路层:提供节点间可靠的数据传输机制,并处理错误检测与纠正。 - 网络层:决定通过哪个路径到达目的地的最佳路由选择。 - 传输层:确保端到端通信的质量和服务水平协议(SLA),如TCP提供的可靠服务或UDP提供的尽力而为之的服务形式[^1]。 #### 难题二:阐述C3线性化算法在Python中的作用 对于熟悉编程语言特性的候选人来说,能够说明C3线性化(C3 Linearization)算法如何影响Python的新式类多重继承非常重要。当涉及到多个父类时,Python采用C3算法来计算方法解析顺序(Method Resolution Order, MRO),从而保证子类可以按照预期的方式调用基类的方法[^2]。 ```python class A(object): pass class B(A): pass class C(B): pass print(C.__mro__) # 输出 (<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>) ``` #### 难题三:讨论结构体内存对齐的原因 针对低级细节的理解也是考察的一部分。例如,在某些情况下,程序员可能注意到自己创建的结构体占用的空间大于所有成员变量所占空间之总和。这是因为编译器会自动向结构体中添加额外的填充字节,以便使每个成员相对于结构体起始处的位置满足特定平台所需的内存边界条件,进而提高访问速度并减少缓存未命中率[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值