LinkedList源码分析
众所周知,LinkedList实现了List接口,LinkedList适用于集合元素先入先出和先入后出的场景,在队列的源码中被频繁使用,我们在使用一般的队列时,就直接可以拿LinkedList来实现,如下:
LinkedList<Integer> queue = new LinkedList<>();
queue.addLast(3); //相当于入队操作
queue.addLast(4);
queue.addLast(5);
System.out.println(queue.removeFirst()); //相当于出队操作(poll)
System.out.println(queue.getFirst()); //相当于peek操作
可见LinkedList的操作十分的灵活!!
1. 整体架构
LinkedList的底层结构是一个双向链表,如下图:
看到上图的结构,注意到:
- 当链表中没有数据的时候,first和last是同一个节点,前后指向都为零;
- 双向链表大小只受内存大小的限制;
1. 类定义
先来看看这个类的定义:
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
注意到了没,他还实现了Deque这个接口,所以LinkedList中还实现了poll()、peek()、push()、pop()等等,这些方法都是实现了Deque这个接口里的抽象方法,而这些方法的实现,就是基于LikedList里面本来的add、remove系列操作,比如看push()和pop()和peek()这三个方法:
public void push(E e) {
addFirst(e); //头插法新增节点
}
public E pop() { //删除头节点
return removeFirst();
}
public E peek() { //返回头节点
final Node<E> f = first;
return (f == null) ? null : f.item;
}
2. 底层结构
在LinedList里面有一个为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;
}
}
3. 常用方法源码解析
1. 构造方法
有两个构造方法,一个是无参构造,一个是传入一个Collection集合,注意该集合的泛型得是当前定义的泛型的子类!
public LinkedList() {
}
//该构造函数将传入的集合(符合要求的)转化为LinkedList
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
注意第二个构造函数:
- 当传入的集合泛型不符合条件时,则会编译出错;
2. 新增操作
即为双向链表,所以新增节点的操作可以为头插,也可以是尾插;
在LikedList里面,定义了两个节点:
transient Node<E> first;
transient Node<E> last;
这两个初始值都为null;
1. add()方法(尾插)
add方法默认是从尾部开始增加节点的:
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
//先获取尾节点
final Node<E> l = last;
//这里通过构造方法就将newNode的prev设置为last,next设置为空
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
//判断是不是空链表(插入前)
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
//记录更改
modCount++;
}
2. addFirst()方法 (头插)
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++;
}
头插为尾插很相似,这里就没有加注释!
3. 删除操作
同样的,删除也分为头部删除和尾部删除
1. remove()方法 (也就是removeFirst方法)
remove方法实现的就是删除头节点,而具体实现查看源码可以发现就是调用了removeFirst:
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; // help GC
first = next;
if (next == null)
//这里代表只有一个节点,此时应将last置空,因为一个节点的时候first和last都是这个节点;
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
2. removeLast()方法
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
可以看出和removeFirst的逻辑是一样的;
3. remove(int index)方法(指定下标删除节点)⭐
public E remove(int index) {
//检查下标是否合法的一系列操作
checkElementIndex(index);
return unlink(node(index));
}
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
//根据下标查找到这个节点并且返回;
Node<E> node(int index) {
//如果index小于size的一半,说明这个节点在双向链表的左半侧,从first向右开始查找;
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
//此时index大于size的一半,说明这个节点在双向链表的右半侧,从last向左开始查找;
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
//删除这个节点的真正操作;
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;
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
//如果要删除的节点后面为空的话,则该节点删除后,就应该把该节点前面的节点设置为last;
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
注意点:
- 可以看出删除指定下标的节点的逻辑是:
- 先通过 checkElementIndex(int index) 方法检查要删除的下标是否存在;
- 然后通过node(int index) 方法找到要删除的下标那个节点;
- 执行unlink(Node node) 方法进行节点删除;
- 结合前面的来看:
unlink代表删除指定节点,unlinkFirst代表删除头节点,unlinkLast代表删除尾节点!
- 结合前面的来看:
- 设计亮点: ⭐
- node(int index)方法在查找节点的时候并不是从头开始遍历,而是通过了一次二分法,来确定是从头还是从尾开始遍历;
4. 节点查询
1. get(int index)方法:获取指定下标的节点值
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
上面已经分析过node这个方法,用来获取指定下标的节点,所以上面查询很简单;
2. indexOf(Object o)方法 :获取指定节点的值的下标
public int indexOf(Object o) {
int index = 0;
if (o == null) {
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++;
}
}
return -1;
}
3. contains(Object o)方法:是否存在某个节点值的节点⭐
public boolean contains(Object o) {
return indexOf(o) != -1;
}
这里直接调用了上面的方法,很巧妙!!!
4. 迭代器
1. 单项迭代器
我们知道,在JDK1.5之前Iterable接口(注意不是Iterator接口,在Iterable接口里定义了获取Iterator的抽象方法)中的iterator()方法是直接在Collection接口中定义的,之后Collection接口就直接实现了Iterable接口 ,自然也就继承了iterator这个抽象方法,从而让自己的子类去完成实现 ,所以最原始的迭代器在每个集合里都可以直接调用iterator()方法去获取它的迭代器 ;
LinkedList也一样,只不过注意iterator的具体实现不是在LinkedList里面完成的,而是它的父抽象类:AbstractSequentialList < E > ,而这个类又继承了AbstractList 类,具体的实现就是在这个类里面实现了迭代器的具体细节;
给上集合的一张类图继承关系:
所以LinkedList使用迭代器操作很简单:
LinkedList<Integer> list = new LinkedList<>();
Iterator<Integer> integerIterator = list.iterator();
while (integerIterator.hasNext()) {
System.out.print(integerIterator.next()+" ");
}
2. 双向迭代器 ⭐
在LinkedList中虽然没有直接实现单向迭代器,但实现了双向迭代器ListItr:
这个类是LinkedList的内部类,实现了ListIterator接口,而ListIterator接口实现了Iterator;
(ListIterator是专为双向迭代设计的一个接口,就是比Iterator多了关于从后往前遍历的抽象方法)
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned;
private Node<E> next;
private int nextIndex;
private int expectedModCount = modCount;
//这里得参数代表迭代的位置从哪开始,如果不填参数,则会调用父类AbstractList的迭代器
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);
nextIndex = index;
}
public boolean hasNext() {
return nextIndex < size;
}
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
public boolean hasPrevious() {
return nextIndex > 0;
}
public E previous() {
checkForComodification();
if (!hasPrevious())
throw new NoSuchElementException();
lastReturned = next = (next == null) ? last : next.prev;
nextIndex--;
return lastReturned.item;
}
public int nextIndex() {
return nextIndex;
}
public int previousIndex() {
return nextIndex - 1;
}
public void remove() {
checkForComodification();
if (lastReturned == null)
throw new IllegalStateException();
Node<E> lastNext = lastReturned.next;
unlink(lastReturned);
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
lastReturned = null;
expectedModCount++;
}
public void set(E e) {
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
lastReturned.item = e;
}
public void add(E e) {
checkForComodification();
lastReturned = null;
if (next == null)
linkLast(e);
else
linkBefore(e, next);
nextIndex++;
expectedModCount++;
}
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (modCount == expectedModCount && nextIndex < size) {
action.accept(next.item);
lastReturned = next;
next = next.next;
nextIndex++;
}
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
然后在LinkedList中提供了获取这个迭代器的方法:
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
3. 迭代器源码分析⭐⭐⭐
1. 使用案例
LinkedList<Integer> list = new LinkedList<>();
list.addLast(1);
list.addLast(2);
list.addLast(3);
//使用双向迭代器从后往前迭代,这里参数得填,否则默认为零,那就从0号下标往前迭代,当然没元素;
ListIterator<Integer> listIterator = list.listIterator(2);
while (listIterator.hasPrevious()) {
System.out.print(listIterator.previous()+" ");
}
System.out.println();
//使用双向迭代器从前往后迭代,这里也可以传参数,不传默认为零
ListIterator<Integer> listIterator1 = list.listIterator();
while (listIterator1.hasNext()) {
System.out.print(listIterator1.next()+" ");
}
输出:
2 1
1 2 3
我们在LinkedList类中看到了自己实现的双向迭代器类:ListItr ,这个类只有一个有参构造,需要传入一个整数,代表开始迭代的位置,那么上面怎么能调用无参的listIterator()方法呢?
原因是无参的那个方法不是LinkedList实现的,是他的父类AbstractList实现的,我们来看看这个类:
我们的ArrayList和LinkedList都继承了这个抽象类,看它的类结构,里面封装了两个内部类:Itr和ListItr,看着两个名字就知道他们是用来实现迭代器的,而ListItr又继承了Itr,看这两个类中的方法:
- Itr类中实现了基本的迭代,这个类实现了Iterator接口,没有双向迭代的功能;
- 所谓基本迭代,进去发现其实就是默认位置从下标0开始,一次往后遍历来实现遍历元素;
- ListItr类继承了Itr,所以它也拥有基本的迭代功能,它也实现了ListIterator接口,所以它能实现双向迭代功能,你看它的那几个方法嘛,比如previous()和hasPrevious();
- ListItr构造方法也是必须要传入参数的,用来指定你从哪开始迭代,因为有了从后往前迭代,所以你得指定你从哪个下标开始嘛;
而在AbstractList类中,提供了iterator()和listIterator(int index)和listIterator(无参)这些方法来获取迭代器:
-
iterator()获取的就是上面的Itr这个类的对象;
public Iterator<E> iterator() { return new Itr(); }
- 我们在LinkedList的源码中看到没有覆写iterator()这个方法,所以LinkedList获取普通迭代器就是使用的父类Abstract类中的iterator方法来获取的,而LinkedList实现了自己的双向迭代器;
- 注意:ArrayList中自己即实现了自己的单向迭代器(iterator()方法),也自己实现了listIractor方法来提供自己的双向迭代器,所以ArrayList在使用迭代器的时候使用的是自己的,不是它的父类Abstract中的;
-
listIterator(int index)获取的就是上面ListItr类的对象:
public ListIterator<E> listIterator(final int index) { rangeCheckForAdd(index); //上面说过,这个双向的迭代器必须传入参数 return new ListItr(index); }
-
而listIterator(无参)就是默认参数为零:
public ListIterator<E> listIterator() { return listIterator(0); }
这个方法正是上面那个问题的解!!LinkedList在使用listIterator却不带参数时,正是调用它的父类中的这个方法;
4. 总结⭐⭐
上面叙述得稍些杂乱,这里记住这几点:
- LinkedList只实现了自己的有参双向迭代器(listIterator(int index)方法获取),若要使用普通迭代器和有参双向迭代器,都是调用父类AbstractList类的;
- 即LinkedList中只有listIterator(int index)这个方法;
- ArrayList都实现了,都是调用自己的;
- 即ArrayList中,iterator(),listIterator()、listIterator(int index)这三个方法它都有;