Java集合ArrayList與LinkedList詳解
前言
對于Java程序員,可以說對于ArrayList
和LinkedList
可謂是十分熟悉了
對于ArrayList和LinkedList,他們都是List接口的一個(gè)實(shí)現(xiàn)類,并且我們知道他們的實(shí)現(xiàn)方式各不相同,例如ArrayList底層實(shí)現(xiàn)是一個(gè)數(shù)組,而LinkedList底層實(shí)現(xiàn)是鏈表,對于數(shù)組來說,插入慢但是查詢快,而對于鏈表來說查詢慢,插入快
今天我們就來分析分析他們的源碼
ArrayList
我們先看一看ArrayList的類圖。他繼承于AbstractList,而這個(gè)類是List類的的骨架,可以說這個(gè)類是List類的基石
成員屬性
/** 序列化ID **/ private static final long serialVersionUID = 8683452581122892189L; /** 默認(rèn)容量 **/ private static final int DEFAULT_CAPACITY = 10; /** 如果傳入的容量為0,那么將使用這個(gè)空元素?cái)?shù)組 **/ private static final Object[] EMPTY_ELEMENTDATA = {}; /** 默認(rèn)空元素?cái)?shù)組,長度為0,傳入元素時(shí)會(huì)初始化為默認(rèn)元素大小 **/ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** 真正存儲(chǔ)數(shù)據(jù)的數(shù)組 **/ transient Object[] elementData; /** 列表中元素的個(gè)數(shù) **/ private int size;
這里需要主要關(guān)注的成員屬性為size
和elementData
,一個(gè)是元素個(gè)數(shù),一個(gè)是真正存儲(chǔ)數(shù)據(jù)的數(shù)組
構(gòu)造函數(shù)
/** 指定數(shù)組長度構(gòu)造 **/ public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } /** 空參構(gòu)造 **/ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** 傳入一個(gè)集合,將該集合的元素初始化到ArrayList種 **/ public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }
擴(kuò)容機(jī)制
我們知道ArrayList是一個(gè)動(dòng)態(tài)數(shù)組,但是他的底層實(shí)現(xiàn)是一個(gè)數(shù)組,那他是怎樣保證動(dòng)態(tài)的呢?
ArrayList每次添加元素之前,都會(huì)檢查添加元素后的元素個(gè)數(shù)是否超過數(shù)組長度,如果超出,那么就會(huì)進(jìn)行擴(kuò)容,而數(shù)組擴(kuò)容通過一個(gè)公開的方法實(shí)現(xiàn),我們也可以手動(dòng)進(jìn)行擴(kuò)容
public void ensureCapacity(int minCapacity) { //判斷數(shù)組是否等于默認(rèn)空數(shù)組,不等于則給minExpand賦值為10 int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY; //判斷參數(shù)是否大于minExpand大于的時(shí)候才會(huì)去擴(kuò)容 if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } } private void ensureExplicitCapacity(int minCapacity) { modCount++;//記錄數(shù)組修改次數(shù) + 1 if (minCapacity - elementData.length > 0) grow(minCapacity); } //真正擴(kuò)容的方法 private void grow(int minCapacity) { //獲取原來的容量 int oldCapacity = elementData.length; //計(jì)算新容量,新容量大小 = 舊容量大小的1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1); //如果需要的容量比新容量還小就用需要的容量 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //如果新容量大于最大容量就用最大的容量 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); //數(shù)據(jù)拷貝 elementData = Arrays.copyOf(elementData, newCapacity); }
我們可以發(fā)現(xiàn)每次擴(kuò)容,ArrayList都會(huì)擴(kuò)容1.5倍,通過位運(yùn)算完成計(jì)算擴(kuò)容大小的,我們知道,擴(kuò)容之后進(jìn)行數(shù)據(jù)遷移這個(gè)操作是很費(fèi)時(shí)間的,比如我們有10w條數(shù)據(jù),這樣的話,進(jìn)行數(shù)據(jù)遷移的時(shí)候,我們會(huì)耗費(fèi)很長時(shí)間,所以我們建議再初始化的時(shí)候就定義一個(gè)容量,這樣可以讓ArrayList的效率提高很多
add方法
//添加元素到尾部 public boolean add(E e) { //檢查是否需要擴(kuò)容 ensureCapacityInternal(size + 1); //將元素添加到數(shù)組末尾,并把size++ elementData[size++] = e; return true; } //添加元素到指定索引 public void add(int index, E element) { //檢查index是否越界 rangeCheckForAdd(index); //檢查是否需要擴(kuò)容 ensureCapacityInternal(size + 1); //將index以及之后的元素向后挪動(dòng)一位 //這樣index就能添加元素了 System.arraycopy(elementData, index, elementData, index + 1, size - index); //添加元素到index的位置 elementData[index] = element; size++; } //將集合c的元素添加到list中 public boolean addAll(Collection<? extends E> c) { //把c轉(zhuǎn)換為數(shù)組 Object[] a = c.toArray(); //拿到c的長度 int numNew = a.length; //檢查是否需要擴(kuò)容 ensureCapacityInternal(size + numNew); // Increments modCount //將a數(shù)組拷貝到原來數(shù)組的末尾 System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; } //將集合c的元素從index位置開始添加到list中 public boolean addAll(int index, Collection<? extends E> c) { //檢查index是否越界 rangeCheckForAdd(index); //將c轉(zhuǎn)換為數(shù)組 Object[] a = c.toArray(); //獲取數(shù)組a的長度 int numNew = a.length; //檢查是否需要擴(kuò)容 ensureCapacityInternal(size + numNew); // Increments modCount //計(jì)算需要移動(dòng)的長度 int numMoved = size - index; if (numMoved > 0) //向后移動(dòng) System.arraycopy(elementData, index, elementData, index + numNew, numMoved); 將數(shù)組a拷貝到原數(shù)組中 System.arraycopy(a, 0, elementData, index, numNew); size += numNew; return numNew != 0; }
get方法
public E get(int index) { //檢查index是否越界 rangeCheck(index); //返回對應(yīng)數(shù)組中指定索引的元素 return elementData(index); }
remove方法
//刪除指定索引的元素 public E remove(int index) { //判斷index是否越界 rangeCheck(index); //將數(shù)組修改次數(shù)+1 modCount++; //拿到對應(yīng)索引的元素 E oldValue = elementData(index); 計(jì)算移動(dòng)位置 int numMoved = size - index - 1; if (numMoved > 0) //移動(dòng)數(shù)組 System.arraycopy(elementData, index+1, elementData, index, numMoved); //size-1,并把尾部元素置為null,這里把尾部置為null主要是為了讓GC起作用 elementData[--size] = null; return oldValue; } //刪除第一個(gè)滿足o.equals(elementData[index]的元素 public boolean remove(Object o) { if (o == null) { //如果o為null使用==進(jìn)行判斷 //從索引為0的元素開始尋找 for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { //否則使用equals判斷 //從索引為0的元素開始尋找 for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; }
小結(jié)
- 如果我們的數(shù)據(jù)量很大,我們可以給List估算一個(gè)容量,然后進(jìn)行初始化,否則會(huì)對效率有影響,畢竟一直進(jìn)行數(shù)據(jù)遷移。
- 最開始Arraylist的容量為10,每次擴(kuò)容為原先容量的1.5倍,但是如果我們已經(jīng)擴(kuò)容的數(shù)組,是不能進(jìn)行縮容的,例如我們添加了10個(gè)元素,這個(gè)時(shí)候已經(jīng)擴(kuò)容了,但是我們刪除最后一個(gè)元素,他是不會(huì)進(jìn)行縮容的
- 我們并沒有看到他方法上有synchronized關(guān)鍵字,所以他也不是線程安全的,我們可以使用Vector或者使用
Collections.synchronizedList(list)
LinkedList
對于LinkedList,它同樣繼承與AbstractList,在前面已經(jīng)說過了,AbstractList是List的骨架,我們還可以看到它實(shí)現(xiàn)了Deque,所以LinkedList既可以看做一個(gè)鏈表也可以看做是一個(gè)隊(duì)列,同樣也可以看做一個(gè)棧,所以LinkedList比較全能
Node類
我們知道LinkedList是一個(gè)雙向鏈表,那么肯定有一個(gè)個(gè)的Node,所以我們先來看一看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節(jié)點(diǎn)有三個(gè)成員屬性,分別是value,前驅(qū)指針,后繼指針,以及一個(gè)全參構(gòu)造方法
成員屬性
/** 元素個(gè)數(shù) */ transient int size = 0; /** 指向鏈表頭結(jié)點(diǎn) */ transient Node<E> first; /** 指向鏈表尾節(jié)點(diǎn) */ transient Node<E> last; /** 序列化ID */ private static final long serialVersionUID = 876323262645176354L;
構(gòu)造函數(shù)
//空參構(gòu)造,沒什么好說的 public LinkedList() { } //將集合c的元素添加到鏈表中 public LinkedList(Collection<? extends E> c) { //調(diào)用空參構(gòu)造 this(); //調(diào)用addAll()這個(gè)方法在下面講述 addAll(c); }
添加
public boolean add(E e) { linkLast(e);//添加元素到末尾 return true; } //將元素添加到指定index位置 public void add(int index, E element) { //檢查索引是否大于size或者小于0 checkPositionIndex(index); //如果索引位置和size相等添加到末尾 if (index == size) linkLast(element); else //添加到指定位置 linkBefore(element, node(index)); } public void addFirst(E e) { linkFirst(e);//添加元素到頭部 } public void addLast(E e) { linkLast(e);//添加元素到末尾 } //添加到頭部 private void linkFirst(E e) { //拿到頭指針 final Node<E> f = first; //new一個(gè)Node節(jié)點(diǎn),值為e,next為頭指針,pre為null final Node<E> newNode = new Node<>(null, e, f); //將頭指針替換為新的 first = newNode; //將f的pre修改為現(xiàn)在的頭指針 if (f == null) last = newNode; else f.prev = newNode; size++; modCount++; } //添加到末尾 void linkLast(E e) { //拿到尾指針 final Node<E> l = last; //new一個(gè)Node節(jié)點(diǎn),值為e,next為null,pre為尾指針 final Node<E> newNode = new Node<>(l, e, null); //替換尾指針 last = newNode; //將l的next修改為現(xiàn)在的尾指針 if (l == null) first = newNode; else l.next = newNode; size++; modCount++; } //將集合c的元素添加到list中 public boolean addAll(Collection<? extends E> c) { //從尾部開始添加 return addAll(size, c); } //真正的addAll方法 public boolean addAll(int index, Collection<? extends E> c) { //檢查index正確 checkPositionIndex(index); //先把c轉(zhuǎn)換為數(shù)組 Object[] a = c.toArray(); //拿到c的長度 int numNew = a.length; if (numNew == 0) return false; //定義兩個(gè)指針,一個(gè)前驅(qū)一個(gè)后繼 Node<E> pred, succ; if (index == size) { succ = null; pred = last; } else { succ = node(index); pred = succ.prev; } //遍歷數(shù)組a for (Object o : a) { @SuppressWarnings("unchecked") E e = (E) o; //new一個(gè)Node節(jié)點(diǎn),值為e,前驅(qū)為pred,后繼為null Node<E> newNode = new Node<>(pred, e, null); //pred為null,證明前驅(qū)為null,當(dāng)前節(jié)點(diǎn)為鏈表的頭結(jié)點(diǎn) if (pred == null) first = newNode; else //前驅(qū)節(jié)點(diǎn)后繼指針指向頭結(jié)點(diǎn) pred.next = newNode; //前驅(qū)節(jié)點(diǎn)后移 pred = newNode; } //succ為null證明index索引位于鏈表最后 if (succ == null) { last = pred; } else { //pred的后繼節(jié)點(diǎn)為succ pred.next = succ; //succ的前驅(qū)節(jié)點(diǎn)為pred succ.prev = pred; } size += numNew; modCount++; return true; }
獲取
//這個(gè)方法是Node類的方法,我們可以看到上面的addAll方法也使用了這個(gè)方法 //這個(gè)方法作用是返回指定索引的非空節(jié)點(diǎn) Node<E> node(int 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; } } //獲取index位置的元素 public E get(int index) { //檢查索引是否正常 checkElementIndex(index); return node(index).item; } //獲取頭部元素 public E getFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return f.item; } //獲取尾部元素 public E getLast() { final Node<E> l = last; if (l == null) throw new NoSuchElementException(); return l.item; }
刪除
//刪除index位置的元素 public E remove(int index) { //檢查索引是否大于size或者小于0 checkElementIndex(index); //刪除index位置的元素 return unlink(node(index)); } //刪除頭部元素,這個(gè)方法是Node類的方法 private E unlinkFirst(Node<E> f) { //拿到頭節(jié)點(diǎn)的值 final E element = f.item; //拿到頭結(jié)點(diǎn)的next值 final Node<E> next = f.next; //將頭結(jié)點(diǎn)的值置為null(幫助GC) f.item = null; //將頭結(jié)點(diǎn)的next置為null(幫助GC) f.next = null; //將頭結(jié)點(diǎn)修改為next first = next; if (next == null) //此時(shí)鏈表沒有元素了 last = null; else next.prev = null; size--; modCount++; return element; } //刪除尾部元素,這個(gè)方法是Node類的方法 private E unlinkLast(Node<E> l) { //拿到尾節(jié)點(diǎn)的值 final E element = l.item; //拿到尾節(jié)點(diǎn)的前驅(qū) final Node<E> prev = l.prev; //將尾結(jié)點(diǎn)的值置為null(幫助GC) l.item = null; //將尾結(jié)點(diǎn)的next置為null(幫助GC) l.prev = null; //將尾指針修改為prev last = prev; if (prev == null) //此時(shí)鏈表沒有元素了 first = null; else prev.next = null; size--; modCount++; return element; }
小結(jié)
- 雖然說鏈表的刪除的效率為O(1),但是在LinkedList中我們需要先利用node方法查詢到指定節(jié)點(diǎn)的位置,然后再去刪除,所以千萬不要誤認(rèn)為LinkedList的remove方法是O(1)
- LinkedList刪除頭部和尾部的元素效率為O(1)
到此這篇關(guān)于Java集合ArrayList與LinkedList詳解的文章就介紹到這了,更多相關(guān)Java ArrayList與LinkedList內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- JAVA LinkedList和ArrayList的使用及性能分析
- java中ArrayList和LinkedList的區(qū)別詳解
- Java中ArrayList和LinkedList的遍歷與性能分析
- java 集合之實(shí)現(xiàn)類ArrayList和LinkedList的方法
- java 中ArrayList與LinkedList性能比較
- java中ArrayList與LinkedList對比詳情
- 區(qū)分Java中的ArrayList和LinkedList
- Java面試崗常見問題之ArrayList和LinkedList的區(qū)別
- Java中ArrayList和LinkedList區(qū)別
- Java ArrayList與LinkedList使用方法詳解
- Java中ArrayList和LinkedList的區(qū)別
相關(guān)文章
關(guān)于SpringBoot改動(dòng)后0.03秒啟動(dòng)的問題
這篇文章主要介紹了SpringBoot改動(dòng)后0.03秒啟動(dòng),本文結(jié)合示例代碼給大家講解的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-12-12Java封裝公共Result結(jié)果返回類的實(shí)現(xiàn)
在使用Java開發(fā)接口請求中,我們需要對請求進(jìn)行進(jìn)行統(tǒng)一返回值,這時(shí)候我們自己封裝一個(gè)統(tǒng)一的Result返回類,本文主要介紹了Java封裝公共Result結(jié)果返回類的實(shí)現(xiàn),感興趣的可以了解一下2023-01-01Netty分布式server啟動(dòng)流程N(yùn)io創(chuàng)建源碼分析
這篇文章主要介紹了Netty分布式server啟動(dòng)流程N(yùn)io創(chuàng)建源碼分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03詳解Java虛擬機(jī)30個(gè)常用知識(shí)點(diǎn)之1——類文件結(jié)構(gòu)
這篇文章主要介紹了Java虛擬機(jī)類文件結(jié)構(gòu),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03通過AOP攔截Spring?Boot日志并將其存入數(shù)據(jù)庫功能實(shí)現(xiàn)
本文介紹了如何使用Spring Boot和AOP技術(shù)實(shí)現(xiàn)攔截系統(tǒng)日志并保存到數(shù)據(jù)庫中的功能,包括配置數(shù)據(jù)庫連接、定義日志實(shí)體類、定義日志攔截器、使用AOP攔截日志并保存到數(shù)據(jù)庫中等步驟,感興趣的朋友一起看看吧2023-08-08詳解springcloud 基于feign的服務(wù)接口的統(tǒng)一hystrix降級(jí)處理
這篇文章主要介紹了詳解springcloud 基于feign的服務(wù)接口的統(tǒng)一hystrix降級(jí)處理,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-06-06Java跨session實(shí)現(xiàn)token接口測試過程圖解
這篇文章主要介紹了Java跨session實(shí)現(xiàn)token接口測試過程圖解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04