花了一天時間,翻譯了一遍 java.util.ArrayList 類的源碼(1700 多行,仍是頗有收穫的),包括註釋和代碼解讀,並提了一些問題,也寫了下本身的理解 點我查看 ArrayList 源碼翻譯。java
若是把 ArrayList 看做一個杯子的話,capacity 就是杯子的容積,也就是表明杯子能裝多少東西,而 size 就是杯子裝的東西的體積。杯子可能裝滿了,也可能沒裝滿,因此 capacity >= size 。capacity 過大和太小都很差,過大會形成浪費,太小又存放不下多個元素的值,capacity == size,則 ArrayList 空間利用率最大,可是不利於添加新的元素。當 ArrayList 實例內的元素個數再也不改變了,可使用 trimToSize() 方法最小化 ArrayList 實例來節省空間,也便是使 capacity == size。git
ArrayList 能夠看作是數組的封裝,使用 elementData 數組來存儲數據,使用 size 來表明 elementData 的非 null 元素個數。elementData 前沒有訪問修飾符,因此只有同類和同包下的類能夠直接方法,外界想要知道 ArrayList 實例內元素的個數就要經過 size 屬性。elementData 數組類型是 Object 類型,能夠存聽任意的引用類型,不能存放基本的數據類型。github
這兩個類常量都是空 Object 數組的引用,都表明 ArrayList 實例的空狀態,也便是 elementData 數組中尚未元素。可是 EMPTY_ELEMENTDATA 是使用帶初始化值的構造方法(有參構造函數,一個是指定初始容量,一個是指定初始集合)時使用的,DEFAULTCAPACITY_EMPTY_ELEMENTDATA 是使用默認的構造方法,也便是無參的構造方法時使用的。編程
擴容是發生在添加操做前的,要保證要添加元素在 elementData 數組中有位置,也便是 size 加上要添加的元素個數要小於 capacity(size + num <= capacity 就說明容量是充足的),因此在添加方法中,先調用 ensureCapacityInternal(int) 方法來確保 elementData 容量充足,而後再進行具體的添加操做。若是 ensureCapacityInternal 方法(ensureCapacityInternal 方法中有調用了其餘方法)發現數組容量不夠了,就會擴容。擴容實際的方法是 grow(int) 方法,使用位運算符來使數組的容量擴容 1.5 倍。可是須要注意的是,沒有指定初始化值的 ArrayList 實例,第一次擴容並非以 1.5 倍擴容的,而是使用的默認容量 10,因此網上不少直接說 ArrayList 擴容是 1.5 倍也有不當之處,這點從 JDK 源碼中能夠很明確的看出來。 若是在構造 ArrayList 實例時,指定初始化值(初始化容量或者集合),那麼就會建立指定大小的 Object 數組,並把該數組對象的引用賦值給 elementData;若是不指定初始化值,在第一次添加元素值時會使用默認的容量大小 10 做爲 elementData 數組的初始容量,使用 Arrays.conpyOf() 方法建立一個 Object[10] 數組。數組
Arrays.copyOf(T[], int length) 方法是 Arrays 工具類中用來進行任意類型數組賦值(包括 null 值),並使數組具備指定長度的方法,ArrayList 中用這個方法來實現 elementData 數組的元素移動。但實際上 Arrays.copyOf 方法最終調用的是 System.arraycopy(U[], int srcPos, T[], desPos, int length) 方法,這個方法是一個本地方法,不能直接看源碼。U 和 T 都是一種泛型,只是爲了便於區分,U 表示的是原始數組(源數組)類型,T 表示的是存放拷貝值的數組(目標數組)類型,srcPos 是指原始數組中的起始位置(從原始數組的哪一個位置開始拷貝),desPos 是指存放拷貝值的數組拷貝起始位置(從目標數組的哪一個位置插入這些拷貝的值),length 表示要拷貝的元素數量(要從原始數組中拷貝多少個)。微信
核心就是避免 ArrayList 內部進行擴容。 一、對於普通少許的 add 操做,若是插入元素的個數已知,最好使用帶初始化參數的構造方法,避免 ArrayList 內部再進行擴容,提升性能。 二、對於大量的 add 操做,最好先使用 ensureCapacity 方法來確保 elementData 數組中有充足的容量來存放咱們後面 add 操做的元素,避免 ArrayList 實例內部進行擴容。上面提到的 ensureCapacityInternal 方法是一個私有方法,不能直接調用,而 ensureCapacity 方法是一個共有方法,專門提供給開發者使用的,提升大量 add 操做的性能。併發
測試代碼以下:函數
ArrayList<Integer> list1 = new ArrayList<>();
int addCount = 1_000_000; // 這個值不要過小,不然效果不明顯
// 沒有優化
long begin1 = System.currentTimeMillis();
for (int i = 0; i < addCount; i++) {
list1.add(i);
}
long end1 = System.currentTimeMillis();
long cost1 = end1 - begin1;
ArrayList<Integer> list2 = new ArrayList<>();
// 有優化
list2.ensureCapacity(addCount);
long begin2 = System.currentTimeMillis();
for (int i = 0; i < addCount; i++) {
list2.add(i);
}
long end2 = System.currentTimeMillis();
long cost2 = end2 - begin2;
System.err.println("大量 add 操做沒有優化前的時間花費:" + cost1);
System.err.println("大量 add 操做優化後的時間花費:" + cost2);
複製代碼
會,無論使修改子列表的值仍是修改父列表的值都會對雙方產生影響。閱讀源碼,就會發現,subList 方法後的子列表對元素的操做實際上調用的仍是父列表中對應的方法。工具
List 集合能夠看做是數組的包裝類型,遍歷並不像數組那樣方便,迭代器是爲了迭代集合中的元素而存在的。Itr 迭代器類實現了 Iterator 接口,ListItr 迭代器類繼承 Itr 迭代器類,而且實現了 ListIterator 接口,因此 ListItr 類的功能比 Itr 類更強大。Itr 類在迭代過程當中不能修改 List 的結構(如 add 操做),不然會拋出併發修改異常 ConcurrentModificationException,而且在 next 方法以後才能 remove 元素,而 ListItr 類還支持在迭代過程當中添加元素,對於 List 集合元素操做更加友好。因此對於 List 集合迭代,最好使用 ListItr 迭代器類。性能
本文轉自個人微信公衆號《編程心路》,查看 原文連接。