集合框架和数组的区别
为什么引入集合概念
使用数组具有局限性:是一种固定大小的数据结构,其元素类型和数量在创建时就已经确定,并且无法更改,不使用就浪费了。
为了解决数组的局限性,引入容器类的概念。容器可以根据需要动态地增加或减少元素。此外,集合框架还提供了丰富的操作方法,如添加、删除、查找、排序等,使得操作数据更加便捷。
区别:
1.存储方式
数组:数组是一种固定大小的数据结构,其元素类型和数量在创建时就已经确定,并且无法更改。数组通过索引来访问元素,索引从0开始,直到数组长度减1。
集合框架:集合框架提供了多种数据结构,如列表(List)、集合(Set)、映射(Map)等,这些数据结构可以根据需要动态地改变大小。集合框架中的容器类可以存储不同类型的对象,并通过迭代器(Iterator)或增强的for循环进行遍历。
2. 灵活性
数组:数组的灵活性较差,一旦创建,其大小就不能改变。如果需要存储更多元素,必须创建一个新的数组,并将旧数组的元素复制到新数组中。
集合框架:集合框架提供了多种灵活的容器类,这些容器类可以根据需要动态地增加或减少元素。此外,集合框架还提供了丰富的操作方法,如添加、删除、查找、排序等,使得操作数据更加便捷。
3. 数据类型
数组:数组中的元素类型必须相同,这是由数组在创建时指定的数据类型决定的。
集合框架:集合框架中的容器类可以存储不同类型的对象,甚至可以是空对象(null)。此外,集合框架还提供了泛型支持,可以在创建容器时指定元素类型,从而避免类型转换错误。
4. 内存管理
数组:数组是静态分配的,即在创建时就分配了固定大小的内存空间。如果数组过大,会浪费内存资源;如果数组过小,则无法满足存储需求。
集合框架:集合框架中的容器类通常是动态分配的,即根据实际需要动态地分配内存空间。这样可以更有效地利用内存资源,避免内存浪费。
5. 迭代方式
数组:数组的迭代通常使用传统的for循环或增强的for循环(foreach)。
集合框架:集合框架提供了迭代器(Iterator)接口和增强的for循环(foreach)来遍历容器中的元素。迭代器提供了一种统一的方式来遍历不同类型的集合容器,而增强的for循环则提供了一种更简洁的语法来遍历集合容器中的元素。
6. 线程安全性
数组:数组本身是线程不安全的,即多个线程可以同时访问和修改同一个数组的元素,这可能导致数据不一致的问题。
集合框架:集合框架中的部分容器类(如Vector、Hashtable等)是线程安全的,它们内部使用了同步机制来确保线程安全。然而,大多数容器类(如ArrayList、HashMap等)并不是线程安全的。如果需要线程安全的集合容器,可以使用Collections类中的静态方法将非线程安全的容器类包装成线程安全的容器类,或者使用Java并发包(java.util.concurrent)中的并发集合类。
JAVA集合框架图
(为方便理解,下图转载自菜鸟教程,地址是:Java 集合框架 | 菜鸟教程)
集合遍历的主要方法:
- 用for循环遍历
- 迭代器遍历
- 用增强型for循环
for循环遍历
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
迭代器遍历
Set<String> set = new HashSet<>();
set.add("X");
set.add("Y");
set.add("Z");
Iterator<String> setIterator = set.iterator();
while (setIterator.hasNext()) {
System.out.println(setIterator.next());
}
增强型for循环
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
for (String item : list) {
System.out.println(item);
}
1.Collection接口
Collection
接口继承自Iterable
接口- Collection:这是集合框架的根接口,提供了集合的基本操作,如添加、删除、遍历等。这个接口本身并不直接实现任何功能,而是由它的子接口(如List、Set和Queue)来提供具体的实现。同时也是Deque的接口,但是Deque不是Collection的直接子接口,Deque是Queue的直接子接口。
- 不能直接实例化
Collection
接口,而是需要使用它的实现类,如ArrayList
、LinkedList
、HashSet
等
接口主要方法:
- boolean add(E e):向集合中添加一个元素。如果集合因为调用而发生变化(即该元素尚未存在于集合中),则返回true。
- boolean remove(Object o):从集合中移除一个指定的元素。如果集合包含指定的元素,则移除它并返回true。
- boolean contains(Object o):检查集合是否包含指定的元素。
- int size():返回集合中的元素数量。
- boolean isEmpty():检查集合是否为空。
- Iterator<E> iterator():返回一个迭代器,用于遍历集合中的元素。
- Object[] toArray():返回一个包含集合中所有元素的数组。
- <T> T[] toArray(T[] a):返回一个包含集合中所有元素的数组,其运行时类型与指定数组的类型相同(如果数组足够大以容纳集合中的所有元素;否则,返回一个具有指定类型的新数组,其长度为集合的大小)。
- boolean containsAll(Collection<?> c):检查集合是否包含指定集合中的所有元素。
- boolean addAll(Collection<? extends E> c):将指定集合中的所有元素添加到该集合中(可选操作)。
- boolean removeAll(Collection<?> c):从集合中移除指定集合中包含的所有元素(可选操作)。
- boolean retainAll(Collection<?> c):仅保留集合中包含在指定集合中的元素(可选操作)。
- void clear():从集合中移除所有元素(可选操作)。
2.List接口
这是Collection接口的子接口,代表一个有序、可重复的集合。List集合中的每个元素都有其对应的顺序索引,因此可以通过索引来访问元素。
接口的实现类:
1.ArrayList类:
基于数组实现,因此具有较快的随机访问速度。但ArrayList在添加或删除元素时可能需要移动其他元素,因此性能可能受到影响。
import java.util.ArrayList; // 引入 ArrayList 类
ArrayList<E> objectName =new ArrayList<>(); // 初始化
E: 泛型数据类型。
以下情况使用 ArrayList :
- 频繁访问列表中的某一个元素。
- 只需要在列表末尾进行添加和删除元素操作。
2.LinkedList类:
这也是List接口的一个实现类,但它基于链表实现。LinkedList在添加或删除元素时性能较好,因为只需要修改相邻元素的指针。但LinkedList的随机访问速度较慢。
以下情况使用 LinkedList :
- 你需要通过循环迭代来访问列表中的某些元素。
- 需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作。
LinkedList 实现了 Queue 接口,可作为队列使用。
LinkedList 实现了 List 接口,可进行列表的相关操作。
LinkedList 实现了 Deque 接口,可作为队列使用。
LinkedList 实现了 Cloneable 接口,可实现克隆。
3.Set接口
这也是Collection接口的子接口,但Set集合中的元素是无序的,且不允许重复。Set接口主要用于判重和去重操作。不能通过get方法得到index(下标)。
接口的实现类:
1.HashSet类:
- 这是Set接口的一个实现类,它基于哈希表实现。HashSet提供了较快的查找和添加速度,但不保证元素的顺序。HashSet允许存储null值。
- HashSet 是无序的,即不会记录插入的顺序。
- HashSet 不是线程安全的, 如果多个线程尝试同时修改 HashSet,则最终结果是不确定的。 您必须在多线程访问时显式同步对 HashSet 的并发访问。
- HashSet 中的元素实际上是对象,一些常见的基本类型可以使用它的包装类。
import java.util.HashSet; // 引入 HashSet 类
HashSet<String> sites = new HashSet<String>();
2.LinkedHashSet类:
主要特性:
- 有序性:
LinkedHashSet
维护元素的插入顺序。- 唯一性:像
HashSet
一样,LinkedHashSet
不允许有重复元素。- 性能:由于底层使用了哈希表(HashMap),
LinkedHashSet
提供了接近 O(1) 的时间复杂度来进行查找、插入和删除操作。- 迭代器:
LinkedHashSet
的迭代器按照元素的插入顺序返回元素。- 非同步:
LinkedHashSet
不是线程安全的,如果在多线程环境中使用,需要额外的同步机制。
使用场景:
- 需要维护元素插入顺序的集合。
- 需要快速查找、插入和删除操作的集合。
3.TreeSet类:
这是SortedSet接口的实现类,它基于红黑树实现。TreeSet可以保证元素处于排序状态,支持自然排序和定制排序。但TreeSet的添加、删除和查找操作的时间复杂度为O(logN)。
主要特性:
- 排序:
TreeSet
维护元素的自然顺序(或者通过提供的比较器排序)。- 唯一性:
TreeSet
不允许有重复元素。- 性能:
TreeSet
的查找、插入和删除操作的时间复杂度通常是 O(log n),因为它基于红黑树实现。- 迭代器:
TreeSet
的迭代器按照元素的排序顺序返回元素。- 非同步:
TreeSet
不是线程安全的,如果在多线程环境中使用,需要额外的同步机制。
使用场景
- 需要对集合中的元素进行排序的集合。
- 需要快速查找、插入和删除操作的集合,同时这些操作需要保持元素的排序顺序。
4.Map接口
Map不是Collection的子接口,但它是Java集合框架中另一个重要的接口。Map接口用于存储键值对(key-value pairs),其中每个键都是唯一的,并且映射到一个值上。Map接口提供了对键值对的添加、删除、查找等操作。
接口的实现类:
1.HashMap:
主要特性:
- HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
- HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。
- HashMap 是无序的,即不会记录插入的顺序。
HashMap 中的元素实际上是对象,一些常见的基本类型可以使用它的包装类。这点和HashSet类似。
2.LinkedHashMap:
主要特性:
LinkedHashMap
是HashMap
的一个子类,它维护了一个双向链表来记录插入顺序或访问顺序。- 如果指定为访问顺序,则每次调用
get
方法后,该键值对会被移动到链表的末尾(这是“最近最少使用”(LRU)缓存策略的基础)。它同样允许一个null
键和多个null
值。
3.TreeMap:
主要特性:
TreeMap
是基于红黑树实现的一个有序的Map
。- 它根据键的自然顺序进行排序,或者根据创建
TreeMap
时提供的Comparator
进行排序。TreeMap
不允许null
键,但允许null
值。
4.ConcurrentHashMap:
主要特性:
ConcurrentHashMap
是一个线程安全的哈希表,它允许并发的读取和一定量的并发写入。- 与
Hashtable
相比,ConcurrentHashMap
提供了更高的并发性能,因为它在内部使用了分段锁(在Java 8及更高版本中,这一实现细节有所改变,但总体上仍然是高并发的)。- 它不允许
null
键和null
值。
5.Queue接口
代表一个队列集合——先进先出
常用方法:
- add(E e):将元素e添加到队列的尾部,如果队列已满,则抛出IllegalStateException。
- offer(E e):将元素e添加到队列的尾部,如果队列已满,则返回false。
- put(E e):将元素e添加到队列的尾部,如果队列已满,则阻塞当前线程直到队列有空间。
- remove():移除并返回队列头部的元素,如果队列为空,则抛出NoSuchElementException。
- poll():移除并返回队列头部的元素,如果队列为空,则返回null。
- take():移除并返回队列头部的元素,如果队列为空,则阻塞当前线程直到队列有元素。
- element():返回队列头部的元素但不移除它,如果队列为空,则抛出NoSuchElementException。
- peek():返回队列头部的元素但不移除它,如果队列为空,则返回null。
接口的实现类:
-
LinkedList:
- LinkedList实现了Deque接口,而Deque是Queue的子接口,因此LinkedList也可以作为Queue使用。
- LinkedList是一个基于链表结构的双向队列,允许在队列的两端进行插入和删除操作。
-
PriorityQueue:
- PriorityQueue是一个基于优先级堆的无界优先级队列。
- 元素按照优先级顺序被移除,而不是按照它们被添加的顺序。
- PriorityQueue不保证同优先级元素的顺序。
-
ArrayDeque:
- ArrayDeque是一个基于数组的双端队列。
- 它不是线程安全的,并且性能通常比LinkedList好,特别是在作为栈或队列使用时。
-
ConcurrentLinkedQueue:
- ConcurrentLinkedQueue是一个基于链接节点的、线程安全的无界队列。
- 它支持高效的并发访问,并且不需要在访问时进行同步。
-
ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、DelayQueue、SynchronousQueue(均为阻塞队列):
- 这些队列都实现了BlockingQueue接口,该接口扩展了Queue接口以支持阻塞操作。
- 阻塞队列在尝试添加元素到已满的队列或从空队列中移除元素时,线程会被阻塞,直到操作可以成功完成。
6.Deque接口
Deque接口代表一个双端队列,它允许在队列的两端进行插入和删除操作。Deque接口继承自Queue接口,并扩展了其功能,使其能够更高效地在两端进行操作。
常用方法:
- addFirst(E e):在队列的开头插入元素e。
- addLast(E e):在队列的末尾插入元素e。
- offerFirst(E e):在队列的开头插入元素e,如果队列已满,则返回false(对于ArrayDeque,这永远不会发生,因为它是无界的)。
- offerLast(E e):在队列的末尾插入元素e,如果队列已满,则返回false(同样,对于ArrayDeque,这永远不会发生)。
- removeFirst():移除并返回队列的第一个元素。
- removeLast():移除并返回队列的最后一个元素。
- pollFirst():移除并返回队列的第一个元素,如果队列为空,则返回null。
- pollLast():移除并返回队列的最后一个元素,如果队列为空,则返回null。
- getFirst():获取但不移除队列的第一个元素。
- getLast():获取但不移除队列的最后一个元素。
- peekFirst():获取但不移除队列的第一个元素,如果队列为空,则返回null。
- peekLast():获取但不移除队列的最后一个元素,如果队列为空,则返回null。
接口的实现类:
-
ArrayDeque:
- ArrayDeque是一个基于数组的双端队列实现,它不是线程安全的。
- 它提供了在队列两端进行高效插入和删除操作的能力。
- 由于它是基于数组的,因此它避免了链表节点所需的额外内存开销,并且通常具有比LinkedList更好的性能。
-
LinkedList:
- 虽然LinkedList主要被看作是一个链表实现的List,但它也实现了Deque接口。
- 这意味着LinkedList也可以作为双端队列使用,允许在队列的两端进行插入和删除操作。
- 然而,由于LinkedList是基于链表的,它的性能可能不如ArrayDeque,特别是在需要大量随机访问的情况下。