JavaSE之集合篇

2023-09-06 八股文
1.Collection和Collections有什么区别?
  1. Collection是一个集合接口,它提供了对集合对象进行基本操作的通用接口方法。
  2. Collections是一个包装类,它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于Java的Collection框架。
2.如果集合存储的是对象如何排序,有几种方式?
  1. 实现Comparable接口
  2. 通过Collections工具类Collections.sort
  3. 通过Stream

详见几种集合的排序方式

3.在遍历时删除List集合中的数据有几种方式?
  1. 通过普通的for循环,如果删除需要做一次i--;只能通过内容删,不可以通过索引删除。
  2. 使用迭代器,使用Iterator的remove方法移除当前对象,不能使用List.remove
  3. 将原来的copy一份副本,遍历原来的list,然后删除副本(fail-safe)
  4. 使用并发安全的集合类
  5. 通过Stream的过滤方法
  6. 通过removeIf方法
  7. 详见如何在遍历时删除集合的数据
4.Set是如何保证元素不重复的?
  1. TreeSet是二叉树实现的,TreeSet中的数据是自动排好序的,不允许放入null值;底层基于TreeMap,TreeMap基于红黑树。
  2. HashSet是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束;底层基于HashMap
5.HashSet,TreeSet,LinkedHashSet有何区别
  • HashSet:最简单的Set,只提供去重的能力
  • LinkedHashSet:和HashSet相比它是顺序的,会记录插入的顺序,性能比HashSet差点
  • TreeSet:提供去重和排序的能力
6.ArrayList、LinkedList与Vector的区别?
  • ArrayList:底层是数组,适用于get/set多的场景
  • LinkedList:底层是双向链表,更适合添加/删除多的场景
  • Vector:相当于加锁的ArrayList
7.ArrayList初始容量是多少?内部是如何扩容的?为什么扩充这么多?

JDK1.7之前ArrayList默认大小是10,JDk1.7之后是0,在第一次插入数据时变为0.

  1. 检查新增元素后是否会超过数组的容量,如果超过,则进行下一步扩容
  2. 设置新的容量为老容量的1.5倍,最多不超过2^31-1,初始10下一次就是15
    • int newCapacity = oldCapacity + (oldCapacity >> 1);
  3. 创建新数据,先将老数据复制到新数组,再插入新数据替换掉之前的数组,之前的数组交给GC回收,扩容完成
  4. 因为>>1相当于除以2,使用位运算效率比除法更高。
8.ArrayList的subList方法有什么需要注意的地方吗?

subList是List接口中定义的一个方法,该方法主要用于返回一个集合中的一段。

  1. SubList是ArrayList的内部类,没有继承关系,所以SubList无法直接强制转换为ArrayList或其他List。
  2. SubList返回的是一个视图,它只是将List中的部分属性直接赋值给了自己,如果需要修改等,可以复制
subList = Lists.newArrayList(subList);
list.stream().skip(strart).limit(end).collect(Collectors.toList());
9.HashMap、Hashtable和ConcurrentHashMap的区别?
  1. HashMap是非线程安全的,Hashtable和ConcurrentHashMap是线程安全的。
  2. HashTable和ConcurrentHashMap的key和value都不允许出现null值,HashMap对null无限制。
  3. HashMap底层是基于数组+链表,HashTable底层是哈希表
10.Hashtable和ConcurrentHashMap如何实现线程安全?

Hashtable中的方法是同步的,所以它是线程安全的。

ConcurrentHashMap使用分段锁和“CAS+Synchronized”机制,在插入时

  • 如果此段没有数据,则使用CAS插入
  • 如果有数据则使用Synchronized锁住第一个节点
11.HashMap、Hashtable和ConcurrentHashMap的扩容机制?
  • HashMap默认16,当元素个数超过容量的75%时会扩大到原来的2倍;
  • Hashtable默认11,当元素个数超过容量的75%时会扩大到原来的2倍+1;
  • ConcurrentHashMap默认16,当元素个数超过容量的75%时会扩大到原来的2倍,且采用分段锁,每个段独立进行扩容操作,避免了整个ConcurrentHashMap的锁竞争。
12.介绍下hashcode和equals以及其使用场景
  • hashcode:返回一个int类型数字,根据一定的hash规则(存储地址,字段,长度等),映射成一个数组,即散列值
  • equals:顶级类Object里面的方法,所有的类都是继承Object,根据匹配规则返回boolean用于匹配对象是否相同,常用逻辑
    1. 地址是否一样
    2. 非空判断和Class类型判断
    3. 强转
    4. 对象里面的字段一一匹配
  • 使用场景:对象比较、或者集合容器里面排重、比较、排序
public class User {
    private int age;
    private String name;
    private Date time;
    // get&&set
    @Override
    public int hashCode() {
        return Objects.hash(age,name,time);
    }

    @Override
    public boolean equals(Object obj) {
        if(this == obj) return true;
        if(obj == null || getClass() != obj.getClass()) return false;
        User user = (User) obj;
        return age == user.age && Objects.equals(name, user.name) && Objects.equals(time, user.time);
    }
}
13.HashMap和TreeMap应该怎么选择?

TreeMap适用于需要排序的情况,可自定义排序列表,底层是平衡二叉树,一般性能比HashMap差;而HashMap底层是数组+链表。

14.如果Map需要排序,该如何排序?

按照添加顺序使用LinkedHashMap,按照自然排序使用TreeMap,自定义排序 TreeMap(Comparetor c)

15.需要线程安全且效率更高的Map该如何选择?

使用ConcurrentHashMap,其线程安全且效率比Hashtable更高

16.看过HashMap的源码吗?其底层是如何实现的?

数组+(链表/红黑树),当链表长度>8会转换成红黑树

17.HashMap内部的bucket数组长度为什么一直都是2的整数次幂?

HashMap计算出Hash值后,使用&与运算(都是1才是1)代替取模%运算,而使用&运算必须要是2的整数次幂-1(转成二进制全是1)才能实现取模%运算的效果。

比如50 & 15 = 50 % 15 = 2

Hash中的&运算

18.为什么ConcurrentHashMap不允许null值?

当一个线程将null值插入到ConcurrentHashMap中时,其他线程可能无法正确判断该位置是否有值,

19.设计一个高效的并发数组

仿CopyOnWriteArrayList,对于修改操作,使用synchronized;对于查询操作,使用volatile

修改数据时进行复制,更新完成后进行替换,也就是说不会影响读操作,进行读取时也不需要加锁。

上次更新: 5 个月前