java面试-java集合(下)


一、hash冲突后,将元素放到链表头还是尾?

  • JDK1.7插入时采用头插法,多线程下,有链表闭环的bug。假设链表原来的元素是元素的顺序是C->B->A,此时线程1和2指向指向C,线程1和2.next指向B,当hashMap扩容并且线程2完成插入,此时链表的状态为A->B->C,此时线程1指向C,线程1.next指向B,此时线程1想要变量就变成C->B->C了,因为此时B.next->C
  • JDK1.8改成了尾插法,主要是为了减少线程安全的问题,转成红黑树后按照红黑树的规则来插了

二、HashMap哪些操作会导致扩容? 扩容机制?resize方法的执行过程?扩容后的位置计算?为什么负载因子是0.75?

  • 为了减少哈希冲突发生的概率,当HashMap元素个数达到一个临界值threshold的时候,就会触发扩容,是一个相当耗时的操作。
  • 扩容时机
    • 第一次调用HashMap的put方法且数组为null时,会调用resize方法对table数组进行初始化,默认大小为16。
    • 当hashMap元素个数大于扩容阈值threshold = 负载因子loadFactor(0.75) * 初始容量capacity(16)时。容量变为原来的2倍,先插入数据再扩容
  • 容量范围:16-2^30个
  • 加载因子过高,扩容频率变低,hash碰撞几率变大,查找时间长,但占用空间小,空间利用率变高
  • 加载因子过低,扩容频率变高,hash碰撞几率变低,查找时间短,但占用空间大,空间利用率变低
  • 扩容机制:扩容时,HashMap 会创建一个新的数组,其容量是原数组容量的两倍。然后将键值对放到新计算出的索引位置上。根据(e.hash & oldCap)是否为0,使得扩容后的位置=原位置 or 原位置 + 旧容量(原哈希值的高位中新增的那一位是否为1,因为位置计算实际上是保留低位值,去掉所有高位值,比如原容量16则保留4位低位,扩容后32为保留5位低位,相差1位,而这1位刚好是原容量16所在的位置,因此只需hash与原容量与操作得到最新的位是1还是0决定新元素的位置,上面的推论都得益于长度是2的倍数和hash的高低位运算)

三、jdk1.8 对 HashMap 主要做了哪些优化呢?为什么?

  • 优化了数据结构:在数组+链表的基础上改为数组+链表+红黑树
  • 优化了链表插入的方式:jdk1.7是头插法,jdk1.8尾插法,主要是因为头插法有闭环的bug
  • 优化了扩容的处理:jdk1.7需要重新执行hash函数重新放置数据的位置,而jdk1.8只需判断hash值与旧容量的与操作是否为0即可,为0则是原位置,否则就是原位置+旧容量
  • 优化了扩容机制:jdk1.7的先判断是否需要扩容,再插入数据,jdk1.8则是先插入数据再扩容
  • 优化了hash函数:jdk1.7经过4个移位和亦或,而jdk1.8只需高低16为亦或就行

四、HashMap 为什么不是线程安全的?

  • jdk1.7在多线程下因为头插法形成闭环,因此扩容可能会死循环。
  • 多线程put可能会导致元素的丢失。因为计算出来的位置可能会被其他线程的put覆盖,本来哈希冲突是应该用链表的,但多线程时由于没有加锁,相同位置的元素可能就被干掉了。
  • put和get并发时,可能导致get为null。线程1执行put时,因为元素个数超出阈值而导致出现扩容,线程2此时执行get,就有可能出现这个问题,因为线程1执行完table = newTab后,线程2中的table此时也发生了变化,此时去get时当然会get到null,因为元素还没有转移。

五、如何解决HashMap线程不安全的问题呢?

  • Collections.synchronizedMap
  • ConcurrentHashMap

六、HashMap 内部节点是有序的吗?

  • 无序,根据hash值排序

七、LinkedHashMap怎么实现有序的?

  • 节点Node有前后节点的指针

八、TreeMap怎么实现有序的?

  • TreeMap通过Comparable接口或者key来排序,底层是红黑树

九、TreeMap 和 HashMap 的区别

  • HashMap是数组+链表+红黑树
  • TreeMap是红黑树

十、HashSet底层实现?

  • HashSet底层基于HashMap实现(除了clone()、writeObject()、readObject()是HashSet⾃⼰实现之外,其他⽅法都是直接调⽤HashMap中的⽅法。HashSet 会自动去重,因为HashMap 的键是唯一的(哈希值),相同键的值会覆盖掉原来的值,

十一、HashMap与Hashtable区别

  • HashMap可以接收null键和值。当key为null时返回的值为0,Hashtable不能接受null键和值,会抛出空指针异常
  • HashMap线程不安全,Hashtable线程安全
  • HashMap默认大小是16,扩容每次为2的指数大小,HashTable中数组默认大小是11 ,扩容方法是old*2 + 1

十二、HashMap和ConcurrentHashMap的区别?

  • ConcurrentHashMap线程安全;对整个桶数组进行了分割分段(Segment),每一个分段上都用lock锁保护,ConCurrentHashMap不允许键值对null
  • HashMap线程不安全;HashMap的键值对允许有null

十三、ConcurrentHashMap和Hashtable区别?

  • ConcurrentHashMap和Hashtable区别主要体现在实现线程安全的方式上不同。
  • 底层数据结构: ConcurrentHashMap和hashMap一样。Hashtable和JDK1.7的HashMap一样
  • 实现线程安全的方式:
    • ConcurrentHashMap:(JDK1.7分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment) JDK1.8,使用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化) HashTable 在每次同步执行时都要锁住整个结构。 ConcurrentHashMap锁的方式是稍微细粒度的
    • Hashtable(同一把锁) :使用synchronized保证线程安全,效率低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态

十四、有哪些线程不安全的集合?高并发情况下如何保证线程安全?√

  • Vector、HashTable、Properties是线程安全的;
  • ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap等都是线程不安全的。
  • 使用线程安全的类,使用synchronized关键字等
  • 普通集合变为同步集合的工具方法Collections.synchronizedList(<? extends Collection> collection);

十五、集合如何排序?

  • 排序:实现Comparator接口compare(T o1,T o2)小于、等于或者大于o2分别返回负整数、0或者正整数

十六、set实现类HashSet、LinkedHashSet、TreeSet的数据结构以及各自有优点

  • HashSet:无序,唯一,底层采用HashMap来保存元素(数组机制)
  • LinkedHashSet:有序,唯一,基于 LinkedHashMap 实现 链表和哈希表组合
  • TreeSet:有序,唯一,红黑树(自平衡的排序二叉树)

十七、Comparable和Comparator区别?

  • Comparable接口用于当前对象和其它对象的比较,compareTo(Object obj) 方法用来排序,在比较类上修改
  • Comparator接口用于传入的两个对象的比较,compare(Object obj1, Object obj2) 方法用来排序,新增一个类专门用于比较

十八、集合类使用注意事项

  • 基于应用的需求来选择使用正确类型的集合。如果元素的大小是固定的且已知优先级此时使用Array,而不是ArrayList
  • 指定初始大小避免重复扩容
  • 使用泛型来保证类型安全,可靠性和健壮性
  • Map中尽量使用不可变类String作为一个key,可避免hashcode的实现和我们自定义类的equals方法
  • 返回零长度的集合或者数组,而不是返回一个null ,这样可以防止底层集合是空的

Array与ArrayList区别与选用?

  • Array可以容纳基本类型和对象,而ArrayList只能容纳对象。
  • Array是指定大小的,而ArrayList大小是固定的,可自动扩容。
  • 列表的大小已经指定且存储和遍历推荐使用Array
  • 多维数组推荐使用Array[][]

总结

本文介绍了java面试-java集合(下),如有问题欢迎私信和评论

  • 14
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程岁月

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值