文章目录
LinkedList源码分析
类结构
与ArrayList不同的是,LinkedList继承AbstractSequentialList,实现了Deque(双端队列)。
AbstractSequentialList类介绍
This class provides a skeletal implementation of the List interface to minimize the effort required to implement this interface backed by a "sequential access" data store (such as a linked list). For random access data (such as an array), AbstractList should be used in preference to this class.
This class is the opposite of the AbstractList class in the sense that it implements the "random access" methods (get(int index), set(int index, E element), add(int index, E element) and remove(int index)) on top of the list's list iterator, instead of the other way around.
......
意思是指此类提供list接口的基本实现,以最大程度地减少实现“顺序访问”数据存储(列如链表)支持的该接口所需要的工作。
来看一下其中的set方法
/**
* Replaces the element at the specified position in this list with the
* specified element (optional operation).
*
*/
public E set(int index, E element) {
try {
//抽象方法,具体实现在LinkedList中
ListIterator<E> e = listIterator(index);
//返回当前元素
E oldVal = e.next();
//设置为新的值
e.set(element);
return oldVal;
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}
/**
* 创建iterator,next为当前节点
*/
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);
nextIndex = index;
}
/**
* Returns the (non-null) Node at the specified element index.
*/
Node<E> node(int index) {
// assert isElementIndex(index);
//因为linkedList是双向链表,判断index离first更近,还是last节点更近,采取不同的遍历方式
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;
}
}
双端队列Deque
linkedList是一个双向链表,很容易看成一个双端队列。
何为双端队列,原JDK文档介绍
支持在两端插入和删除元素的线性集合。名称双端队列是“双端队列”的缩写,通常发音为“ deck”。大多数Deque实现对它们可能包含的元素数量没有固定的限制,但是此接口支持容量受限的双端队列以及没有固定大小限制的双端队列。
该接口定义了访问双端队列两端的元素的方法。提供了用于插入,删除和检查元素的方法。这些方法中的每一种都以两种形式存在:一种在操作失败时引发异常,另一种返回一个特殊值(根据操作而为null或false)。插入操作的后一种形式专门设计用于容量受限的Deque实现。在大多数实现中,插入操作不会失败。
类介绍
原文档介绍
Doubly-linked list implementation of the List and Deque interfaces. Implements all optional list operations, and permits all elements (including null).
All of the operations perform as could be expected for a doubly-linked list. Operations that index into the list will traverse the list from the beginning or the end, whichever is closer to the specified index.
Note that this implementation is not synchronized. If multiple threads access a linked list concurrently, and at least one of the threads modifies the list structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more elements; merely setting the value of an element is not a structural modification.) This is typically accomplished by synchronizing on some object that naturally encapsulates the list. If no such object exists, the list should be "wrapped" using the Collections.synchronizedList method. This is best done at creation time, to prevent accidental unsynchronized access to the list:
List list = Collections.synchronizedList(new LinkedList(...));
The iterators returned by this class's iterator and listIterator methods are fail-fast: if the list is structurally modified at any time after the iterator is created, in any way except through the Iterator's own remove or add methods, the iterator will throw a ConcurrentModificationException. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.
Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs.
总结为以下几点:
-
LinkedList基于双端队列实现,包含所有list的操作,同时允许任何值,包括null。
-
LinkedList非线程同步的,如果多个线程同时操作,需保持外部同步,或者使用Collections的同步方法实现。
-
fail-fast机制,由于实现了iterators接口,在创建iterators时,以任何方式(除了通过迭代器自己的删除或添加方法)对列表进行结构修改,会报出ConcurrentModificationException异常。
-
内部结构是链表,所以LinkedList的删除,添加操作时间复杂度为O(1),查找时间复杂度为O(n),查找函数内部有一定的优化,判断离头还是尾部更近一点,从哪端开始遍历。
-
链表从0开始计数,index=0,表示第一个元素,读取index=size的时,会数组越界。
内部结构
源码介绍
成员变量
transient int size = 0;
/**
* Pointer to first node.
*/
transient Node<E> first;
/**
* Pointer to last node.
*/
transient Node<E> last;
/**
* 链表结构变化次数,用来iterator的fast-fail机制判断
*
*/
protected transient int modCount = 0;
其中Node节点为内部私有静态类
private static class Node<E> {
//元素数据
E item;
//next节点
Node<E> next;
//prev节点
Node<E> prev;
//node的构造函数,在构造节点时,就表明当前节点的next,prev节点,使后续指针的
//指向变得简单,只需将其他的节点指向该node,而无需再调整node的指针
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
构造函数
/**
* Constructs an empty list.
* 构建一个空链表 size == 0 first == next == null
*/
public LinkedList() {
}
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
* 集合中所有元素必须是链表中泛型指定的类型或者继承该类型,否则在运行时取数据会报转换异常
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public LinkedList(Collection<? extends E> c) {
this();
//将集合中所有元素添加到链表中
addAll(c);
}
添加元素
由于实现了双端队列,其添加元素共有6个方法,在abstractList的基础上新增了addFirst(E),addLast(E)方法
add(E)
/**
* Appends the specified element to the end of this list.
*
*/
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* Appends the specified element to the end of this list.
* 都是添加到链表尾部,一个返回true,一个不返回值
*/
public void addLast(E e) {
linkLast(e);
}
/**
* Links e as last element.
*/
void linkLast(E e) {
//获取last节点取值l
final Node<E> l = last;
//构造一个新的节点 新节点的pre是l,next为空
final Node<E> newNode = new Node<>(l, e, null);
//将last节点置为新加节点
last = newNode;
//如果l为空,表示链表为空,同时将first节点置为新节点,此时链表中有一个元素,first == last
if (l == null)
first = newNode;
else
//否则 l.next节点指向新节点
l.next = newNode;
//链表大小加一
size++;
//修改次数加一
modCount++;
}
add(int , E )
/**
* Inserts the specified element at the specified position in this list.
* Shifts the element currently at that position (if any) and any
* subsequent elements to the right (adds one to their indices).
*
*/
public void add(int index, E element) {
//index不合法,抛出异常 0=<index<=size 合法
checkPositionIndex(index);
if (index == size)
//如果为size大小,直接添加到last位置
linkLast(element);
else
//node(index) 找到index下标node的位置,前面已经给出源码
linkBefore(element, node(index));
}
/**
* Inserts element e before non-null Node succ.
*/
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
//获取succ的前驱节点
final Node<E> pred = succ.prev;
//构造新节点 新节点的pre为pred,next为succ, 数据为E
final Node<E> newNode = new Node<>(pred, e, succ);
//succ的前驱节点指向newNode
succ.prev = newNode;
//pred为null,表示succ为链表的first节点
if (pred == null)
//把first节点置为新节点
first = newNode;
else
//不为null的话,表示succ为非first的其他链表中的节点,存在前驱节点,将他的前驱节点指向新节点
pred.next = newNode;
size++;
modCount++;
}
第一,二步——找到succ节点的prev节点,并创建一个新的node
succ节点可能时first节点也可能不是head节点所以就出现下面两种情况
第三,四部,调整succ的prev指针和,pred的next节点,也分两种情况考虑,如下图。
add(int,Collection)
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);
//将集合转为Object数组
Object[] a = c.toArray();
//获取数组长度
int numNew = a.length;
if (numNew == 0)
return false;
//获取添加节点的位置,包括下标为index的节点和其前驱节点
Node<E> pred, succ;
//如果index为size大小,表示无当前节点,前驱节点为last,从last后添加元素
if (index == size) {
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
}
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
//构造新节点 prev = pred next =null,next会在后面调整,所以这边初始化为空
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
//如果pred为空,需要调整新插入的第一个节点为first节点
first = newNode;
else
//调整pred的next指针
pred.next = newNode;
//将pred节点指向newNode
pred = newNode;
}
//如果succ节点为空,将last指向pred即最后一个添加的节点
if (succ == null) {
last = pred;
} else {
//否 调整最后一个添加节点的next指针,同时调整succ的prev指针
pred.next = succ;
succ.prev = pred;
}
//调整size大小
size += numNew;
modCount++;
return true;
}
删除元素
同添加元素一样,删除元素也有与之对应的几个方法
remove()
remove()方法同removeFirst()一样。删除first节点
/**
* Retrieves and removes the head (first element) of this list.
*
*/
public E remove() {
return removeFirst();
}
/**
* Removes and returns the first element from this list.
*
* @return the first element from this list
* @throws NoSuchElementException if this list is empty
*/
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
/**
* Unlinks non-null first node f.
*/
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
//取first中元素
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
//first指针后移
first = next;
if (next == null)
//当只有一个元素时 被删除后 last指针置为空
last = null;
else
//调整first节点的前驱节点
next.prev = null;
size--;
modCount++;
return element;
}
remove(int)
/**
* Removes the element at the specified position in this list. Shifts any
* subsequent elements to the left (subtracts one from their indices).
* Returns the element that was removed from the list.
*
*/
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
E unlink(Node<E> x) {
final E element = x.item;
final Node<E> prev = x.prev;
final Node<E> next = x.next;
//先处理前驱节点, 判断被删除的是否是头节点 是的话first节点后移
if(prev == null)
first = next;
else {
prev.next = next;
x.prev = null;
}
//再处理后继节点 判断是否删除的是尾节点 是的话 last前移
if (next == null)
last = prev;
else {
next.prev = prev;
x.prev = null;
}
x.item = null;
size--;
modCount++;
return element;
}
remove(Object)
同ArrayList删除object方法一样,因为数组链表非有序数据结构,只能从头开始遍历,发现第一个相同的元素进行删除,该方法只会删除查到的第一个节点 removeFirstOccurrence方法同remove(Object)方法相同。
/**
* null和非null分开判断,是因为链表中也可能存在null,不论谁在前,做equals时可能包空指针问题
* 此处可以优化为Objects.equals(obj1,obj2)方法
*/
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
/**
* Removes the first occurrence of the specified element in this
* list (when traversing the list from head to tail). If the list
* does not contain the element, it is unchanged.
*
*/
public boolean removeFirstOccurrence(Object o) {
return remove(o);
}
/**
* 从last开始遍历,删除最后一个相等的元素
*
*/
public boolean removeLastOccurrence(Object o) {
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
get/set元素
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
//index >= 0 && index < size
checkElementIndex(index);
//返回node节点的item
return node(index).item;
}
/**
* Replaces the element at the specified position in this list with the
* specified element.
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
//node节点的元素设置为新值
x.item = element;
//返回旧值
return oldVal;
}
序列化
//每个类包含唯一的序列化ID,反序列化时会进行校验
private static final long serialVersionUID = 876323262645176354L;
/**
* Saves the state of this {@code LinkedList} instance to a stream
* (that is, serializes it).
*
* @serialData The size of the list (the number of elements it
* contains) is emitted (int), followed by all of its
* elements (each an Object) in the proper order.
*/
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();
// Write out size
s.writeInt(size);
// Write out all elements in the proper order.
for (Node<E> x = first; x != null; x = x.next)
s.writeObject(x.item);
}
/**
* Reconstitutes this {@code LinkedList} instance from a stream
* (that is, deserializes it).
*/
@SuppressWarnings("unchecked")
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
// Read in size
int size = s.readInt();
// Read in all elements in the proper order.
for (int i = 0; i < size; i++)
//调用linkLast依次把元素添加到last后面
linkLast((E)s.readObject());
}
遍历
LinkedList实现了iterator接口,内部包含一个实现了ListIterator的ListItr类。
ListIterator不同于Iterator提供了向前遍历元素的方法。关于Iterator前两篇文章中已有说明,不再详细展开。
/**
* Returns an iterator over the elements in this list (in proper
* sequence).<p>
*
* This implementation merely returns a list iterator over the list.
*
* @return an iterator over the elements in this list (in proper sequence)
*/
public Iterator<E> iterator() {
return listIterator();
}
public ListIterator<E> listIterator() {
return listIterator(0);
}
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
private class ListItr implements ListIterator<E> {
//当前遍历的节点
private Node<E> lastReturned;
//下一个节点
private Node<E> next;
//下一个节点的索引位置
private int nextIndex;
private int expectedModCount = modCount;
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();
//指针依次后移 索引下标加1
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;
//移除当前元素,实际调用LinkedList中unLink方法进行删除
unlink(lastReturned);
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
lastReturned = null;
//同步expectedModCount
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++;
}
//1.8里新增,可给出一个表达式,作为条件筛选
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内部是一个双向链表结构,通过fist节点,last节点实现元素的增删改,通过分析源码可以看出,java在其中编码做了很多优化的调整。比如add元素,实际内部调用了linkLast或者linkFast方法,将能抽象在一起的方法最大化的抽象,并在上层提供多种不同的操作方法。另外在定义Node节点的时候,只保留了三个参数的构造方法,java底层的码农应该也是经过思考得出,其他的构造方法并不实用,且容易导致指针操作复杂。在具体使用的时候,如果确定了前驱节点,则可以传入prev,确定了后继节点,则可以传入next。不确定的或者后面存在变更的,应该传入null。
另外LinkedList实际上是一个双端队列,源码中包含队列的很多方法peek,pop,push,pull,offer方法这里未给出,实际上其内部调用依然时add和remove的相关方法。具体在工作中,如果我们需要一个双端队列,第一想到的应该是linkedList。
当把一件事情做好后,你应该可以相信自己能把很多事情做好。
2020-02-03
湖北加油,武汉加油
来源:CSDN
作者:宏小白
链接:https://blog.csdn.net/sinat_22143835/article/details/104159054