LinkedList是基于双向链表的 List接口的实现,在实际开发中十分常用,通过分析源码和解析之后记录一下自己的理解。
底层是双向非循环链表
顺序访问高效,随机访问低效
有序且可重复
插入和删除效率高,查找效率低
LinkedList线程不同步
LinkedList允许元素为空
继承(实现)关系
extends
|---AbstractSequentialList
implements
|---Cloneable
|---List
|---Deque
|---java.io.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;
}
}
构造方法
1.无参构造
public LinkedList() {
}
解析:LinkedList的无参构造是空的
2.含参构造
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
//从index位置,将集合c的元素插入新的列表中
public boolean addAll(int index, Collection<? extends E> c) {
//检查index位置是否越界
checkPositionIndex(index);
//将传入的集合转为数组
Object[] a = c.toArray();
//记录数组长度
int numNew = a.length;
//如果长度为0,返回false
if (numNew == 0)
return false;
Node<E> pred, succ;
//如果是插入到末尾位置,后继节点为空,前驱节点为尾节点
if (index == size) {
succ = null;
pred = last;
} else {
//如果不是插入到末尾,找到插入的位置
succ = node(index); //插入位置
pred = succ.prev; //插入位置的前驱
}
//遍历数组,将数组中元素一次插入
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
//生成一个新节点
Node<E> newNode = new Node<>(pred, e, null);
//插入位置的前驱为空,说明是在开头插入
if (pred == null)
first = newNode;
else
//不是在开头插入,插入位置指向新节点
pred.next = newNode;
pred = newNode;
}
//插入位置为null,说明是在末尾插入
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
//集合长度增加
size += numNew;
//修改次数+1
modCount++;
return true;
}
解析:构建一个包含传入的集合的集合。
关键方法
1.public boolean add(E e)
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
//记录下尾节点
final Node<E> l = last;
//新建一个节点,前驱为l,内容为传入的e,后继为空,
//相当于 last<—— e ——> null
final Node<E> newNode = new Node<>(l, e, null);
//将新插入的节点作为尾节点
last = newNode;
//如果l为空,也就是插入之前该列表是空的,那么将新插入的节点作为头结点
if (l == null)
first = newNode;
else
//否则,让之前的尾节点的指向新插入的节点
//相当于 l(原尾节点last) <——> newNode(新插入的节点&现尾节点last) ——> null
l.next = newNode;
//长度+1
size++;
//修改次数+1
modCount++;
}
解析:在链表末尾插入指定元素,底层直接调用linkLast方法来实现,主要就是完成末尾插入之后对整个双向链表的头和尾以及节点指向的维护。
2.public void add(int index, E element)
public void add(int index, E element) {
//位置合法性检查
checkPositionIndex(index);
//如果是在末尾插入,直接调用linkLast插入
if (index == size)
linkLast(element);
else
//如果不是在末尾插入,调用linkBefore插入
linkBefore(element, node(index));
}
//e:要插入的元素
//succ:要插入那个位置节点
void linkBefore(E e, Node<E> succ) {
//记录下要插入位置的前驱节点
final Node<E> pred = succ.prev;
//创建新节点 前驱为要插入位置的前驱,内容为传入的e,后继节点为之前插入位置的那个节点
//即: pred <—— newNode ——>succ
final Node<E> newNode = new Node<>(pred, e, succ);
//让之前插入位置的那个节点向前指向新节点
//即: pred <—— newNode <——> succ
succ.prev = newNode;
//如果插入位置前驱节点为空,说明插入的节点是第一个节点,将其作为头结点
if (pred == null)
first = newNode;
else
//否则将前驱节点指向新节点
//即:pred <——> newNode
pred.next = newNode;
//长度+1
size++;
//修改次数+1
modCount++;
}
解析:在指定位置插入元素。判断如果是在末尾插入,调用linkLast进行插入,如果不是,调用linkBefore进行插入。
3.public E remove()
public E remove() {
return removeFirst();
}
public E removeFirst() {
//记录下头结点
final Node<E> f = first;
//如果头结点为空,即集合为空,抛出异常
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
//头结点解链
private E unlinkFirst(Node<E> f) {
//保存(头)结点的内容
final E element = f.item;
//保存(头)节点的后继节点
final Node<E> next = f.next;
//(头)节点内容置为空
f.item = null;
//断开(头)节点与其后继节点的联系
f.next = null;
//头结点置为它的原头结点的后继节点
first = next;
//如果原头结点的后继节点为空,说明现在整个集合都为空,将尾节点置为空
if (next == null)
last = null;
else
//否则,将哟删除的节点置空
next.prev = null;
//长度-1
size--;
//修改次数+1
modCount++;
return element;
}
解析:删除头结点。对头结点进行解链操作。
4.public E remove(int index)
public E remove(int index) {
//检查要index是否合法
checkElementIndex(index);
//获取将要删除的节点解链,并返回删除的元素
return unlink(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 unlink(Node<E> x) {
//记录下要删除节点的内容(元素、后继、前驱)
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 null <—— x
prev.next = next;
x.prev = null;
}
//如果要删除的节点的后继为空,说明删除的是尾节点
if (next == null) {
//将删除的节点的前驱作为尾节点
last = prev;
} else {
//否则,删除的不是尾节点,直接让要删除的节点的后继指向要删除的节点的前驱
//要删除的节点向后指向空
//即完成了: prev <——> next null <—— x ——>null
next.prev = prev;
x.next = null;
}
//要删除的节点的内容置空
x.item = null;
//长度-1
size--;
//修改次数+1
modCount++;
//返回删除节点的内容
return element;
}
解析:删除指定位置的节点。对该位置的节点进行解链操作。要注意一下获取对应下标的节点,使用了一次二分,将集合长度的一半与要查找的下标比较,如果下标小于集合长度一半,则从前往后查找,否则从后往前查找,减少了不必要的遍历。
5.public boolean remove(Object o)
public boolean remove(Object o) {
//要删除的元素是否为空
if (o == null) {
//从头到尾遍历链表,找到第一个为空的节点,执行解链操作,返回true
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
//要删除的元素不为空
//从头到尾遍历链表,找到内容与要删除节点内容相同的节点,执行解链操作,返回true
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
//如果没有找到,删除失败返回false
return false;
}
//将要删除的节点解链
E unlink(Node<E> x) {
//记录下要删除节点的内容(元素、后继、前驱)
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 null <—— x
prev.next = next;
x.prev = null;
}
//如果要删除的节点的后继为空,说明删除的是尾节点
if (next == null) {
//将删除的节点的前驱作为尾节点
last = prev;
} else {
//否则,删除的不是尾节点,直接让要删除的节点的后继指向要删除的节点的前驱
//要删除的节点向后指向空
//即完成了: prev <——> next null <—— x ——>null
next.prev = prev;
x.next = null;
}
//要删除的节点的内容置空
x.item = null;
//长度-1
size--;
//修改次数+1
modCount++;
//返回删除节点的内容
return element;
}
解析:删除指定元素的节点。注意判断元素相同的指标是equals方法,删除对象型元素注意重写equals方法。
6.public E get(int index)
public E get(int index) {
//检查index合法性
checkElementIndex(index);
//获取index位置节点的数据域
return node(index).item;
}
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;
}
}
解析:获取指定位置上的元素。要注意一下获取对应下标的节点,使用了一次二分,将集合长度的一半与要查找的下标比较,如果下标小于集合长度一半,则从前往后查找,否则从后往前查找,减少了不必要的遍历。
遍历
public E next() {
//检查并发修改
checkForComodification();
//如果没有下一个元素了,抛出异常
if (!hasNext())
throw new NoSuchElementException();
//迭代移动
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
//判断还有没有下一个元素
public boolean hasNext() {
return nextIndex < size;
}
//检查并发修改
final void checkForComodification() {
//如果修改次数不等于迭代器创建时记录的修改次数,则说明在迭代过程中修改了,抛出并发修改异常
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}