在实际开发中,我们在实现List接口来创建集合的时候,并非只能通过数组(ArrayList)来实现,我们还可以通过链表(LinkedList)来创建一个集合。在LinkedList中,他内部的每一个元素都指向下一个元素,每一个节点都是一个单独的存储空间。
链表结构如图所示:
1、类结构
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
由上述代码可以知道
(1)LinkedList同时实现了List接口和Queue接口,所以它既是一个双向链表,也是一个双端队列。允许添加任何元素,包括null元素;
(2)LinkedList的所有方法的实现都体现了双链表的特性,所有有关索引的操作都自动从LinkedList的开头或结尾开始遍历;
(3)其内部由若干个Node对象组成;
(4)LinkedList和ArrayList最大的区别在于前者不存在扩容因为采用链表结构,每次添加元素,都会创建新的Node节点并分配空间,所以不存在扩容;
(5)适合数据频繁添加和删除操作,写多读少的场景。
2、成员变量
transient int size = 0; // 表示元素个数
transient Node<E> first; // 头节点
transient Node<E> last; // 尾节点
(1)加上transient关键字的作用时保证该属性不会被序列化;
(2)分别定义了链表中元素的个数以及头节点、尾节点;
头节点的固定引用(方便节点的创建、添加和链表的查找遍历)链表必须先创建添加头节点,才能创建添加第二个节点,以此类推查找遍历同理,链表是顺序访问列表,只能从开头或者末尾开始挨个访问所以头节点不可能匿名,需要一个固定引用方便后续的使用
3、构造方法
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
(1)LinkedList中定义了两个构造方法,分别无参构造方法和有参构造方法;
(2)无参构造方法用来对LinkedList进行初始化;
(3)有参构造方法可以直接传入一个一个集合对象,调用addAll()方法直接存在LinkedList中。
节点的顺序需要与指定集合c的元素顺序完全一致
4、主要方法
(1)linkFirst(E e)
//创建一个包含元素e的新节点
//并链接至链表的首部(linkFirst),这个节点就是链表的新首节点
//方法存在的意义:LinkedList同时implement了接口Queue和Deque
private void linkFirst(E e) {
//假设链表不为空,获取并传递首节点的引用
//传递后,引用f和first同时指向首节点
//这一步实际上也是复制首节点的引用
final Node<E> f = first;
//分别提供以下参数创建一个节点对象newNode:
//①、上一个节点的引用null
//②、元素e
//③、下一个节点的引用f
//newNode其实就是新的首节点,两点原因:
//①、在链表中,只有首节点的上一个节点引用为null
//②、原首节点f成为newNode的下一个节点
//③、节点关系的维护放在了构造函数中(参考截图)
final Node<E> newNode = new Node<>(null, e, f);
//将newNode赋值给first,保证first永远都是首节点的引用
first = newNode;
//上面的代码只初始化了first,并未初始化last
//如果首节点f为null,说明链表为空
//所以新创建的节点newNode既是首节点也是尾节点
if (f == null)
//初始化last
last = newNode;
else
//如果首节点f不为空
//需要考虑两方面:元素和关系
//元素由客户端程序员添加
//这里只需考虑关系
//首节点f的prev肯定为null
//现在创建了新的首节点newNode
//f自然就是第二节点
//所以需要通过代码表示:
//f的上一个节点是newNode
//以此保证各个节点的关系正确
f.prev = newNode;
//新增一个节点,长度+1
size++;
//继承自抽象类AbstractList,保证快速失败机制的正常执行
//新增节点是结构性修改
modCount++;
}
(2)linkLast(E e):
//创建一个包含元素e的新节点,并链接至链表的尾部(linkLast)
//这个节点就是链表的新尾节点
//方法存在的意义:LinkedList同时implement了接口Queue和Deque
//实现它们的方法时可以有效避免代码重复
void linkLast(E e) {
//假设链表不为空,获取并传递尾节点的引用
//传递后,引用l和last同时指向尾节点
//这一步实际上也是复制尾节点的引用
final Node<E> l = last;
//创建一个节点对象newNode://①、上一个节点的引用l②、元素e
//③、下一个节点的引用null
//newNode其实就是新的尾节点,两点原因:
//①、在链表中,只有尾节点的下一个节点引用为null
//②、原尾节点l成为newNode的上一个节点
final Node<E> newNode = new Node<>(l, e, null);
//将newNode赋值给last,保证last永远都是尾节点的引用
last = newNode;
//上面的代码只初始化了last,并未初始化first
//如果尾节点l为null,说明链表为空
//所以新创建的节点newNode既是尾节点也是首节点
if (l == null)
//初始化first
first = newNode;
else
//如果尾节点l不为空
//需要考虑两方面:元素和关系
//元素由客户端程序员添加
//这里只需考虑关系
//尾节点l的next肯定为null
//现在创建了新的尾节点newNode
//l自然就是倒数第二节点
//所以需要通过代码表示:
//l的下一个节点是newNode
//以此保证各个节点的关系正确
l.next = newNode;
//新增一个节点,长度+1
size++;
//继承自抽象类AbstractList,保证快速失败机制的正常执行
modCount++;
}
5、常用方法
(1)boolean add(E e):添加新元素至链表尾部,默认调用linkLast()添加到尾部;
public boolean add(E e) {
linkLast(e);
return true;
}
(2)void addFirst(E e):添加新元素至链表头部
public void addFirst(E e) {
linkFirst(e);
}
(3)void addLast(E e):添加新元素至链表尾部
public void addLast(E e) {
linkLast(e);
}
(4)E get(int index):遍历链表,找到指定位置的元素
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
(5)E getFirst():获取链表中的头元素
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
(6)E getLast():获取链表中的尾元素
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
(7)E remove():删除链表中的头元素
public E remove() {
return removeFirst();
}
(8)boolean remove(Object o):删除指定内容的元素
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
(9) E remove(int index):删除指定位置的元素
public E remove(int index) {
checkElementIndex(index);//检查index是否合法
return unlink(node(index));
}
(10)E removeFirst():删除链表中的头元素
public void addFirst(E e) {
linkFirst(e);
}
(11)E removeLast():删除链表中的尾元素
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
(12)default void sort(Comparator c):按照Comparator比较器,将链表中的所有元素进行排序
@SuppressWarnings({"unchecked", "rawtypes"})
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);
}
}
(13)T[] toArray(T[] a):将链表转换为数组
<T> T[] toArray(T[] a);
6、ArrayList和LinkedList的区别