在数学世界中,集合是指具有某种特性性质的事物汇成的集体,对应的英文是Set,它具有确定性,无序性,互异性等特点。而Java中的集合是用于存储对象的工具类容器,它实现了常用的数据结构,提供了一系列公开的方法用于增加,删除,修改,遍历和查找数据。
框架图主要分为两类:第一类是按照单个元素存储的Collection;第二类是按照Key-Value存储的Map。
Collection接口
Collection接口是java集合框架的根接口,它继承了Iterable接口,提供元素遍历的功能。对于集合框架而言,这个接口定义了集合基本的操作,包括添加、删除、判断是否有元素、清空等:
Method | Description |
---|---|
boolean add(E a) | 确保此集合包含指定的元素(可选操作) |
boolean addAll(Collection<? extends E> c) | 将指定集合中的所有元素添加到此集合(可选操作) |
void clear() | 删除集合内所有元素(可选操作) |
boolean contains(Object o) | 如果此集合包含指定的元素,则返回 true |
boolean containsAll(Collection<?> c) | 如果此集合包含指定 集合中的所有元素,则返回true |
boolean equals(Object o) | 将指定的对象与此集合进行比较以获得相等性 |
int hashCode() | 返回此集合的哈希码值 |
boolean isEmpty | 如果此集合不包含元素,则返回 true |
Iterator<E> iterator() | 返回集合的迭代器 |
default Spliterator<T> spliterator() | 返回集合的Spliterator |
default void forEach(Consumer<? super E>action) | 对Iterable的每个元素执行给定的操作,直到所有元素都被处理或动作引发异常。 除非实现类另有规定,否则按照迭代的顺序执行操作(如果指定了迭代顺序) |
boolean remove(Object o) | 从该集合中删除指定元素的单个实例(如果存在)(可选操作) |
boolean removeAll(Collection<?>c) | 删除指定集合中包含的所有此集合的元素(可选操作) |
default boolean removeIf(Predicate<? extends E>filter) | 删除满足给定谓词的此集合的所有元素 |
boolean retainAll(Collection<?> c) | 仅保留此集合中包含在指定集合中的元素(可选操作) |
int size() | 返回此集合中的元素数 |
Object[] toArray() | 返回一个包含此集合中所有元素的数组 |
<T>T[] toArray(T[] a) | 返回包含此集合中所有元素的数组; 返回的数组的运行时类型是指定数组的运行时类型 |
default Stream<E> Stream() | 返回以此集合作为源的Stream |
default Stream<E> parallelStream() | 返回可能并行的以此集合作为源的Stream |
如果实现Collection接口的每一个类都要提供如此多的例行方法是一件很烦人的事情。Java类库提供一个类AbstractCollection,它将基础方法size和iterator抽象化,但是在此提供例行方法。
List集合
List集合是线性数据结构的主要实现,集合元素通常存在明确的上一个和下一个元素,也存在明确的第一个元素和最后一个元素。List集合存储一组不唯一(可以有多个元素引用相同对象)的有序对象。该类常用的是ArrayList和LinkedList两个集合类。
ArrayList和LinkedList的区别:
-
1. 是否保证线程安全:
ArrayList
和LinkedList
都是不同步的,也就是不保证线程安全; -
2. 底层数据结构:
Arraylist
底层使用的是Object
数组;LinkedList
底层使用的是 双向链表 数据结构 -
3. 插入和删除是否受元素位置的影响: ①
ArrayList
采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e)
方法的时候,ArrayList
会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element)
)时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ②LinkedList
采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。 -
4. 是否支持快速随机访问:
LinkedList
不支持高效的随机元素访问,而ArrayList
支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)
方法)。 -
5. 内存空间占用: ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。
由数组支持的有序集合可以快速地随机方法,因此适合使用List方法并提供一个整数索引来访问(get(index))。与之不同的是,链表尽管有序,但是随机访问很慢,最好使用迭代器进行访问。为了避免对链表完成随机访问操作,引入了一个标记接口RandomAccess。这个接口不包含任何方法。可以用它来测试一个特定的集合是否支持高效的随机访问。
public interface RandomAccess { }
在 binarySearch(
)方法中,它要判断传入的list 是否 RamdomAccess
的实例,如果是,调用indexedBinarySearch()
方法,如果不是,那么调用iteratorBinarySearch()
方法
public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);
else
return Collections.iteratorBinarySearch(list, key);
}
ArrayList
实现了 RandomAccess
接口, 而 LinkedList
没有实现。为什么呢?我觉得还是和底层数据结构有关!ArrayList
底层是数组,而 LinkedList
底层是链表。数组天然支持随机访问,时间复杂度为 O(1),所以称为快速随机访问。链表需要遍历到特定位置才能访问特定位置的元素,时间复杂度为 O(n),所以不支持快速随机访问。,ArrayList
实现了 RandomAccess
接口,就表明了他具有快速随机访问功能。 RandomAccess
接口只是标识,并不是说 ArrayList
实现 RandomAccess
接口才具有快速随机访问功能的!
Queue集合
Queue(队列)是一种先进先出数据结构,队列是一种特殊的线性表,只允许在表的一端进行获取操作,在表的另一端进行插入操作。
Map集合
Map集合是以Key-Value键值对作为存储元素实现的哈希结构,Key按照某种哈希函数计算后是唯一的,Value是可以重复的。Map指向Collection的箭头仅表示两个类的依赖关系。
Set集合
Set是不允许出现重复元素的集合类型,Set体系最常用的是HashSet,TreeSet和LinkedHashSet三个集合类。
- HashSet从源码分析是使用HashMap来实现的,只是Value固定为一个静态对象,使用Key保证集合元素的唯一性,但它不保证集合元素的顺序。
- TreeSet从源码分析是使用TreeMap实现的,底层为树结构,在添加新元素到集合时,按照某种比较原则将其插入合适的位置,保证插入后的集合任然是有序的。
- LinkedHashSet继承自HashSet,具有HashSet的优点,内部使用链表维护了元素插入顺序。
迭代器
Iterator接口包含四个方法:
public interface Iterator<E>
{
E next();
boolean hasNext();
void remove();
default void forEachRemaining(Consumer<? super E> action);
}
查看集合中的元素:
- hasNext()和next()一起用。
- for each循环,编译器将for each循环翻译为带有迭代器的循环。
- 可以调用forEachRemaining方法并提供一个lambda表达式(它会处理一个元素)。对迭代器的每个元素调用这个lambda表达式,直到再没有元素为止。
iterator.forEachRemaining(element->do something with element);
参考文章: