public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
(1)AbstractSequentialList:List的骨架实现,对AbstractList做了部分修改
(2)List:定义了链表的相关操作,有序(插入顺序)、可通过索引访问、允许存在重复元素、允许添加null元素
(3)Deque :即能将LinkedList当作双端队列使用
(4)Cloneable:一种标记接口,实现改接口能够实现克隆,若不实现该接口,用Object.clone会报错
(5)Serializable:一种标记接口,实现改接口,他支持序列化,能够通过序列化进行传输
// 链表节点个数
transient int size = 0;
// 头结点
transient Node<E> first;
// 尾结点
transient Node<E> last;
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;
}
}
LinkedList的底层数据结构是双向链表,该双向链表由一个个的Node构成,LinkedList中一个元素对应一个Node:
(1)无参构造
public LinkedList() {
}
(2)Collection extends E>型构造方法
public LinkedList(Collection<? extends E> c) {
this(); // 调用空构造器进行初始化
addAll(c); //
}
//添加所有的方法
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
会调用addAll()方法将集合中的元素添加到链表中,添加的操作后面会详细介绍。
(1)add(E e)
public boolean add(E e) {
linkLast(e);//默认添加在链表的尾部
return true;
}
void linkLast(E e) {
// 指向链表的尾部
final Node<E> l = last;
// 以链表的尾部为驱动点创建一个新的节点
final Node<E> newNode = new Node<>(l, e, null);
// 将链表的尾部指向新的节点
last = newNode;
// 如果链表为空,那么该节点既是头节点也是尾节点
if (l == null)
first = newNode;
//链表不为空,那么将该结点作为原链表尾部的后继节点
else
l.next = newNode;
// 元素的个数自增
size++;
// 修改次数自增
modCount++;
}
(2)add(int index,E e)
public void add(int index, E element) {
// 检查索引是否合法,index在0~size-1范围内
checkPositionIndex(index);
// 如果index==size,直接在链表的最后插入元素,相当于add(E e)方法
if (index == size)
linkLast(element);
// 在链表内部添加元素
else
linkBefore(element, node(index));
}
// 获取指定索引处的节点
Node<E> node(int index) {
//如果索引位置靠链表前半部分,从头开始遍历
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
//否则,从尾开始遍历
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
// 在链表内部添加元素的方法
// 新建值为e的节点并将其连接到succ之前(succ不为null)
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
// 指定节点的前置
final Node<E> pred = succ.prev;
// 新建节点,前置节点为succ的前置节点,则e元素就是要插入的元素,后置节点为succ
final Node<E> newNode = new Node<>(pred, e, succ);
// 构建双向链表,succ的前置节点为新的节点
succ.prev = newNode;
// 如果前驱节点为null,则把newNode赋值给first
if (pred == null)
first = newNode;
// 构建双向链表
else
pred.next = newNode;
// 元素的个数增加
size++;
// 修改次数增加
modCount++;
}
(3)addAll(Collection extends E> c)
// 封装完之后依次添加在链表的尾部
public boolean addAll(Collection<? extends E> c) {
// 调用addAll(size,c)方法,见(4)分析
return addAll(size, c);
}
(4)addAll(int index, Collection extends E> c)
//将集合从指定位置开始插入
public boolean addAll(int index, Collection<? extends E> c) {
// 检查索引是否在0-size之间
checkPositionIndex(index);
// 将有数据集合转化为数组,得到集合的数据
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
// 得到插入位置的前置节点和后置节点
Node<E> pred, succ;
//如果插入位置为尾部,前置节点为last,后置节点为null
if (index == size) {
succ = null;
pred = last;
//否则,调用node()方法得到后置节点,再得到前置节点
} else {
succ = node(index);
pred = succ.prev;
}
// 遍历数据将数据插入
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
// 新建节点,这里newNode.next为null,因为其指向将是下一个遍历的节点
Node<E> newNode = new Node<>(pred, e, null);
// 插入链表的头部
if (pred == null)
first = newNode;
// 将新节点连接到pred之后
else
pred.next = newNode;
// 更新pred
pred = newNode;
}
// succ为null说明if (index == size) 为ture
if (succ == null) {
// 直接更新尾部的节点就好
last = pred;
// 否则,将插入的链表与先前链表连接起来
} else {
pred.next = succ;
succ.prev = pred;
}
// 更新size
size += numNew;
modCount++;
return true;
}
(1)remove()
public E remove() {
// 默认删除头部节点
return removeFirst();
}
// 删除头部节点的方法
public E removeFirst() {
// 保证f为first且f不为null
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
//删除头部节点
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
// 获取后置节点
final Node<E> next = f.next;
// 设置为null,有利于GC
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
(2)remove(int index)
// 移除指定索引处的元素
public E remove(int index) {
// 检查索引
checkElementIndex(index);
return unlink(node(index));
}
// 删除节点
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
//获取前置节点和后置节点
final Node<E> next = x.next;
final Node<E> prev = x.prev;
// 进行移除该元素之后的操作
if (prev == null) {
// 删除的是第一个节点
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
// 删除的是最后一个元素
last = prev;
} else {
next.prev = prev;
x.next = null;
}
// 把item置为null,让垃圾回收器回收
x.item = null;
// 移除一个节点,size自减
size--;
modCount++;
return element;
}
(3)remove(Object o)
public boolean remove(Object o) {
// 删除第一个值为null的节点
if (o == null) {
// 从前向后遍历
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
// 删除第一个值为o的节点
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
// 不匹配,返回false
return false;
}
// 修改指定索引处的元素值
public E set(int index, E element) {
// 检查索引
checkElementIndex(index);
// 获取index处的节点
Node<E> x = node(index);
// 取出该节点的元素,供返回使用
E oldVal = x.item;
// 用新元素替换旧元素
x.item = element;
// 返回旧元素
return oldVal;
}
(1)get(int index) 获取指定位置的元素
// 查看指定索引处的元素值
public E get(int index) {
// 检查索引是否合法
checkElementIndex(index);
// 获取索引处节点的值
return node(index).item;
}
(2)getFirst()获取头结点元素
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
(3)getLast() 获取尾结点的元素值
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
(4)indexOf(Object o)根据对象得到索引
// 返回第一个值为o的节点的索引
public int indexOf(Object o) {
int index = 0;
if (o == null) {
// 寻找第一个值为null的节点的索引
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
// 寻找第一个值为o的节点的索引
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
// 未找到,返回-1
return -1;
}
相同点:
(1)都是List接口下的实现类具有List提供的方法
(2)二者均属于线性表,均满足:有序(插入顺序)、允许存在重复元素、允许元素值为null、可通过索引访问元素
(3)安全性:都是非线程安全的集合(都可能会出现并发性异常ConcurrentModificationException())
不同点:
(1)ArrayList底层数据结构是数组,物理地址是连续的;LinkedList底层数据结构是双向链表,由一个个节点构成,物理地址是不连续的。
(2)ArrayList需动态扩容,进行数组的创建和拷贝,其容量一般都会大于元素个数,因此会浪费部分空间,但每个元素无需分配额外空间指向其他元素;LinkedList的元素个数就是其容量,但是它的每个节点中都需额外空间来指向前驱节点和后继节点。
(3)ArrayList可通过索引计算出元素的地址,直接定位到元素所在的位置,因此查询、修改操作较快,但增加、删除操作较慢,因为增加、删除后需要移动后面所有的元素;LinkedList的所有操作需要遍历链表才能定位到指定的元素,其增加、删除操作较快,因为增加、删除后,后面的节点无需移动,但查询、修改操作相比ArrayList较慢,因为需要遍历。
(4)LinkedList因为实现了Deque双向队列接口,具有特有的方法:例如addFirst() 、 addLast()