目录
5. 讲一下java里面list的几种实现,几种实现有什么不同?
7. 为什么ArrayList不是线程安全的,具体来说是哪里不安全?
9. 线程安全的 List, CopyonWriteArraylist是如何实现线程安全的?
14. HashMap的put(key,val)和get(key)过程
15. HashMap一般用什么做Key?为啥String适合做Key呢?
18. 重写HashMap的equal和hashcode方法需要注意什么?
23. Hashmap和Hashtable有什么不一样的?Hashmap一般怎么用?
25. ConcurrentHashMap用了悲观锁还是乐观锁?
28. hashtable 和concurrentHashMap有什么区别?
29. 说一下HashMap和Hashtable、ConcurrentMap的区别?

概念
1. 说说Java中的集合?
用过的一些 Java 集合类:
- ArrayList: 动态数组,实现了
List接口,支持动态增长。 - LinkedList: 双向链表,也实现了
List接口,支持快速的插入和删除操作。 - HashMap: 基于哈希表的
Map实现,存储键值对,通过键快速查找值。 - HashSet: 基于
HashMap实现的Set集合,用于存储唯一元素。 - TreeMap: 基于红黑树实现的有序
Map集合,可以按照键的顺序进行排序。 - 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的线程安全的变体,其中所有写操作(add,set等)都通过对底层数组进行全新复制来实现,允许存储null元素。即当对象进行写操作时,使用了Lock锁做同步处理,内部拷贝了原数组,并在新数组上进行添加操作,最后将新数组替换掉旧数组;若进行的读操作,则直接返回结果,操作过程中不需要进行同步。
并发 Queue:
ConcurrentLinkedQueue:是一个适用于高并发场景下的队列,它通过无锁的方式(CAS),实现了高并发状态下的高性能。通常,ConcurrentLinkedQueue的性能要好于BlockingQueue。BlockingQueue:与ConcurrentLinkedQueue的使用场景不同,BlockingQueue的主要功能并不在于提升高并发时的队列性能,而在于简化多线程间的数据共享。BlockingQueue提供一种读写阻塞等待的机制,即如果消费者速度较快,则BlockingQueue则可能被清空,此时消费线程再试图从BlockingQueue读取数据时就会被阻塞。反之,如果生产线程较快,则BlockingQueue可能会被装满,此时,生产线程再试图向BlockingQueue队列装入数据时,便会被阻塞等待。
并发 Deque:
LinkedBlockingDeque:是一个线程安全的双端队列实现。它的内部使用链表结构,每一个节点都维护了一个前驱节点和一个后驱节点。LinkedBlockingDeque没有进行读写锁的分离,因此同一时间只能有一个线程对其进行操作ConcurrentLinkedDeque:ConcurrentLinkedDeque是一种基于链接节点的无限并发链表。可以安全地并发执行插入、删除和访问操作。当许多线程同时访问一个公共集合时,ConcurrentLinkedDeque是一个合适的选择。
3. Collections和Collection的区别?
Collection:是 Java 集合框架中的一个接口,它是所有集合类的基础接口。它定义了一组通用的操作和方法,如添加、删除、遍历等,用于操作和管理一组对象。Collection接口有许多实现类,如List、Set和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:作为动态数组,其内部元素以数组形式顺序存储的,所以

455

被折叠的 条评论
为什么被折叠?



