HashMap面试题一

HashMap面试题汇总

hashmap

-JDK7JDK8
数据结构数组+链表数组+链表/红黑树
链表插入头插法尾插法
key==nulltable[0]-
Iterator去删除元素modCount 实现 fail-fast-
resize头插法,链表数据顺序翻转。多线程会造成循环链表尾插法
hash实现(h = key.hashCode()) ^ (h >>> 16)
EntryNode
先判断是否需要扩容,再插入先进行插入,插入完成再判断是否需要扩容
在多线程环境下,扩容时会造成环形链或数据丢失在多线程环境下,会发生数据覆盖的情况
  1. 当链表长度 >=8 时,链表转换为红黑树。TREEIFY_THRESHOLD = 8;((桶的数量必须大于64,小于64的时候只会扩容))
  2. 当链表长度 <=6 时,红黑树转换为链表。UNTREEIFY_THRESHOLD = 6;(O(logn))
  3. 当两个对象的hashCode相同会发生什么?
    因为hashCode相同,不一定就是相等的(equals方法比较),所以两个对象所在数组的下标相同,"碰撞"就此发生。又因为HashMap使用链表存储对象,这个Node会存储到链表中。
  4. JDK1.8中,是通过hashCode()的高16位异或低16位实现的:(h=k.hashCode())^(h>>>16),主要是从速度,功效和质量来考虑的,减少系统的开销,也不会造成因为高位没有参与下标的计算,从而引起的碰撞。
    如果当n即数组长度很小,假设是16的话,那么n - 1即为1111 ,这样的值和hashCode直接做按位与操作,实际上只使用了哈希值的后4位。如果当哈希值的高位变化很大,低位变化很小,这样就很容易造成哈希冲突了,所以这里把高低位都利用起来,从而解决了这个问题。
  5. 为什么要用异或运算符?
    保证了对象的hashCode的32位值只要有一位发生改变,整个hash()返回值就会改变。尽可能的减少碰撞。
  6. 数组扩容的过程?
    创建一个新的数组,其容量为旧数组的两倍,并重新计算旧数组中结点的存储位置。
    结点在新数组中的位置只有两种,原下标位置或原下标+旧数组的大小。
    当HashMap中的其中一个链表的对象个数如果达到了8个,此时如果数组长度没有达到64,那么HashMap会先扩容解决,如果已经达到了64,那么这个链表会变成红黑树,结点类型由Node变成TreeNode类型。当然,如果映射关系被移除后,下次执行resize方法时判断树的结点个数低于6,也会再把树转换为链表
  7. 拉链法导致的链表过深问题为什么不用二叉查找树代替,而选择红黑树?为什么不一直使用红黑树?
    二叉查找树在特殊情况下会变成一条线性结构。
    红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,引入红黑树就是为了查找数据快,解决链表查询深度的问题。
    红黑树属于平衡二叉树,但是为了保持"平衡"是需要付出代价的,但是该代价所损耗的资源要比遍历线性链表要少,所以当长度大于8的时候,会使用红黑树,如果链表长度很短的话,根本不需要引入红黑树,引入反而会慢。
  8. LinkedHashMap保存了记录的插入顺序,在用Iterator遍历时,先取到的记录肯定是先插入的;遍历比HashMap慢;
  9. TreeMap实现SortMap接口,能够把它保存的记录根据键排序(默认按键值升序排序,也可以指定排序的比较器)
  10. HashMap是线程不安全的,HashTable是线程安全的;
    由于线程安全,所以HashTable的效率比不上HashMap;
    HashMap最多只允许一条记录的键为null,允许多条记录的值为null,而HashTable不允许;
    HashMap默认初始化数组的大小为16,HashTable为11,前者扩容时,扩大两倍,后者扩大两倍+1;
    HashMap需要重新计算hash值,而HashTable直接使用对象的hashCode;
  11. HashMap的底层数组长度为何总是2的n次方?
    使数据分布均匀,减少碰撞
    当length为2的n次方时,h&(length - 1) 就相当于对length取模,而且在速度、效率上比直接取模要快得多
  12. 为什么默认是16呢?怎么不是4?不是8?
    这应该就是个经验值,既然一定要设置一个默认的2^n 作为初始值,那么就需要在效率和内存使用上做一个权衡。这个值既不能太小,也不能太大。
  13. JDK1.7中rehash的时候,旧链表迁移新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置,但是JDK1.8不会倒置。
  14. 把默认容量设置成initialCapacity / 0.75F + 1.0F是一个在性能上相对好的选择,但是,同时也会牺牲些内存。

红黑树

性质1. 节点是红色或黑色。
性质2. 根节点是黑色。
性质3. 每个叶节点是黑色的。
性质4. 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

concurrentHashmap

-JDK7JDK8
DEFAULT_CONCURRENCY_LEVEL16
MIN_SEGMENT_TABLE_CAPACITY2
segmentSegment<K,V> extends ReentrantLocksun.misc.Unsafe UNSAFE;//CAS
rehash(node)扩容只是针对某个segment扩容,链表是头插法
分段的数组+链表数组+链表/红黑二叉树
分段锁并发控制使用 synchronized 和 CAS 来操作
  1. HashMap的键值对允许有null,但是ConCurrentHashMap都不允许
  2. 在JDK1.8中,放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。
  3. Unsafe类是"final"的,不允许继承。且构造函数是private的。因此我们无法在外部对Unsafe进行实例化。通过反射来获取Unsafe。
  4. jdk1.7 concurrentHashMap ,put的时候计算segment[] 下标时为什么取hash值的高位呢?
    两层数组长度一样的话,如果计算下标的方法相同,每次两层的下标就会相同,第二层只能用其中的一个位置,其他的位置用不到,会造成空间浪费。
  5. JDK1.7的ConcurrentHashMap中的put方法,在根据key计算得出Segment数组下标之后,
    使用了UNSAFE.getObject(segments, (j << SSHIFT) + SBASE),根据数组对象和偏移量来获取数组下标的元素,
    那么为什么不直接使用segments[j]这种方式呢?
    直接内存访问,为了加快速度。segments[j]会进行越界检查,效率低。系统类库因为需要大量调用,所以在保证正确的前提下,尽量做优化。
  6. ReentrantLock.tryLock() //不阻塞
    ReentrantLock.lock() //阻塞
    while (!tryLock()){} //耗费CPU
  7. ConcurrentHashMap在JDK1.8中,为什么要使用内置锁synchronized来代替重入锁ReentrantLock?
    粒度降低了;
    JVM开发团队没有放弃synchronized,而且基于JVM的synchronized优化空间更大,更加自然。
    在大量的数据操作下,对于JVM的内存压力,基于API的ReentrantLock会开销更多的内存。

hashtable

  1. synchronized关键字
回答: HashMap是Java中的一个常用数据结构,它的底层是由hash数组和单向链表实现的。每个数组元素都是一个链表,通过Node内部类实现了Map.Entry接口来存储键值对。HashMap通过put和get方法来存储和获取数据。\[1\] 在重写equals方法时,我们需要同时重写hashCode方法。这是因为在HashMap中,查找value是通过key的hashCode来进行的。当找到对应的hashCode后,会使用equals方法来比较传入的对象和HashMap中的key对象是否相同。因此,为了保证正确的查找和比较,我们需要同时重写equals和hashCode方法。\[2\]\[3\] HashMap在什么时候进行扩容呢?当HashMap中的元素数量超过了负载因子(默认为0.75)与当前容量的乘积时,就会进行扩容扩容是为了保持HashMap的性能,因为当元素数量过多时,链表的长度会变长,查找效率会下降。扩容的过程是创建一个新的数组,将原数组中的元素重新分配到新数组中,然后将新数组替换为原数组。\[3\] #### 引用[.reference_title] - *1* *2* *3* [史上最全Hashmap面试总结,51道附带答案,持续更新中...](https://blog.csdn.net/androidstarjack/article/details/124507171)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值