主頁 >  其他 > ArrayList原始碼深度剖析,從最基本的擴容原理,到魔幻的迭代器和fast-fail機制,你想要的這都有!!!

ArrayList原始碼深度剖析,從最基本的擴容原理,到魔幻的迭代器和fast-fail機制,你想要的這都有!!!

2022-07-12 18:54:11 其他

ArrayList原始碼深度剖析

本篇文章主要跟大家分析一下ArrayList的源代碼,閱讀本文你首先得對ArrayList有一些基本的了解,至少使用過它,如果你對ArrayList的一些基本使用還不太熟悉或者在閱讀本文的時候感覺有點困難,你可以先閱讀這篇文章ArrayList設計與實作,自己動手寫ArrayList,

ArrayList繼承體系分析

  • RandomAccess,這個介面的含義表示可以隨機訪問ArrayList當中的資料,拿什么是隨機訪問呢?隨機訪問就是表示我們可以在常量時間復雜度內訪問資料,也就是時間復雜度是O(1),因為在ArrayList當中我們使用的最基本的資料型別是陣列,而陣列是可以隨機訪問的,比如像下面這樣,
  public static void main(String[] args) {
    int[] data = https://www.cnblogs.com/Chang-LeHung/p/new int[10];

    for (int i = 0; i < 10; i++)
      data[i] = i;

    System.out.println("data[5] = " + data[5]);
  }

而鏈表是不可以隨機訪問的,比如說我們想通過下標訪問鏈表當中的某個資料,需要從頭結點或者尾節點開始遍歷,直到遍歷到下標對應的資料,比如下圖中的單鏈表找到第3個資料,需要從頭開始遍歷,而這個時間復雜度為O(n)

  • Serializable,這個介面主要用于序列化,所謂序列化就是能將物件寫入磁盤,反序列化就是能夠將物件從磁盤當中讀取出來,如果想序列化和反序列化ArrayList的實體物件就必須實作這個介面,如果沒有實作這個介面,在實體化的時候程式執行會報錯,比如下面就是一個序列化的例子,
import java.io.*;
import java.util.Objects;

class TestPerson implements Serializable{
  String name;

  Integer age;

  private static final long serialVersionUID = 9999L;

  @Override
  public String toString() {
    return "TestPerson{" +
        "name='" + name + '\'' +
        ", age=" + age +
        '}';
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    TestPerson that = (TestPerson) o;
    return that.age.equals(this.age) && that.name.equals(this.name);
  }

  @Override
  public int hashCode() {
    return Objects.hash(name, age);
  }

  public TestPerson(String name, Integer age) {
    this.name = name;
    this.age = age;
  }

}

public class SerialTest {
  public static void main(String[] args) throws IOException, ClassNotFoundException {
    TestPerson leHung = new TestPerson("LeHung", 18);
    FileOutputStream os = new FileOutputStream("objtest");
    ObjectOutputStream outputStream = new ObjectOutputStream(os);
    // 序列化資料
    outputStream.writeObject(leHung);
    FileInputStream is = new FileInputStream("objtest");
    ObjectInputStream stream = new ObjectInputStream(is);
    // 反序列化資料
    TestPerson object = (TestPerson) stream.readObject();
    System.out.println(object);
    System.out.println(object == leHung);
    System.out.println(object.equals(leHung));
  }
}

如果上面程式當中的TestPerson沒有implements Serializable,則上述代碼會報例外java.io.NotSerializableException:

  • Cloneable,實作Cloneable介面那么實作Cloneable的類就能夠呼叫clone這個方法,如果沒有實作Cloneable介面就呼叫方法,則會拋出例外java.lang.CloneNotSupportedException

  • List,這個介面主要定義了一些集合常用的方法讓ArrayList進行實作,比如addaddAllcontainsremovesetsizeindexOf等等方法,

  • AbstractList,這個抽象類也實作了List介面里面的方法,并且為其提供了默認代碼實作,比如說AbstractList中對indexOf的實作如下:

// 這個方法的作用就是回傳物件 o 在容器當中的下標
public int indexOf(Object o) {
    // 通過迭代器去遍歷資料
    ListIterator<E> it = listIterator();
    if (o==null) {
        while (it.hasNext())
            if (it.next()==null)
                // 回傳資料 o 的下標
                return it.previousIndex();
    } else {
        while (it.hasNext())
            if (o.equals(it.next()))
                // 回傳資料 o 的下標
                return it.previousIndex();
    }
    return -1;
}

集合的addAll方法實作如下:

// 這個函式的作用就是在 index 的位置插入集合 c 當中所有的元素
public boolean addAll(int index, Collection<? extends E> c) {
    rangeCheckForAdd(index);
    boolean modified = false;
    for (E e : c) {
        add(index++, e);
        modified = true;
    }
    return modified;
}

ArrayList關鍵欄位分析

ArrayList當中主要有以下這些欄位:

// ArrayList 當中默認初始化容量,也就是初始化陣列的大小
private static final int DEFAULT_CAPACITY = 10;
// 存放具體資料的陣列 ArrayList 底層就是使用陣列進行存盤的
transient Object[] elementData; 
// size 表示容器當中資料的個數 注意和容器的長度區分開來
private int size;
// 當容器當中沒有元素的時候將 elementData 賦值為以下資料(不同情況不一樣)
private static final Object[] EMPTY_ELEMENTDATA = https://www.cnblogs.com/Chang-LeHung/p/{};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 下面兩個函式是 ArrayList 的建構式,從下面兩個函式當中
// 我們可以看出 EMPTY_ELEMENTDATA 和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 使用區別
// EMPTY_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);
    }
}

public ArrayList() {
    this.elementData = https://www.cnblogs.com/Chang-LeHung/p/DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

ArrayList主要方法分析

  • add方法,這個方法用于往容器的末尾增加資料,也是ArrayList當中最核心的方法,他的主要作業流程如下圖所示:

他首先呼叫函式ensureCapacityInternal確保ArrayList當中的陣列長度能夠滿足需求,不然陣列會報陣列下標越界例外,add函式呼叫程序當中所涉及到的函式如下,

public boolean add(E e) {
    // 這個函式的主要目的是確保 elementData 的容量有 size + 1
    // 否則存盤資料的時候陣列就會越界
    ensureCapacityInternal(size + 1);
    // size 表示容器當中資料的個數 注意和容器的長度區分開來
    // 加入資料之后 容器當中資料的個數也要 + 1
    elementData[size++] = e;
    return true;
}

// minCapacity 表示 ArrayList 中的陣列最小的長度
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(
        // 這個函式計算陣列的最小長度
        calculateCapacity(elementData, minCapacity)
    );
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    // 如果是無參構造的話,取默認長度和需求長度 minCapacity 中比較大的值
    if (elementData =https://www.cnblogs.com/Chang-LeHung/p/= DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

private void ensureExplicitCapacity(int minCapacity) {
    // 這個表示容器發生改變的次數,我們在后續分析迭代器的時候進行分析
    // 它跟容器擴容沒關系,現在可以不用管他
    modCount++;

    // 如果最小的需求容量 minCapacity 大于現在容器當中陣列的長度,則需要進行擴容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    // 新陣列的長度為原陣列的長度的1.5倍,右移一位相當于除以2
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 如果新陣列的長度,小于需要的最小的容量,則更新陣列的長度為 minCapacity
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    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);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
    MAX_ARRAY_SIZE;
}

上述代碼的呼叫流程如下:

  • get函式,獲取對應下標的資料,
public E get(int index) {
    // 進行陣列下標的檢查,如果下標超過 ArrayList 中資料的個數,則拋出例外
    // 注意這里是容器當中資料的個數 不是陣列的長度
    rangeCheck(index);

    return elementData(index);
}

private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

E elementData(int index) {
    // 回傳對應下標的資料
    return (E) elementData[index];
}
  • remove函式,洗掉ArrayList當中的資料,
// 通過下標洗掉資料,這個函式的意義是洗掉下標為 index 的資料
public E remove(int index) {
    // 首先檢查下標是否合法,如果不合法,拋出下標越界例外
    rangeCheck(index);

    modCount++;
    E oldValue = https://www.cnblogs.com/Chang-LeHung/p/elementData(index);
	// 因為洗掉某個資料,需要將該資料后面的資料往陣列前面移動
    // 這里需要計算需要移動的資料的個數
    int numMoved = size - index - 1;
    if (numMoved > 0)
        // 通過拷貝移動資料
        // 這個函式的意義是將 index + 1和其之后的資料整體移動到 index
        // 的位置
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    // 因為最后一個資料已經拷貝到前一個位置了,所以可以設定為 null
    // 可以做垃圾回收了
    elementData[--size] = null; 

    return oldValue;
}

// 這個函式的意義是洗掉容器當中的第一個等于 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 方法原理一致
private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
}

  • set方法,這個方法主要是用于設定指定下標的資料,這個方法比較簡單,
public E set(int index, E element) {
    rangeCheck(index);

    E oldValue = https://www.cnblogs.com/Chang-LeHung/p/elementData(index);
    elementData[index] = element;
    return oldValue;
}

ArrayList中那些不為人知的方法

ensureCapacity方法

public void ensureCapacity(int minCapacity) {
    int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
        // any size if not default element table
        ? 0
        // larger than default for default empty table. It's already
        // supposed to be at default size.
        : DEFAULT_CAPACITY;

    if (minCapacity > minExpand) {
        ensureExplicitCapacity(minCapacity);
    }
}

這個方法我們在前面已經提到過了,不知道大家有沒有觀察到他的訪問修飾符是public,為什么要設定為public呢?這個意思很明顯,我們可以在使用ArrayList的時候自己呼叫這個方法,防止當我們在往容器中加入資料的時候頻繁因為陣列長度不夠重新申請記憶體,而原來的陣列需要從新釋放,這會給垃圾回收器造成壓力,我們在ArrayList設計與實作,自己動手寫ArrayList這篇文章當中寫過一段測驗程式去測驗這個方法,感興趣的同學可以去看看!!!

toString方法

我們首先來看一下下面代碼的輸出

public class CodeTest {

  public static void main(String[] args) {
    LinkedList<Integer> list = new LinkedList<>();
    for (int i = 0; i < 10; i++) {
      list.add(i);
    }
    System.out.println(list);
  }
}
// 輸出結果:
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

執行上面一段代碼我們可以在控制臺看見對應的輸出,我們知道最終列印在螢屏上的是一個字串,那這個字串怎么來的呢,我們列印的是一個物件,它是怎么得到字串的呢?我們可以查看System.out.println的源代碼:

public void println(Object x) {
    String s = String.valueOf(x);
    synchronized (this) {
        print(s);
        newLine();
    }
}

從上述代碼當中我們可以看見通過String s = String.valueOf(x);這行代碼得到了一個字串,然后進行列印,我們在進入String.valueOf方法看看是如何得到字串的:

public static String valueOf(Object obj) {
    return (obj == null) ? "null" : obj.toString();
}

我們可以看到如果物件不為 null 最終是呼叫物件的toString方法得到的字串,因此當列印一個物件的時候,最侄訓列印這個物件的toString方法回傳的字串

toString方法沒有直接在ArrayList當中實作,而是在它繼承的類AbstractList當中實作的,toString的源代碼如下所示:

public String toString() {
    // 得到 ArrayList 的迭代器 這個迭代器我們稍后細說
    Iterator<E> it = iterator();
    // 如果容器當中沒有資料則回傳空
    if (! it.hasNext())
        return "[]";
    // 額,寫這個代碼的工程師應該不懂中文 哈哈哈
    StringBuilder sb = new StringBuilder();
    sb.append('[');
    for (;;) {
        E e = it.next();
        // 將物件加入到 StringBuilder 當中,這里加入的也是一個物件
        // 但是在 append 源代碼當中會同樣會使用 String.ValueOf 
        // 得到物件的 toString 方法的結果
        sb.append(e == this ? "(this Collection)" : e);
        if (! it.hasNext())
            return sb.append(']').toString();
        sb.append(',').append(' ');
    }
}

上述代碼的整個程序還是比較清晰的,大致程序如下:

  • 如果容器當中沒有資料,直接回傳[],
  • 如果容器當中有資料的話,那么通過迭代每個資料,呼叫StringBuilderappend方法,將資料加入到輸出的StringBuilder物件當中,下面是append的源代碼,
// StringBuilder 的 append 方法
@Override
public StringBuilder append(Object obj) {
    return append(String.valueOf(obj));
}

// StringBuilder 的 append 方法的多載方法
@Override
public StringBuilder append(String str) {
    super.append(str);
    return this;
}


// String 類中的 valueOf方法
public static String valueOf(Object obj) {
    return (obj == null) ? "null" : obj.toString();
}

我們可以發現最終appendStringBuilder當中的字串仍然是ArrayList當中資料物件的toString方法回傳的資料,

equals方法

ArrayList當中的equals方法和toString方法一樣,equlas方法也是在類AbstractCollection當中實作的,其源代碼如下:

public boolean equals(Object o) {
    if (o == this)
        return true;
    if (!(o instanceof List))
        return false;

    ListIterator<E> e1 = listIterator();
    ListIterator<?> e2 = ((List<?>) o).listIterator();
    while (e1.hasNext() && e2.hasNext()) {
        E o1 = e1.next();
        Object o2 = e2.next();
        if (!(o1==null ? o2==null : o1.equals(o2)))
            return false;
    }
    return !(e1.hasNext() || e2.hasNext());
}

上面代碼的主要流程:

  • 首先判斷othis是否是同一個物件,如果是則回傳true,比如下面這種情況:
ArrayList<Object> list = new ArrayList<>();
list.equals(ArrayList);
  • 如果物件沒有實作List介面回傳false
  • 逐個判斷鏈表里面的物件是否相等(呼叫鏈表當中存盤的物件的equals方法),如果兩個鏈表當中節點數目一樣而且都相等則回傳true否則回傳false

通過上面的分析我們可以發現ArrayList方法并沒有讓比較的物件是ArrayList物件,只需要實作List介面并且資料數目和內容都相同,這樣equals方法回傳的結果就是true,比如下面代碼就驗證的這個結果:

LinkedList<Integer> list = new LinkedList<>();
ArrayList<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
    list.add(i);
    arrayList.add(i);
}
System.out.println(arrayList.equals(list)); // 結果為 true

clone方法

ArrayList的方法比較簡單,就是拷貝了原ArrayList當中的陣列中的資料,

public Object clone() {
    try {
        ArrayList<?> v = (ArrayList<?>) super.clone();
        v.elementData = https://www.cnblogs.com/Chang-LeHung/p/Arrays.copyOf(elementData, size);
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
}

整個拷貝程序如下如所示:

雖然發生了陣列的拷貝,但是拷貝之后的陣列中資料的指向并沒有發生變化,也就是說兩個陣列指向的內容是一樣的,如果一個陣列改變了所指向的資料,另外一個陣列當中的資料也會發生變化,比如下面的代碼:

package makeyourowncontainer.test;

import java.util.ArrayList;

class Person {

  String name;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  @Override
  public String toString() {
    return "Person{" +
        "name='" + name + '\'' +
        '}';
  }
}


public class ArrayListTest {

  public static void main(String[] args) {

    ArrayList<Person> o1 = new ArrayList<>();
    Person person = new Person();
    person.setName("一無是處的研究僧");
    o1.add(person);
    Object o2 = o1.clone();
    System.out.println("o1 = " + o1);
    System.out.println("o2 = " + o2);
    ((ArrayList<Person>) o2).get(0).setName("LeHung");
    System.out.println("改變資料之后");
    System.out.println("o1 = " + o1);
    System.out.println("o2 = " + o2);
  }
}
// 輸出結果
o1 = [Person{name='一無是處的研究僧'}]
o2 = [Person{name='一無是處的研究僧'}]
改變資料之后
o1 = [Person{name='LeHung'}]
o2 = [Person{name='LeHung'}]

神秘的迭代器Iterator

Iterator介紹

我們在分析toString方法的時候,有下面這樣一行代碼:

Iterator<E> it = iterator();

然后不斷通過迭代器的hasNextnext方法對資料進行迭代,比如下面這個例子:

public void testArrayList() {
    ArrayList<Integer> list = new ArrayList<>();
    for (int i = 0; i < 10; i++)
        list.add(i);
    Iterator<Integer> iterator = list.iterator();
    while (iterator.hasNext()) {
        System.out.println(iterator.next());
    }
}

// iterator 方法回傳的物件
public Iterator<E> iterator() {
    return new Itr();
}

Iterator欄位分析

Itr類是ArrayList的內部類,接下來我們仔細分析Itr類的實作,

Itr類當中主要有一下幾個欄位:

int cursor;       // 下一個元素的下標 當我們 new 這個物件的時候這個值默認初始化為0
				  // 我們使用的時候也是0這個值,因此不用顯示初始化
int lastRet = -1; // 上一個通過 next 方法回傳的元素的下標
int expectedModCount = modCount; 
// modCount 表示陣列當中資料改變的次數 modCount 是
// ArrayList 當中的類變數 expectedModCount 是 ArrayList
// 內部類 Itr 中的類變數 然后將這個變數保存到 expectedModCount當中
// 使用 expectedModCount 主要用于 fast-fail 機制這個我們后面會分析

我們現在來花點時間好好談一下modCount(英文全稱為:modifications count,修改次數)這個欄位,當ArrayList當中發生一次結構修改(Structural modifications)時,modCount就++,所謂結構修改就是那些讓ArrayList當中陣列的資料個數size發生變化的操作,比如說addremove方法,因為這兩個方法一個是增加資料,一個是洗掉資料,都會導致容器當中資料個數發生變化,而set方法就不會是的modCount發生變化,因為沒有改變容器當中資料的個數,

Iterator的初始化方法:

private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;

    Itr() {}
}

在初始化方法當中,沒有任何操作也就印證了我們前面在分析欄位的時候說的 cursor的初始化的值為0,

Iterator重要方法

接下來分析迭代器當中比較重要的兩個方法nexthasNext

public boolean hasNext() {
    // 這個 size 是外部類 ArrayList 當中的 size 表示的是 ArrayList
    // 當中資料元素的個數,cursor 的初始值為 0 每呼叫一個 next cursor
    // 的值就+1,當等于 size 是容器當中的資料已經遍歷完成了 hasNext 就回傳 false 了
    return cursor != size;
}

@SuppressWarnings("unchecked")
public E next() {
    // 這個方法主要是用于檢測在資料迭代的程序當中 ArrayList 是否發生 `結構修改`
    // 如果發生結構修改就拋出 ConcurrentModificationException 例外
    checkForComodification();
    int i = cursor;
    if (i >= size)
        throw new NoSuchElementException();
    Object[] elementData = https://www.cnblogs.com/Chang-LeHung/p/ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    // 更改 cursor 的值 并將其設定為下一個回傳元素的下標 這一點我們在
    // 欄位分析的時候已經談到過了
    cursor = i + 1;
    // 回傳資料 運算式 lastRet = i 的回傳值為 i 
    // 這個運算式不僅將 lastRet 的值賦值為 i 同時回傳 i
    // 因此可以回傳下標為 i 的資料
    return (E) elementData[lastRet = i];
}

// 這個方法主要是用于檢測在資料迭代的程序當中 ArrayList 是否發生 `結構修改`
// 如果發生結構修改就拋出 ConcurrentModificationException 例外
final void checkForComodification() {
    // 如果發生 `結構修改` 那么 modCount 的值會++ 那么就和 expectedModCount 不相等了
    // expectedModCount 初始化的時候令其等于 expectedModCount
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

為什么要拋出ConcurrentModificationException例外呢,我們先想想是什么導致modCount發生變化,肯定迭代器在進行遍歷的同時,修改了modCount的值,通常這種現象的發生出現在并發的情況下,因此拋出ConcurrentModificationException例外,像這種通過迭代器遍歷程序進行檢查并且當發生不符合條件的情況下拋出例外的現象就稱作Fast-fail

其實我們也可以在不使用并發的情況讓迭代器拋出這個例外,我們只需要在迭代器迭代的時候我們對ArrayList進行addremove操作即可,比如像下面這樣就會拋出ConcurrentModificationException

public void testArrayList() {
    ArrayList<Integer> list = new ArrayList<>();
    for (int i = 0; i < 10; i++)
        list.add(i);
    Iterator<Integer> iterator = list.iterator();
    while (iterator.hasNext()) {
        System.out.println(iterator.next());
        list.add(55);
    }
}

Iterator中的remove方法

public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    // 進行合法性檢查,看是否需要拋出例外
    checkForComodification();

    try {
        // 呼叫 ArrayList 的remove方法實作
        ArrayList.this.remove(lastRet);
        cursor = lastRet;
        lastRet = -1;
        // 因為 remove 會改變 modCount 的值,因此需要將 expectedModCount 重新賦值
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

ArrayList雜談

時間復雜度分析

  • 因為ArrayList是隨機存取的,因此我們通過下標查找資料的時間復雜度是O(1)
  • 插入資料的時間復雜度是O(n)

擴容機制

還記的我們在ArrayList設計與實作,自己動手寫ArrayList當中自己實作ArrayList使用的擴容機制嗎?我們自己的擴容機制為擴容為原來長度的兩倍,而ArrayList當中的擴容機制為擴容為原來的1.5倍,

假設我們在使用ArrayList的時候沒有指定初始化的時候陣列的長度,也就是說初始長度為ArrayList的默認長度也就是10,那么當我們不停地往容器當中增加資料,擴容導致的陣列長度的變化如上圖所示,橫軸表示擴容次數,縱軸表示陣列長度,藍色的擴容為原陣列長度的1.5倍,另外一條是2倍,我們很清晰的發現擴容為原來的2倍在后期的陣列長度將會遠大于擴容1.5倍,這很可能會導致我們會浪費很大的陣列空間,比如說剛好加入最后一個資料的時候導致ArrayList進行擴容操作,這可能是ArrayList在設計時候的考量,

本篇文章我們仔細分析介紹了ArrayList的原始碼,希望大家有所識訓,我是LeHung,我們下期再見!!!

更多容器設計的內容在專案https://github.com/Chang-LeHung/CSCore!!!

關注公眾號:一無是處的研究僧,了解更多計算機知識,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/498629.html

標籤:其他

上一篇:演算法競賽進階指南0x41并查集

下一篇:Leetcode 2 兩數相加

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more