線性鏈表 LinkedList 學(xué)習(xí),比起 HashMap 真是簡單多了。
@[toc]
LinkedList
特點
- 有序,但內(nèi)存空間中可能比較分散;
- 存儲相對較快、獲取相對較慢;
- 存儲的數(shù)據(jù)可以為 null;
- 線程不安全。
LinkedList 繼承/實現(xiàn)
- 繼承
AbstractSequentialList
:
線性序列結(jié)構(gòu)的抽象,繼承于 AbstractList;
抽象方法listIterator()
,子類必須實現(xiàn)此方法用于創(chuàng)建迭代器;
內(nèi)部封裝使用該迭代器實現(xiàn)的add()
、set()
、remove()
等方法。
public abstract ListIterator<E> listIterator(int index);
- 實現(xiàn)
List
接口:具有線性表的特性。 - 實現(xiàn)
Deque
接口:該接口繼承于Queue
接口,具有隊列特性。
參數(shù)以及構(gòu)造函數(shù)
- 參數(shù)
// 結(jié)點數(shù)量
transient int size = 0;
// 頭結(jié)點
transient Node<E> first;
// 尾結(jié)點
transient Node<E> last;
- 構(gòu)造函數(shù)
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
傳入 Collection c
參數(shù)的構(gòu)造函數(shù)的實現(xiàn)就是遍歷將結(jié)點存入 LinkedList
中。
基礎(chǔ)元素
比較簡單的一個靜態(tài)內(nèi)部類,一個雙向結(jié)點。
private static class Node<E> {
E item; // 數(shù)據(jù)
Node<E> next; // 前一結(jié)點
Node<E> prev; // 后一結(jié)點
// 構(gòu)造器:前一結(jié)點、數(shù)據(jù)、后一結(jié)點
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
添加元素 add(E e)
/ offer(E e)
添加單個元素
public boolean offer(E e) {
return add(e);
}
public boolean add(E e) {
linkLast(e);
return true;
}
重要函數(shù): linkLast()
向隊尾添加元素,多個添加元素的功能都依靠該方法實現(xiàn)。
void linkLast(E e) {
// 先找到尾結(jié)點
final Node<E> l = last;
// 創(chuàng)建新結(jié)點,新結(jié)點的前一結(jié)點指向尾結(jié)點,后一結(jié)點為 null
final Node<E> newNode = new Node<>(l, e, null);
// 新加入的結(jié)點要作為最后一個結(jié)點記錄
last = newNode;
if (l == null) // 如果尾結(jié)點為 null,說明是空的,將首結(jié)點也置為新結(jié)點
first = newNode;
else // 尾結(jié)點存在,新結(jié)點放到尾結(jié)點后面
l.next = newNode;
size++;
modCount++;
}
- 新結(jié)點創(chuàng)建后,把頭結(jié)點指向隊列的尾結(jié)點,尾結(jié)點指向 null。注意此時只是新結(jié)點指向了前一結(jié)點指向了尾結(jié)點;
- 尾結(jié)點為 null 說明隊列是空的,需要把頭結(jié)點也指向新結(jié)點;
- 如果隊列不為空,需要把尾結(jié)點的下一結(jié)點指向新結(jié)點。雙向鏈表的一種體現(xiàn),新結(jié)點的前方指向尾結(jié)點、尾結(jié)點的后方指向新結(jié)點。
指定位置添加元素
LinkedList 是有序的,所以是可以往中間插入數(shù)據(jù)的:
public void add(int index, E element) {
// 1. 檢查下標(biāo)
checkPositionIndex(index);
// 2. 尾部插入數(shù)據(jù)
if (index == size)
linkLast(element);
else // 3. 指定位置插入數(shù)據(jù)
linkBefore(element, node(index));
}
- 檢查下標(biāo),大于等于零且小于等于結(jié)點數(shù)量 size;
- 如果要插入的數(shù)據(jù)位置等于 size,說明要添加到鏈表尾部,調(diào)用上面的 linkLast 方法插入即可;
- 指定位置添加的話先找到這個位置的數(shù)據(jù):
Node<E> node(int index) {
// assert isElementIndex(index);
// 如果下標(biāo)小于長度的一半,則去前面查找
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;
}
}
- 查找時二分了一下,后面還是遍歷。
- 有意思的是前半截查找是正著遍歷,后半截查找是倒著遍歷。
- 正序遍歷只需找到所查元素的前一個元素即可,結(jié)果是前一結(jié)點的 next;
- 倒序遍歷同理,找到所查元素后一個元素,結(jié)果是 prev 結(jié)點。
找到 index 位置的元素之后,就可以掰斷鏈表,再把新數(shù)據(jù)鏈接進(jìn)來:
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
// 先找到前一個元素
final Node<E> pred = succ.prev;
// 創(chuàng)建新結(jié)點,指定前后結(jié)點
final Node<E> newNode = new Node<>(pred, e, succ);
// 原來位置的結(jié)點的頭結(jié)點再置為新結(jié)點
succ.prev = newNode;
// 最后判斷,空鏈表設(shè)置頭結(jié)點、非空添加到后面
if (pred == null)
first = newNode;
else
pred.next = newNode;
// 統(tǒng)計容量
size++;
// 統(tǒng)計修改次數(shù)
modCount++;
}
添加一堆元素
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index); // 判斷下標(biāo)是否越界,需要 >=0 && <=size
// 轉(zhuǎn)換為數(shù)組
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
Node<E> pred, succ;
// 如果是在尾部添加,當(dāng)前結(jié)點是 null,前一結(jié)點是最后結(jié)點
if (index == size) {
succ = null;
pred = last;
} else {
// 否則找到當(dāng)前下標(biāo)的結(jié)點,pred 記錄前一結(jié)點
succ = node(index);
pred = succ.prev;
}
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
// 創(chuàng)建新結(jié)點,設(shè)置前結(jié)點和數(shù)據(jù)
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null) // 熟悉的設(shè)置頭結(jié)點
first = newNode;
else // 如果前一結(jié)點存在,往后添加
pred.next = newNode;
// pred 指向新結(jié)點,下次遍歷接著往后添加
pred = newNode;
}
// 如果是在鏈表尾部添加的,last 結(jié)點標(biāo)記為最后添加的結(jié)點
if (succ == null) {
last = pred;
} else {
// 如果不是在鏈表尾部添加,last 結(jié)點不用動
// 并且把最后遍歷的結(jié)點和插入位置的結(jié)點連起來,放在了前面
pred.next = succ;
succ.prev = pred;
}
// 標(biāo)記次數(shù)
size += numNew;
modCount++;
return true;
}
添加元素到頭尾
public void addFirst(E e) {
linkFirst(e);
}
private void linkFirst(E e) {
// 找到頭結(jié)點
final Node<E> f = first;
// 創(chuàng)建新結(jié)點,頭結(jié)點作為后結(jié)點
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
// 如果頭結(jié)點也不存在,說明是空的,尾結(jié)點也是新結(jié)點
if (f == null)
last = newNode;
else // 頭結(jié)點的前結(jié)點是新結(jié)點,鏈接起來
f.prev = newNode;
size++;
modCount++;
}
public void addLast(E e) {
linkLast(e);
}
// 上文有寫到該方法,往鏈表后添加大多調(diào)用次方法
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++;
}
清空元素 clear()
public void clear() {
// 遍歷置空,從頭開始
for (Node<E> x = first; x != null; ) {
// 先找到 x 的下一個元素,遍歷完賦值給 x
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
// 歸零
first = last = null;
size = 0;
modCount++;
}
獲取元素
獲取下標(biāo) indexOf(Object o)
& 是否包含 contains(Object o)
獲取下標(biāo) indexOf() 方法,找到返回下標(biāo),找不到返回 -1:
// 主要就是遍歷,注意區(qū)分查詢 null 值的情況
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;
}
contains 調(diào)用的是 indexOf()
方法,返回 -1 說明沒查到,反之返回 true。
public boolean contains(Object o) {
return indexOf(o) != -1;
}
獲取某元素 get()
首先查詢下標(biāo)是否越界,越界拋異常。
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
接著還是調(diào)用 node()
方法傳入下標(biāo),返回獲取到的數(shù)據(jù)即可。
Node<E> node(int index) {
// assert isElementIndex(index);
// 再復(fù)習(xí)一遍,先分成兩半。
// 如果在前半部分正序遍歷,只需查到前一個下標(biāo)即可
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;
}
}
獲取頭元素 element() peek()
element()
是從 Queue 接口實現(xiàn)來的方法,功能是返回頭元素,特點是查不到拋出NoSuchElementException 異常。
public E element() {
return getFirst();
}
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
peek()
方法元素返回頭元素,查不到返回 null,不會拋出異常:
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
刪除元素 remove() poll()
remove()
方法默認(rèn)刪除的是頭元素,如果是 null 的話會拋 NoSuchElementException異常:
public E remove() {
return removeFirst();
}
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
會調(diào)用 unlinkFirst()
方法移除首元素:
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
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 = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
poll()
方法不會拋出異常:
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
刪除某位置的元素 remove(int index)
涉及下標(biāo)的依舊先確定下標(biāo)是否越界,然后找到改位置的元素:
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
調(diào)用 node(index)
方法查找元素,之前多次見過,就是那個把數(shù)據(jù)分成兩半去查詢的。
然后再調(diào)用 unlink()
方法把該元素從鏈中移除,要注意頭尾結(jié)點的處理:
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;
// 要移除的結(jié)點前面沒有結(jié)點了,說明自己是頭結(jié)點
if (prev == null) {
// 因為自己作為頭結(jié)點移除,頭結(jié)點變?yōu)樽约旱南乱粋€結(jié)點
first = next;
} else {
// 自己不是頭結(jié)點,讓自己前面結(jié)點跟自己后面結(jié)點鏈接
prev.next = next;
x.prev = null;
}
// 自己后面沒有結(jié)點了,說明自己是尾結(jié)點
if (next == null) {
// 尾結(jié)點置為自己的前一結(jié)點
last = prev;
} else {
// 自己不是尾結(jié)點,讓自己后面的結(jié)點跟自己前面的結(jié)點鏈接
next.prev = prev;
x.next = null;
}
// 置空、元素數(shù)量 -1
x.item = null;
size--;
modCount++;
return element;
}
刪除某元素 remove(Object o)
刪除某元素就是根據(jù)值遍歷找到該元素,執(zhí)行 unlink()
斷鏈刪除,注意刪除 null 值的情況。
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;
}
設(shè)置某位置的值 set()
檢查下標(biāo)、找到該位置的數(shù)據(jù)、更新值即可。
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
迭代器
線性迭代器 ListItr
常用的獲取迭代器:
LinkedList linkedList = new LinkedList();
linkedList.iterator();// 會創(chuàng)建 LinkedList 的線性迭代器,
該方法調(diào)用 LinkedList
的 listIterator()
方法:
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
ListItr 源碼:
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned;
private Node<E> next;
private int nextIndex;
private int expectedModCount = modCount;
ListItr(int index) {
// 默認(rèn)傳的 0,next 值為頭結(jié)點
// 倒序迭代器會傳元素數(shù)量 size,next 為 null
next = (index == size) ? null : node(index);
nextIndex = index;
}
public boolean hasNext() {
return nextIndex < size;
}
public E next() {
// 判斷遍歷過程中數(shù)據(jù)是否被修改,出現(xiàn)問題拋異常
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
// 記錄最后返回的值,然后指針后移
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
// 是否包含前一個元素,下標(biāo) >=0 說明存在前一元素
public boolean hasPrevious() {
return nextIndex > 0;
}
public E previous() {
checkForComodification();
if (!hasPrevious())
throw new NoSuchElementException();
// 倒序遍歷用,記錄的前一數(shù)據(jù)為 null 則返回 LinkedList 的最后一個數(shù)據(jù)
// 下次遍歷,next 就不為 null 了,返回 前一個元素
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();
// 先記錄要移除的下一元素 next 結(jié)點
Node<E> lastNext = lastReturned.next;
// 把下一結(jié)點移除
unlink(lastReturned);
// 如果指針結(jié)點就是要移除的結(jié)點,后移一下
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
lastReturned = null;
expectedModCount++;
}
public void set(E e) {
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
// 默認(rèn)設(shè)置最后返回數(shù)據(jù)的值
lastReturned.item = e;
}
public void add(E e) {
checkForComodification();
lastReturned = null;
// next 為 null 說明后面沒數(shù)據(jù)了,添加到隊尾
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();
}
}
倒序迭代器
private class DescendingIterator implements Iterator<E> {
// 傳入 size 創(chuàng)建倒序迭代器
private final ListItr itr = new ListItr(size());
public boolean hasNext() {
return itr.hasPrevious();
}
public E next() {
return itr.previous();
}
public void remove() {
itr.remove();
}
}
- 因為創(chuàng)建時傳遞的是 size,迭代器創(chuàng)建時指針結(jié)點 next 為 null,調(diào)用
previous()
方法會返回最后一個元素。下次就會返回最后一個元素的前一元素。
同步性
- 不同步,遍歷時如果數(shù)據(jù)修改會拋異常;
- 多線程下可能造成數(shù)據(jù)覆蓋、丟失。
解決方法:
List list = Collections.synchronizedList(new LinkedList(...));
總結(jié)及對比
ArrayList
- 基于數(shù)組,查詢速度較快,時間復(fù)雜度 O(1);
- 添加刪除需要復(fù)制數(shù)據(jù),影響效率;
- 容量不足時需要擴(kuò)容。
LinkedList
- 基于鏈表,元素必須挨個查詢。下標(biāo)查詢或許可以通過二分提高效率;
- 頭尾添加刪除數(shù)據(jù)較快,往固定位置添加數(shù)據(jù)還是需要遍歷查找位置;
- 無需擴(kuò)容,只有內(nèi)存夠,使勁添加。