一、List
public interface List<E> extends Collection<E>
List是一个有序集合(也称为序列),其特点是有序可重复。
从上图可以看出List 继承自Collection接口,除了复用了Collection接口定义的方法,还定义了自己的方法,如下所示:
-
将指定集合中的所有元素插入到此列表中的指定位置
boolean addAll(int index, Collection<? extends E> c);
-
将此列表的每个元素替换为将运算符应用于该元素的结果(从jdk8开始)
default void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final ListIterator<E> li = this.listIterator();
while (li.hasNext()) {
li.set(operator.apply(li.next()));
}
}
举例如下:ArrayList<Integer> integers = new ArrayList<>(); integers.add(1); integers.add(2); integers.add(3); //将集合中的元素分别乘以10 integers.replaceAll(i -> i * 10); integers.forEach(s->{ System.out.println(s); }); /* 输入结果如下: 10 20 30 */
-
根据Comparator对集合进行排序
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
-
根据索引获取集合元素
E get(int index);
-
根据索引修改集合对应元素
E set(int index, E element);
-
根据索引为集合添加元素
将当前位于该位置的元素(如果有)和任何后续元素向右移动(在其索引中添加 1)。void add(int index, E element);
-
根据索引移除集合元素
将任何后续元素向左移动(从其索引中减去 1)。E remove(int index);
-
返回此集合中首次出现的指定元素的索引,如果此列表不包含该元素,则返回 -1
int indexOf(Object o);
-
返回此集合中指定元素的最后出现的索引,如果此列表不包含该元素,则返回 -1
int lastIndexOf(Object o);
-
返回一个列表迭代器
ListIterator<E> listIterator();
-
从指定索引开始返回一个列表迭代器
返回此列表中元素的列表迭代器(按正确的顺序),从列表中的指定位置开始。指定的索引指示对 next 的初始调用将返回的第一个元素。对 previous 的初始调用将返回具有指定索引减 1 的元素。
ListIterator<E> listIterator(int index);
-
返回此集合在指定的 fromIndex(含)和 toIndex(不含)之间的部分的视图
List<E> subList(int fromIndex, int toIndex);
注意:此处对subList返回的集合进行操作会影响到原来的集合
ArrayList<Integer> integers = new ArrayList<>(); integers.add(1); integers.add(2); integers.add(3); List<Integer> subList = integers.subList(0, 1); subList.add(99); integers.forEach(System.out::println); System.out.println("========================"); subList.forEach(System.out::println); /* 输出结果: 1 99 2 3 ======================== 1 99 */
二、ArrayList
1、基本定义
ArrayList是List 接口的 可动态调整数组大小的实现。实现所有可选的 list 操作,并允许所有元素,包括 null。
2、主要特点
-
ArrayList是基于object数组的,在初始化ArrayList时,会构建空数组(Object[] elementData={})。
-
ArrayList是一个无序的,它是按照添加的先后顺序排列,当然,他也提供了sort方法,如果需要对ArrayList进行排序,只需要调用这个方法,提供Comparator比较器即可。
-
size、isEmpty、get、set、iterator 和 listIterator 操作以恒定时间 O ( 1 ) O(1) O(1)运行。
例如,访问数组中的一个特定元素(例如 array[index])通常是一个 O(1)操作,因为无论数组有多大,你都可以在相同的时间内访问它。相比之下,遍历整个数组通常是一个 O(n) 操作,因为需要遍历的元素数量与数组的大小成正比。
-
add 操作在末尾添加元素是在摊销常数时间 O ( 1 ) O(1) O(1)内运行,即添加 n 个元素需要 O ( n ) O(n) O(n) 时间。所有其他操作都以线性时间运行(粗略地说)
3、数组扩容
private static final int DEFAULT_CAPACITY = 10;
如果是第一次添加元素,数组的长度被扩容到默认的capacity,也就是10.
关于扩容分两种:
- ①原来数组的长度为10,新加的元素长度 < 10/2 ,那么扩容后长度为(10+10/2) =15
- ②原来数组的长度为10,新加的元素长度 > 10/2 ,那么扩容后长度为(10+新加元素长度)
4、线程安全
ArrayList 是一个非常常用的列表类,但它是线程不安全的。当多个线程同时对 ArrayList 进行修改操作时,可能会导致数据不一致、数组越界异常等问题。
如何保证线程安全:
- 使用 Vector 类:Vector 是 ArrayList 的一个线程安全的变体,它的方法都是同步的,但性能较低。
- 使用 Collections.synchronizedList 方法:这个方法可以将现有的 ArrayList 包装成一个线程安全的列表。使用时需要注意,迭代器的遍历操作也需要手动同步。
- 手动同步:可以在操作 ArrayList 的代码块上使用 synchronized 关键字或显式锁来保证线程安全。
- 避免共享:如果可能,避免在多个线程间共享同一个 ArrayList 实例。
三、LinkedList
1、基本定义
LinkedList 是一个实现了 List 接口的双向链表。与 ArrayList 不同,LinkedList 的每个元素都由一个节点表示,每个节点都包含数据和指向前一个节点以及后一个节点的引用。这种数据结构使得在列表的任何位置添加或删除元素都变得高效,因为这些操作只需要改变几个节点的引用。
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
2、主要特点
- 动态数组:LinkedList 内部使用链表结构,可以动态地增长和缩小。
- 双向链表:每个元素都有指向前一个和后一个元素的引用,这使得向前或向后遍历都很方便。
- 内存占用:相比于 ArrayList,LinkedList 在每个元素上都有一定的额外内存开销,因为它需要存储额外的引用。
- 操作性能:在列表的头部或中间插入或删除元素时,LinkedList 通常比 ArrayList 更高效,因为不需要像 ArrayList 那样移动元素来维护数组的连续性。
3、链表扩容
对于 LinkedList,添加元素的操作通常只需要创建一个新的节点,并将其链接到链表中的适当位置。由于 LinkedList 是一个链式数据结构,它不需要像数组那样预留固定大小的空间,因此不会因为容量问题而导致性能瓶颈。
在 LinkedList 中添加元素通常涉及以下步骤:
- 创建新节点:创建一个新的节点,并将元素存储在这个节点中。
- 链接节点:将新节点插入到链表中的适当位置。如果是在末尾添加,就将新节点的 next 引用设置为 null,并将链表中最后一个节点的 previous 引用指向新节点。如果是在头部添加,就更新链表的头节点引用。
- 更新引用:如果添加的位置在链表中间,还需要更新前一个节点的 next 引用和后一个节点的 previous 引用
总的来说,LinkedList 不需要扩容,因为它不是基于数组实现的。它的动态性质使得在任何位置添加或删除元素都相对高效,但这种数据结构在内存使用和遍历速度上可能不如 ArrayList 高效。
4、线程安全
和 ArrayList 一样,LinkedList 也是非线程安全的。在多线程环境中,直接使用 LinkedList 可能会导致数据不一致和其它线程安全问题。
如何保证线程安全:
- 使用 Collections.synchronizedList 方法:这个方法可以将 LinkedList 包装成一个线程安全的列表,但需要注意,迭代器的遍历操作也需要手动同步。
- 手动同步:在操作 LinkedList 的代码块上使用 synchronized 关键字或显式锁来保证线程安全。
- 避免共享:如果可能,避免在多个线程间共享同一个 LinkedList 实例。