集合詳解(二)----ArrayList原始碼剖析(JDK1.7)
ArrayList
ArrayList是List類的一個典型的實現,是基於陣列實現的List類,因此,ArrayList封裝了一個動態的、可變長度的Object[]
陣列。ArrayList是通過initialCapacity
引數來設定陣列長度的,當向ArrayList新增的資料超出了ArrayList的長度之後,initialCapacity
會自動增加。
私有屬性
ArrayList定義了兩個私有屬性:
//elementData儲存ArrayList內的元素,size表示它包含的元素的數量。
private transient Object[] elementData;
private int size;
其中有一個關鍵字:transient:Java的serialization提供了一種持久化物件例項的機制。當持久化物件時,可能有一個特殊的物件資料成員,我們不想用serialization機制來儲存它。為了在一個特定物件的一個域上關閉serialization,可以在這個域前加上關鍵字transient。
public class UserInfo implements Serializable {
private static final long serialVersionUID = 996890129747019948L;
private String name;
private transient String psw;
public UserInfo(String name, String psw) {
this.name = name;
this.psw = psw;
}
public String toString() {
return "name=" + name + ", psw=" + psw;
}
}
public class TestTransient {
public static void main(String[] args) {
UserInfo userInfo = new UserInfo("張三", "123456");
System.out.println(userInfo);
try {
// 序列化,被設定為transient的屬性沒有被序列化
ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(
"UserInfo.out"));
o.writeObject(userInfo);
o.close();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
try {
// 重新讀取內容
ObjectInputStream in = new ObjectInputStream(new FileInputStream(
"UserInfo.out"));
UserInfo readUserInfo = (UserInfo) in.readObject();
//讀取後psw的內容為null
System.out.println(readUserInfo.toString());
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
被標記為transient的屬性在物件被序列化的時候不會被儲存。
構造方法
ArrayList提供了三種方式的構造器。
// ArrayList帶容量大小的建構函式。
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
//ArrayList無引數構造引數,預設容量10
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}
// 建立一個包含collection的ArrayList
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray(); //呼叫toArray()方法把collection轉換成陣列
size = elementData.length; //把陣列的長度賦值給ArrayList的size屬性
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
在這有一個地方需要注意下,就是在JDK1.6中無引數的構造方法是這麼寫的:
// ArrayList無參建構函式。預設容量是10。
public ArrayList() {
this(10);
}
在1.7前,會預設在記憶體中直接分配10個空間,但是在1.7有了改變,會先在記憶體中分配一個物件的記憶體空間,但是這個物件是沒有長度的。但是在你進行新增的時候,預設的會去拿物件的預設大小來作比較。
ArrayList的動態擴容(核心)
當ArrayList進行add操作的時候,如果新增的元素超出了陣列的長度,怎麼辦?
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
add方法會去呼叫下面的方法,根據傳入的最小需要容量minCapacity來和陣列的容量長度對比,若minCapactity大於或等於陣列容量,則需要進行擴容。
private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//超出了陣列可容納的長度,需要進行動態擴充套件
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
擴容的時候會去呼叫grow()方法來進行動態擴容,在grow中採用了位運算,我們知道位運算的速度遠遠快於整除運算:
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//這才是動態擴充套件的精髓,看到這個方法,ArrayList瞬間被打回原形
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//首先得到陣列的舊容量,然後進行oldCapacity + (oldCapacity >> 1),將oldCapacity 右移一位,其效果相當於oldCapacity /2,整句的結果就是設定新陣列的容量擴充套件為原來陣列的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//再判斷一下新陣列的容量夠不夠,夠了就直接使用這個長度建立新陣列,
//不夠就將陣列長度設定為需要的長度
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//判斷有沒超過最大限制,如果超出限制則呼叫hugeCapacity
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//將原來陣列的值copy新陣列中去, ArrayList的引用指向新陣列
//這兒會新建立陣列,如果資料量很大,重複的建立的陣列,那麼還是會影響效率,
//因此鼓勵在合適的時候通過構造方法指定預設的capaticy大小
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;
}
有一點需要注意的是,容量擴充,是建立一個新的陣列,然後將舊陣列上的陣列copy到新陣列,這是一個很大的消耗,所以在我們使用ArrayList時,最好能預計資料的大小,在第一次建立時就申請夠記憶體。
看一下JDK1.6的動態擴容的實現原理:
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3)/2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
從程式碼上,我們可以看出區別:
第一:在容量進行擴充套件的時候,其例項如整除運算將容量擴充套件為原來的1.5倍加1,而jdk1.7是利用位運算,從效率上,jdk1.7就要快於jdk1.6。
第二:在算出newCapacity時,其沒有和ArrayList所定義的MAX_ARRAY_SIZE作比較,為什麼沒有進行比較呢,原因是jdk1.6沒有定義這個MAX_ARRAY_SIZE最大容量,也就是說,其沒有最大容量限制的,但是jdk1.7做了一個改進,進行了容量限制。
相關文章
- Java集合原始碼剖析——ArrayList原始碼剖析Java原始碼
- 【Java集合原始碼剖析】ArrayList原始碼剖析Java原始碼
- Java 集合框架 ArrayList 原始碼剖析Java框架原始碼
- Java集合詳解(二):ArrayList原理解析Java
- ArrayList詳解-原始碼分析原始碼
- 集合-ArrayList 原始碼解析原始碼
- HashMap jdk1.7和1.8原始碼剖析HashMapJDK原始碼
- 【Java集合原始碼剖析】Vector原始碼剖析Java原始碼
- 【Java集合原始碼剖析】HashMap原始碼剖析Java原始碼HashMap
- 【Java集合原始碼剖析】Hashtable原始碼剖析Java原始碼
- 【Java集合原始碼剖析】TreeMap原始碼剖析Java原始碼
- 【Java集合】ArrayList原始碼分析Java原始碼
- JAVA集合:ArrayList原始碼分析Java原始碼
- Java集合(三) ArrayList詳解Java
- 集合原始碼分析[3]-ArrayList 原始碼分析原始碼
- 【Java集合原始碼剖析】LinkedList原始碼剖析Java原始碼
- 【Java集合原始碼剖析】LinkedHashmap原始碼剖析Java原始碼HashMap
- Java集合之ArrayList原始碼解析Java原始碼
- Java 集合框架------ArrayList原始碼分析Java框架原始碼
- java集合原始碼分析(三):ArrayListJava原始碼
- ArrayList原始碼剖析與程式碼實測原始碼
- 【Java集合原始碼剖析】Java集合框架Java原始碼框架
- Java集合:HashMap原始碼剖析JavaHashMap原始碼
- Java集合原始碼學習(2)ArrayListJava原始碼
- JAVA ArrayList集合底層原始碼分析Java原始碼
- 集合框架原始碼學習之ArrayList框架原始碼
- Java集合乾貨——ArrayList原始碼分析Java原始碼
- java集合學習(一):詳解ArrayListJava
- 集合框架-ArrayList集合的toString()方法原始碼解析框架原始碼
- ArrayList 從原始碼角度剖析底層原理原始碼
- Java集合框架原始碼剖析:ArrayDequeJava框架原始碼
- 死磕 java集合之ArrayList原始碼分析Java原始碼
- Java集合-ArrayList原始碼解析-JDK1.8Java原始碼JDK
- Java集合乾貨1——ArrayList原始碼分析Java原始碼
- Java 集合框架(二)—— ArrayListJava框架
- Java 集合 ArrayList 原始碼分析(帶著問題看原始碼)Java原始碼
- SDWebImage原始碼剖析(二)Web原始碼
- 【原始碼解析】- ArrayList原始碼解析,絕對詳細原始碼