2020金三银四冲击BAT必备面试题(上篇):集合类+阻塞队列+锁

最新互联网大厂面试真题、Java程序员面试攻略(面试前的准备、面试中的技巧)请访问GitHub

一、集合类

1. ArrayList的扩容机制

  1. 每次扩容是原来容量的1.5倍,通过移位的方法实现。
  2. 使用copyOf的方式进行扩容。

扩容算法是首先获取到扩容前容器的大小。然后通过oldCapacity + (oldCapacity >> 1) 来计算扩容后的容器大小newCapacity。这里用到了>> 右移运算,即容量增大原来的1.5倍。还要注意的是,这里扩充容量时,用的时Arrays.copyOf方法,其内部也是使用的System.arraycopy方法。

区别:

  • arraycopy()需要目标数组,将原数组拷贝到你自己定义的数组里,而且可以选择拷贝的起点和长度以及放入新数组中的位置。
  • copyOf()是系统自动在内部新建一个数组,并返回该数组。

2. 数组和ArrayList的区别

  • 数组可以包含基本类型,ArrayList成员只能是对象。
  • 数组大小是固定的,ArrayList可以动态扩容。

3. ArrayList和LinkedList的区别

  • 线程安全:ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
  • 数据结构:LinkedList 是基于双向链表实现的,ArrayList 是基于数组实现的。
  • 快速随机访问:ArrayList 支持随机访问,所以查询速度更快,LinkedList 添加、插入、删除元素速度更快。
  • 内存空间占用:ArrayList的空间浪费主要体现在在list列表的结尾会预留一定的容量空间,LinkedList使用Node来存储数据每个Node中不仅存储元素的值,还存储了前一个 Node 的引用和后一个 Node 的引用,占用内存更多。
  • 遍历方式选择:实现了RandomAccess接口的list,优先选择普通for循环 ,其次foreach,未实现RandomAccess接口的list, 优先选择iterator遍历(foreach遍历底层也是通过iterator实现的),大size的数据,千万不要使用普通for循环。

4. 如何创建同步的List

可以通过Collections.sychronizeList将list转换成同步list,或者直接使用CopyOnWriteArrayList。

5. CopyOnWriteArrayList

  • 读时不加锁,写入时加锁,写入时创建一个新入组将老数组拷贝进入新数组,并将数据加入新数组。
  • 只能保证最终一致性。

6. Vector

ArrayList线程安全的一个版本,底层通过synchronize加锁实现线程安全。

7. HashMap扩容机制

HashMap使用resize()方法来进行扩容,计算table数组的新容量和Node在新数组中的新位置,将旧数组中的值复制到新数组中,从而实现自动扩容。

  • 当空的HashMap实例添加元素时,会以默认容量16为table数组的长度扩容,此时 threshold = 16 * 0.75 = 12。
  • 当不为空的HashMap实例添加新元素数组容量不够时,会以旧容量的2倍进行扩容,当然扩容也是大小限制的,扩容后的新容量要小于等于规定的最大容量,使用新容量创建新table数组,然后就是数组元素Node的复制了,计算Node位置的方法是 index = (n-1) & hash,这样计算的好处是,Node在新数组中的位置要么保持不变,要么是原来位置加上旧数组的容量值,在新数组中的位置都是可以预期的(有规律的),并且链表上Node的顺序也不会发生改变。

8. HashMap为什么不是线程安全的

  • 没有锁操作,两个线程操作同一个hashMap会出现线程安全的问题,可能会导致数据丢失。
  • resize的时候会出现死锁,以为hash冲突之后采用链地址法解决hash冲突,但是两个线程都进行扩容的时候,链表使用头插法,导致出现循环引用,出现死锁。1.8之后 链表都是采用尾插法。避免了死循环的问题。

9. 为什么HashMap的hashCode要高16位异或hashCode

因为元素所处位置只与低n位相关,高16位与hashcode进行异或是为了减少碰撞。

异或是两者相同返回0 不相同返回1。

10. 为什么HashMap的容量要是2的N次幂

  • 取模时分配更均匀。
  • 扩容成本更低。

2^n下有特性:x%2^n=x&(2^n-1)只有2的幂次方才有此特性。

11. ConcurrentHashMap的实现

  • jdk1.7之前,使用分段锁来实现,默认支持的并发度为16,segment继承自reetrantlock,segment充当锁角色。每个segment中包含一个小的hash表。size方法将segment的count相加,计算两次,如果两次结果相同,说明计算准确,否则每个segment重新加锁计算。
  • jdk1.8之后取消分段锁的设计,采用CAS+Synchronized保证线程安全。主要是锁住链表的头结点。size方法使用一个volatile变量baseCount记录元素个数,当插入新数据或者删除数据的时候会更新baseCount的值。

12. ConcurrentHashMap1.7与1.8异同

  • 1.8取消了分段锁,锁的粒度更小,减少并发冲突的概率。
  • 1.8采用了链表+红黑树的实现方式,对查询的提升很大。

13. 为什么ConcurrentHashMap读操作不加锁

  • ConcurrentHashMap只保证最终一致性,并不能保证强一致性。
  • 对于value使用valitile关键字,保证内存可见,能够被多线程同时读,并且不会读到过期的值。根据java内存模型的hanpends-befor原则,对volatitle的写入操作先于读操作,即使两个线程同时读取和写入同一个变量,也能是get操作拿到最新值
  • Node使用volatitle关键字标识是为了数组扩容时的可见性。

14. LinkedHashMap的实现

基于hashMap和双向链表实现的,线程不安全。

15. HashSet的实现

  • 底层是通过hashMap实现的。
  • 判断两个对象是否相等,先判断hashCode是否相等,如果相等再判断equals,这就是为什么重写equals方法要重写hashCode方法。

16. TreeMap的实现

底层使用红黑树实现。根据键值进行排序,key必须实现Compareable接口或者构造TreeMap时传入Comparetor。

17. TreeSet的实现

底层使用TreeMap实现,即使用红黑树进行实现。
Set判断两个元素是否相等,先判断hashCode再使用equals

18. 解决Hash冲突的方法

  • 开放定址法
  • 链地址法
  • 再hash法

19. List、Map、Set存储的null值

  • list null值,加几个存几个。
  • set null值 只存一个。
  • map只存在一个null值对。

20.

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值