ArrayList源碼分析

目錄

ArrayList簡介

ArrayList和vector的區別(了解即可)

ArrayList添加null值

?ArrayList和LinkedList區別

?ArrayList核心源碼解讀

?ArrayList擴容機制分析

一步一分析ArrayList擴容機制

?hugeCapacity()方法

System.arraycopy()

?Arrays.copyOf()方法


ArrayList簡介

ArrayList 的底層是數組隊列,相當于動態數組。與 Java 中的數組相比,它的容量能動態增長。在添加大量元素前,應用程序可以使用ensureCapacity操作來增加 ArrayList 實例的容量。這可以減少遞增式再分配的數量。

ArrayList繼承于AbstractList,實現了List,RandomAccess,Cloneable,java.io.Serializable這些接口。


public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable{}

?List?:是一個列表,支持添加,刪除,查找等操作,并且可以通過下標隨機訪問。

RandomAccess?:這是一個標志接口,表明實現這個接口的List結合支持快速隨機訪問的。在ArrayList中,我們即可通過元素的序號快速獲取元素對象,這就是隨機訪問。

Cloneable:表明有拷貝能力,可以進行深拷貝或淺拷貝操作。

Serializable:表明他可以進行序列化操作,也就是可以將對象轉換為字節流對象進行持久化存存儲網絡傳輸,非常方便。

ArrayList和vector的區別(了解即可)

ArrayList是List的主要實現類,底層使用Object[]存儲,適用于頻繁的查找工作,線程不安全。

Vector是List的古老實現類,底層使用Object[]存儲,線程安全。?

ArrayList添加null值

ArrayList中可以存儲任何類型的對象,包括null值。不過,不建議向ArrayList添加null值,null值毫無意義,會讓代碼難以維護比如忘記做判空處理就會導致空指針異常。

ArrayList<String> listOfStrings = new ArrayList<>();
listOfStrings.add(null);
listOfStrings.add("java");
System.out.println(listOfStrings);
[null, java]

?ArrayList和LinkedList區別

是否保證線程安全:ArrayList和LinkedList都是不同步的,也就是不保證線程安全;

底層數據結構:ArrayList底層使用的是Object數組;LinkedList底層使用的是雙向鏈表數據結構(JDK1.6 之前為循環鏈表,JDK1.7 取消了循環。注意雙向鏈表和雙向循環鏈表的區別)

插入和刪除是否受元素位置的影響:

ArrayList 采用數組存儲,所以插入和刪除元素的時間復雜度受元素位置的影響。 比如:執行add(E e)方法的時候, ArrayList 會默認在將指定的元素追加到此列表的末尾,這種情況時間復雜度就是 O(1)。但是如果要在指定位置 i 插入和刪除元素的話(add(int index, E element)),時間復雜度就為 O(n)。因為在進行上述操作的時候集合中第 i 和第 i 個元素之后的(n-i)個元素都要執行向后位/向前移一位的操作

LinkedList 采用鏈表存儲,所以在頭尾插入或者刪除元素不受元素位置的影響(add(E e)addFirst(E e)addLast(E e)removeFirst()removeLast()),時間復雜度為 O(1),如果是要在指定位置 i 插入和刪除元素的話(add(int index, E element)remove(Object o),remove(int index)), 時間復雜度為 O(n) ,因為需要先移動到指定位置再插入和刪除。

是否支持快速隨機訪問: LinkedList 不支持高效的隨機元素訪問,而 ArrayList(實現了 RandomAccess 接口) 支持。快速隨機訪問就是通過元素的序號快速獲取元素對象(對應于get(int index)方法)。

內存空間占用: ArrayList 的空間浪費主要體現在在 list 列表的結尾會預留一定的容量空間,而 LinkedList 的空間花費則體現在它的每一個元素都需要消耗比 ArrayList 更多的空間(因為要存放直接后繼和直接前驅以及數據)。


?ArrayList核心源碼解讀

?ArrayList擴容機制分析

先從構造函數說起,ArrayList有三種方式來進行初始化

?以無參數構造方法創建ArrayList時,實際上初始化賦值的是一個空數組。當真對數組進行添加元素操作時,才真正分配容量。即向數組中添加第一個元素時,數組容量擴為10。

一步一分析ArrayList擴容機制

這里以無參構造函數創建ArrayList為例分析。

add方法

/**
* 將指定的元素追加到此列表的末尾。
*/
public boolean add(E e) {// 加元素之前,先調用ensureCapacityInternal方法ensureCapacityInternal(size + 1);  // Increments modCount!!// 這里看到ArrayList添加元素的實質就相當于為數組賦值elementData[size++] = e;return true;
}

注意:JDK11移除了ensureCapacityInternal()和ensureExplicitCapacity()方法

ensureCapacityInternal方法源碼如下:

// 根據給定的最小容量和當前數組元素來計算所需容量。
private static int calculateCapacity(Object[] elementData, int minCapacity) {// 如果當前數組元素為空數組(初始情況),返回默認容量和最小容量中的較大值作為所需容量if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {return Math.max(DEFAULT_CAPACITY, minCapacity);}// 否則直接返回最小容量return minCapacity;
}// 確保內部容量達到指定的最小容量。
private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

?ensureCapacityInternal方法非常簡單,內部直接調用了ensureExplicitCapacity()方法:

//判斷是否需要擴容
private void ensureExplicitCapacity(int minCapacity) {modCount++;//判斷當前數組容量是否足以存儲minCapacity個元素if (minCapacity - elementData.length > 0)//調用grow方法進行擴容grow(minCapacity);
}

?我們來分析一下:

1.當我們要add進第一個元素到ArrayList時,elementData.length為0,此時還是一個空數組,因為執行了ensureCapacityInternal方法,所以minCapacity此時為10.此時,minCapacity-elementData.length大于0,成立,所以會進入到grow(minCapacity);方法。

2.添加第3、4到第十個元素的時候,依然不會執行grow方法,數組容量都為10。

知道添加第11個元素的時候,minCapacity為11比elementData.length(10)要大。進入到grow方法進行擴容。

grow方法

/*** 要分配的最大數組大小*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;/*** ArrayList擴容的核心方法。*/
private void grow(int minCapacity) {// oldCapacity為舊容量,newCapacity為新容量int oldCapacity = elementData.length;// 將oldCapacity 右移一位,其效果相當于oldCapacity /2,// 我們知道位運算的速度遠遠快于整除運算,整句運算式的結果就是將新容量更新為舊容量的1.5倍,int newCapacity = oldCapacity + (oldCapacity >> 1);// 然后檢查新容量是否大于最小需要容量,若還是小于最小需要容量,那么就把最小需要容量當作數組的新容量,if (newCapacity - minCapacity < 0)newCapacity = minCapacity;// 如果新容量大于 MAX_ARRAY_SIZE,進入(執行) `hugeCapacity()` 方法來比較 minCapacity 和 MAX_ARRAY_SIZE,// 如果minCapacity大于最大容量,則新容量則為`Integer.MAX_VALUE`,否則,新容量大小則為 MAX_ARRAY_SIZE 即為 `Integer.MAX_VALUE - 8`。if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// minCapacity is usually close to size, so this is a win:elementData = Arrays.copyOf(elementData, newCapacity);
}

int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次擴容之后容量都會變為原來的 1.5 倍左右(oldCapacity 為偶數就是 1.5 倍,否則是 1.5 倍左右)! 奇偶不同,比如:10+10/2 = 15, 33+33/2=49。如果是奇數的話會丟掉小數.

?hugeCapacity()方法

從上面 grow() 方法源碼我們知道:如果新容量大于 MAX_ARRAY_SIZE,進入(執行) hugeCapacity() 方法來比較 minCapacityMAX_ARRAY_SIZE,如果 minCapacity 大于最大容量,則新容量則為Integer.MAX_VALUE,否則,新容量大小則為 MAX_ARRAY_SIZE 即為 Integer.MAX_VALUE - 8

private static int hugeCapacity(int minCapacity) {if (minCapacity < 0) // overflowthrow new OutOfMemoryError();// 對minCapacity和MAX_ARRAY_SIZE進行比較// 若minCapacity大,將Integer.MAX_VALUE作為新數組的大小// 若MAX_ARRAY_SIZE大,將MAX_ARRAY_SIZE作為新數組的大小// MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :MAX_ARRAY_SIZE;
}

System.arraycopy()和Arrays.copyOf()方法

System.arraycopy()

源碼:

    // 我們發現 arraycopy 是一個 native 方法,接下來我們解釋一下各個參數的具體意義/***   復制數組* @param src 源數組* @param srcPos 源數組中的起始位置* @param dest 目標數組* @param destPos 目標數組中的起始位置* @param length 要復制的數組元素的數量*/public static native void arraycopy(Object src,  int  srcPos,Object dest, int destPos,int length);

?場景:

    /*** 在此列表中的指定位置插入指定的元素。*先調用 rangeCheckForAdd 對index進行界限檢查;然后調用 ensureCapacityInternal 方法保證capacity足夠大;*再將從index開始之后的所有成員后移一個位置;將element插入index位置;最后size加1。*/public void add(int index, E element) {rangeCheckForAdd(index);ensureCapacityInternal(size + 1);  // Increments modCount!!//arraycopy()方法實現數組自己復制自己//elementData:源數組;index:源數組中的起始位置;elementData:目標數組;index + 1:目標數組中的起始位置; size - index:要復制的數組元素的數量;System.arraycopy(elementData, index, elementData, index + 1, size - index);elementData[index] = element;size++;}

?我們寫一個簡單的方法進行測試一下:

public class ArraycopyTest {public static void main(String[] args) {// TODO Auto-generated method stubint[] a = new int[10];a[0] = 0;a[1] = 1;a[2] = 2;a[3] = 3;System.arraycopy(a, 2, a, 3, 3);a[2]=99;for (int i = 0; i < a.length; i++) {System.out.print(a[i] + " ");}}}

?結果:

0 1 99 2 3 0 0 0 0 0

?Arrays.copyOf()方法

源碼:

    public static int[] copyOf(int[] original, int newLength) {// 申請一個新的數組int[] copy = new int[newLength];// 調用System.arraycopy,將源數組中的數據進行拷貝,并返回新的數組System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));return copy;}

?場景:

   /**以正確的順序返回一個包含此列表中所有元素的數組(從第一個到最后一個元素); 返回的數組的運行時類型是指定數組的運行時類型。*/public Object[] toArray() {//elementData:要復制的數組;size:要復制的長度return Arrays.copyOf(elementData, size);}

?個人覺得使用?Arrays.copyOf()方法主要是為了給原有數組擴容,測試代碼如下:

public class ArrayscopyOfTest {public static void main(String[] args) {int[] a = new int[3];a[0] = 0;a[1] = 1;a[2] = 2;int[] b = Arrays.copyOf(a, 10);System.out.println("b.length"+b.length);}
}

結果:

10

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

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

相關文章

NX二次開發C#---通過Face找Edges,再通過Edges找Curve

文章介紹了一個名為AskFaceEdge的靜態方法&#xff0c;用于處理3D建模中的邊緣曲線生成。該方法通過NX Open API調用&#xff0c;主要功能是獲取指定面的邊緣并生成相應的曲線。方法接收兩個參數&#xff1a;faceTag&#xff08;面標簽&#xff09;和curveLoop&#xff08;曲線…

設計模式筆記_創建型_工廠模式

1. 工廠模式簡介 工廠模式是一種創建型設計模式&#xff0c;主要用于創建對象實例。 它通過定義一個接口或抽象類來創建對象&#xff0c;而不是直接實例化具體類&#xff0c;從而將對象的創建過程與使用過程分離。 工廠模式通常分為兩種類型&#xff1a; 簡單工廠模式&#x…

2025.6.16總結

工作&#xff1a;今天閉環了個遺留問題。在做專項評估時寫得太簡單&#xff0c;這讓測試經理質疑你的測試質量。如果換位思考&#xff0c;你是測試經理&#xff0c;你該怎么去把握風險和保證產品的質量&#xff0c;就知道寫得太簡單&#xff0c;沒有可信度。 找開發看了下后臺…

記錄:安裝VMware、Ubuntu、ROS2

安裝了VMware&#xff0c;就能夠在Windows系統裝安裝Ubuntu&#xff0c;使用Linux系統。安裝了Ubuntu&#xff0c;就能在里面安裝ROS2&#xff0c;之后寫代碼控制機器人兒。 安裝VMware 我安裝的是16 pro【具體是vmware16.2.4】&#xff0c;下載網站&#xff1a;VMware Works…

將后端數據轉換為docx文件

使用docx npm install docx 按照注釋處理數據并轉換為對應的bolb數據流 <template><Button type"primary" click"handleDocxCreate">{{buttonTitle || "報告生成"}}</Button> </template><script> import {Doc…

數據結構排序算法合集

快排 private static void quickSort(int[] ret) { quick(ret,0,ret.length-1); } private static void quick(int[] ret, int left, int right) { if(left>right) 記一下這里是大于等于 return; int pivot partition(ret,left,right); quick(ret…

【算法筆記】紅黑樹插入操作

紅黑樹插入與調整詳解 一、紅黑樹的五大性質 紅黑樹是一種自平衡的二叉搜索樹&#xff08;BST&#xff09;&#xff0c;其核心特性如下&#xff1a; 顏色屬性&#xff1a;每個節點非紅即黑根屬性&#xff1a;根節點必須為黑色葉子屬性&#xff1a;所有的 NIL 葉子節點都是黑…

認知計算革命:從算法創新到產業落地的AI專業核心應用全景

??一、自動化機器學習&#xff08;AutoML&#xff09;?? ??技術機理與產業實踐深度剖析?? ??神經網絡架構搜索&#xff08;NAS&#xff09;?? 強化學習方案&#xff1a;Google Brain的NASNet采用策略梯度優化卷積單元進化算法方案&#xff1a;DeepMind的AmeobaNe…

篇章十 論壇系統——業務開發——板塊和帖子

目錄 1.板塊 1.1 思路 1.2 實現邏輯 1.3 參數要求 1.4 實現步驟 1.Mapper.xml 2.Mapper.java 3.Service接口 4.Service實現 5.單元測試 6.Controller 7.測試API 8.前后端交互 2.帖子 1.1思路?編輯 1.2 參數要求 ?編輯 1.3 實現步驟 1.Mapper.xml 2.Mapper…

React Native 上線前的準備與企業實戰經驗總結

上線前的準備與企業實戰經驗總結 關鍵要點 熱更新簡化部署&#xff1a;CodePush 和 Expo OTA 允許快速推送 JavaScript 和資源更新&#xff0c;繞過應用商店審核&#xff0c;適合修復 Bug 或小規模功能迭代。監控與分析提升質量&#xff1a;Sentry 提供實時錯誤跟蹤&#xff…

【AI時代速通QT】第一節:C++ Qt 簡介與環境安裝

目錄 前言 一、為什么是 Qt&#xff1f;—— C 開發者的必備技能 二、Qt 的核心魅力&#xff1a;不止于跨平臺 2.1 優雅之一&#xff1a;代碼隔離&#xff0c;清晰明了 2.2 優雅之二&#xff1a;信號與槽&#xff08;Signal & Slot&#xff09;機制 2.3 優雅之三&…

pandas學習筆記

前言 總結才是知識&#xff0c;作者習慣不好&#xff0c;不會總結&#xff0c;導致函數一旦不使用就會忘記怎么使用&#xff0c;特此寫了本文&#xff0c;用于給自己一個復習的資料. 提示&#xff1a;如果你是小白&#xff0c;每個代碼請自己敲打。 一 pandas的介紹 Pandas is…

算法題(力扣每日一題)—改變一個整數能得到的最大差值

給你一個整數 num 。你可以對它進行以下步驟共計 兩次&#xff1a; 選擇一個數字 x (0 < x < 9). 選擇另一個數字 y (0 < y < 9) 。 數字 y 可以等于 x 。 將 num中所有出現 x 的數位都用 y 替換。 令兩次對 num 的操作得到的結果分別為 a 和 b 。 請你返回 a 和 b…

Kubernetes筆記

1.簡介 Kubernetes的本質是一組服務器集群&#xff0c;它可以在集群的每個節點上運行特定的程序&#xff0c;來對節點中的容器進行管理。目的是實現資源管理的自動化&#xff0c;主要提供了如下的主要功能&#xff1a; 自我修復&#xff1a;一旦某一個容器崩潰&#xff0c;能夠…

Flutter——數據庫Drift開發詳細教程(八)

目錄 自定義 SQL 類型定義類型使用自定義類型在 Dart 中在 SQL 中 方言意識支持的 SQLite 擴展json1fts5地緣壟斷 自定義 SQL 類型 Drift 的核心庫主要以 SQLite3 為目標平臺編寫。這體現在Drift 開箱即用的SQL 類型上——這些類型由 SQLite3 支持&#xff0c;并新增了一些由 …

安卓遠控工具 CRaxsRat v7.6 安裝與使用教程(僅供合法測試學習)

在當今的信息安全領域&#xff0c;移動設備已成為重點關注對象。本文將介紹一款用于遠程管理與教學研究的工具 —— CRaxsRat v7.6&#xff0c;并詳細講解其安裝與使用流程。本教程僅供網絡安全愛好者在合法授權環境下學習使用&#xff0c;嚴禁任何非法用途。 &#x1f50d; 一…

容器的本質是進程

前言 Linux 容器的本質&#xff0c;是一個被隔離和限制的進程。 與虛擬機不同&#xff0c;容器無需虛擬化一個完整的操作系統&#xff0c;所以它比虛擬機更輕量級&#xff0c;效率也更高。 Linux 容器通過 namespaces 技術來隔離容器的視圖&#xff0c;使得容器進程只能看到…

LeetCode 第75題:顏色分類

給定一個包含紅色、白色和藍色、共n個元素的數組nums&#xff0c;原地對它們進行排序&#xff0c;使得相同顏色的元素相鄰&#xff0c;并按照紅色、白色、藍色順序排序。 使用整數0、1和2分布表示紅色、白色和藍色。 必須在不使用庫內置sort函數的情況下解決這個問題。 示例1&a…

PHP基礎-函數

函數是一段可重復使用的代碼塊&#xff0c;可以將一系列操作封裝起來&#xff0c;使代碼更加模塊化、可維護和可重用&#xff0c;來大大節省我們的開發時間和代碼量&#xff0c;提高編程效率。在PHP中你可以使用&#xff1a; 內置函數&#xff08;如 strlen()、array_merge()&a…

【FastAPI高級實戰】結合查詢參數與SQLModel Joins實現高效多表查詢(分頁、過濾、計數)

想象一下&#xff0c;你正在開發一個超酷的Web應用&#xff0c;比如一個博客平臺或者一個在線商店。你的API不僅要能把數據&#xff08;比如文章列表、商品信息&#xff09;展示給用戶&#xff0c;更要聰明到能理解用戶的各種“小心思”&#xff1a;用戶可能想看最新的文章、搜…