【JavaSE面试篇】Java集合部分高频八股汇总

目录

概念

1. 说说Java中的集合?

2. Java中的线程安全的集合有什么?

3. Collections和Collection的区别?

4. 集合遍历的方法有哪些?

List

5. 讲一下java里面list的几种实现,几种实现有什么不同?

6. Arraylist和LinkedList的区别?

7. 为什么ArrayList不是线程安全的,具体来说是哪里不安全?

8. ArrayList的扩容机制说一下

9. 线程安全的 List, CopyonWriteArraylist是如何实现线程安全的?

Map

10. 如何对map进行快速遍历?

11. HashMap实现原理介绍一下?

12. 了解的哈希冲突解决方法有哪些?

13. HashMap是线程安全的吗?

14. HashMap的put(key,val)和get(key)过程

15. HashMap一般用什么做Key?为啥String适合做Key呢?

16. 为什么HashMap要用红黑树而不是平衡二叉树?

17. hashmap key可以为null吗?

18. 重写HashMap的equal和hashcode方法需要注意什么?

19. 列举HashMap在多线程下可能会出现的问题?

20. HashMap的扩容机制介绍一下

21. HashMap的大小为什么是2的n次方大小呢?

22. 说说hashmap的负载因子

23. Hashmap和Hashtable有什么不一样的?Hashmap一般怎么用?

24. ConcurrentHashMap怎么实现的?

25. ConcurrentHashMap用了悲观锁还是乐观锁?

26. HashTable 底层实现原理是什么?

27. HashTable线程安全是怎么实现的?

28. hashtable 和concurrentHashMap有什么区别?

29. 说一下HashMap和Hashtable、ConcurrentMap的区别?

Set

30. Set集合有什么特点?如何实现key无重复的?

31. 有序的Set是什么?记录插入顺序的集合是什么?


概念

1. 说说Java中的集合?

用过的一些 Java 集合类:

  1. ArrayList: 动态数组,实现了 List 接口,支持动态增长。
  2. LinkedList: 双向链表,也实现了 List 接口,支持快速的插入和删除操作。
  3. HashMap: 基于哈希表的 Map 实现,存储键值对,通过键快速查找值。
  4. HashSet: 基于 HashMap 实现的 Set 集合,用于存储唯一元素。
  5. TreeMap: 基于红黑树实现的有序 Map 集合,可以按照键的顺序进行排序。
  6. LinkedHashMap: 基于哈希表和双向链表实现的 Map 集合,保持插入顺序或访问顺序。

2. Java中的线程安全的集合有什么?

在 java.util 包中的线程安全的类主要 2 个,其他都是非线程安全的。

  • Vector:线程安全的动态数组,其内部方法基本都经过 synchronized 修饰,如果不需要线程安全,并不建议选择,毕竟同步是有额外开销的。Vector 内部是使用对象数组来保存数据,可以根据需要自动的增加容量,当数组已满时,会创建新的数组,并拷贝原有数组数据。
  • Hashtable:线程安全的哈希表,HashTable 的加锁方法是给每个方法加上 synchronized 关键字,这样锁住的是整个 Table 对象,不支持 null 键和值,由于同步导致的性能开销,所以已经很少被推荐使用,如果要保证线程安全的哈希表,可以用 ConcurrentHashMap

java.util.concurrent 包提供的都是线程安全的集合:

并发 Map:

  • ConcurrentHashMap:它与 HashTable 的主要区别是二者加锁粒度的不同,在 JDK1.7,ConcurrentHashMap 加的是分段锁,也就是 Segment 锁,每个 Segment 含有整个 table 的一部分,这样不同分段之间的并发操作就互不影响。在 JDK 1.8 ,它取消了 Segment 字段,直接在 table 元素上加锁,实现对每一行进行加锁,进一步减小了并发冲突的概率。对于 put 操作,如果 Key 对应的数组元素为 null,则通过 CAS 操作(Compare and Swap)将其设置为当前值。如果 Key 对应的数组元素(也即链表表头或者树的根元素)不为 null,则对该元素使用 synchronized 关键字申请锁,然后进行操作。如果该 put 操作使得当前链表长度超过一定阈值,则将该链表转换为红黑树,从而提高寻址效率。
  • ConcurrentSkipListMap:实现了一个基于 SkipList(跳表)算法的可排序的并发集合,SkipList 是一种可以在对数预期时间内完成搜索、插入、删除等操作的数据结构,通过维护多个指向其他元素的 “跳跃” 链接来实现高效查找。

并发 Set:

  • ConcurrentSkipListSet:是线程安全的有序的集合。底层是使用 ConcurrentSkipListMap 实现。
  • CopyOnWriteArraySet:是线程安全的 Set 实现,它是线程安全的无序的集合,可以将它理解成线程安全的 HashSet。有意思的是,CopyOnWriteArraySet 和 HashSet 虽然都继承于共同的父类 AbstractSet;但是,HashSet 是通过 “散列表” 实现的,而 CopyOnWriteArraySet 则是通过 “动态数组(CopyOnWriteArrayList)” 实现的,并不是散列表。

并发 List:

  • CopyOnWriteArrayList:它是 ArrayList 的线程安全的变体,其中所有写操作(addset 等)都通过对底层数组进行全新复制来实现,允许存储 null 元素。即当对象进行写操作时,使用了 Lock 锁做同步处理,内部拷贝了原数组,并在新数组上进行添加操作,最后将新数组替换掉旧数组;若进行的读操作,则直接返回结果,操作过程中不需要进行同步。

并发 Queue:

  • ConcurrentLinkedQueue:是一个适用于高并发场景下的队列,它通过无锁的方式(CAS),实现了高并发状态下的高性能。通常,ConcurrentLinkedQueue 的性能要好于 BlockingQueue 。
  • BlockingQueue:与 ConcurrentLinkedQueue 的使用场景不同,BlockingQueue 的主要功能并不在于提升高并发时的队列性能,而在于简化多线程间的数据共享。BlockingQueue 提供一种读写阻塞等待的机制,即如果消费者速度较快,则 BlockingQueue 则可能被清空,此时消费线程再试图从 BlockingQueue 读取数据时就会被阻塞。反之,如果生产线程较快,则 BlockingQueue 可能会被装满,此时,生产线程再试图向 BlockingQueue 队列装入数据时,便会被阻塞等待。

并发 Deque:

  • LinkedBlockingDeque:是一个线程安全的双端队列实现。它的内部使用链表结构,每一个节点都维护了一个前驱节点和一个后驱节点。LinkedBlockingDeque 没有进行读写锁的分离,因此同一时间只能有一个线程对其进行操作
  • ConcurrentLinkedDequeConcurrentLinkedDeque 是一种基于链接节点的无限并发链表。可以安全地并发执行插入、删除和访问操作。当许多线程同时访问一个公共集合时,ConcurrentLinkedDeque 是一个合适的选择。

3. Collections和Collection的区别?

  • Collection:是 Java 集合框架中的一个接口,它是所有集合类的基础接口。它定义了一组通用的操作和方法,如添加、删除、遍历等,用于操作和管理一组对象。Collection 接口有许多实现类,如 ListSet 和 Queue 等。
  • Collections(注意有一个 s):是 Java 提供的一个工具类,位于 java.util 包中。它提供了一系列静态方法,用于对集合进行操作和算法。Collections 类中的方法包括排序、查找、替换、反转、随机化等等。这些方法可以对实现了 Collection 接口的集合进行操作,如 List 和 Set

4. 集合遍历的方法有哪些?

在 Java 中,集合的遍历方法主要有以下几种:

  • 普通 for 循环:可以使用带有索引的普通 for 循环来遍历 List
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");

for (int i = 0; i < list.size(); i++) {
    String element = list.get(i);
    System.out.println(element);
}
  • 增强 for 循环(for-each 循环):用于循环访问数组或集合中的元素。
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");

for (String element : list) {
    System.out.println(element);
}
  • Iterator 迭代器:可以使用迭代器来遍历集合,特别适用于需要删除元素的情况。
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");

Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
    String element = iterator.next();
    System.out.println(element);
}
  • ListIterator 列表迭代器ListIterator 是迭代器的子类,可以双向访问列表并在迭代过程中修改元素。
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");

ListIterator<String> listIterator= list.listIterator();
while(listIterator.hasNext()) {
    String element = listIterator.next();
    System.out.println(element);
}

  • 使用 forEach 方法: Java 8 引入了 forEach 方法,可以对集合进行快速遍历。
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");

list.forEach(element -> System.out.println(element));
  • Stream API: Java 8 的 Stream API 提供了丰富的功能,可以对集合进行函数式操作,如过滤、映射等。
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");

list.stream().forEach(element -> System.out.println(element));

List

常见的 List 集合(非线程安全):

  • ArrayList :基于动态数组实现,它允许快速的随机访问,即通过索引访问元素的时间复杂度为 O (1)。在添加和删除元素时,如果操作位置不是列表末尾,可能需要移动大量元素,性能相对较低。适用于需要频繁随机访问元素,而对插入和删除操作性能要求不高的场景,如数据的查询和展示等。
  • LinkedList :基于双向链表实现,在插入和删除元素时,只需修改链表的指针,不需要移动大量元素,时间复杂度为 O (1)。但随机访问元素时,需要从链表头或链表尾开始遍历,时间复杂度为 O (n)。适用于需要频繁进行插入和删除操作的场景,如队列、栈等数据结构的实现,以及需要在列表中间频繁插入和删除元素的情况。

常见的 List 集合(线程安全):

  • Vector :和 ArrayList 类似,也是基于数组实现。Vector 中的方法大多是同步的,这使得它在多线程环境下可以保证数据的一致性,但在单线程环境下,由于同步带来的开销,性能会略低于 ArrayList 。
  • CopyOnWriteArrayList :在对列表进行修改(如添加、删除元素)时,会创建一个新的底层数组,将修改操作应用到新数组上,而读操作仍然在原数组上进行,这样可以保证读操作不会被写操作阻塞,实现了读写分离,提高了并发性能。适用于读操作远远多于写操作的并发场景,如事件监听列表等,在这种场景下可以避免大量的锁竞争,提高系统的性能和响应速度。

 

5. 讲一下java里面list的几种实现,几种实现有什么不同?

  • ArrayList:是应用更加广泛的动态数组实现,它本身不是线程安全的,所以性能要好很多。与 Vector 近似,ArrayList 也是可以根据需要调整容量,不过两者的调整逻辑有所区别,Vector 在扩容时会提高 1 倍,而 ArrayList 则是增加 50%。
  • LinkedList:顾名思义是 Java 提供的双向链表,所以它不需要像上面两种那样调整容量,它也不是线程安全的。

这几种实现具体在什么场景下应该用哪种?

  • Vector 和 ArrayList:作为动态数组,其内部元素以数组形式顺序存储的,所以
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值