日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

容器源码分析之ArrayList(二)

發布時間:2024/2/28 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 容器源码分析之ArrayList(二) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

ArrayList的類聲明

首先看一下ArrayList的類聲明

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

ArrayList類主要是繼承AbstractList類并實現了List接口,實現Cloneable,Serializable接口,使得ArrayList具有克隆,序列化功能。
而實現RandomAccess接口使得ArrayList具有快速訪問,即通過下標訪問的功能。

ArrayList的屬性

private static final long serialVersionUID = 8683452581122892189L;transient Object[] elementData; // non-private to simplify nested class access

首先我們看到serialVersionUID的聲明,為了保持序列化的兼容性,
參考:Effective Java之謹慎地實現Serializable(七十四)

elementData數組用來存儲ArrayList中的元素,從這個可以看出,ArrayList是底層是借組于數組來實現的。為什么聲明為transient呢?
是因為避免默認的序列化帶來的內存浪費,參考:Effective Java之考慮自定義的序列化模式(七十五)

有關容器類的序列化問題在后面的容器源碼中就不贅述了,無非就是把默認的序列化不滿足需求,自定義一個序列化。

private int size; private static final int DEFAULT_CAPACITY = 10;

size用來記錄ArrayList中存儲的元素的個數。

DEFAULT_CAPACITY是ArrayList中存儲的元素的默認個數,默認是10。

private static final Object[] EMPTY_ELEMENTDATA = {};//下面這個是共享空常量數組private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

這兩個是共享空常量數組,用于空的實例對象,區別是DEFAULTCAPACITY_EMPTY_ELEMENTDATA知道如何擴張;
(這里先知道他們的區別,至于為什么看到后面就知道了)

protected transient int modCount = 0;

使用迭代器遍歷的時候,用來檢查列表中的元素是否發生結構性變化(列表元素數量發生改變)了,主要在多線程環境下需要使用,防止一個線程正在迭代遍歷,另一個線程修改了這個列表的結構,如果這種情況發生,拋出
ConcurrentModificationException,實現了快速失敗(fail-fast)。

ArrayList的構造函數

1.無參構造方法

public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}

源碼上介紹的功能為:構造一個初始容量為 10 的空列表。
DEFAULTCAPACITY_EMPTY_ELEMENTDATA的長度為什么是10呢?在下面的add(E e)函數源碼的介紹中會給出答案。

2.指定容量構造方法

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中情況進行了處理。當參數小于0時,拋異常。當參數等于0時,用空的常量數組對象EMPTY_ELEMENTDATA來初始化底層數組elementData。

3.容器作為參數的構造函數

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;}}

將容器Collection轉化為數組賦給數組elementData,還對Collection轉化是否轉化為了Object[]進行了檢查判斷。如果Collection為空,則就將空的常量數組對象EMPTY_ELEMENTDATA賦給了elementData;

Add的所有方法

public boolean add(E e) {ensureCapacityInternal(size + 1); // Increments modCount!!elementData[size++] = e;return true;}

既然此函數中涉及到ensureCapacityInternal函數,那我們就看一下這個函數,源碼如下:

private void ensureCapacityInternal(int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);}ensureExplicitCapacity(minCapacity);}

繼續看

private void ensureExplicitCapacity(int minCapacity) {modCount++;// overflow-conscious codeif (minCapacity - elementData.length > 0)grow(minCapacity);}

從源碼可以看出,如果elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA,則就用默認容量10來進行開辟空間。這里的源碼就解釋了DEFAULTCAPACITY_EMPTY_ELEMENTDATA數組和EMPTY_ELEMENTDATA數組的區別之所在。

當我們用無參構造函數來實例化一個對象時,只需要調用一次add(E e) 就能構造的一個長度為10的數組對象。
小小程序看出端倪:

public static void main(String[] args) {ArrayList<Integer> list1 = new ArrayList<>(0);ArrayList<Integer> list2 = new ArrayList<>();list1.add(3);list2.add(3);System.out.println(list1.get(3)); }

debug看到的兩個對象區別

![](/Users/yj/Desktop/屏幕快照 2018-01-08 下午9.53.24.png)
![](/Users/yj/Desktop/屏幕快照 2018-01-08 下午9.53.42.png)

if (minCapacity - elementData.length > 0)grow(minCapacity);

當添加的元素數目大于了數組elementData,就grow:

private void grow(int minCapacity) {// overflow-conscious codeint oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1);/*下面兩個if的作用為處理兩種情況:1)第一種情況為:如果newCapacity擴展的過小。則應該至少擴張到所需的空間大小minCapacity2)第二種情況為:newCapacity擴張的過大,如果過大,則用Integer.MAX_VALUE來代替。*/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 final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

int newCapacity = oldCapacity + (oldCapacity >> 1);

這段代碼其實就是把elementData的大小擴展成原來的1.5倍,細心的人會發現,如果是elementData通過無參構造方法賦值為DEFAULTCAPACITY_EMPTY_ELEMENTDATA數組那么它將從10開始擴張。
EMPTY_ELEMENTDATA的話它將從2開始擴張。

有存在一個小疑點:
elementData = Arrays.copyOf(elementData, newCapacity);

實際上最終調用到一個系統方法

public static native void arraycopy(Object src, int srcPos,Object dest, int destPos,int length);

這是因為C在操作內存的效率遠高于Java。所以在許多優化文檔中都能看見推薦使用arraycopy方法來批量處理數組。

終于把add方法有了初步的了解,其他帶參數的add方法其實都一樣,無非就是

  • 檢查參數的有效性
  • 添加addmode,并檢查是否需要擴張
  • 添加元素

    其他方法

    為什么將其他方法放在同一欄中呢?因為so ez

    首先get和set方法就不必多言了,不用看都知道怎么做吧?
    remove方法需要注意一下,

public E remove(int index) {rangeCheck(index);modCount++;E oldValue = elementData(index);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 workreturn oldValue;}

其實也就是將數組從index+1位置開始到末尾的元素拷貝到從index開始處)
也并不是什么高明的方法。

另一個remove的重載方法:

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;}

fastRemove(index)?難道是很高明的方法?馬上去瞧瞧

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}

其實區別不大,區別是fastRemove函數沒有對index進行有效性檢查,以及沒有返回移除的舊值,為什么?想想就知道了~

需要注意的地方:

第一點:有3種遍歷數組的方法,iterator迭代器,用array.get隨機訪問,用for-each循環,但是使用隨機訪問(即,通過索引序號訪問)效率最高,而使用迭代器的效率最低!

第二點:ArrayList提供了2個toArray()函數:

Object[] toArray() <T> T[] toArray(T[] contents)

調用 toArray() 函數會拋出“java.lang.ClassCastException”異常,但是調用 toArray(T[] contents) 能正常返回 T[]。

toArray() 會拋出異常是因為 toArray() 返回的是 Object[] 數組,將 Object[] 轉換為其它類型(如如,將Object[]轉換為的Integer[])則會拋出“java.lang.ClassCastException”異常,因為Java不支持向下轉型。

解決該問題的辦法是調用 T[] toArray(T[] contents) , 而不是 Object[] toArray()。

public static Integer[] vectorToArray2(ArrayList<Integer> v) {Integer[] newText = (Integer[])v.toArray(new Integer[0]);return newText; }

總結

以上是生活随笔為你收集整理的容器源码分析之ArrayList(二)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。