简介:
之前没有看过关于链表的源码,习惯了数据结构,刚刚看的时候感觉还有点不适应,但是越看越有意思,哈哈。和ArrayList一样,无非是一些增删改查的功能实现,接下来还是围绕这些分析。
成员变量
首先还是一些成员变量,链表重要的成员变量还是比较少的。
/**
* 链表的长度
*/
transient int size = 0;
/**
* 头节点
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* 尾节点
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
构造器
LinkedList提供了两种构造器:空参构造器和有一个集合参数的构造器。
/**
* 空构造器
*/
public LinkedList() {
}
/**
* 指定集合的构造器
*/
public LinkedList(Collection<? extends E> c) {
this();
// 调用添加方法
addAll(c);
}
内部类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;
}
}
下面是addAll()方法,但是不建议之前没有看过链表相关源码先看这个方法,还是先看其他的方法,主要是方法太长了,打击看下去的积极性。下面是代码:
/**
* 从尾节点将指定集合中的元素添加到链表中
*/
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
/**
* 从指定节点将指定集合中的元素添加到链表中
*/
public boolean addAll(int index, Collection<? extends E> c) {
// 检查索引是否正确
checkPositionIndex(index);
// 将指定集合转换成数组
Object[] a = c.toArray();
// 记录指定集合的长度,用于计算添加之后链表的长度。
int numNew = a.length;
if (numNew == 0)
return false;
// 记录插入的节点位置,pred(前一个节点),succ(后一个节点)
Node<E> pred, succ;
if (index == size) {
// 即从为节点插入
// 没有后一个节点,所以为null
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)
// pred 为null,说明是从头节点插入,所以将新创建出的节点设置为头节点
first = newNode;
else
//
pred.next = newNode;
// 将记录前一个节点的 pred 指向新节点,以便遍历插入下一个节点
pred = newNode;
}
// 前面链接好了,肯定要链接后面的一部分节点
if (succ == null) {
// succ 为null,说明没有下一个节点,将遍历创建的最后一个节点设置为尾节点
last = pred;
} else {
// 否则,将创建的最后一个节点和succ节点(也就是之前断开的后面的一部分链表的第一个节点)链接
pred.next = succ;
succ.prev = pred;
}
// 链表长度加上指定集合的长度
size += numNew;
// 操作记录数加1
modCount++;
return true;
}
这里有一个node()方法,这个方法挺重要的,下面很多地方都用到了这个方法。
/**
* 寻找指定位置的节点
*/
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;
}
}
下图帮助理解,这个过程
add()方法
/**
* 添加一个元素,从尾节点插入
*/
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* 在指定位置插入指定元素,可以看到本质上还是调用的从尾节点插入的方法,或者从指定节点前面插入的方法。
*/
public void add(int index, E element) {
// 检查索引是否正确
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
/**
* 将指定元素插入头节点
*/
public void addFirst(E e) {
// 实际插入头节点的操作
linkFirst(e);
}
/**
* 将指定元素插入尾节点
*/
public void addLast(E e) {
// 实际插入尾节点的操作
linkLast(e);
}
addFirst()这个方法本质上调用的是从头节点插入节点的方法,add()和addLast()本质上调用的是从尾节点插入节点的方法。
/**
* 将指定元素插入尾节点,这里居然没有用private修饰,具体原因我就不清楚了。
*/
void linkLast(E e) {
// 记录插入之前的尾节点
final Node<E> l = last;
// 创建新节点,prev=l(插入之前的尾节点),item=e,next=null
final Node<E> newNode = new Node<>(l, e, null);
// 将新节点赋值为尾节点
last = newNode;
if (l == null)
// 如果插入之前尾节点为null(即没有元素),则将新节点也赋值为头节点
first = newNode;
else
// 否则,将新节点赋值为插入之前的尾节点的 next
l.next = newNode;
// 链表的长度加1
size++;
// 操作记录数加1
modCount++;
}
看一下从头插入的方法吧,和从尾插入基本上是一样的。这些方法都被定义为private,所以具体调用的方法符合平常使用的方法名。
/**
* 将指定元素插入头节点
*/
private void linkFirst(E e) {
// 记录插入之前的头节点
final Node<E> f = first;
// 创建新节点,prev=null,item=e,next=f(插入之前的头节点)
final Node<E> newNode = new Node<>(null, e, f);
// 将新节点赋值为头节点
first = newNode;
if (f == null)
// 如果插入之前头节点为null(即没有元素),则将新节点也赋值为尾节点
last = newNode;
else
// 否则,将新节点赋值为插入之前的头节点的 prev
f.prev = newNode;
// 链表的长度加1
size++;
// 操作记录数加1
modCount++;
}
下图是linkFirst方法的图解;
下面是从指定位置之前插入节点的方法:
/**
* 在指定节点的前面插入指定元素,指定节点必须是 non-null (非 null)
*/
void linkBefore(E e, Node<E> succ) {
// 记录指定节点 succ 的前面一个节点
final Node<E> pred = succ.prev;
// 创建新节点,prev=pred,item=e,next=succ
final Node<E> newNode = new Node<>(pred, e, succ);
// 将新节点赋值为指定节点 succ 的前一个节点
succ.prev = newNode;
if (pred == null)
// 如果指定节点的prev为 null,说明指定节点 succ 为头节点,所以将新节点设置为头节点
first = newNode;
else
// 否则,将新节点赋值为指定节点 succ 的前一个节点的 next (下一个节点)
pred.next = newNode;
// 链表的长度加1
size++;
// 操作记录数加1
modCount++;
}
下面是这个方法的图解:
remove()方法
分为删除头节点和删除为节点。
/**
* 删除头节点
*/
public E removeFirst() {
// 记录头节点
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
// 实际删除头节点的操作
return unlinkFirst(f);
}
/**
* 删除尾节点
*/
public E removeLast() {
// 记录尾节点
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
// 实际删除尾节点的操作
return unlinkLast(l);
}
/**
* 删除方法,默认从头节点开始删除
*/
public E remove() {
return removeFirst();
}
本质上还是调用private的方法
/**
* 删除头节点
*/
private E unlinkFirst(Node<E> f) {
// 记录头节点的元素值
final E element = f.item;
// 记录头节点的下一个节点
final Node<E> next = f.next;
// 将头节点的item 和 next赋值为null ,帮助GC更快回收
f.item = null;
f.next = null; // help GC
// 将头节点的下一个节点设置为头节点
first = next;
if (next == null)
// 头节点的下一个节点为null ,证明原链表只有一个节点,设置尾节点为null
last = null;
else
// 否则,将头节点的下一个节点的 prev(前一个节点) 赋值为null
next.prev = null;
// 链表的长度减1
size--;
// 操作记录数加1
modCount++;
// 返回被删除的头节点对应的元素值
return element;
}
/**
* 删除尾节点
*/
private E unlinkLast(Node<E> l) {
// 记录尾节点的元素值
final E element = l.item;
// 记录尾节点的前一个节点
final Node<E> prev = l.prev;
// 将尾节点的item 和 next赋值为null ,帮助GC更快回收
l.item = null;
l.prev = null; // help GC
// 将尾节点的前一个节点设置为尾节点
last = prev;
if (prev == null)
// 尾节点的前一个节点为null ,证明原链表只有一个节点,设置头节点为null
first = null;
else
// 否则,将尾节点的前一个节点的 next(后一个节点) 赋值为null
prev.next = null;
// 链表的长度减1
size--;
// 操作记录数加1
modCount++;
// 返回被删除的尾节点对应的元素值
return element;
}
接下在就是删除指定节点的方法了
/**
* 删除指定节点
*/
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) {
// 如果指定节点的 prev 为null,说明指定节点为头节点,则将指定节点的下一个节点设置为头节点
first = next;
} else {
// 否则,将指定节点的前一个节点的next 赋值为 下一个节点
prev.next = next;
// 将指定节点的prev赋值为null,加快gc
x.prev = null;
}
if (next == null) {
// 如果指定节点的 next 为null,说明指定节点为尾节点,则将指定节点的前一个节点设置为尾节点
last = prev;
} else {
// 否则,将指定节点的下一个节点的prev 赋值为 前一个节点
next.prev = prev;
x.next = null;
}
// 将指定节点对应的元素赋值为null,加快GC
x.item = null;
// 链表的长度减1
size--;
// 操作记录数加1
modCount++;
// 返回被删除的指定节点对应的元素值
return element;
}
get()方法
获取头节点和尾节点,使用的是first,last这两个成员变量进行操作
/**
* 获取头节点的元素值
*/
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
/**
* 获取尾节点的元素值
*/
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
下面是从指定节点位置删除节点的方法,重点还是之前的寻找node位置的方法。
/**
* 获取指定位置的节点的元素值
*/
public E get(int index) {
// 检查索引是否正确
checkElementIndex(index);
// 检查索引是否正确,并获取改节点的元素值返回
return node(index).item;
}
Set方法
/**
* 修改指定索引位置的节点的元素值
*/
public E set(int index, E element) {
// 检查索引是否正确
checkElementIndex(index);
// 检查索引是否正确
Node<E> x = node(index);
// 记录修改指点的元素值
E oldVal = x.item;
// 修改
x.item = element;
// 返回修改值之前的值
return oldVal;
}
其他方法
/**
* 判断元素是否存在于链表中
*/
public boolean contains(Object o) {
return indexOf(o) != -1;
}
/**
* 获取指定元素在链表的位置
*/
public int indexOf(Object o) {
int index = 0;
if (o == null) {
// 寻找第一个null元素的位置,null不能调用equals方法
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
// 寻找指定元素的位置
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
// 没找到对应的元素,返回-1
return -1;
}