*本篇文章已授權(quán)微信公眾號(hào) guolin_blog (郭霖)獨(dú)家發(fā)布
一紧憾、概述
LinkedList稿辙,相對(duì)于ArrayList嘱巾,大家可能平時(shí)使用LinkedList要少一些,其實(shí)有時(shí)候使用LinkedList比ArrayList效率高很多物咳,當(dāng)然锣险,這得視情況而定。
本文將帶大家深入LinkedList源碼览闰,分析其背后的實(shí)現(xiàn)原理芯肤,以便以后在合適的情況下進(jìn)行使用。
之前我所知道的LinkedList的知識(shí):
- LinkedList底層是鏈表結(jié)構(gòu)
- 插入和刪除比較快(O(1))压鉴,查詢則相對(duì)慢一些(O(n))
- 因?yàn)槭擎湵斫Y(jié)構(gòu)崖咨,所以分配的空間不要求是連續(xù)的
二、鏈表
因?yàn)長inkedList源碼中很多地方是進(jìn)行鏈表操作,所以先帶大家復(fù)習(xí)一下鏈表的基礎(chǔ)知識(shí).以前用C語言實(shí)現(xiàn)的鏈表,大家可以去看一下,地址:https://github.com/xfhy/dataStructure
1. 單鏈表
一個(gè)節(jié)點(diǎn)中包含數(shù)據(jù)和下一個(gè)節(jié)點(diǎn)的指針(注意,是下一個(gè)節(jié)點(diǎn)的指針,而不是下一個(gè)節(jié)點(diǎn)數(shù)據(jù)的指針),尾節(jié)點(diǎn)沒有下一個(gè)節(jié)點(diǎn),所以指向null.訪問某個(gè)節(jié)點(diǎn)只能從頭節(jié)點(diǎn)開始查找,然后依次往后遍歷.
2. 單向循環(huán)鏈表
單向循環(huán)鏈表比單鏈表多了一個(gè)尾節(jié)點(diǎn)的指針指向的是頭結(jié)點(diǎn).
3. 雙向鏈表
雙向鏈表的每個(gè)節(jié)點(diǎn)包含以下數(shù)據(jù):上一個(gè)節(jié)點(diǎn)的指針,自己的數(shù)據(jù),下一個(gè)節(jié)點(diǎn)的指針.尾節(jié)點(diǎn)沒有下一個(gè)節(jié)點(diǎn),所以指向null.這樣的結(jié)構(gòu),比如我拿到鏈表中間的一個(gè)節(jié)點(diǎn),即可以往前遍歷,也可以往后遍歷.
4. 雙向循環(huán)鏈表
雙向循環(huán)鏈表的尾節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)是頭結(jié)點(diǎn),頭節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)是尾節(jié)點(diǎn).
三晴弃、LinkedList的繼承關(guān)系
源碼中的定義:
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
AbstractSequentialList這個(gè)類提供了List的一個(gè)骨架實(shí)現(xiàn)接口,以盡量減少實(shí)現(xiàn)此接口所需的工作量由“順序訪問”數(shù)據(jù)存儲(chǔ)(如鏈接列表)支持。對(duì)于隨機(jī)訪問數(shù)據(jù)(如數(shù)組)上鞠,應(yīng)使用AbstractList優(yōu)先于此類际邻。
實(shí)現(xiàn)了List接口,意味著LinkedList元素是有序的,可以重復(fù)的,可以有null元素的集合.
Deque是Queue的子接口,Queue是一種隊(duì)列形式,而Deque是雙向隊(duì)列,它支持從兩個(gè)端點(diǎn)方向檢索和插入元素.
實(shí)現(xiàn)了Cloneable接口,標(biāo)識(shí)著可以它可以被復(fù)制.注意,ArrayList里面的clone()復(fù)制其實(shí)是淺復(fù)制(不知道此概念的趕快去查資料,這知識(shí)點(diǎn)非常重要).
實(shí)現(xiàn)了Serializable 標(biāo)識(shí)著集合可被序列化。
四芍阎、看LinkedList源碼前的準(zhǔn)備
1. 節(jié)點(diǎn)定義
private static class Node<E> {
E item; //該節(jié)點(diǎn)的數(shù)據(jù)
Node<E> next; //指向下一個(gè)節(jié)點(diǎn)的指針
Node<E> prev; //指向上一個(gè)節(jié)點(diǎn)的指針
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
Node是LinkedList的靜態(tài)內(nèi)部類.
為什么是靜態(tài)內(nèi)部類?我覺得可能原因如下:普通內(nèi)部類會(huì)有外部類的強(qiáng)引用,而靜態(tài)內(nèi)部類就沒有.有外部類的強(qiáng)引用的話,很容易造成內(nèi)存泄漏,寫成靜態(tài)內(nèi)部類可以避免這種情況的發(fā)生.
2. 成員變量
看構(gòu)造方法之前先看看幾個(gè)屬性:
//鏈表長度
transient int size = 0;
/**
* 頭結(jié)點(diǎn)
*/
transient Node<E> first;
/**
* 尾節(jié)點(diǎn)
*/
transient Node<E> last;
這里為什么要存在一個(gè)成員變量尾節(jié)點(diǎn)?我感覺是為了方便,比如查找相應(yīng)索引處元素+插入元素到最后.查找相應(yīng)索引處元素時(shí),先判斷索引是在前半段還是在后半段,如果是在后半段,那么直接從尾節(jié)點(diǎn)出發(fā),從后往前進(jìn)行查找,這樣速度更快.在插入元素到最后時(shí),可以直接通過尾節(jié)點(diǎn)方便的進(jìn)行插入.
3. 構(gòu)造方法
下面是構(gòu)造方法源碼:
/**
* 構(gòu)造一個(gè)空列表
*/
public LinkedList() {
}
/**
* 構(gòu)造列表通過指定的集合
*/
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
兩個(gè)構(gòu)造方法都比較簡單,就是構(gòu)造一個(gè)列表,其中的addAll()方法待會(huì)兒放到后面分析.
思考:為什么LinkedList沒有提供public LinkedList(int initialCapacity)這種構(gòu)建指定大小列表的構(gòu)造方式?
因?yàn)锳rrayList有這種構(gòu)造方法public ArrayList(int initialCapacity)
,ArrayList提供這種構(gòu)造方法的好處在于在知道需要多大的空間的情況下,可以按需構(gòu)造列表,無需浪費(fèi)多余的空間和不必要的生成新數(shù)組的操作.而LinkedList可以很輕松動(dòng)態(tài)的增加元素(O(1)),所以沒必要一開始就構(gòu)造一個(gè)有很多元素的列表,到時(shí)需要的時(shí)候再按需加上去就行了.
五世曾、添加元素
1. add(E e)
方法作用:將e添加到鏈表末尾,返回是否添加成功
/**
* 添加指定元素到鏈表尾部
*/
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* Links e as last element.將e添加到尾部
*/
void linkLast(E e) {
//1. 暫記尾節(jié)點(diǎn)
final Node<E> l = last;
//2. 構(gòu)建節(jié)點(diǎn) 前一個(gè)節(jié)點(diǎn)是之前的尾節(jié)點(diǎn)
final Node<E> newNode = new Node<>(l, e, null);
//3. 新建的節(jié)點(diǎn)是尾節(jié)點(diǎn)了
last = newNode;
//4. 判斷之前鏈表是否為空
//為空則將新節(jié)點(diǎn)賦給頭結(jié)點(diǎn)(相當(dāng)于空鏈表插入第一個(gè)元素,頭結(jié)點(diǎn)等于尾節(jié)點(diǎn))
//非空則將之前的尾節(jié)點(diǎn)指向新節(jié)點(diǎn)
if (l == null)
first = newNode;
else
l.next = newNode;
//5. 鏈表長度增加
size++;
modCount++;
}
大體思路:
- 構(gòu)建一個(gè)新的節(jié)點(diǎn)
- 將該新節(jié)點(diǎn)作為新的尾節(jié)點(diǎn).如果是空鏈表插入第一個(gè)元素,那么頭結(jié)點(diǎn)=尾節(jié)點(diǎn)=新節(jié)點(diǎn);如果不是,那么將之前的尾節(jié)點(diǎn)指向新節(jié)點(diǎn).
- 增加鏈表長度
小細(xì)節(jié)
boolean add(E e)
添加成功返回true,添加失敗返回false.我們?cè)诖a中沒有看到有返回false的情況啊,直接在代碼中寫了個(gè)返回true,什么判斷條件都沒有,啊??
仔細(xì)想想,分配內(nèi)存空間不是必須是連續(xù)的,所以只要是還能給它分配空間,就不會(huì)添加失敗.當(dāng)空間不夠分配時(shí)(內(nèi)存溢出),會(huì)拋出OutOfMemory.
2. addLast(E e)
方法作用:添加元素到末尾. 內(nèi)部實(shí)現(xiàn)和add(E e)
一樣.
public void addLast(E e) {
linkLast(e);
}
3. addFirst(E e)
方法作用:添加元素到鏈表頭部
public void addFirst(E e) {
linkFirst(e);
}
/**
* 添加元素到鏈表頭部
*/
private void linkFirst(E e) {
//1. 記錄頭結(jié)點(diǎn)
final Node<E> f = first;
//2. 創(chuàng)建新節(jié)點(diǎn) next指針指向之前的頭結(jié)點(diǎn)
final Node<E> newNode = new Node<>(null, e, f);
//3. 新建的節(jié)點(diǎn)就是頭節(jié)點(diǎn)了
first = newNode;
//4. 判斷之前鏈表是否為空
//為空則將新節(jié)點(diǎn)賦給尾節(jié)點(diǎn)(相當(dāng)于空鏈表插入第一個(gè)元素,頭結(jié)點(diǎn)等于尾節(jié)點(diǎn))
//非空則將之前的頭結(jié)點(diǎn)的prev指針指向新節(jié)點(diǎn)
if (f == null)
last = newNode;
else
f.prev = newNode;
//5. 鏈表長度增加
size++;
modCount++;
}
大體思路:
- 構(gòu)建一個(gè)新的節(jié)點(diǎn)
- 將該新節(jié)點(diǎn)作為新的頭節(jié)點(diǎn).如果是空鏈表插入第一個(gè)元素,那么頭結(jié)點(diǎn)=尾節(jié)點(diǎn)=新節(jié)點(diǎn);如果不是,那么將之前的頭節(jié)點(diǎn)的prev指針指向新節(jié)點(diǎn).
- 增加鏈表長度
4. push(E e)
方法作用:添加元素到鏈表頭部 這里的意思比擬壓棧.和pop(出棧:移除鏈表第一個(gè)元素)相反.
內(nèi)部實(shí)現(xiàn)是和addFirst()
一樣的.
public void push(E e) {
addFirst(e);
}
5. offer(),offerFirst(E e),offerLast(E e)
方法作用:添加元素到鏈表頭部. 內(nèi)部實(shí)現(xiàn)其實(shí)就是add(e)
public boolean offer(E e) {
return add(e);
}
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
/**
* 添加元素到末尾
*/
public boolean offerLast(E e) {
addLast(e);
return true;
}
6. add(int index, E element)
方法作用:添加元素到指定位置,可能會(huì)拋出IndexOutOfBoundsException
//添加元素到指定位置
public void add(int index, E element) {
//1. 越界檢查
checkPositionIndex(index);
//2. 判斷一下index大小
//如果是和list大小一樣,那么就插入到最后
//否則插入到index處
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
//檢查是否越界
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* Returns the (non-null) Node at the specified element index.
返回指定元素索引處的(非空)節(jié)點(diǎn)。
*/
Node<E> node(int index) {
// assert isElementIndex(index);
/**
* 這里的思想非常巧妙,如果index在鏈表的前半部分,那么從first開始往后查找
否則,從last往前面查找
*/
//1. 如果index<size/2 ,即index在鏈表的前半部分
if (index < (size >> 1)) {
//2. 記錄下第一個(gè)節(jié)點(diǎn)
Node<E> x = first;
//3. 循環(huán)從第一個(gè)節(jié)點(diǎn)開始往后查,直到到達(dá)index處,返回index處的元素
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
//index在鏈表的后半部分
//4. 記錄下最后一個(gè)節(jié)點(diǎn)
Node<E> x = last;
//5. 循環(huán)從最后一個(gè)節(jié)點(diǎn)開始往前查,直到到達(dá)index處,返回index處的元素
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
/**
* Links e as last element.
將e鏈接到list最后一個(gè)元素
*/
void linkLast(E e) {
//1. 記錄最后一個(gè)元素l
final Node<E> l = last;
//2. 構(gòu)建一個(gè)新節(jié)點(diǎn),數(shù)據(jù)為e,前一個(gè)是l,后一個(gè)是null
final Node<E> newNode = new Node<>(l, e, null);
//3. 現(xiàn)在新節(jié)點(diǎn)是最后一個(gè)元素了,所以需要記錄下來
last = newNode;
//4. 如果之前l(fā)ist為空,那么first=last=newNode,只有一個(gè)元素
if (l == null)
first = newNode;
else
//5. 非空的話,那么將之前的最后一個(gè)指向新的節(jié)點(diǎn)
l.next = newNode;
//6. 鏈表長度+1
size++;
modCount++;
}
/**
* Inserts element e before non-null Node succ.
在非null節(jié)點(diǎn)succ之前插入元素e谴咸。
*/
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
//1. 記錄succ的前一個(gè)節(jié)點(diǎn)
final Node<E> pred = succ.prev;
//2. 構(gòu)建一個(gè)新節(jié)點(diǎn),數(shù)據(jù)是e,前一個(gè)節(jié)點(diǎn)是pred,下一個(gè)節(jié)點(diǎn)是succ
final Node<E> newNode = new Node<>(pred, e, succ);
//3. 將新節(jié)點(diǎn)作為succ的前一個(gè)節(jié)點(diǎn)
succ.prev = newNode;
//4. 判斷pred是否為空
//如果為空,那么說明succ是之前的頭節(jié)點(diǎn),現(xiàn)在新節(jié)點(diǎn)在succ的前面,所以新節(jié)點(diǎn)是頭節(jié)點(diǎn)
if (pred == null)
first = newNode;
else
//5. succ的前一個(gè)節(jié)點(diǎn)不是空的話,那么直接將succ的前一個(gè)節(jié)點(diǎn)指向新節(jié)點(diǎn)就可以了
pred.next = newNode;
//6. 鏈表長度+1
size++;
modCount++;
}
大體思路:
- 首先判斷一下插入的位置是在鏈表的最后還是在鏈表中間.
- 如果是插入到鏈表末尾,那么將之前的尾節(jié)點(diǎn)指向新節(jié)點(diǎn)
- 如果是插入到鏈表中間
- 需要先找到鏈表中index索引處的節(jié)點(diǎn).
- 將新節(jié)點(diǎn)賦值為index處節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)
- 將index處節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)的next指針賦值為新節(jié)點(diǎn)
哇,這里描述起來有點(diǎn)困難,,,,不知道我描述清楚沒有.如果沒看懂我的描述,看一下代碼+再結(jié)合代碼注釋+畫一下草圖應(yīng)該更清晰一些.
6. addAll(int index, Collection<? extends E> c)
方法作用:將指定集合的所有元素插入到index位置
//將指定集合的所有元素插入到末尾位置
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
//將指定集合的所有元素插入到index位置
public boolean addAll(int index, Collection<? extends E> c) {
//1. 入?yún)⒑戏ㄐ詸z查
checkPositionIndex(index);
//2. 將集合轉(zhuǎn)成數(shù)組
Object[] a = c.toArray();
//3. 記錄需要插入的集合元素個(gè)數(shù)
int numNew = a.length;
//4. 如果個(gè)數(shù)為0,那么插入失敗,不繼續(xù)執(zhí)行了
if (numNew == 0)
return false;
//5. 判斷一下index與size是否相等
//相等則插入到鏈表末尾
//不相等則插入到鏈表中間 index處
Node<E> pred, succ;
if (index == size) {
succ = null;
pred = last;
} else {
//找到index索引處節(jié)點(diǎn) 這樣就可以方便的拿到該節(jié)點(diǎn)的前后節(jié)點(diǎn)信息
succ = node(index);
//記錄index索引處節(jié)點(diǎn)前一個(gè)節(jié)點(diǎn)
pred = succ.prev;
}
//6. 循環(huán)將集合中所有元素連接到pred后面
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
//如果前一個(gè)是空,那么將新節(jié)點(diǎn)作為頭結(jié)點(diǎn)
if (pred == null)
first = newNode;
else
//指向新節(jié)點(diǎn)
pred.next = newNode;
pred = newNode;
}
//7. 判斷succ是否為空
//為空的話,那么集合的最后一個(gè)元素就是尾節(jié)點(diǎn)
//非空的話,那么將succ連接到集合的最后一個(gè)元素后面
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
//8. 鏈表長度+numNew
size += numNew;
modCount++;
return true;
}
大體思路:
- 將需要添加的集合轉(zhuǎn)成數(shù)組a
- 判斷需要插入的位置index是否等于鏈表長度size,如果相等則插入到鏈表最后;如果不相等,則插入到鏈表中間,還需要找到index處節(jié)點(diǎn)succ,方便拿到該節(jié)點(diǎn)的前后節(jié)點(diǎn)信息.
- 記錄index索引處節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)pred,循環(huán)將集合中所有元素連接到pred的后面
- 將集合最后一個(gè)元素的next指針指向succ,將succ的prev指針指向集合的最后一個(gè)元素
六轮听、刪除元素
1. remove(),removeFirst()
方法作用: 移除鏈表第一個(gè)元素
/**
* 移除鏈表第一個(gè)節(jié)點(diǎn)
*/
public E remove() {
return removeFirst();
}
/**
* 移除鏈表第一個(gè)節(jié)點(diǎn)
*/
public E removeFirst() {
final Node<E> f = first;
//注意:如果之前是空鏈表,移除是要報(bào)錯(cuò)的喲
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
/**
* Unlinks non-null first node f.
* 將第一個(gè)節(jié)點(diǎn)刪掉
*/
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
//1. 記錄第一個(gè)節(jié)點(diǎn)的數(shù)據(jù)值
final E element = f.item;
//2. 記錄下一個(gè)節(jié)點(diǎn)
final Node<E> next = f.next;
//3. 將第一個(gè)節(jié)點(diǎn)置空 幫助GC回收
f.item = null;
f.next = null; // help GC
//4. 記錄頭節(jié)點(diǎn)
first = next;
//5. 如果下一個(gè)節(jié)點(diǎn)為空,那么鏈表無節(jié)點(diǎn)了 如果不為空,將頭節(jié)點(diǎn)的prev指針置為空
if (next == null)
last = null;
else
next.prev = null;
//6. 鏈表長度-1
size--;
modCount++;
//7. 返回刪除的節(jié)點(diǎn)的數(shù)據(jù)值
return element;
}
大體思路:其實(shí)就是將第一個(gè)節(jié)點(diǎn)移除并置空,然后將第二個(gè)節(jié)點(diǎn)作為頭節(jié)點(diǎn).思路還是非常清晰的,主要是對(duì)細(xì)節(jié)的處理.
2. remove(int index)
方法作用:移除指定位置元素
//移除指定位置元素
public E remove(int index) {
//檢查入?yún)⑹欠窈戏? checkElementIndex(index);
//node(index)找到index處的節(jié)點(diǎn)
return unlink(node(index));
}
//移除節(jié)點(diǎn)x
E unlink(Node<E> x) {
// assert x != null;
//1. 記錄該節(jié)點(diǎn)數(shù)據(jù)值,前一個(gè)節(jié)點(diǎn)prev,后一個(gè)節(jié)點(diǎn)next
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
//2. 判斷前一個(gè)節(jié)點(diǎn)是否為空
if (prev == null) {
//為空的話,那么說明之前x節(jié)點(diǎn)是頭節(jié)點(diǎn) 這時(shí)x的下一個(gè)節(jié)點(diǎn)成為頭節(jié)點(diǎn)
first = next;
} else {
//非空的話,將前一個(gè)節(jié)點(diǎn)的next指針指向x的下一個(gè)節(jié)點(diǎn)
prev.next = next;
//x的prev置為null
x.prev = null;
}
//3. 判斷x后一個(gè)節(jié)點(diǎn)是否為空
if (next == null) {
//為空的話,那么說明之前x節(jié)點(diǎn)是尾節(jié)點(diǎn),這時(shí)x的前一個(gè)節(jié)點(diǎn)成為尾節(jié)點(diǎn)
last = prev;
} else {
//為空的話,將x的下一個(gè)節(jié)點(diǎn)的prev指針指向prev(x的前一個(gè)節(jié)點(diǎn))
next.prev = prev;
//x的next指針置空
x.next = null;
}
//4. x節(jié)點(diǎn)數(shù)據(jù)值置空
x.item = null;
//5. 鏈表長度-1
size--;
modCount++;
//6. 將x節(jié)點(diǎn)的數(shù)據(jù)值返回
return element;
}
大體思路:
- 首先找到index索引處的節(jié)點(diǎn)(這樣就可以方便的獲取該節(jié)點(diǎn)的前后節(jié)點(diǎn)),記為x
- 記錄x的前(prev)后(next)節(jié)點(diǎn)
- 將x的前一個(gè)節(jié)點(diǎn)prev節(jié)點(diǎn)的next指針指向next,將x節(jié)點(diǎn)的后一個(gè)節(jié)點(diǎn)的prev指針指向prev節(jié)點(diǎn).
- 將x節(jié)點(diǎn)置空,鏈表長度-1
3. remove(Object o)
方法作用:從此鏈表中刪除第一次出現(xiàn)的指定元素o
public boolean remove(Object o) {
//1. 判斷o是否為空
if (o == null) {
//為null 循環(huán),找第一個(gè)數(shù)據(jù)值為null的節(jié)點(diǎn)
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
//刪除該節(jié)點(diǎn)
unlink(x);
return true;
}
}
} else {
//非空 循環(huán),找第一個(gè)與o的數(shù)據(jù)值相等的節(jié)點(diǎn)
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
//刪除該節(jié)點(diǎn)
unlink(x);
return true;
}
}
}
return false;
}
大體思路:
- 首先判斷入?yún)⑹欠駷閚ull
- 如果為null,那么循環(huán)遍歷鏈表,從頭節(jié)點(diǎn)開始往后查找,找到第一個(gè)節(jié)點(diǎn)的數(shù)據(jù)值為null的,直接刪除該節(jié)點(diǎn).
- 如果非null,那么循環(huán)遍歷鏈表,從頭節(jié)點(diǎn)開始往后查找,找到第一個(gè)節(jié)點(diǎn)的數(shù)據(jù)值為o的,直接刪除該節(jié)點(diǎn).
這里的循環(huán)遍歷鏈表的代碼,我覺得還是比較通用的,從頭節(jié)點(diǎn)開始,通過不斷的將x賦值為下一個(gè)元素,直到遍歷到為null的地方結(jié)束,這樣就完美的遍歷完了鏈表所有節(jié)點(diǎn).
4. removeFirstOccurrence(Object o)
方法作用:從此鏈表中刪除第一次出現(xiàn)的指定元素o. 內(nèi)部其實(shí)就是上面的remove(o);
public boolean removeFirstOccurrence(Object o) {
return remove(o);
}
5. removeLast()
方法作用:移除最后一個(gè)元素并返回
public E removeLast() {
final Node<E> l = last;
//如果鏈表是空的,那么就要拋出一個(gè)錯(cuò)誤
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
/**
* Unlinks non-null last node l.
移除鏈表最后一個(gè)元素
*/
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
//1. 記錄尾節(jié)點(diǎn)數(shù)據(jù)值
final E element = l.item;
//2. 找到尾節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)prev
final Node<E> prev = l.prev;
//3. 將尾節(jié)點(diǎn)置空 方便GC
l.item = null;
l.prev = null; // help GC
//4. 將last賦值為prev
last = prev;
//5. 判斷prev是否為null
//為空的話,說明之前鏈表就只有1個(gè)節(jié)點(diǎn),現(xiàn)在刪了之后,頭節(jié)點(diǎn)和尾節(jié)點(diǎn)都為null了
//非空,直接將新任尾節(jié)點(diǎn)的next指針指向null
if (prev == null)
first = null;
else
prev.next = null;
//6. 鏈表長度-1
size--;
modCount++;
//7. 返回之前尾節(jié)點(diǎn)數(shù)據(jù)值
return element;
}
大體思路:
- 判斷鏈表是否有節(jié)點(diǎn), 沒有節(jié)點(diǎn)直接拋錯(cuò)誤....
- 首先找到倒數(shù)第二個(gè)節(jié)點(diǎn)(可能沒有哈,沒有的話,說明鏈表只有一個(gè)節(jié)點(diǎn))prev
- 然后將尾節(jié)點(diǎn)置空,prev的next指針指向null
6. removeLastOccurrence(Object o)
方法作用:從此鏈表中刪除最后一次出現(xiàn)的指定元素o.
實(shí)現(xiàn):其實(shí)和上面的remove(o)是一樣的,只不過這里遍歷時(shí)是從尾節(jié)點(diǎn)開始往前查找的.
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;
}
7. poll()
方法作用:獲取第一個(gè)元素的同時(shí)刪除第一個(gè)元素,當(dāng)鏈表無節(jié)點(diǎn)時(shí),不會(huì)報(bào)錯(cuò). 這里的unlinkFirst()上面已分析過.
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
8. pop()
方法作用:獲取第一個(gè)元素的同時(shí)刪除第一個(gè)元素,當(dāng)鏈表無節(jié)點(diǎn)時(shí),會(huì)報(bào)錯(cuò).
public E pop() {
return removeFirst();
}
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
七、修改元素
1. set(int index, E element)
方法作用:設(shè)置index處節(jié)點(diǎn)數(shù)據(jù)值為element
public E set(int index, E element) {
//1. 入?yún)z測(cè)
checkElementIndex(index);
//2. 找到index處節(jié)點(diǎn),上面已分析該方法
Node<E> x = node(index);
//3. 保存該節(jié)點(diǎn)舊值
E oldVal = x.item;
//4. 替換為新值
x.item = element;
//5. 將舊值返回
return oldVal;
}
大體思路:非常簡單,就是首先找到index處節(jié)點(diǎn),替換該節(jié)點(diǎn)數(shù)據(jù)值
八岭佳、查詢?cè)?/h2>
1. element()
方法作用:獲取鏈表第一個(gè)元素. 方法比較簡單,就是將鏈表頭節(jié)點(diǎn)數(shù)據(jù)值進(jìn)行返回
public E element() {
return getFirst();
}
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
2. get(int index)
方法作用:獲取指定索引處元素. 方法比較簡單,就是通過node(index)找到index索引處節(jié)點(diǎn),然后返回其數(shù)據(jù)值
public E get(int index) {
//1. 入?yún)z測(cè)
checkElementIndex(index);
//2. 獲取指定索引處節(jié)點(diǎn)數(shù)據(jù)值
return node(index).item;
}
3. getFirst()
方法作用:獲取鏈表第一個(gè)元素. 非常簡單,就是將first的數(shù)據(jù)值返回
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
4. getLast()
方法作用:獲取鏈表最后一個(gè)元素. 非常簡單,就是將last的數(shù)據(jù)值返回
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
5. 通過listIterator()遍歷
這也是查詢的一種,哈哈
我們先來看看listIterator(int index)
方法,就是new了一個(gè)ListItr進(jìn)行返回.ListItr是LinkedList的內(nèi)部類.
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
接下來,我們看看這個(gè)內(nèi)部類:
private class ListItr implements ListIterator<E> {
//上一次返回的節(jié)點(diǎn)
private Node<E> lastReturned;
//下一個(gè)節(jié)點(diǎn)
private Node<E> next;
//下一個(gè)節(jié)點(diǎn)索引
private int nextIndex;
private int expectedModCount = modCount;
ListItr(int index) {
// assert isPositionIndex(index);
//如果是最后一個(gè)節(jié)點(diǎn),那么返回next是null
//如果不是最后一個(gè)節(jié)點(diǎn),那么找到該index索引處節(jié)點(diǎn)
next = (index == size) ? null : node(index);
nextIndex = index;
}
public boolean hasNext() {
//判斷是否還有下一個(gè)元素
return nextIndex < size;
}
//獲取下一個(gè)元素
public E next() {
checkForComodification();
//1. 如果沒有下一個(gè)元素 拋異常
if (!hasNext())
throw new NoSuchElementException();
//2. 記錄上一次遍歷到的節(jié)點(diǎn)
lastReturned = next;
//3. 往后移
next = next.next;
//4. 索引+1
nextIndex++;
//5. 將遍歷到的節(jié)點(diǎn)數(shù)據(jù)值返回
return lastReturned.item;
}
public boolean hasPrevious() {
//判斷是否還有前一個(gè)元素
return nextIndex > 0;
}
//獲取前一個(gè)元素
public E previous() {
checkForComodification();
//1. 如果沒有前一個(gè)元素,則拋異常
if (!hasPrevious())
throw new NoSuchElementException();
//2. 當(dāng)next是null的時(shí)候,賦值為last
//不是null的時(shí)候,往前移動(dòng)
lastReturned = next = (next == null) ? last : next.prev;
//3. index-1 因?yàn)槭峭? nextIndex--;
//4. 將遍歷到的節(jié)點(diǎn)數(shù)據(jù)值返回
return lastReturned.item;
}
public int nextIndex() {
return nextIndex;
}
public int previousIndex() {
return nextIndex - 1;
}
//移除當(dāng)前遍歷到的元素
public void remove() {
checkForComodification();
//1. 移除當(dāng)前遍歷到的元素為null,直接拋錯(cuò)誤
if (lastReturned == null)
throw new IllegalStateException();
//2. 記錄當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)
Node<E> lastNext = lastReturned.next;
//3. 刪除當(dāng)前節(jié)點(diǎn)
unlink(lastReturned);
//4. 如果next == lastReturned,說明當(dāng)前是從前往后遍歷的,那么將next賦值為下一個(gè)節(jié)點(diǎn)
//如果不相等,那么說明是從后往前遍歷的,這時(shí)只需要將index-1就行了
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
//5. 將移除的節(jié)點(diǎn)置空
lastReturned = null;
expectedModCount++;
}
//設(shè)置當(dāng)前正在遍歷的節(jié)點(diǎn)的值 啥?用ListIterator居然可以在遍歷的時(shí)候修改值,,666
public void set(E e) {
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
//設(shè)置當(dāng)前遍歷的節(jié)點(diǎn)的值
lastReturned.item = e;
}
//添加一個(gè)值
public void add(E e) {
checkForComodification();
lastReturned = null;
//如果next為null,那么添加到最后
//否則,將e元素添加到next的前面
if (next == null)
linkLast(e);
else
linkBefore(e, next);
nextIndex++;
expectedModCount++;
}
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
//循環(huán) 往后遍歷 沒遍歷一個(gè)節(jié)點(diǎn)就回調(diào)當(dāng)前節(jié)點(diǎn)的數(shù)據(jù)值
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();
}
}
這里的ListIterator有點(diǎn)強(qiáng)
- ListIterator只能用于List及其子類型血巍。
- 有add()方法,可以往鏈表中添加對(duì)象
- 可以通過hasNext()和next()往后順序遍歷,也可以通過hasPrevious()和previous()實(shí)現(xiàn)往前遍歷
- 可以通過nextIndex()和previousIndex()返回當(dāng)前索引處的位置
- 可以通過set()實(shí)現(xiàn)當(dāng)前遍歷對(duì)象的修改
九、總結(jié)
好了,又到了總結(jié)的時(shí)候,相信各位認(rèn)真看完的應(yīng)該對(duì)鏈表的基本操作非常熟悉了.
下面我們來總結(jié)一下LinkedList的關(guān)鍵點(diǎn)
LinkedList關(guān)鍵點(diǎn)
- 底層是雙向鏈表存儲(chǔ)數(shù)據(jù),并且記錄了頭節(jié)點(diǎn)和尾節(jié)點(diǎn)
- 添加元素非成核妫快,如果是添加到頭部和尾部的話更快,因?yàn)橐呀?jīng)記錄了頭節(jié)點(diǎn)和尾節(jié)點(diǎn),只需要鏈接一下就行了. 如果是添加到鏈表的中間部分的話,那么多一步操作,需要先找到添加索引處的元素(因?yàn)樾枰溄拥竭@里),才能進(jìn)行添加.
- 遍歷的時(shí)候,建議采用forEach()進(jìn)行遍歷,這樣可以在每次獲取下一個(gè)元素時(shí)都非常輕松(
next = next.next;
). 然后如果是通過fori
和get(i)
的方式進(jìn)行遍歷的話,效率是極低的,每次get(i)
都需要從最前面(或者最后面)開始往后查找i索引處的元素,效率很低. - 刪除也是非呈龉眩快,只需要改動(dòng)一下指針就行了,代價(jià)很小.
本文有寫的不對(duì)地方,還請(qǐng)多多包涵,歡迎批評(píng)指正.
這是我的筆記的其中一篇,需要看其他筆記的請(qǐng)移步 https://github.com/xfhy/notes叶洞,歡迎star鲫凶、fork.