竟然還有人說ArrayList是2倍擴容,今天帶你手撕ArrayList源碼

来源:https://www.cnblogs.com/yidengjiagou/archive/2022/06/10/16363063.html
-Advertisement-
Play Games

ArrayList是我們開發中最常用到的集合,但是很多人對它的源碼並不瞭解,導致面試時,面試官問的稍微深入的問題,就無法作答,今天我們一起來探究一下ArrayList源碼。 1. 簡介 ArrayList底層是數組,允許元素是null,能夠動態擴容 size、isEmpty、get、set、add ...


ArrayList是我們開發中最常用到的集合,但是很多人對它的源碼並不瞭解,導致面試時,面試官問的稍微深入的問題,就無法作答,今天我們一起來探究一下ArrayList源碼。

1. 簡介

  • ArrayList底層是數組,允許元素是null,能夠動態擴容
  • size、isEmpty、get、set、add 等方法時間複雜度都是 O (1)
  • 非線程安全,併發修改時,會拋出ConcurrentModificationException

2. 初始化

// 初始容量
private static final int DEFAULT_CAPACITY = 10;

// 空數組
private static final Object[] EMPTY_ELEMENTDATA = {};

// 預設空數組
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 存儲元素的數組
transient Object[] elementData;

// 無參初始化,預設是空數組
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

// 有參初始化,指定容量大小
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);
    }
}

切記:無參初始化的時候,預設是空數組,並沒有初始化容量大小,容量是在第一次添加元素的才進行初始化。

3. 添加元素

public boolean add(E e) {
  // 確保數組容量夠用,size是元素個數
  ensureCapacityInternal(size + 1);
  // 直接賦值
  elementData[size++] = e;
  return true;
}

// 確保數組容量夠用
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

// 計算所需最小容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
  	// 如果數組等於空數組,最小容量為10
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
  	// 如果所需最小容量大於數組長度,就進行擴容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

看一下擴容邏輯:

// 擴容,就是把舊數據拷貝到新數組裡面
private void grow(int minCapacity) {
  int oldCapacity = elementData.length;
  // oldCapacity >> 1 是把oldCapacity除以2,意思是1.5倍擴容
  int newCapacity = oldCapacity + (oldCapacity >> 1);

  // 如果擴容後的容量小於最小容量,擴容後的容量就等於最小容量
  if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;

  // 如果擴容後的容量大於Integer的最大值,就用Integer最大值
  if (newCapacity - MAX_ARRAY_SIZE > 0)
    newCapacity = hugeCapacity(minCapacity);
 
  // 擴容並賦值給原數組
  elementData = Arrays.copyOf(elementData, newCapacity);
}

可以看到:

  • 擴容是以原容量的1.5倍擴容,並不是翻倍擴容
  • 最大容量是Integer的最大值
  • 添加元素時,沒有對元素校驗,可以是null

再看一下數組拷貝的邏輯,這裡都是Arrays類裡面的方法了:

/**
 * @param original  原數組
 * @param newLength 新的容量大小
 */
public static <T> T[] copyOf(T[] original, int newLength) {
    return (T[]) copyOf(original, newLength, original.getClass());
}

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    // 創建一個新數組,容量是新的容量大小
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
  	// 把原數組的元素拷貝到新數組
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

最終調用了System類的數組拷貝方法,是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);

4. 刪除單個元素

public boolean remove(Object o) {
  	// 判斷要刪除的元素是否為null
    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;
}

// 刪除該位置上的元素
private void fastRemove(int index) {
    modCount++;
  	// 計算需要移動的元素的個數
    int numMoved = size - index - 1;
    if (numMoved > 0)
      	// 從index+1位置開始拷貝,也就是後面的元素整體向左移動一個位置
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
  	// 數組最後一個元素賦值為null,防止會導致記憶體泄漏
    elementData[--size] = null;
}

可以知道,刪除元素,就是遍曆數組,迴圈比較是否等於目標值。如果相等,就把該位置後面的元素整體向左移動一個位置,再把數組最後一個元素賦值為null。

5. 批量刪除

// 批量刪除ArrayList和集合c都存在的元素
public boolean removeAll(Collection<?> c) {
    // 非空校驗
    Objects.requireNonNull(c);
    // 批量刪除
    return batchRemove(c, false);
}

private boolean batchRemove(Collection<?> c, boolean complement){
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;
    boolean modified = false;
    try {
        for (; r < size; r++)
            if (c.contains(elementData[r]) == complement)
                // 把需要保留的元素左移
                elementData[w++] = elementData[r];
    } finally {
				// 當出現異常的時候,可能不相等
        if (r != size) {
            // 1:可能是上面的for迴圈出現了異常
            // 2:可能是其它線程添加了元素
            System.arraycopy(elementData, r,
                             elementData, w,
                             size - r);
            w += size - r;
        }
      	// 把不需要保留的元素賦值為null
        if (w != size) {
            for (int i = w; i < size; i++)
                elementData[i] = null;
            modCount += size - w;
            size = w;
            modified = true;
        }
    }
    return modified;
}

可以知道,批量刪除的時候,只會進行一次數組拷貝,比用for迴圈單個刪除效率更高,所以刪除一批元素的時候,儘量用removeAll()方法。

5. 總結

本文分析了ArrayList的初始化、put、add、remove、動態擴容等方法的底層源碼,相信大家對於ArrayList有了更深層次的瞭解,下篇一塊學習一下LinkedList的源碼。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ​ 1,建一個表 create table 表名( 屬性a 類型 限制, 屬性b 類型 限制 ) ​編輯 2,向表裡添加數據 insert into Tset(屬性a,屬性b) values(a的數據,b的數據) 給屬性a,屬性b添加數據 insert into Tset values(a的數據,b ...
  • 排序演算法總結 1.十大經典演算法及性能 2.具體排序演算法 1.冒泡排序 迴圈過程中比較相鄰兩個數大小,通過交換正確排位,迴圈整個數組即可完成排序 圖片演示 代碼實現Java //冒泡排序 public static Integer[] Bubble(Integer[] array){ boolean ...
  • 前言 不知道有沒有小伙伴跟我一樣,每次刷抖音都不想動手,好希望能夠有什麼東西代替我的手指,我給它一個命令,它就明白我要什麼。於 是,我利用空閑時間操作了一下,終於用Python把自動刷抖音給搞出來了,這下真的是解決了我長久以來的困擾… 工具準備 1.Python3.7.7 2.adb(Android ...
  • 我們知道 Python 有很多運算符可以進行數學運算,如果是簡單的問題還好說,但是要處理一些相對複雜的問題也要我們自己一行一行手動的來編寫嗎?答案當然不是,Python 提供了 math 模塊對一些數學運算提供了支持。 1.簡介 math 模塊提供了對 C 標准定義的數學函數的訪問,但該模塊並不支持 ...
  • Skywalking介紹 Skywalking是一個國產的開源框架,2015年有吳晟個人開源,2017年加入Apache孵化器,國人開源的產品,主要開發人員來自於華為,2019年4月17日Apache董事會批准SkyWalking成為頂級項目,支持Java、.Net、NodeJs等探針,數據存儲支持 ...
  • 面試總結 最近棧長面試了一個 5 年經驗的 Java 程式員,簡歷和個人介紹都提到了精通 Java 多線程,於是我就問了幾個多線程方面的問題: 1、實現多線程有哪幾種方式,如何返回結果? 2、多個線程如何實現順序訪問? 3、兩個線程如何進行數據交換? 4、如何統計 5 個線程的運行總耗時? 5、如何 ...
  • 1 背景與挑戰 1.1 背景介紹 1.1.1 課程概述 瞭解雙11 的歷程 學習當前主流的電商系統架構體系 瞭解大促對電商系統的一些挑戰 面對大促活動,站在架構師角度思考,可能有哪些問題,如何應對 1.1.2 雙11歷程 (最早接觸雙11的年份?) ​ 起於2009年,剛開始的雙十一還並不出名,電商 ...
  • 一個工作了6年的粉絲,去阿裡面試,在第一面的時候被問到”Mysql的事務隔離級別“。 他竟然沒有回答上來,一直在私信向我訴苦。 我說,你只能怪年輕時候的你,那個時候不夠努力導致現在的你技術水平不夠。 好吧,關於這個問題,看看普通人和高手的回答。 普通人: Mysql的事務隔離級別它有四種 1.讀已提 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...