java集合類之LinkedList詳解
一、LinkedList簡介
由於LinkedList是一個實現了Deque的雙端隊列,所以LinkedList既可以當做Queue,又可以當做Stack,在將LinkedList當做Stack時,使用pop()、push()、peek()方法需要註意的是LinkedList內部是將鏈表頭部當做棧頂,鏈表尾部當做棧底
LinkedList是一個雙向鏈表,沒有初始化大小,也沒有擴容機制,就是一直在前面或者後面新增就好
特點:隨機訪問慢、插入刪除速度快
二、源碼分析
由於LinkedList實現了List和Deque兩個接口,所以LinkedList方法分兩種,一種是List接口的方法,第二種是Deque接口的方法
1、全局變量
(1)當前鏈表中的數據個數
transient int size = 0;
(2)指向鏈表頭部
transient Node<E> first;
(3)指向鏈表尾部
transient Node<E> last;
2、構造函數
(1)不帶參數的構造方法
public LinkedList() {
}
(2)帶collection參數的構造方法
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);//
}
說明:當使用第二個構造方法時,會調用addAll()方法將集合中的元素添加到鏈表中
3、方法
(1)LinkLast(E e)
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++; }
說明:LinkLast方法就是一個鏈表尾部添加一個雙端節點的操作,但是需要註意對鏈表為空時頭節點的處理
(2) node(ine index)(根據索引index返回node節點)
Node<E> node(int index) {
// assert isElementIndex(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;
}}
(3)linkBefore(E e, Node< E > succ)
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;}
(4)linkFirst(E e)
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);//新建節點,以頭節點為後繼節點
first = newNode;
//如果鏈表為空,last節點也指向該節點
if (f == null)
last = newNode;
//否則,將頭節點的前驅指針指向新節點
else
f.prev = newNode;
size++;
modCount++;}
說明:
- 創建newNode節點,將newNode的後繼指針指向succ,前驅指針指向pred
- 將succ的前驅指針指向newNode
- 根據pred是否為null,進行不同操作。
- 如果pred為null,說明該節點插入在頭節點之前,要重置first頭節點
- 如果pred不為null,那麽直接將pred的後繼指針指向newNode即可
(5)unlink(Node< E > 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 = next;如果刪除的節點是頭節點,令頭節點指向該節點的後繼節點
} else {
prev.next = next;//將前驅節點的後繼節點指向後繼節點
x.prev = null;
}
//刪除後繼指針
if (next == null) {
last = prev;//如果刪除的節點是尾節點,令尾節點指向該節點的前驅節點
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
(6)unlinkFirst(Node
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;
}
(7)unlinkLast(Node< E > 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;
}
(1)List接口的添加操作
add(E e)用於將元素添加到鏈表尾部
public boolean add(E e) {
linkLast(e);
return true;
}
add(int index,E e)用於在指定位置添加元素
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
說明:1、檢查index的範圍,否則拋出異常 2、如果插入位置是鏈表尾部,那麽調用LindLast方法 3、如果插入位置是鏈表中間,那麽調用linkBefore方法
addAll有兩個方法,一個參數的方法表示將集合元素添加到鏈表尾部;而兩個參數的方法指定了開始插入的位置。
//將集合插入到鏈表尾部,即開始索引位置為size
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
//將集合從指定位置開始插入
public boolean addAll(int index, Collection< ? extends E> c) {
//Step 1:檢查index範圍
checkPositionIndex(index);
//Step 2:得到集合的數據
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
//Step 3:得到插入位置的前驅節點和後繼節點
Node<E> pred, succ;
//如果插入位置為尾部,前驅節點為last,後繼節點為null
if (index == size) {
succ = null;
pred = last;
}
//否則,調用node()方法得到後繼節點,再得到前驅節點
else {
succ = node(index);
pred = succ.prev;
}
//Step 4:遍歷數據將數據插入
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;
}
//如果插入位置在尾部,重置last節點
if (succ == null) {
last = pred;
}
//否則,將插入的鏈表與先前鏈表連接起來
else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;}
說明:1. 檢查index索引範圍
2. 得到集合數據
3. 得到插入位置的前驅和後繼節點
4. 遍歷數據,將數據插入到指定位置
(2)Deque接口的添加操作
push(E e)
public void push(E e) {
addFirst(e);
}
說明:push(E e)方法用於將元素添加到鏈表頭部
addFirst(E e)
public void addFirst(E e) {
linkFirst(e);}
說明:addFirst(E e)方法用於將元素添加到鏈表頭部
addLast(E e)
public void addLast(E e) {
linkLast(e);}
說明:addLast()方法用於將元素添加到鏈表尾部,與add方法實現一樣,只不過return有區別
offer(E e)
public boolean offer(E e) {
return add(e);}
說明:此方法用於將數據添加到鏈表尾部,其內部調用了add(E e)方法
offerFirst(E e)
public boolean offerFirst(E e) {
addFirst(e);
return true;}
說明:此方法用於將數據插入鏈表頭部,與addFirst區別在於該方法返回特定的返回值,而addFirst返回值為void
offerLast(E e)
public boolean offerLast(E e) {
addLast(e);
return true;}
說明:offerLast()與addLast()的區別和offerFirst()和addFirst()的區別一樣
(3)根據位置取數據
獲取任意位置的get(int index)方法
public E get(int index) {
//檢查邊界
checkElementIndex(index);
return node(index).item;}
說明:get(int index)方法根據指定索引返回數據,如果索引越界,那麽會拋出異常
獲取位置為0的頭節點數據
LinkedList中有多種方法可以獲得頭節點的數據,實現大同小異,區別在於對鏈表為空時的處理,是拋出異常還是返回null
其中getFirst()和element()方法將會在鏈表為空時,拋出異常:
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
public E element() {
return getFirst();
}
peek()和peekFirst()在鏈表為空時,返回null
獲取位置為size-1的尾節點數據
getLast()方法在鏈表為空時,會拋出異常
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
peekLast()方法在鏈表為空時,返回null
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
(4)根據對象獲得索引(一旦匹配,立即返回索引)
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;
}
lastIndexOf(Object o)
//返回最後一個匹配的索引
public int lastIndexOf(Object o) {
int index = size;
if (o == null) {
//從後向前遍歷
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {
//從後向前遍歷
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (o.equals(x.item))
return index;
}
}
return -1;
}
(5)檢查鏈表是否包含對象
contains(Object o)
public boolean contains(Object o) {
return indexOf(o) != -1;
}
說明:contains(Object o)方法檢查對象o是否存在於鏈表中,此方法調用了IndexOf方法,只要返回結果不是-1,那就說明該對象存在於鏈表中
(6)刪除指定對象
remove(Object o)
public boolean remove(Object o) {
//如果刪除對象為null
if (o == null) {
//從前向後遍歷
for (Node<E> x = first; x != null; x = x.next) {
//一旦匹配,調用unlink()方法和返回true
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
//從前向後遍歷
for (Node<E> x = first; x != null; x = x.next) {
//一旦匹配,調用unlink()方法和返回true
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
說明:當刪除指定對象時,只需調用remove(Object o)即可,不過該方法一次只會刪除一個匹配的對象,如果刪除了匹配對象,返回true,否則false。
(7)按照位置刪除對象
刪除任意指定位置的對象
public E remove(int index) {
//檢查index範圍
checkElementIndex(index);
//將節點刪除
return unlink(node(index));
}
刪除頭節點的對象
remove()、removeFirst()、pop()在鏈表為空時將拋出NoSuchElementException
public E remove() {
return removeFirst();
}
public E pop() {
return removeFirst();
}
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
poll()、pollFirst()在鏈表為空時返回null
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
public E pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
(8)刪除尾節點的元素
removeLast()在鏈表為空將拋出NoSuchElementException
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
pollLast()方法在鏈表為空的時候,返回null
public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);
}
(9)叠代器操作
iterator()、listIterator()、listIterator(int index)
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);
}
4、內部類
(1)Node< E >
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的定義可以看出鏈表是一個雙端鏈表的結構
(2)ListItr
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned;
private Node<E> next;
private int nextIndex;
private int expectedModCount = modCount;//保存當前modCount,確保fail-fast機制
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);//得到當前索引指向的next節點
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;
}
//獲取前一個節點,將next節點向前移
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();
}
}
三、Deque接口說明
Deque是Queue的子接口,我們知道Queue是一種隊列形式,而Deque則是雙向隊列,它支持從兩個端點方向檢索和插入、刪除元素
方法
當Deque當做隊列使用時(先進先出FIFO),添加元素是添加到隊尾,刪除時刪除的是頭部元素,其中的方法有:
隊列方法 | Deque方法 |
---|---|
add(e) | addLast(e) |
offer(e) | offerLast(e) |
remove() | removeFirst() |
poll() | pollFirst() |
element() | getFirst() |
peek() | peekFirst() |
Deque也能當棧用(後進先出)。這時入棧、出棧元素都是在雙端隊列的頭部進行,其中的方法有:
棧方法 | Deque方法 |
---|---|
push(e) | addFirst(e) |
oop() | removeFirst() |
peek() | peekFirst() |
方法詳解:
void addFirst(E e);
將對象e插入到雙端隊列頭部,容間不足時,拋出IllegalStateException異常;
void addLast(E e);
將對象e插入到雙端隊列尾部,容間不足時,拋出IllegalStateException異常;
boolean offerFirst(E e);
將對象e插入到雙端隊列頭部
boolean offerLast(E e);
將對象e插入到雙端隊列尾部;
E removeFirst();
獲取並移除隊列第一個元素,隊列為空,拋出NoSuchElementException異常;
E removeLast();
獲取並移除隊列最後一個元素,隊列為空,拋出NoSuchElementException異常;
E pollFirst();
獲取並移除隊列第一個元素,隊列為空,返回null;
E pollLast();
獲取並移除隊列最後一個元素,隊列為空,返回null;
E getFirst();
獲取隊列第一個元素,但不移除,隊列為空,拋出NoSuchElementException異常;
E getLast();
獲取隊列最後一個元素,但不移除,隊列為空,拋出NoSuchElementException異常;
E peekFirst();
獲取隊列第一個元素,隊列為空,返回null;
E peekLast();
獲取隊列最後一個元素,隊列為空,返回null;
boolean removeFirstOccurrence(Object o);
移除第一個滿足 (o==null ? e==null : o.equals(e)) 的元素
boolean removeLastOccurrence(Object o);
移除最後一個滿足 (o==null ? e==null : o.equals(e)) 的元素
void push(E e);
將對象e插入到雙端隊列頭部;
E pop();
移除並返回雙端隊列的第一個元素
Iterator
雙端隊列尾部到頭部的一個叠代器;
實現場景
Deque的實現類主要分為兩種場景
一般場景:
- LinkedList大小可變的鏈表雙端隊列,允許元素為null
- ArrayDeque大小可變的數組雙端隊列,不允許null
並發場景
- LinkedBlockingDeque 如果隊列為空時,獲取操作將會阻塞,知道有元素添加
java集合類之LinkedList詳解