1.簡介
LinkedList 是用鏈表結構存儲數據的,很適合數據的動態插入和刪除,隨機訪問和遍歷速度比較慢。另外,他還提供了 List 接口中沒有定義的方法,專門用於操作表頭和表尾元素,可以當作堆棧、隊列和雙向隊列使用。
LinkedList是實現了List接口和Deque接口的雙端鏈表。 LinkedList底層的鏈表結構使它支持高效的插入和刪除操作,另外它實現了Deque接口,使得LinkedList類也具有隊列的特性。
LinkedList不是線程安全的,如果想使LinkedList變成線程安全的,可以調用靜態類Collections類中的synchronizedList方法:
List list=Collections.synchronizedList(new LinkedList(...));
2.內部結構
3.構造方法
1)空構造方法
public LinkedList() { }
2)已有的集合創建鏈表構造方法
public LinkedList(Collection<? extends E> c) { this(); addAll(c); }
4.添加元素方法
1)add(E e)方法:將元素添加到鏈表尾部
public boolean add(E e) { linkLast(e);//這里就只調用了這一個方法 return true; } /** * 鏈接使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++; }
2)add(int index, E e):在指定位置添加元素
public void add(int index, E element) { checkPositionIndex(index); //檢查索引是否處於[0-size]之間 if (index == size)//添加在鏈表尾部 linkLast(element); else//添加在鏈表中間 linkBefore(element, node(index)); }
linkBefore方法需要給定兩個參數,一個插入節點的值,一個指定的節點,所以我們又調用了node(索引)去找到索引對應的節點
3)addAll(Collection c):將集合插入到鏈表尾部
public boolean addAll(Collection<? extends E> c) { return addAll(size, c); }
4)addAll(int index, Collection c):將集合從指定位置開始插入
public boolean addAll(int index, Collection<? extends E> c) { //1:檢查index范圍是否在size之內 checkPositionIndex(index); //2:toArray()方法把集合的數據存到對象數組中 Object[] a = c.toArray(); int numNew = a.length; if (numNew == 0) return false; //3:得到插入位置的前驅節點和后繼節點 Node<E> pred, succ; //如果插入位置為尾部,前驅節點為last,后繼節點為null if (index == size) { succ = null; pred = last; } //否則,調用node()方法得到后繼節點,再得到前驅節點 else { succ = node(index); pred = succ.prev; } // 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; }
上面可以看出中的addAll方法通常包括下面四個步驟:
- 檢查index范圍是否在size之內
- toArray()方法把集合的數據存到對象數組中
- 得到插入位置的前驅和后繼節點
- 遍歷數據,將數據插入到指定位置
5)addFirst(E e):將元素添加到鏈表頭部
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; //如果鏈表為空,last節點也指向該節點 if (f == null) last = newNode; //否則,將頭節點的前驅指針指向新節點,也就是指向前一個元素 else f.prev = newNode; size++; modCount++; }
6)addLast(E e):將元素添加到鏈表尾部,與add(E e)方法一樣
public void addLast(E e) { linkLast(e); }
5.根據位置取數據方法
1)get(int index): 根據指定索引返回數據
public E get(int index) { //檢查index范圍是否在size之內 checkElementIndex(index); //調用Node(index)去找到index對應的node然后返回它的值 return node(index).item; }
2)獲取頭節點(index= 0)數據方法:
public E getFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return f.item; } public E element() { return getFirst(); } public E peek() { final Node<E> f = first; return (f == null) ? null : f.item; } public E peekFirst() { final Node<E> f = first; return (f == null) ? null : f.item; }
區別:
peek(),peekFirst() 對鏈表為空時返回null,
getFirst() 和element() 方法將會在鏈表為空時,拋出異常
element()方法的內部就是使用getFirst()實現的。它們會在鏈表為空時,拋出NoSuchElementException
3)獲取尾節點(index= -1)數據方法:
public E getLast() { final Node<E> l = last; if (l == null) throw new NoSuchElementException(); return l.item; } public E peekLast() { final Node<E> l = last; return (l == null) ? null : l.item; }
兩者區別:
getLast()方法在鏈表為空時,會拋出NoSuchElementException
peekLast()只是會返回null。
6.根據對象得到索引的方法
1)int 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; }
2)int 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; }
7.檢查鏈表是否包含某對象
1)contains(Object o):檢查對象o是否存在於鏈表中
public boolean contains(Object o) { return indexOf(o) != -1; }
8.刪除元素方法
1)刪除頭節點:remove(),removeFirst(),pop()
public E pop() { return removeFirst(); } public E remove() { return removeFirst(); } public E removeFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return unlinkFirst(f); }
2)removeLast(),pollLast():刪除尾節點
public E removeLast() { final Node<E> l = last; if (l == null) throw new NoSuchElementException(); return unlinkLast(l); } public E pollLast() { final Node<E> l = last; return (l == null) ? null : unlinkLast(l); }
區別:
removeLast()在鏈表為空時將拋出NoSuchElementException
pollLast()方法返回null。
3)remove(Object o):刪除指定元素
public boolean remove(Object o) { //如果刪除對象為null 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; }
當刪除指定對象時,只需調用remove(Object o)即可,不過該方法一次只會刪除一個匹配的對象,如果刪除了匹配對象,返回true,否則false。
4)unlink(Node 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; }
5)remove(int index):刪除指定位置的元素
public E remove(int index) { //檢查index范圍 checkElementIndex(index); //將節點刪除 return unlink(node(index)); }
參考:https://snailclimb.top/JavaGuide/#/java/collection/LinkedList