1.基本原理:LinkedList底层基于双向链表实现的,随机查找慢,需要从头开始一个一个比较,删除、插入快,只需要修改节点的引用就行了,没有扩容的说法
2.成员变量分析
/**
* 元素数量
*/
transient int size = 0;
/**
* 第一个元素,封装为一个Node
*/
transient Node<E> first;
/**
* 最后一个元素,封装为一个Node
*/
transient Node<E> last;
3.Node节点分析
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;
}
}
4.双向链表数据结构:LinkedList里面有一个双向链表数据结构,每个节点就是一个Node,Node有一个prev指向上一个节点,next指向下一个节点,item存放实际元素值,first指向第一个Node,last指向最后一个Node,由下面图表示
5.boolean 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++;
}
画图分析
刚开始链表没有元素,first和last都指向null
第一次添加元素
final Node<E> l = last;
这一句以后多了一个l指针指向null
final Node<E> newNode = new Node<>(l, e, null);
这一句构造了一个Node节点,prev指向l也为null,item为e,next为null,newNode 指向这个Node
last = newNode;
这一句以后last指向newNode指向的内容
if (l == null)
first = newNode;
else
l.next = newNode;
接着l==null成立,执行first = newNode,first 指向newNode指向的内容
方法执行完毕,局部变量指针去掉变成下面这样
现在集合有元素了,再次添加元素
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
构造一个新的节点prev指向l指向的内容,item为e,next为null
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
l==null不成立了执行 l.next = newNode;
方法执行完毕,局部变量指针去掉变成下面这样
后面add方法以此类推新增一个节点
6.void add(int index, E element)源码分析
/**
* 在指定位置插入一个元素
*/
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
看看checkPositionIndex(index);
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
也就如果指定index不在0到size之间就抛出IndexOutOfBoundsException异常
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
如果index == size成立说明是往最后一个位置插入元素,调用 linkLast(element),和add()方法一样了,否则就是队列中间插入元素调用linkBefore(element, node(index))方法
先看看node(index)方法
/**
* 返回指定位置的元素
*/
Node<E> node(int index) {
// size >> 1相当于size÷2的意思,也就是说如果index小于size的一半,从头开始遍历
// 如果index大于size的一半,从尾部开始倒着遍历
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;
}
}
再看看linkBefore(element, node(index))方法
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
假设链表中本来有3个元素,调用了add(1, "张三"),通过node(1)方法,找到了第一个位置的元素,然后调用了linkBefore("张三", succ)方法,刚开始数据结构如下图所示
接下来我们一行代码一行代码分析
final Node<E> pred = succ.prev;
多了个pred指针指向succ的prev指向的内容
final Node<E> newNode = new Node<>(pred, e, succ);
创建了一个Node,perv指向pred指向的内容,item为指定的新元素,next为succ指向的内容
succ.prev = newNode;
succ的prev指向newNode指向的内容
if (pred == null)
first = newNode;
else
pred.next = newNode;
如果pred==null说明,succ的prev为null也就是index为0,第一个元素那么first就执行newNode了,否则执行pred.next = newNode;pred的next指向newNode指向的内容
方法执行完毕,取出局部变量后变为下面这样
指定位置插入元素操作就完成了
7.E set(int index, E element)源码分析
/**
* 覆盖指定位置的元素,返回旧值
*/
public E set(int index, E element) {
// 检查是否越界
checkElementIndex(index);
// 取出指定位置的元素
Node<E> x = node(index);
// item指定位置元素的item设置为新元素,return旧元素
E oldVal = x.item;
x.item = element;
return oldVal;
}
8.public void addLast(E e)源码分析
/**
* 插入到最后一个
*/
public void addLast(E e) {
linkLast(e);
}
调用的linkLast(e)方法
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++;
}
经过上面方法的分析,这个一看就明白,在最后位置插入一个元素
9.public void addFirst(E e)源码分析
/**
* 插入到第一个
*/
public void addFirst(E e) {
linkFirst(e);
}
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
经过上面方法的分析,这个一看就明白,在第一个位置插入一个元素
10.E get(int index)源码分析
/**
* 获取指定位置的元素
*/
public E get(int index) {
// 校验index是否在0-size之间
checkElementIndex(index);
// 通过node方法判断index在小于size÷2就从头开始遍历找到元素,否则从尾部开始遍历找到元素
return node(index).item;
}
11.E getFirst()源码分析
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
直接返回first节点的item
12.E getLast()源码分析
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
直接返回last节点的item
13.E 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;
}
x.item = null;
size--;
modCount++;
return element;
}
先通过node(index)拿到要删除的元素,分析下unlink(node(index))方法
假设目前集合有4个元素,下图结构
调用了remove(1)方法,先通过node找到了item为张三的元素,调用unlink(node(index))
final E element = x.item;
element 指向x.item
final Node<E> next = x.next;
next指向x.next
final Node<E> prev = x.prev;
prev指向x.perv
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
如果prev == null说明要删除的是第一个元素让first指向next就行了,删除中间的元素执行
prev.next = next;
x.prev = null;
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
如果next == null说明要删除最后一个元素,last指向prev,删除中间元素执行
next.prev = prev;
x.next = null;
x.item = null;
方法执行完毕局部变量去掉,没有引用的Node节点去掉变成下面这个样子