JAVA集合知识点总结
说说 List, Set, Queue, Map 四者的区别?
- List:存储的元素是有序的、可重复的。
- Set: 存储的元素不可重复的。
- Queue: 按特定的排队规则来确定先后顺序,存储的元素是有序的、可重复的。
- Map: 使用键值对(key-value)存储,key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值。
List
ArrayList 和 数组 的区别?
ArrayList 内部基于动态数组实现,比 Array(静态数组) 使用起来更加灵活:
- ArrayList会根据实际存储的元素动态地扩容或缩容,而- Array被创建之后就不能改变它的长度了。
- ArrayList允许你使用泛型来确保类型安全,- Array则不可以。
- ArrayList中只能存储对象。对于基本类型数据,需要使用其对应的包装类(如 Integer、Double 等)。- Array可以直接存储基本类型数据,也可以存储对象。
- ArrayList支持插入、删除、遍历等常见操作,并且提供了丰富的 API 操作方法,比如- add()、- remove()等。- Array只是一个固定长度的数组,只能按照下标访问其中的元素,不具备动态添加、删除元素的能力。
- ArrayList创建时不需要指定大小,而- Array创建时必须指定大小。
ArrayList 和 Vector 的区别?
- Vector线程安全,ArrayList线程不安全。
Vector 和 Stack 的区别?
- Vector和- Stack两者都是线程安全的,都是使用- synchronized关键字进行同步处理。
- Stack继承自- Vector,是一个后进先出的栈,而- Vector是一个列表。
随着 Java 并发编程的发展,Vector 和 Stack 已经被淘汰,推荐使用并发集合类(例如 ConcurrentHashMap、CopyOnWriteArrayList 等)或者手动实现线程安全的方法来提供安全的多线程操作支持。
LinkedList 为什么不能实现 RandomAccess 接口?
由于 LinkedList 底层数据结构是链表,内存地址不连续,只能通过指针来定位,不支持随机快速访问,所以不能实现 RandomAccess 接口。
ArrayList 与 LinkedList 区别?
- 是否保证线程安全: ArrayList和LinkedList都是不同步的,也就是不保证线程安全;
- 底层数据结构: ArrayList底层使用的是Object数组;LinkedList底层使用的是 双向链表 ;
- 插入和删除是否受元素位置的影响: - ArrayList采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。
- LinkedList采用链表存储,所以在头尾插入或者删除元素不受元素位置的影响。
 
- 是否支持快速随机访问: LinkedList不支持高效的随机元素访问,而ArrayList(实现了RandomAccess接口) 支持。
- 内存空间占用:ArrayList的空间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。
Queue
ArrayDeque 与 LinkedList 的区别
ArrayDeque 和 LinkedList 都实现了 Deque 接口,两者都具有队列的功能,但两者有什么区别呢?
- ArrayDeque是基于可变长的数组和双指针来实现,而- LinkedList则通过链表来实现。
- ArrayDeque不支持存储- NULL数据,但- LinkedList支持。
- ArrayDeque是在 JDK1.6 才被引入的,而- LinkedList早在 JDK1.2 时就已经存在。
- ArrayDeque插入时可能存在扩容过程, 不过均摊后的插入操作依然为 O(1)。虽然- LinkedList不需要扩容,但是每次插入数据时均需要申请新的堆空间,均摊性能相比更慢。
从性能的角度上,选用 ArrayDeque 来实现队列要比 LinkedList 更好。此外,ArrayDeque 也可以用于实现栈。
什么是 BlockingQueue?
BlockingQueue (阻塞队列)是一个接口,继承自 Queue。BlockingQueue阻塞的原因是其支持当队列没有元素时一直阻塞,直到有元素;还支持如果队列已满,一直等到队列可以放入新元素时再放入。
Java 中常用的阻塞队列实现类有以下几种:
- ArrayBlockingQueue:使用数组实现的有界阻塞队列。在创建时需要指定容量大小,并支持公平和非公平两种方式的锁访问机制。
- LinkedBlockingQueue:使用单向链表实现的可选有界阻塞队列。在创建时可以指定容量大小,如果不指定则默认为- Integer.MAX_VALUE。和- ArrayBlockingQueue不同的是, 它仅支持非公平的锁访问机制。
- PriorityBlockingQueue:支持优先级排序的无界阻塞队列。元素必须实现- Comparable接口或者在构造函数中传入- Comparator对象,并且不能插入 null 元素。
ArrayBlockingQueue 和 LinkedBlockingQueue 有什么区别
ArrayBlockingQueue 和 LinkedBlockingQueue 是 Java 并发包中常用的两种阻塞队列实现,它们都是线程安全的。不过,不过它们之间也存在下面这些区别:
- 底层实现:ArrayBlockingQueue基于数组实现,而LinkedBlockingQueue基于链表实现。
- 是否有界:ArrayBlockingQueue是有界队列,必须在创建时指定容量大小。LinkedBlockingQueue创建时可以不指定容量大小,默认是Integer.MAX_VALUE,也就是无界的。但也可以指定队列大小,从而成为有界的。
- 锁是否分离: ArrayBlockingQueue中的锁是没有分离的,即生产和消费用的是同一个锁;LinkedBlockingQueue中的锁是分离的,即生产用的是putLock,消费是takeLock,这样可以防止生产者和消费者线程之间的锁争夺。
- 内存占用:ArrayBlockingQueue需要提前分配数组内存,而LinkedBlockingQueue则是动态分配链表节点内存。这意味着,ArrayBlockingQueue在创建时就会占用一定的内存空间,且往往申请的内存比实际所用的内存更大,而LinkedBlockingQueue则是根据元素的增加而逐渐占用内存空间。
Map
HashMap 和 Hashtable 的区别?
- 线程是否安全: HashMap是非线程安全的,Hashtable是线程安全的,因为Hashtable内部的方法基本都经过synchronized修饰。
- 对 Null key 和 Null value 的支持:HashMap可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个;Hashtable 不允许有 null 键和 null 值,否则会抛出NullPointerException。
- 初始容量大小和每次扩充容量大小的不同: ① 创建时如果不指定容量初始值,Hashtable默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1。HashMap默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。② 创建时如果给定了容量初始值,那么Hashtable会直接使用你给定的大小,而HashMap会将其扩充为 2 的幂次方大小。也就是说HashMap总是使用 2 的幂作为哈希表的大小,后面会介绍到为什么是 2 的幂次方。
- 底层数据结构: JDK1.8 以后的 HashMap在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树),以减少搜索时间。Hashtable没有这样的机制。
ConcurrentHashMap 和 Hashtable 的区别?
实现线程安全的方式(重要):
- 在 JDK1.7 的时候,ConcurrentHashMap对整个桶数组进行了分割分段(Segment,分段锁),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。
- 到了 JDK1.8 的时候,ConcurrentHashMap已经摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用synchronized和 CAS 来操作。
- Hashtable(同一把锁) :使用- synchronized来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。