Java集合框架,通常被称为容器,是一系列定义在java.util
包中的接口和实现类。它的核心功能是将多个元素组织到一个单元里,以便用户能够对这些元素进行高效且方便的存储、检索和管理。
这张图是Java集合框架的结构图,展示了Java中不同集合类型的层次和关系。在最顶层是Iterable
接口,它是所有集合类的根接口,提供了遍历集合元素的能力。下一级是Collection
接口,它继承自Iterable
,并提供了集合的基本功能,如添加、删除和遍历元素。
Collection
接口有下面几个子接口:
List
:一个有序集合,可以包含重复的元素。ArrayList
和LinkedList
都是实现了List
接口的类,其中ArrayList
基于数组,LinkedList
基于链表结构。Queue
:一个用于保持元素先进先出(FIFO)顺序的集合。LinkedList
也实现了Queue
接口。Deque
:双端队列接口,扩展了Queue
接口,元素可以从两端插入或移除。LinkedList
实现了这个接口。Set
:一个不允许重复元素的集合。HashSet
和TreeSet
都是实现了Set
接口的类,其中HashSet
基于哈希表,TreeSet
基于红黑树,能自动排序。Map
不是Collection
的子接口,但它也是集合框架的一部分。Map
接口定义了键值对的存储和访问,HashMap
和TreeMap
是其两个主要实现。
HashMap
:底层为哈希桶,查询时间复杂度为O(1)TreeMap
:底层为红黑树,查询的时间复杂度为O(辅助类和接口:
Arrays
和Collections
是包含静态方法的工具类,用于操作数组和集合。Iterator
和ListIterator
是接口,用于遍历集合元素。Comparable
和Comparator
是接口,用于定义对象的比较规则,通常用于排序。在Java的集合框架中,List
是一个接口,继承自Collection
,它定义了可以存储一系列有序元素的操作和方法。这些元素可以通过整数索引进行访问和操作。
任何List
对象也是一个Collection
,它继承了Collection
接口的所有方法。同时,因为Collection
接口扩展了Iterable
接口,这代表所有的List
对象也都是Iterable
的,可以使用迭代器进行元素遍历。
List
官方文档:List官方文档
其中较为常用的方法如下表:
方法签名 | 解释 |
---|---|
boolean add(E e) |
尾插元素 e |
void add(int index, E element) |
将元素 e 插入到下标 index 位置 |
boolean addAll(Collection<? extends E> c) |
尾插集合 c 中的所有元素 |
E remove(int index) |
删除下标 index 位置的元素 |
boolean remove(Object o) |
删除遇到的第一个元素 o |
E get(int index) |
获取下标 index 位置的元素 |
E set(int index, E element) |
将下标 index 位置的元素设置为 element |
void clear() |
清空列表 |
boolean contains(Object o) |
判断元素 o 是否在列表中 |
int indexOf(Object o) |
返回元素 o 第一次出现的下标 |
int lastIndexOf(Object o) |
返回元素 o 最后一次出现的下标 |
List<E> subList(int fromIndex, int toIndex) |
截取从下标 fromIndex 到 toIndex (不包括)的部分列表 |
注意:List
是个接口,并不能直接用来实例化。
如果要使用List
,必须去实例化List
的实现类。在集合框架中,ArrayList
和LinkedList
都实现了List
接口。
在集合框架中,ArrayList
是一个普通的类,实现了List接口,具体框架图如下:
ArrayList
在 Java 中是一种顺序表的实现。它底层是使用数组来存储元素的,因此它能够提供快速的随机访问能力。ArrayList
的数组是动态扩容的,当数组达到其容量极限时,会创建一个新的更大的数组,并将旧数组的内容复制到新数组中。
ArrayList
是以泛型方式实现的,使用时必须要先实例化。ArrayList
实现了RandomAccess
接口,表明ArrayList
支持随机访问。ArrayList
实现了Cloneable
接口,表明ArrayList
是可以clone
的。ArrayList
实现了Serializable
接口,表明ArrayList
是支持序列化的。ArrayList
不是线程安全的,在单线程下可以使用,在多线程中最好选择CopyOnWriteArrayList
。ArrayList
底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表。方法签名 | 解释 |
---|---|
boolean add(E e) |
在列表尾部添加元素 e 。 |
void add(int index, E element) |
在列表的指定 index 位置插入元素 e 。 |
boolean addAll(Collection<? extends E> c) |
将集合 c 中的所有元素添加到列表的尾部。 |
E remove(int index) |
移除列表中指定 index 位置的元素,并返回该元素。 |
boolean remove(Object o) |
移除列表中遇到的第一个元素 o ,如果列表包含指定的元素,则返回 true 。 |
E get(int index) |
返回列表中指定 index 位置的元素。 |
E set(int index, E element) |
替换列表中指定 index 位置的元素为 element ,并返回原来的元素。 |
void clear() |
移除列表中的所有元素。 |
boolean contains(Object o) |
如果列表包含指定的元素 o ,则返回 true 。 |
int indexOf(Object o) |
返回列表中第一次出现元素 o 的 index 。 |
int lastIndexOf(Object o) |
返回列表中最后出现元素 o 的 index 。 |
List<E> subList(int fromIndex, int toIndex) |
返回列表中从 fromIndex (包含)到 toIndex (不包含)的部分视图。 |
ArrayList
可以使用三种方式进行遍历:for
循环+下标、for-each
、迭代器
1 | public class demo1 { |
这三种方法都有其适用场景:
for-each
循环时修改集合,将会抛出 ConcurrentModificationException
异常。使用迭代器的 remove()
方法可以避免这个问题。以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10。
int newCapacity = oldCapacity + (oldCapacity >> 1)
所以, ArrayList 每次扩容之后容量都会变为原来的 1.5 倍左右(oldCapacity 为偶数就是 1.5 倍,否则是 1.5 倍左右)奇偶不同,比如:10+10/2 = 15, 33+33/2=49,如果是奇数的话会丢掉小数。ArrayList 的扩容操作涉及创建一个新的数组并将旧数组的内容复制到新数组中,但是这个操作非常的昂贵,会导致性能变的很低。
ArrayList
源码中扩容方式:
1 | // 添加一个新的元素 'e' 到列表中 |
ArrayList
扩容机制的详细步骤总结:
检测扩容需求:当尝试向 ArrayList
添加一个元素,且内部数组已满时(即当前元素数量等于数组的容量),ArrayList
需要进行扩容以容纳更多的元素。
预估扩容大小:
newCapacity = oldCapacity + (oldCapacity >> 1)
。ArrayList
将会按照这个更大的所需容量来进行扩容。int
的最大值)。如果新容量大于 ArrayList
允许的最大容量,则会抛出 OutOfMemoryError
。使用 Arrays.copyOf
进行扩容:
Arrays.copyOf
方法实现的,它会在内部分配新的数组空间,并将旧数组的内容复制到新数组中。在集合框架中,LinkedList
也实现了List
接口,具体如下:
LinkedList
在Java中是链表的实现,它不像 ArrayList
那样使用数组来存储元素,而是使用节点来构建一个链表。每个节点都包含一个数据元素和一个指向下一个节点的引用。这种数据结构允许动态添加和删除元素而无需像 ArrayList
那样频繁地进行数组的扩容和复制操作。
LinkedList
实现了List接口。LinkedList
的底层使用了双向链表。LinkedList
没有实现RandomAccess
接口,因此LinkedList
不支持随机访问。LinkedList
的任意位置插入和删除元素时效率比较高,时间复杂度为LinkedList
比较适合任意位置插入的场景。LinkedList
官方文档:LinkedList官方文档
方法签名 | 描述 |
---|---|
boolean add(E e) |
在列表尾部插入元素 e 。 |
void add(int index, E element) |
在指定索引位置 index 插入元素 element 。 |
boolean addAll(Collection<? extends E> c) |
将集合 c 中的元素依次添加到列表的尾部。 |
E remove(int index) |
删除指定索引位置 index 的元素并返回它。 |
boolean remove(Object o) |
删除遇到的第一个等于 o 的元素。 |
E get(int index) |
获取指定索引位置 index 的元素。 |
E set(int index, E element) |
将指定索引位置 index 的元素设置为 element 。 |
void clear() |
清空列表,移除所有元素。 |
boolean contains(Object o) |
判断列表是否包含元素 o 。 |
int indexOf(Object o) |
返回第一个等于 o 的元素的索引位置。 |
int lastIndexOf(Object o) |
返回最后一个等于 o 的元素的索引位置。 |
List<E> subList(int fromIndex, int toIndex) |
截取部分列表,从 fromIndex 到 toIndex 。 |
1 | public class demo2 { |
LinkedList
的三种遍历方式:
for-each
循环适用于正向遍历,并且简单易用。方面 | ArrayList | LinkedList |
---|---|---|
存储空间上 | 物理上一定连续,逻辑上也连续 | 逻辑上连续,但物理上不一定连续 |
随机访问 | 支持 O(1) 时间复杂度 | 不支持 O(1) 随机访问,平均为 O(N) 时间复杂度 |
头插 | 需要搬移元素,效率低,时间复杂度为 O(N) | 只需修改引用的指向,时间复杂度为 O(1) |
插入 | 空间不够时需要扩容 | 没有容量的概念,不需要扩容 |
应用场景 | 元素高效存储和频繁访问 | 任意位置插入和删除频繁,顺序访问时效率较低 |
Stack 继承了 Vector,Vector 和 ArrayList 类似,都是动态的顺序表,不同的是Vector是线程安全的。
方法名 | 描述 |
---|---|
push(E item) |
将元素压入栈顶。 |
pop() |
移除并返回栈顶的元素。 |
peek() |
返回栈顶的元素,但不移除它。 |
isEmpty() |
检查栈是否为空。 |
search(Object o) |
返回对象在栈中的位置(基于 1),找不到返回 -1。 |
size() |
返回栈中元素的数量。 |
clear() |
清空栈中的所有元素。 |
contains(Object o) |
判断栈是否包含指定的元素。 |
iterator() |
返回一个用于遍历栈的迭代器。 |
toString() |
返回栈的字符串表示形式。 |
1 | public class Demo3 { |
for-each
循环遍历: 直接使用 for-each
语法遍历 Stack
。适用于简单的正向遍历操作。
正向迭代器遍历: 使用 Iterator
进行正向遍历。适用于需要在遍历过程中进行更复杂操作的场景,如插入、删除。
反向迭代器遍历: 使用 ListIterator
进行反向遍历。ListIterator
可以在栈的任意位置开始遍历,这里我们从栈的末尾开始(即栈顶)。
在 Java 中,Stack
是一个具体的类,而 Queue
只是一个接口。虽然 Stack
类存在,但现在通常使用 Deque
接口及其实现类(如 ArrayDeque
)来替代 Stack
的功能。这因为 Stack
的很多特性并不完全适合于栈操作(如线程安全和同步机制的开销)。
Queue
接口继承自Collection接口,继承了Collection中的所有方法。
Queue官方文档:队列Queue
注意:Queue是个接口,在实例化时必须实例化LinkedList的对象,因为LinkedList实现了Queue接口
方法名 | 描述 |
---|---|
boolean add(E e) |
将指定的元素插入到队列的尾部。如果成功则返回 true ,如果队列已满则抛出 IllegalStateException |
boolean offer(E e) |
尝试将元素插入到队列的尾部,如果成功则返回 true ,如果队列已满则返回 false |
E remove() |
移除并返回队列头部的元素。如果队列为空则抛出 NoSuchElementException |
E poll() |
移除并返回队列头部的元素。如果队列为空则返回 null |
E element() |
返回队列头部的元素,但不移除它。如果队列为空则抛出 NoSuchElementException |
E peek() |
返回队列头部的元素,但不移除它。如果队列为空则返回 null |
双端队列(Deque)是指允许两端都可以进行入队和出队操作的队列,元素可以从队头出队和入队,也可以从队尾出队和入队。
注意:Deque也是一个接口,使用时必须创建LinkedList的对象。
栈和队列均可以使用该接口。
1 | Deque<Integer> stack = new ArrayDeque<>(); //双端队列的线性实现 |
方法名 | 描述 |
---|---|
void addFirst(E e) |
在双端队列的头部插入指定的元素 |
void addLast(E e) |
在双端队列的尾部插入指定的元素 |
boolean offerFirst(E e) |
尝试在双端队列的头部插入指定的元素 |
boolean offerLast(E e) |
尝试在双端队列的尾部插入指定的元素 |
E removeFirst() |
移除并返回双端队列头部的元素 |
E removeLast() |
移除并返回双端队列尾部的元素 |
E pollFirst() |
移除并返回双端队列头部的元素 |
E pollLast() |
移除并返回双端队列尾部的元素 |
E getFirst() |
返回双端队列头部的元素,但不移除它 |
E getLast() |
返回双端队列尾部的元素,但不移除它 |
E peekFirst() |
返回双端队列头部的元素,但不移除它 |
E peekLast() |
返回双端队列尾部的元素,但不移除它 |
boolean removeFirstOccurrence(Object o) |
移除第一次出现的指定元素 |
boolean removeLastOccurrence(Object o) |
移除最后一次出现的指定元素 |
void push(E e) |
将元素推入到栈的顶部(双端队列的头部) |
E pop() |
移除并返回栈顶的元素(双端队列的头部) |
1 | public class Demo4 { |
PriorityQueue
用于实现基于优先级的队列。继承自 AbstractQueue<E>
类,并实现了Queue 接口。
方法名 | 描述 |
---|---|
boolean add(E e) |
插入指定元素到优先队列中 |
boolean offer(E e) |
插入指定元素到优先队列中 |
E peek() |
获取但不移除队列的头部元素;如果队列为空,返回 null |
E poll() |
获取并移除队列的头部元素;如果队列为空,返回 null |
boolean isEmpty() |
如果队列为空,则返回 true |
int size() |
返回队列中的元素个数 |
void clear() |
移除队列中的所有元素 |
boolean contains(Object o) |
如果队列中包含指定元素,则返回 true |
Iterator<E> iterator() |
返回队列中元素的迭代器 |
boolean remove(Object o) |
从队列中移除指定元素 |
PriorityQueue
默认使用自然排序(元素必须实现 Comparable
接口),也可以在构造时传入一个 Comparator
进行自定义排序。PriorityQueue
的遍历不会按照优先级顺序进行。如果需要按优先级顺序处理元素,应使用 poll
方法逐个移除元素。Java 集合框架中的 Set
接口是一个不包含重复元素的集合。它继承自 Collection
接口。
方法 | 描述 |
---|---|
add(E e) |
向集合中添加指定元素,如果集合中已经包含该元素,则返回 false |
remove(Object o) |
从集合中移除指定元素,如果该元素存在,则返回 true |
clear() |
移除集合中的所有元素 |
contains(Object o) |
如果集合中包含指定元素,则返回 true |
isEmpty() |
如果集合为空,则返回 true |
size() |
返回集合中元素的数量 |
iterator() |
返回集合中元素的迭代器 |
addAll(Collection<? extends E> c) |
将指定集合中的所有元素添加到此集合中(如果尚未存在) |
containsAll(Collection<?> c) |
如果此集合包含指定集合中的所有元素,则返回 true |
removeAll(Collection<?> c) |
从此集合中移除指定集合中包含的所有元素 |
retainAll(Collection<?> c) |
仅保留此集合中那些包含在指定集合中的元素 |
toArray() |
返回包含此集合中所有元素的数组 |
<T> toArray(T[] a) |
返回包含此集合中所有元素的数组;返回数组的运行时类型与指定数组的类型相同 |
1 | public class SetDemo { |
Set系列集合的特点:无序、不重复、没有索引,所以,Set集合没办法用普通的for循环遍历。
SortedSet
是Java集合框架中的一个接口,表示一个元素保持特定顺序的集合。该接口保证集合中的元素按照自然顺序(例如数字按大小顺序,字符串按字典顺序)或通过指定的比较器进行排序。
方法 | 描述 |
---|---|
comparator() |
返回此集合中使用的比较器,如果没有使用比较器,则返回 null |
first() |
返回此集合中的第一个(最低)元素 |
last() |
返回此集合中的最后一个(最高)元素 |
headSet(E toElement) |
返回此集合中从第一个元素到 toElement 之前的部分视图 |
tailSet(E fromElement) |
返回此集合中从 fromElement (包含)到最后的部分视图 |
subSet(E fromElement, E toElement) |
返回此集合中从 fromElement (包含)到 toElement 之前的部分视图 |
spliterator() |
创建一个 Spliterator ,用来遍历集合中的元素 |
TreeSet 是SortedSet接口的常用实现类。
1 | public class Demo6 { |
SortedSet不允许存储null
元素。
操作SortedSet
时要确保提供的元素实现了Comparable
接口,或者在构造集合时提供了Comparator
。
由于TreeSet
是基于树的数据结构实现的,因此其基本操作(如添加、删除、查找)的时间复杂度为
TreeSet
实现了NavigableSet
接口,并间接实现了SortedSet
接口。TreeSet
基于红黑树数据结构实现,能够保证元素有序且无重复。
方法 | 描述 |
---|---|
add(E e) |
将指定的元素添加到此 set 中(如果该元素尚未存在)。 |
clear() |
从此 set 中移除所有元素。 |
contains(Object o) |
如果此 set 包含指定的元素,则返回 true。 |
isEmpty() |
如果此 set 不包含元素,则返回 true。 |
remove(Object o) |
如果指定元素存在于此 set 中,则将其移除。 |
size() |
返回此 set 中的元素数量(其容量)。 |
iterator() |
返回在此 set 中的元素上进行迭代的迭代器。 |
first() |
返回此 set 中当前第一个(最低)元素。 |
last() |
返回此 set 中当前最后一个(最高)元素。 |
subSet(E fromElement, E toElement) |
返回此 set 的部分视图,其元素范围从 fromElement (包括)到 toElement (不包括)。 |
headSet(E toElement) |
返回此 set 的部分视图,其元素严格小于 toElement 。 |
tailSet(E fromElement) |
返回此 set 的部分视图,其元素大于或等于 fromElement 。 |
descendingSet() |
返回此 set 中所包含元素的逆序视图。 |
HashSet 集合底层采用哈希表存储数据。哈希表中最重要的值叫:哈希值
哈希值:对象的整数表现形式
方法 | 描述 |
---|---|
add(E e) |
将指定的元素添加到此 set 中(如果该元素尚未存在)。 |
clear() |
从此 set 中移除所有元素。 |
contains(Object o) |
如果此 set 包含指定的元素,则返回 true。 |
isEmpty() |
如果此 set 不包含元素,则返回 true。 |
remove(Object o) |
如果指定元素存在于此 set 中,则将其移除。 |
size() |
返回此 set 中的元素数量(其容量)。 |
iterator() |
返回在此 set 中的元素上进行迭代的迭代器。 |
clone() |
返回此 HashSet 实例的浅表副本:并不复制这些元素本身。 |
toArray() |
返回包含此 set 中所有元素的数组。 |
toArray(T[] a) |
返回包含此 set 中所有元素的数组;返回数组的运行时类型是指定数组的类型。 |
retainAll(Collection<?> c) |
仅保留此 set 中那些包含在指定集合中的元素。 |
removeAll(Collection<?> c) |
移除此 set 中那些包含在指定集合中的元素。 |
addAll(Collection<? extends E> c) |
将指定集合中的所有元素添加到此 set 中。 |
equals(Object o) |
比较指定对象与此 set 的相等性。 |
hashCode() |
返回此 set 的哈希码值。 |
spliterator() |
创建一个 late-binding 和 fail-fast 的 Spliterator。 |