ArrayList原始碼分析(JDK1.8)
不積跬步,無以至千里;不積小流,無以成江海。從基礎做起,一點點積累,加油!
ArrayList的定義
public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- 從ArrayList<\E>可以看出它是支援泛型的,它繼承自AbstractList,實現了List、RandomAccess、Cloneable、java.io.Serializable介面。
Collection介面的方法:
List:
定義
public interface List<E> extends Collection<E>
List介面方法:
AbstractList提供了List介面的預設實現(個別方法為抽象方法(get方法))。
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>
//ArrayList中使用的modCount就是AbstractList中宣告的
protected transient int modCount = 0;
AbstractList中對於List集合的判斷equals()方法
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());
}
這個方法我覺得寫得特別好,放在這裡留著。。。
- AbstractList繼承了AbstractCollection類,實現了List集合的除了get方法之外的所有方法:
- RandomAccess是一個標記介面,介面內沒有定義任何內容。
- 實現了Cloneable介面的類,可以呼叫Object.clone方法返回該物件的淺拷貝。
- 通過實現 java.io.Serializable 介面以啟用其序列化功能。未實現此介面的類將無法使其任何狀態序列化或反序列化。序列化介面沒有方法或欄位,僅用於標識可序列化的語義。
ArrayList的資料域
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
*/
private int size;
transient 關鍵字
- Java的Serializable提供了一種序列化物件例項的機制。當序列化物件時,可能有一個特殊的物件資料成員,我們不想用Serializable機制來儲存它。為了在一個特定物件的一個域上關閉Serializable,可以在這個域前加上關鍵字transient。
- transient是Java語言的關鍵字,用來表示一個域不是該物件序列化的一部分。當一個物件被序列化的時候,transient型變數的值不包括在序列化的表示中,然而非transient型的變數是被包括進去的。
看個列子:
public class Person implements Serializable{
private static final long serialVersionUID = -3319659493471806232L;
private String name;
private transient String pass;
public Person(String name, String pass) {
this.name = name;
this.pass = pass;
}
@Override
public String toString() {
return "Person [name=" + name + ", pass=" + pass + "]";
}
}
public class TestTransient {
public static void main(String[] args) {
Person person = new Person("Tome", "1234");
System.out.println(person);
try {
// 序列化,被設定為transient的屬性沒有被序列化
ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(
"person.out"));
o.writeObject(person);
o.close();
} catch (Exception e) {
e.printStackTrace();
}
try {
// 重新讀取內容
ObjectInputStream in = new ObjectInputStream(new FileInputStream(
"person.out"));
Person readPerson = (Person) in.readObject();
// 讀取後psw的內容為null
System.out.println(readPerson.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**執行結果
*Person [name=Tome, pass=1234]
*Person [name=Tome, pass=null]
*/
被標記為transient的屬性在物件被序列化的時候不會被儲存。
構造方法分析
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
private static final Object[] EMPTY_ELEMENTDATA = {};
public MyArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public MyArrayList(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(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;
}
}
三個構造方法:
- 無參構造方法
- 使用無參構造方法建立的是一個length為0的內部陣列,當第一次插入資料時,會對其擴容,擴容為預設的大小10
- 帶有初始容量的構造方法
- 建立一個預設容量的陣列
- 帶有初始集合的構造方法
add(E e)方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 檢查是否需要擴容
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//如果建立ArrayList時用的是無參構造器,則第一次插入時會進行一次擴容並且擴到預設陣列大小10
//然而如果是使用帶有容量引數的構造器且initialCapacity=0時,則不會進行這一步,直接擴充套件陣列容量為minCapacity
//這就導致一個結果,擴容之後帶引數的構造器的陣列length為1,而預設的為10
//所以,帶容量引數的初始化時注意自己需要的大小
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
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);
}
容量的拓展將導致陣列元素的複製,多次拓展容量將執行多次整個陣列內容的複製。若提前能大致判斷list的長度,呼叫ensureCapacity調整容量,將有效的提高執行速度。
可以理解提前分配好空間可以提高執行速度,但是測試發現提高的並不是很大,而且若list原本資料量就不會很大效果將更不明顯。
add(int index, E element)
public void add(int index, E element) {
rangeCheckForAdd(index);//檢查index是否越界
ensureCapacityInternal(size + 1); // 確保容量
System.arraycopy(elementData, index, elementData, index + 1,//將index以後的元素後移一個
size - index);
elementData[index] = element;
size++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)//元素可以新增在最後尾端
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//本地的C/C++庫方法,直接操縱記憶體,速度更快
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
indexOf(Object o)
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
remove(int index)
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; // 將物件的引用置為null,讓垃圾回收器可以回收
return oldValue;
}
//刪除等其他操作和插入不同,插入可以將元素插在最後面,而刪除等操作是在原有的元素上做修改,所以範圍檢查不一樣
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
clear()
public void clear() {
modCount++;
// 清空讓垃圾回收器清理
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
clone()
返回此 ArrayList 例項的淺表副本。(不復制這些元素本身。)
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
呼叫父類的clone方法返回一個物件的副本,將返回物件的elementData陣列的內容賦值為原物件elementData陣列的內容,將副本的modCount設定為0。
removeRange(int fromIndex, int toIndex)
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// clear to let GC do its work
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}
執行過程是將elementData從toIndex位置開始的元素向前移動到fromIndex,然後將toIndex位置之後的元素全部置空順便修改size。
ArrayList<Person> arrayList = new ArrayList<Person>();
arrayList.add(new Person(2));
arrayList.add(new Person(3));
arrayList.add(new Person(4));
arrayList.add(new Person(5));
arrayList.add(new Person(6));
arrayList.subList(2, 4).clear();
上面這段程式碼的執行結果與removeRange(2, 4)一樣,原因是:
arrayList.subList(2, 4)建立一個AbstractList的子類SubList的例項,呼叫clear方法時由於子類SubList沒有重寫,所以直接呼叫AbstractList的clear方法,然而,在AbstractList的clear方法中,呼叫的是removeRange方法,該方法在SubList中重寫了;所以,呼叫的是子類的removeRange,在SubList中有一個指向外部類的指標parent,在removeRange方法中通過parent呼叫了外部類的removeRange方法。所以經過輾轉呼叫,最終呼叫的還是外部類的removeRange方法
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
//SubList類中沒有重寫AbstractList的clear方法
private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent;//指向外部類
private final int parentOffset;
private final int offset;
int size;
SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}
...
protected void removeRange(int fromIndex, int toIndex) {
checkForComodification();
parent.removeRange(parentOffset + fromIndex,parentOffset + toIndex);
//呼叫外部類的removeRange(),所以最終修改的就是外部類陣列的元素,即相當於呼叫了外部類的removeRange方法
this.modCount = parent.modCount;
this.size -= toIndex - fromIndex;
}
}
//AbstractList類中申明的clear()方法
public void clear() {
removeRange(0, size());//由於SubList重寫了removeRange方法,所以呼叫SubList的removeRange方法,
}
每一次對陣列做新增元素,刪除元素等改變陣列大小或者容量的操作時都會引起對modCount的加1, 究竟這個值有什麼作用呢?
//返回一個遍歷的指標
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
int cursor; // 下一個要返回的元素的下標
int lastRet = -1; // 最後一個元素的下標
int expectedModCount = modCount; //這個的作用就是當用itr來遍歷集合時,不允許往裡面新增或者刪除元素
public boolean hasNext() {//hasNext方法不會檢查expectedModCount 的值,不會引發ConcurrentModificationException異常
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {//next方法在最開始的時候就會使用checkForComodification進行檢查
//所以如果在呼叫next之前對集合進行了改變則會丟擲異常
checkForComodification();//檢查是否改變
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = MyArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
//remove方法首先會呼叫checkForComodification進行檢查
//同時,在MyArrayList.this.remove(lastRet)時會改變modCount,expectedModCount = modCount
//又讓情況變為正常,所以刪除之後這兩個的值又會想等,在之後呼叫next的方法時不會炮竹異常
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
MyArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = MyArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = MyArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
ArrayList的三種遍歷
- 使用下標(在遍歷的時候可以對集合進行新增刪除)
MyArrayList<Integer> array = new MyArrayList<Integer>();
array.add(1);
array.add(2);
array.add(1, 3);
array.display();
for(int i = 0; i < array.size(); i++){
System.out.println(array.get(i));
array.remove(new Integer(3));
if (i == 1) {
array.add(1);
}
}
- 使用iterator
Iterator<Integer> it = array.iterator();
while(it.hasNext()){
//array.add(4); 會導致modCount發生變化,從而導致next方法的checkModCount丟擲異常
int value = it.next();
//array.remove(new Integer(value));同樣會導致modCount發生變化
it.remove();//但是可以這樣,因為Iterator的remove方法刪除元素之後對modCount又進行了復原
System.out.println(value);
}
- 使用forEach迴圈(其實本質就是利用的Iterator迴圈)先是it.hasNext(),然後在將value = it.next(),所以執行這段程式碼時,會先列印第一個值,在進行第二輪迴圈的value = it.next()時丟擲異常
for(Integer value : array){
//array.add(4);//發生錯誤
//array.remove(value);
System.out.println(value);
}