java arraylist底層實現原理_ArrayList和LinkedList底層原理

ArrayList和LinkedList都是List的實現類,是在日常開發中經常被使用到的兩個集合,我們來結合源碼看下兩個集合的不同之處。

先來看下ArrayList的源碼:

// 默認的初始化大小private static final int DEFAULT_CAPACITY = 10;

ArrayList的底層數數組結構,我們創建ArrayList的時候,可以使用指定數組大小的構造函數或者直接是默認的構造函數。當使用默認構造函數的時候,數組的初始化大小是0,當第一次調用add()方法的時候,會變成默認的初始化大小10。

使用ArrayList的最多的就是add()方法了,我們直接來看add()方法的源碼:

public boolean add(E e) {//調用ensureCapacityInternal()看是否需要初始化以及擴容ensureCapacityInternal(size + 1); // Increments modCount!!//將數據添加到數組中elementData[size++] = e;return true;}

來看ensureCapacityInternal()方法:

private void ensureCapacityInternal(int minCapacity) {// 如果elementData為空,也就是第一次add的話,則返回默認容量和minCapacity中的最大值if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);}// ensureExplicitCapacity()方法則進一步判斷是否需要擴容ensureExplicitCapacity(minCapacity);}private void ensureExplicitCapacity(int minCapacity) {modCount++;// 如果添加后的容量大于原來的容量,則調用擴容方法if (minCapacity - elementData.length > 0)grow(minCapacity);}private void grow(int minCapacity) {// 原來的容量int oldCapacity = elementData.length;//將原來的容量右移一位,相當于是*2^-1,總容量為原來的1.5倍int newCapacity = oldCapacity + (oldCapacity >> 1);if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// 將舊數據拷貝到新數組中elementData = Arrays.copyOf(elementData, newCapacity);}

ArrayList的add方法邏輯很清晰,也沒有過多的方法嵌套,就是添加數據到數組中,判斷一下是否需要擴容,需要的話就進行擴容操作。

116c4a9b07fe822ba2f01ca5b11157cf.png山東掌趣網絡科技

get方法:

public E get(int index) {rangeCheck(index);checkForComodification();return ArrayList.this.elementData(offset + index);}

get()就是直接獲取該索引處的元素。

接下來我們就來分析下remove()方法:

remove(int index)方法:

public E remove(int index) {// 越界檢查rangeCheck(index);modCount++;E oldValue = elementData(index);// 判斷是否是最后一個元素int numMoved = size - index - 1;// 如果不是,則需要將index之后的元素往前移動一位if (numMoved > 0)System.arraycopy(elementData, index+1, elementData, index,numMoved);// 將最后一個元素刪除,幫助GCelementData[--size] = null; // clear to let GC do its workreturn oldValue;}

remove(Object o)方法:

public boolean remove(Object o) {if (o == null) {for (int index = 0; index < size; index++)if (elementData[index] == null) {fastRemove(index);return true;}} else {for (int index = 0; index < size; index++)if (o.equals(elementData[index])) {fastRemove(index);return true;}}return false;}

remove邏輯比較簡單,直接來看fastRemove()方法:

private void fastRemove(int index) {// modCount,繼承于AbstractList。記錄著集合的修改次數modCount++;int numMoved = size - index - 1;// 判斷是否是最后一個元素,這里的操作和remove(index)一樣if (numMoved > 0)System.arraycopy(elementData, index+1, elementData, index,numMoved);elementData[--size] = null; // clear to let GC do its work}

接下來我們來看下刪除的例子:

37ccb9ff1c4aec56dc65d33ef3b3b7c4.png山東掌趣網絡科技

public class ArrayListDemo {public static void main(String[] args) {List list=new ArrayList();list.add("jack");list.add("tom");list.add("tom");list.add("mary");list.add("danel");System.out.println("刪除之前的集合--:"+list);// for循環刪除字符串為tom的元素for(int i=0;i

我們看到,集合中元素為“tom”的并未完全刪除掉,結合上面remove(int index)的源碼,List調用remove(index)方法后,會移除index位置上的元素,index之后的元素就全部依次往前移,當刪除掉一個元素后,size的值就變成了4,此時tom的索引位置為1,由于之前遍歷的時候,i已經是1了,再次遍歷的時候,i是從2開始,所以tom這個元素邊不會再被遍歷到,所以會存在漏刪的情況。

e08918b170accef7be8843fca37d7a2e.png山東掌趣網絡科技

再來看一個例子,使用foreach來遍歷刪除:

for(String name : list){if("tom".equals(name)){list.remove(name);}}Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)at java.util.ArrayList$Itr.next(ArrayList.java:851)at ArrayListDemo.main(ArrayListDemo.java:14)

代碼報了ConcurrentModificationException異常,為什么會出現這個異常呢,還記得上面源碼分析的時候提到過一個成員變量modCount嗎,modCount繼承于AbstractList,記錄著集合的修改次數,也就是每次add或者remove的話modCount值都會被修改,根據報錯的地方,跟蹤到它的源碼:

final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}

expectedModCount表示對ArrayList修改次數的期望值,我們來看下expectedModCount的源碼位置,是內部類Itr的成員變量:

private class Itr implements Iterator {int cursor; // index of next element to returnint lastRet = -1; // index of last element returned; -1 if no suchint expectedModCount = modCount;public boolean hasNext() {return cursor != size;}、、、、

foreach之所以能工作,是因為這些集合類都實現了Iterable接口,該接口中定義了Iterator迭代器的產生方法,并且foreach就是通過Iterable接口在序列中進行移動,foreach語法最終被編譯器轉為了對Iterator.next()的調用,

每次foreach迭代的時候都有會有兩步操作:

第一步:iterator.hasNext() //判斷是否有下個元素

public boolean hasNext() {return cursor != size;}

第二步:下個元素是什么

public E next() {checkForComodification();int i = cursor;if (i >= size)throw new NoSuchElementException();Object[] elementData = ArrayList.this.elementData;if (i >= elementData.length)throw new ConcurrentModificationException();cursor = i + 1;return (E) elementData[lastRet = i];}

我們打印的異常也說明了這一點,Iterator初始化的時候將modCount 的值賦給了expectedModCount,此時這是一個固定值,而當調用remove方法的時候,會修改modCount ,當modCount 值與expectedModCount值不相等的時候,就會報ConcurrentModificationException異常。

那需要怎樣刪除ArrayList的數據呢?我們再看Iterator的源碼,里面有一個刪除的方法:

public void remove() {if (lastRet < 0)throw new IllegalStateException();checkForComodification();try {ArrayList.this.remove(lastRet);cursor = lastRet;lastRet = -1;expectedModCount = modCount;} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}}

這個remove方法的源碼將修改后的modCount的值又重新復制給了expectedModCount ,所以不會再報ConcurrentModificationException異常,正確的刪除是獲取list集合的迭代器,然后通過迭代器調用它的remove方法。

Iterator iterator = list.iterator();while (iterator.hasNext()){String name = (String)iterator.next();if("tom".equals(name)){iterator.remove();}}

LinkedList源碼分析:

相比于ArrayList,它額外實現了雙端隊列接口Deque,這個接口主要是聲明了隊頭,隊尾的一系列方法。LinkedList是一個雙向鏈表結構,這里定義了鏈表的頭結點和尾結點:

// 鏈表的頭元素transient Node first;// 鏈表的尾元素transient Node last;

LinkedList的內部類,用以表示一個鏈表節點,一個節點除了保持自身的數據外,還持有前,后兩個節點的引用。所以就數據存儲上來說,它相比使用數組作為底層數據結構的ArrayList來說,會更加耗費空間。但也正因為這個特性,它刪除,插入節點很快

private static class Node {E item;Node next;Node prev;Node(Node prev, E element, Node next) {this.item = element;this.next = next;this.prev = prev;}}

每一個元素在LinkedList中都是在Node中存儲,每個Node中同時還存儲了當前元素的前節點和后節點。

我們再來看LikedList的add方法:

public boolean add(E e) {linkLast(e);return true;}

add方法沒做什么事情,只調用了linkLast()方法,真正做事的是linkLast(E e),來看下這個方法:

void linkLast(E e) {// 將原尾結點賦值給lfinal Node l = last;// 新節點的頭節點是原集合的尾結點,它自己的尾節點為空final Node newNode = new Node<>(l, e, null);// 將新節點賦值給尾節點last = newNode;// 如果原集合的尾結點是null的話,說明原集合沒有值,它的頭結點就是現 在的新增的數據if (l == null)first = newNode;else //否則的話將原尾結點的下一個節點指向當前新增的數據l.next = newNode;size++; // 集合數量+1modCount++; //修改次數+1}

add()方法是將數據插入到鏈表的尾部,通過add(int index, E element)方法可以在指定位置插入指定元素。

public void add(int index, E element) {// 檢查指針是否越界checkPositionIndex(index);// 如果指定位置是最后一個的話,則直接在尾部插入if (index == size)linkLast(element);elselinkBefore(element, node(index));}

看下linkBefore(E e, Node succ)的源碼:

void linkBefore(E e, Node succ) {// assert succ != null;final Node pred = succ.prev;// 新節點的前驅節點是原索引處節點的前驅節點,next節點是原索引處節點final Node newNode = new Node<>(pred, e, succ);// 將原索引處的節點的前驅節點修改為新的節點succ.prev = newNode;if (pred == null)first = newNode;else//將原前驅節點的next節點修改為新的節點pred.next = newNode;size++;modCount++;}

get方法源碼:

Node node(int index) {// assert isElementIndex(index);// 首先判斷該索引時位于前半段還是后半段if (index < (size >> 1)) {Node x = first;for (int i = 0; i < index; i++)x = x.next;return x;} else {Node x = last;for (int i = size - 1; i > index; i--)x = x.prev;return x;}}

LinkedList的get首先判斷需要獲取的數據在該鏈表的前半段還是后半段,這樣可以減少需要遍歷的數據,由于需要遍歷數據,所以獲取數據的速度會比ArrayList慢。

再來看下刪除的方法

public E remove(int index) {// 檢查下標是否越界checkElementIndex(index);return unlink(node(index));}E unlink(Node x) {// 得到要刪除的元素final E element = x.item;// 獲得刪除元素的前驅和后置節點final Node next = x.next;final Node prev = x.prev;// 前驅節點為null的話說明要刪除的節點是第一個節點if (prev == null) {first = next;} else {// 修改前驅節點的next節點指向被刪除節點的next節點prev.next = next;x.prev = null;}// 后置節點為null的話說明要刪除的節點是最后一個節點if (next == null) {last = prev;} else {// 修改next節點的前驅節點指向被刪除節點的前驅節點next.prev = prev;x.next = null;}x.item = null;size--;modCount++;return element;}

remove(Object o)和remove(int index)稍有差別,但是基本都是調用unlink(Node x)方法。

在使用for循環或者是foreach迭代的時候,和ArrayList一樣,也會有漏刪或者報錯,因此仍然建議使用迭代器中的remove方法進行迭代刪除。

值得一提的是LinkedList還實現Dequeue接口,接口定義了隊列數據結構,元素是有序的(按插入順序),先進先出

可以利用LinkedList實現一個隊列。LinkedList中的方法:

// 將指定的元素插入此隊列public boolean offer(E e) {return add(e);}// 獲取但不移除此隊列的頭;如果此隊列為空,則返回 null。public E peek() {final Node f = first;return (f == null) ? null : f.item;}// 獲取并移除此隊列的頭,如果此隊列為空,則返回 null。public E poll() {final Node f = first;return (f == null) ? null : unlinkFirst(f);}// 獲取并移除此隊列的頭public E remove() {return removeFirst();}、、、

例如簡單的隊列:

public class DequeDemo {public static void main(String[] args) {Queue queue = new LinkedList();queue.offer("tom");queue.offer("jack");queue.offer("mike");while (queue.peek()!=null){System.out.println(queue.poll());}}}

作為雙端隊列的一些方法,如: addFirst(Object ob):在隊首增加元素addLast(Object obj):在隊尾增加;peekFirst():查看隊首;peekLast:查看隊尾;pollFirst:移除隊首;pollLast:移除隊尾例如:

public class DequeDemo {public static void main(String[] args) {Deque queue = new LinkedList();queue.offer("tom");queue.offer("jack");queue.offer("mike");queue.addFirst("dannel");queue.addLast("mary");while (queue.peek()!=null){System.out.println(queue.poll());}}}danneltomjackmikemary

由于LinkedList底層是用雙向鏈表實現的,沒有初始化大小,也沒有擴容的機制。

總結

ArrayList和LinkedList都實現了List的接口,但是LinkedList還實現了Deque隊列接口,因此還可以當做隊列使用,

ArrayList的底層結構是數組,獲取元素的速度快,但是插入速度相對LinkedList較慢,LinkedList的底層是雙向鏈表結構,插入和刪除比較快,但是查找的速度相對ArrayList較慢。另外ArrayList和LinkedList在并發情況下沒有對數據進行同步,是線程不安全的。

山東掌趣網絡科技

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/533361.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/533361.shtml
英文地址,請注明出處:http://en.pswp.cn/news/533361.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

java ee jaas_java-ee – Tomcat-Jaas – 如何檢索主題?

i knew that and it works, but I need to retrieve subject to get also roleprincipal不幸的是,它在Java EE中的工作方式不同. JAAS主題只是一個“主要包”,其中哪些代表用戶/調用者主體和/或角色主體根本不是標準化的.每個其他容器在這里做不同的事情. Javadoc for Tomcat’…

java jive歌詞_Java Jive_Manhattan Transfer with Phil Collins_高音質在線試聽_Java Jive歌詞|歌曲下載_酷狗音樂...

Manhattan Transfer with Phil Collins - Java Jive&#xfeff;[id:$00000000][ar:曼哈頓行者爵士][ti:Java Jive (LP Version)][by:][hash:99bf26cac4ad13e15925a56eb724027f][al:][sign:][qq:][total:0][offset:0][00:00.05]The Manhattan Transfer - Java Jive[00:10.57]I …

java 3_Java 3 (Java的數據類型)

Java的數據類型主要內容&#xff1a;1Java數據類型的分類2.8種基本數據類型3.理解引用類型的特點一、什么是數據類型&#xff1f;計算機語言將數據按性質進行分類&#xff0c;每一類稱為一種數據類型&#xff1b;數據類型定義了數據的性質、取值范圍、存儲方式、對數據所能進行…

java快捷鍵 --_Java中的快捷方式“或分配”(| =)運算符

如果是關于可讀性&#xff0c;我就有了將測試數據與測試邏輯分離的概念。代碼示例&#xff1a;// declare dataDataType [] dataToTest new DataType[] {defaultStock,defaultWholesale,defaultRetail,defaultDelivery}// define logicboolean checkIfAnyNegative(DataType []…

tcp網絡通信教程 java_基于java TCP網絡通信的實例詳解

JAVA中設計網絡編程模式的主要有TCP和UDP兩種&#xff0c;TCP是屬于即時通信&#xff0c;UDP是通過數據包來進行通信&#xff0c;UDP當中就會牽扯到數據的解析和傳送。在安全性能方面&#xff0c;TCP要略勝一籌&#xff0c;通信過程中不容易出現數據丟失的現象&#xff0c;有一…

java博客論壇設計報告_javaweb課程設計報告個人博客網站的實現(Java).doc

javaweb課程設計報告個人博客網站的實現(Java)項目名稱&#xff1a; 個人博客網站的實現(Java) 學生姓名&#xff1a;學 號&#xff1a;班 級&#xff1a;指導教師&#xff1a;2014年12月23日目錄1 緒論11.1系統應用意義11.2主要設計任務11.3開發及運行環境11.3.1 JSP的基礎——…

java replace stringbuilder_java.lang.StringBuilder.replace()方法實例

全屏java.lang.StringBuilder.replace()方法按照這個順序&#xff0c;在指定的字符串的子字符串替換字符。子串開始在指定start的 索引&#xff0c;并延伸到該字符 end - 1&#xff0c;或如果序列的末端不存在這樣的字符。聲明以下是java.lang.StringBuilder.replace()方法的聲…

中小學課java_java畢業設計_springboot框架的中小學排課與實現

這是一個基于java的畢業設計項目,畢設課題為springboot框架的中小學排課與實現, 是一個采用b/s結構的javaweb項目, 開發工具eclipsei/eclipse, 項目框架jspspringbootmybatis, 中小學排課與實現采用mysql進行數據存儲, 并基于mybatis進行了orm實體關系映射, 該中小學排課與實現…

java 文件設置為只讀文件系統_Java如何設置文件為只讀?

在java編程中&#xff0c;如何設置文件為只讀&#xff1f;此示例演示如何使用File類的file.setReadOnly()和file.canWrite()方法設置文件為只讀模式。package com.yiibai;import java.io.File;public class ReadOnlyFile {public static void main(String[] args) {File file …

wordcount linux java_linux下在eclipse上運行hadoop自帶例子wordcount

啟動eclipse&#xff1a;打開windows->open perspective->other->map/reduce 可以看到map/reduce開發視圖。設置Hadoop location.打開windows->show view->other-> map/reduce Locations視圖&#xff0c;在點擊大象后【new Hadoop location】彈出的對話框(Ge…

php java執行linux_java_java執行Linux命令的方法,本文實例講述了java執行Linux命 - phpStudy...

java執行Linux命令的方法本文實例講述了java執行Linux命令的方法。分享給大家供大家參考。具體實現方法如下&#xff1a;public class StreamGobbler extends Thread {InputStream is;String type;public StreamGobbler(InputStream is, String type) {this.is is;this.type …

java怎么接收前端請求_前端json post 請求 后端怎么接收

前端提交POST /api/test HTTP/1.1Host: 192.168.135.69:81Connection: keep-aliveContent-Length: 18Origin: http://192.168.135.69:81User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15…

minimum在java中的意思_Java Calendar getMinimum()用法及代碼示例

Calendar類中的getMinimum(int calndr_field)方法用于返回此Calendar實例的給定日歷字段(int calndr_field)的最小值。用法:public abstract int getMinimum(int calndr_field)參數&#xff1a;該方法采用一個參數calndr_field&#xff0c;該參數表示要操作的日歷字段。返回值&…

django mysql 一對多_請教,django中 如何向帶有外鍵(一對多和多對多)數據庫中批量插入數據?...

已自行解決&#xff0c;代碼如下&#xff1a;json格式&#xff1a;[{"標題": "小武","內容": "測試","類型":["情感","文學","散文"]"文章資源":[{"title":"小武.1…

安裝php no permision,php安裝過程中的No package ‘xxx’ found問題

php No package ‘oniguruma’ found今天安裝php7.4的時候遇到這樣的一個報錯&#xff0c;然后yum install oniguruma oniguruma-devel&#xff0c;重試安裝php&#xff0c;依然報錯&#xff0c;又編譯安裝oniguruma&#xff0c;重試安裝php&#xff0c;還是報錯&#xff0c;問…

php httpclient.class.php,php實現httpclient類示例

class httpClient {public $buffer null; // buffer 獲取返回的字符串public $referer null; // referer 設置 HTTP_REFERER 的網址public $response null; // response 服務器響應的 header 信息public $request null; // request 發送到服務器的 header 信息private $…

大學php老師,php高校教師總結計劃系統

通過使用本系統&#xff0c;可以規范工作流程&#xff0c;提高辦公效率&#xff0c;增強團隊協同工作能力&#xff0c;實現科學的公文處理、事物管理、會議安排和人力管理&#xff0c;量化運營資源&#xff0c;預防管理真空&#xff0c;降低運行成本。還可以實現便利的信息發布…

好用的php空間,推薦國內三個優質的免費PHP空間

1.億家免費國內PHP空間這是我見過最好的免費國內PHP空間了&#xff0c;這個BLOG就是由他的空間支撐的&#xff0c;所以你看到我這個空間的穩定&#xff0c;快速就代表著他們空間的優質了&#xff0c;推薦注冊地址&#xff1a;www.e9china.net這個先要在他們論壇上發帖子&#x…

java處理臟數據,Java程序的臟數據問題

臟數據(Out-of-date data)&#xff0c;指過時的數據。假如在您的java程序中存在臟數據&#xff0c;將或多或少地給軟件系統帶來一些問題&#xff0c;如&#xff1a;無法實時地應用已經發生改變的配置&#xff0c;軟件系統出現一些莫名其妙的、難以重現的、后果嚴重的錯誤等等。…

制作自己的 Docker 容器

軟件開發最大的麻煩事之一&#xff0c;就是環境配置。用戶必須保證操作系統的設置&#xff0c;各種庫和組件的安裝&#xff0c;只有它們都正確&#xff0c;軟件才能運行。docker從根本上解決問題&#xff0c;軟件安裝的時候&#xff0c;把原始環境一模一樣地復制過來。 以 koa-…