LinkedList 的介绍:是Java collections 集合的一种实现,底层数据结构是一种双向链表,LinkedList 所包含的元素是以节点(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;
}
}
Node节点的数据结构如下:
LinkedList 在源码中的介绍如下:
/**
* Doubly-linked list implementation of the {@code List} and {@code Deque}
* interfaces. Implements all optional list operations, and permits all elements
* (including {@code null}).
* 实现了List列表和双向队列Deque的双向链表,实现了列表所有的方法,允许插入任何类型的元素(包括null)。
*
* <p>
* 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.
* 所有的操作都能像双向列表一样被预期。索引到列表中的操作将从头或者末尾开始遍历列表,这以较接近指定索引的一端为准。
*
* <p>
* <strong>Note that this implementation is not synchronized.</strong> If
* multiple threads access a linked list concurrently, and at least one of the
* threads modifies the list structurally, it <i>must</i> 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.
* LinkedList 实现类是不同步的、非线程安全的。若多个线程同时访问一个
* LinkedList 实例,并且至少有一个线程在结构上改变了该 列表,那么它必须保持外部同步。
* (结构上的改变是指插入或删除一个或者多个元素元素,或者显式调整底层数组的大小的操作,给
* 一个元素赋值不是结构上的改变。)外部同步一般通过对自然封装列表对象进行同步操作来完成。
*
* If no such object exists, the list should be "wrapped" using the
* {@link Collections#synchronizedList Collections.synchronizedList} method.
* This is best done at creation time, to prevent accidental unsynchronized
* access to the list:
* 如果不存在这样的对象,则应该使用 Collections.synchronizedList 方法将该
* 列表“包装”起来。 这个操作最好在创建的时候完成,以防止意外对列表进行不同步的访问:
*
* <pre>
* List list = Collections.synchronizedList(new LinkedList(...));
* </pre>
*
* <p>
* The iterators returned by this class's {@code iterator} and
* {@code listIterator} methods are <i>fail-fast</i>: if the list is
* structurally modified at any time after the iterator is created, in any way
* except through the Iterator's own {@code remove} or {@code add} methods, the
* iterator will throw a {@link 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.
* LinkedList 类的 iterator 和 listIterator 方法返回的迭代器是快速失败
* (注:fail-fast 机制是java 集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能
* 会产生fail-fast事件。例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合
* 的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException
* 异常,产生fail-fast事件。)的:在创建迭代器之后,除非通过迭代器自身的 remove 或 add 方法从
* 结构上对列表进行修改,否则在任何时间以任何方式对列表进行修改,迭代器都会抛出 ConcurrentModificationException。
* 因此,面对并发的修改,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。
* <p>
* 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
* {@code ConcurrentModificationException} on a best-effort basis. Therefore, it
* would be wrong to write a program that depended on this exception for its
* correctness: <i>the fail-fast behavior of iterators should be used only to
* detect bugs.</i>
* 注意迭代器的快速失败机制无法得到保证,一般来说,不可能对是否出现不同步并发修
* 改做出任何硬性保证。快速失败迭代器在尽力而为地抛出 ConcurrentModificationException异常。
* 所以,为了提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误的做法:迭代器的快速失败 机制应该仅用于检测 bug。
* <p>
* This class is a member of the
* <a href="{@docRoot}/../technotes/guides/collections/index.html"> Java
* Collections Framework</a>.
*
* @author Josh Bloch
* @see List
* @see ArrayList
* @since 1.2
* @param <E>
* the type of elements held in this collection
*/
LinkedList 的成员变量:
1.size:包含的元素个数
2.first:第一个节点
3.last:最后一个节点
transient int size = 0;// 节点个数
/**
* Pointer to first node. Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
* 第一个节点,若链表为空,则 first 是不变的,是null
*/
transient Node<E> first;//
/**
* Pointer to last node. Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
* 最后一个节点,若链表为空,则 last 是不变的,是null
*/
transient Node<E> last;
LinkedList 的构造函数:
1.LinkedList():空构造函数
2.LinkedList(Collection<? extends E> c):指定集合为参数的构造函数,LinkedList 元素的顺序就是该初始集合的迭代器返回的元素顺序
public LinkedList() {
}
/**
* Constructs a list containing the elements of the specified collection, in
* the order they are returned by the collection's iterator.
* 以指定集合为参数的构造函数,LinkedList 元素的顺序就是该初始集合的迭代器返回的元素顺序。
*
* @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);
}
插入元素的方法:
1.add(E e):在 LinkedList 的末端插入一个元素,复杂度O(1)
2.add(int index, E element) :在指定的位置插入一个元素,复杂度O(1)
3.addAll(Collection<? extends E> c):在 LinkedList 的末端插入一个集合,复杂度O(1)
4.addAll(int index, Collection<? extends E> c):在指定的位置插入一个集合,复杂度O(n)
/**
* Appends the specified element to the end of this list.
* 在列表的末端插入一个元素
* <p>
* This method is equivalent to {@link #addLast}.
*
* @param e
* element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* 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).
* 在指定的位置插入一个元素,原先位置及其后面的元素依次后移
* @param index
* index at which the specified element is to be inserted
* @param element
* element to be inserted
* @throws IndexOutOfBoundsException
* {@inheritDoc}
*/
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
/**
* Appends all of the elements in the specified collection to the end of
* this list, in the order that they are returned by the specified
* collection's iterator. The behavior of this operation is undefined if the
* specified collection is modified while the operation is in progress.
* (Note that this will occur if the specified collection is this list, and
* it's nonempty.)
* 在列表的末端插入一个集合
* @param c
* collection containing elements to be added to this list
* @return {@code true} if this list changed as a result of the call
* @throws NullPointerException
* if the specified collection is null
*/
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
/**
* Inserts all of the elements in the specified collection into this list,
* starting at the specified position. Shifts the element currently at that
* position (if any) and any subsequent elements to the right (increases
* their indices). The new elements will appear in the list in the order
* that they are returned by the specified collection's iterator.
* 在指定的位置插入一个集合
* @param index
* index at which to insert the first element from the specified
* collection
* @param c
* collection containing elements to be added to this list
* @return {@code true} if this list changed as a result of the call
* @throws IndexOutOfBoundsException
* {@inheritDoc}
* @throws NullPointerException
* if the specified collection is null
*/
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);
Object[] a = c.toArray();
int numNew = a.length;
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;
}
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
以
add(E) 方法插入一个新元素的实现来分析源码的操作,
/**
* Appends the specified element to the end of this list.
* 在列表的末端插入一个元素
* <p>
* This method is equivalent to {@link #addLast}.
*
* @param e
* element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
linkLast(e);
return true;
}
执行 linkLast(E e) 方法将列表的最后一个节点置为包含该元素 e 的节点,
/**
* Links e as last element.
* 将 e 链接为链表的最后一个元素
*/
void linkLast(E e) {
final Node<E> l = last;//临时变量 l 保存当前列表的最后一个元素
final Node<E> newNode = new Node<>(l, e, null);//构造一个包含待插入元素 e 的新节点
last = newNode;//将当前列表的最后一个节点设置为待插入的节点
if (l == null)//若插入之前的链表的最后一个节点是null,即插入之前的链表是空链表
first = newNode;//则待插入的节点也是第一个节点
else
l.next = newNode;//否则,将插入之前的最后一个节点的下一个节点设置为待插入的新节点
size++;//当前链表所包含的节点个数+1
modCount++;
}
通过上述描述可知,在构造一个空链表之后,frst 和 last 节点都是 null,当插入一个元素时,将 first 和 last 的节点设置为插入的元素节点,往后若再往链表的末端插入元素时,仍然只需要像第一次插入一个元素一样,先构造出包含元素的新节点,并且将链表的 last 节点的引用指向新节点。另外,也可以注意到 fist 节点的 prev 节点永远是 null,last 节点的 next 节点也永远是null。
如下右侧图观察可得,从 first 节点是链表的入口节点,后面的元素依次是存储在 next 节点中,直至 last 节点。
使用 add(int index, E element) 在指定位置插入一个元素的方法的操作同理,
/**
* 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).
* 在指定的位置插入一个元素,原先位置及其后面的元素依次后移
*
* @param index
* index at which the specified element is to be inserted
* @param element
* element to be inserted
* @throws IndexOutOfBoundsException
* {@inheritDoc}
*/
public void add(int index, E element) {
checkPositionIndex(index);//检查index 索引的合理性,即是否越界
if (index == size)//若 index 为当前链表的大小,则说明刚好在末尾插入元素
linkLast(element);//直接调用 linkLst(E e) 方法
else
linkBefore(element, node(index));//在指定的节点前插入一个元素,通过node(int index) 获取指定的节点
}
若非在链表的末尾执行插入操作,则需执行 linkBefore(E e, Node<E> succ) 方法,在不为 null 的节点前面插入一个元素,
/**
* Inserts element e before non-null Node succ.
* 在不为 null 的节点前面插入一个元素
*/
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;// succ的前一个节点
final Node<E> newNode = new Node<>(pred, e, succ);// 构造包含待插入元素的节点,其前后节点此时都已知
succ.prev = newNode;// 将succ的前一个节点设置为新节点
if (pred == null)// 若succ的前一个节点为 null,说明 succ 是第一个节点
first = newNode;// 则 first 设置为 新节点
else
pred.next = newNode;// pred 的下一个节点设置为新节点
size++;
modCount++;
}
在列表末端插入集合的操作是将集合先转化为数组,然后逐个构造节点,再将节点一个一个地设置为当前链表最后一个节点的 next 节点实现插入。
/**
* Appends all of the elements in the specified collection to the end of
* this list, in the order that they are returned by the specified
* collection's iterator. The behavior of this operation is undefined if the
* specified collection is modified while the operation is in progress.
* (Note that this will occur if the specified collection is this list, and
* it's nonempty.) 在列表的末端插入一个集合
*
* @param c
* collection containing elements to be added to this list
* @return {@code true} if this list changed as a result of the call
* @throws NullPointerException
* if the specified collection is null
*/
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
执行 addAll(int index, Collection<? extends E> c) 方法,在链表的末尾插入元素,此刻 index=size ,
/**
* Inserts all of the elements in the specified collection into this list,
* starting at the specified position. Shifts the element currently at that
* position (if any) and any subsequent elements to the right (increases
* their indices). The new elements will appear in the list in the order
* that they are returned by the specified collection's iterator.
* 在指定的位置插入一个集合
*
* @param index
* index at which to insert the first element from the specified
* collection
* @param c
* collection containing elements to be added to this list
* @return {@code true} if this list changed as a result of the call
* @throws IndexOutOfBoundsException
* {@inheritDoc}
* @throws NullPointerException
* if the specified collection is null
*/
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);
Object[] a = c.toArray();//集合转化为数组
int numNew = a.length;
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);//构造新节点,pred为当前插入位置的前一个节点,后一个节点为 null
if (pred == null)//若前一个节点为 null ,则说明index =0
first = newNode;//将 first 设置为新节点
else
pred.next = newNode;//否则当前插入位置的前一个节点的后一个节点设置为新节点
pred = newNode;//最后更新pred 为下一次要插入位置上的节点的前一个节点
}
if (succ == null) {//刚好在列表的末端插入集合
last = pred;//`pred就是最后一个节点
} else {
pred.next = succ;//插入后的节点的下一个节点置为原本索引位置上的节点
succ.prev = pred;//原本索引位置上的节点的前一个节点职位插入后的节点
}
size += numNew;
modCount++;
return true;
}
若是需要在指定的位置上插入集合,则直接调用 addAll(int index, Collection<? extends E> c) 方法即可。
LinkedList 的访问、修改和删除元素的方法
1.访问:getFirst(),获取列表第一个元素,复杂度O(1)
2.访问:getLast(),获取列表最后一个元素,复杂度O(1)
3.访问:get(int index),获取指定位置上的元素,从头或者从尾开始依次遍历,复杂度O(n)
4.修改:set(int index, E element),修改指定位置上的元素,复杂度O(n)
5.移除:remove(int index),移除指定位置上的节点,则可实现移除指定位置的元素,复杂度O(n)
/**
* Returns the first element in this list.
* 获取链表第一个节点的元素值
* @return the first element in this list
* @throws NoSuchElementException
* if this list is empty
*/
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
/**
* Returns the last element in this list.
* 获取链表最后一个节点的元素值
* @return the last element in this list
* @throws NoSuchElementException
* if this list is empty
*/
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
/**
* 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) {
checkElementIndex(index);
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;//修改之前的元素
x.item = element;//更新为新的元素
return oldVal;//修改成功,则将修改前的元素返回
}
/**
* 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.
* 移除指定位置上的元素
*
* @param index
* the index of the element to be removed
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException
* {@inheritDoc}
*/
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
不难发现,LinkedList 的访问、修改和删除元素的方法实际是基于节点的操作,而且是根据节点的索引以及改变节点的前后节点操作的。
插入操作,add(E e) 和 addAll(Collection<? extends E> c) 的复杂度是O(1),若不是在 LinkedList 的末端插入元素或者集合需要先查找到第几个元素,直接指针指向操作,因此 add(int index, E element) 和 addAll(int index, Collection<? extends E> c) 的时间复杂度是O(n)。
而移除操作remove(int index) ,删除元素,直接指针指向操作,复杂度O(1)。
总结:
1.LinkedList基于链表的数据结构,是不同步、非线程安全的;
2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针;
3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。