ArrayList詳解
閱讀目錄
- 什麽是ArrayList
- ArrayList特點
- ArrayList繼承的類和實現的接口
- ArrayList內部數組擴容
- ArrayList遍歷
各種原因,前兩年做C語言去了,現在重新做JAVA, 感覺自己基礎很不紮實,要好好學習啦, 先從簡單的開始~
以下內容基於jdk1.7.0_79源碼;
什麽是ArrayList
可以簡單的認為是一個動態數組;實際上ArrayList就是用數組實現的,長度不夠時,調用Arrays.copyOf方法,拷貝當前數組到一個新的長度更大的數組;
ArrayList特點
隨機訪問速度快,插入和移除性能較差(數組的特點);
支持null元素;
有順序;
元素可以重復;
線程不安全;
ArrayList繼承的類和實現的接口
如下圖,是與ArrayList相關的接口和類,下面將一一介紹各個接口和類中的方法;
PS:ArrayList中的方法主要是由Collection接口和List接口定義的;
Iterable接口
實現此接口以便支持foreach語法,如下代碼,ArrayList可以直接使用foreach語句遍歷元素:
package com.pichen.basis.col; import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { List<Integer> list = new ArrayList<Integer>(); for(int i = 0; i < 10; i++){ list.add(i); } //foreach語法 for(Integer i : list){ System.out.print(i.toString() + " "); } } }
Collection接口
int size()方法:
返回集合的大小,在ArrayList類中有一個int類型的size私有屬性,當調用size方法時,直接返回該屬性;
boolean isEmpty()方法:
判斷集合是否為空,在ArrayList中,通過判斷size == 0來判斷集合是否為空;
boolean contains(Object o)方法:
判斷集合是否含有對象o,在ArrayList中,通過判斷indexOf(o) >= 0來判斷是否含有o對象;
查看indexOf(o)方法,代碼如下,主要功能是返回元素第一次出現時的下標索引,所以當下標索引大於等於0時,表示集合中存在該元素:
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; }
註意這裏的相等判斷,調用的是o對象的equals方法,所以在調用contains方法時,要特別關註集合中對象的equals方法,是否有被重寫過,如Integer、String的equals方法是被重寫過的,一般我們自己定義的對象,如果沒重寫equals的話,默認調用的是Object的equals方法,舉個例子,看一下就明白了:
package com.pichen.basis.col; import java.util.ArrayList; import java.util.List; class Dog{ } public class ContainsTest { public static void main(String[] args) { List<Dog> dogList = new ArrayList<Dog>(); Dog dog1 = new Dog(); Dog dog2 = new Dog(); dogList.add(dog1); System.out.println(dogList.contains(dog2));//false List<String> strList = new ArrayList<String>(); strList.add("teststr"); String str = new String("teststr"); System.out.println(strList.contains(str));//true } }
Iterator<E> iterator()方法:
返回一個叠代器對象,用於遍歷集合,事實上,ArrayList類裏有兩個內部類ArrayList.Itr和ArrayList.ListItr,分別對應Iterator叠代器和ListIterator叠代器,後者比前者功能更加強大;從ArrayList.ListItr繼承自ArrayList.Itr就可以看出來,ListIterator叠代器支持更多的操作,如判斷前面還有沒有元素,即hasPrevious()方法,等;
Object[] toArray()方法:
將集合ArrayList轉換成Object數組,有時候需要用到數組的一些api時,可以使用該方法,註意返回的結果是Object類型的數組,如果想返回指定類型的數組,可以使用以下方法,<T> T[] toArray(T[] a);
<T> T[] toArray(T[] a)方法:
集合轉數組,返回指定類型的數組,註意入參T[] a需要指定數組存儲的空間,返回值為指定類型的數組;舉個例子,假如有一個Integer類型的集合,如果想把它轉換成Integer類型的數組,可以這樣寫:Integer[] arr = list.toArray(new Integer[list.size()]);
boolean add(E e)方法:
在集合最後面增加一個元素,在ArrayList中,其實現就是在其內部數組後面增加一個元素,不過要先保證內部數組長度足夠,如下代碼:
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
boolean remove(Object o)方法:
在集合中移除對象o,在ArrayList中,其實現較add方法復雜,涉及空對象判斷,equals比較,數組移動等,性能相對較差;
boolean containsAll(Collection<?> c)方法:
判斷是否包含集合c中的所有元素,在ArrayList中,其實現方法是遍歷集合c中的每一個元素,調用contains方法,判斷集合是否包含該元素,只要有一個不包含就返回false,如下代碼:
public boolean containsAll(Collection<?> c) { for (Object e : c) if (!contains(e)) return false; return true; }
boolean addAll(Collection<? extends E> c)方法:
將集合c中的所有元素加到目標集合中去,在ArrayList中,其實現是先將集合c轉換成數組,然後通過數組拷貝實現;
boolean removeAll(Collection<?> c)方法:
移除目標集合中含有‘集合c中元素’的所有元素,在ArrayList中,最終還是操作數組,性能相對較差;
boolean retainAll(Collection<?> c)方法:
移除目標集合中‘不包含集合c中元素’的所有元素,在ArrayList中removeAll方法和retainAll方法都是通過調用ArrayList的batchRemove方法來實現的,後續詳細了解該方法的實現;
void clear()方法:
移除目標集合中的所有元素,在ArrayList中,就是將其內部數組所有元素賦null;
boolean equals(Object o)和int hashCode()方法
在ArrayLisy中,上面兩個方法都被重寫,equals方法依次取出集合中的所有元素進行比較,通過元素的equals方法,判斷是否相等,全部相等返回true;
hashCode方法的計算是通過所有元素的hashCode計算得到;順便說下hashcode,在java中隨處可見,一般用在HashMap, Hashtable, HashSet等等中,可用於減少equals方法的調用,快速訪問元素等,其實就是散列表的概念,如比較元素先比較其hashcode,如果hashcode不相等,那麽這兩個元素肯定不相等,也就不用調用其equals方法了;
demo代碼:以上方法的簡單使用
package com.pichen.basis.col; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; public class Test { public static void main(String[] args) { List<Integer> list = new ArrayList<Integer>(); for(int i = 0; i < 10; i++){ list.add(i); } //直接打印 System.out.println(list.toString()); //size() System.out.println(list.size()); //contains System.out.println(list.contains(2)); //iterator Iterator<Integer> iterator = list.iterator(); while(iterator.hasNext()){ System.out.print(iterator.next() + " "); } //toArray Object[] objArr = list.toArray(); Integer[] intArr = list.toArray(new Integer[list.size()]); System.out.println("\n" + list.toArray()); //add list.add(5); //remove list.remove(5); System.out.println(list); //containsAll System.out.println(list.containsAll(Arrays.asList(5,6))); //addAll list.addAll(Arrays.asList(555,666)); System.out.println(list); //removeAll list.removeAll(Arrays.asList(555,666)); System.out.println(list); //clear list.clear(); System.out.println(list); } }
List接口
除了Collection中定義的方法為,該接口增加了以下方法
boolean addAll(int index, Collection<? extends E> c);
在ArrayList中,該方法是在指定位置處增加一個集合中的所有元素,該操作涉及數組移動;
E get(int index);
返回下標為index的元素;
E set(int index, E element);
改變下標為index的元素的值
void add(int index, E element);
在下標為index的地方插入元素element,該操作涉及數組移動;
E remove(int index);
移除下標為index的元素,該操作涉及數組移動;
int indexOf(Object o);
返回元素o的最小下標,通過調用o的equals方法與集合中的元素進行比較;
int lastIndexOf(Object o);
返回元素o的最大下標,通過調用o的equals方法與集合中的元素進行比較;
ListIterator<E> listIterator();
返回listIterator叠代器,該叠代器支持向前操作;
ListIterator<E> listIterator(int index);
返回listIterator叠代器,從特定的位置開始,該叠代器支持向前操作;
List<E> subList(int fromIndex, int toIndex);
返回下標在fromIndex和toIndex之間的元素集合;
demo代碼:以上方法的簡單使用:
View CodeRandomAccess, Cloneable, java.io.Serializable接口
這三個接口是標識接口,裏面都是空的;
RandomAccess標識其支持快速隨機訪問;
Cloneable標識其支持對象復制;
Serializable標識其可序列化;
AbstractCollection類
大部分方法前面已經說明過了,不過該類下的contains方法、toArray方法等,遍歷的時候都是使用更加通用的叠代器方式進行遍歷;
AbstractList類
大部分方法前面已經說明過了,不過該類中有兩個私有內部類Itr和ListItr,對應的分別是兩個叠代器;
ArrayList類
ArrayList的具體實現
成員屬性:
private static final int DEFAULT_CAPACITY = 10;//初始容量
private static final Object[] EMPTY_ELEMENTDATA = {};//空ArrayList實例共享的一個空數組
private transient Object[] elementData; //真正存儲ArrayList中的元素的數組;
private int size;//存儲ArrayList的大小,註意不是elementData的長度;
除了其父接口定義的方法外,該類增加了以下方法
public ArrayList(int initialCapacity):
構造函數,指定初始大小
public ArrayList()
構造函數,使用共享的EMPTY_ELEMENTDATA空數組
public ArrayList(Collection<? extends E> c)
構造函數,通過集合初始化ArrayList
public void trimToSize()
節省空間用的,ArrayList是通過數組實現的,大小不夠時,增加數組長度,有可能出現數組長度大於ArrayList的size情況;
public void ensureCapacity(int minCapacity)
保證ArrayList能容納minCapacity個元素;
私有方法
略
ArrayList內部數組擴容
查看ArrayList的add源碼方法,如下:
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
在往ArrayList增加e對象前,要先保證內部數組空間足夠,即調用ensureCapacityInternal判斷,查看其代碼,如下:
private void ensureCapacityInternal(int minCapacity) { if (elementData == EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); }
入參minCapacity為ArrayList要保證的最小空間大小,首先判斷內部數組elementData是否是初始的空數組(當ArrayList是空實例的情況),如果是的話,且minCapacity比DEFAULT_CAPACITY小,則設minCapacity為默認的初始容量大小DEFAULT_CAPACITY;接著調用ensureExplicitCapacity方法,查看其代碼,如下:
private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }
首先,是對修改次數modCount加一,這裏的modCount給ArrayList的叠代器使用的,在並發操作被修改時,提供快速失敗行為(保證modCount在叠代期間不變,否則拋出ConcurrentModificationException異常,可以查看源碼859行),接著判斷minCapacity是否大於當前ArrayList內部數組長度,大於的話調用grow方法對內部數組elementData擴容,grow方法代碼如下:
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); }
首先,獲取內部數組elementData長度,並對它的大小增加1.5倍(oldCapacity + (oldCapacity >> 1)),賦給newCapacity,當newCapacity仍然比minCapacity(要保證的最小空間大小)小的時候,直接讓newCapacity 等於minCapacity;然後再判斷newCapacity 是不是大於MAX_ARRAY_SIZE,如果大於的話,調用hugeCapacity方法,處理大容量空間的情況,該方法判斷是否溢出(拋出OutOfMemoryError),以及當minCapacity大於MAX_ARRAY_SIZE時,直接返回Integer.MAX_VALUE;最後就是真正地數組擴容了,調用Arrays.copyOf方法,拷貝舊的內部數組內容到一個新的(長度為newCapacity)的數組,並更改內部數組elementData的引用為新數組;
ArrayList遍歷
一般工作中用的比較多的是遍歷操作,ArrayList支持三種方式
- for循環下標遍歷;
- 叠代器(Iterator和ListIterator);
- foreach語句。
package com.pichen.basis.col; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.ListIterator; public class Test { public static void main(String[] args) { List<Integer> list = new ArrayList<Integer>(); for(int i = 0; i < 10; i++){ list.add(i); } //直接打印 System.out.println(list.toString()); //for循環 System.out.println("for循環:"); for(int i = 0; i < list.size(); i++){ System.out.print(list.get(i) + " "); } //foreach System.out.println("\nforeach:"); for(Integer i : list){ System.out.print(i + " "); } //iterator System.out.println("\niterator:"); Iterator<Integer> iterator = list.iterator(); while(iterator.hasNext()){ System.out.print(iterator.next() + " "); } //listIterator System.out.println("\nlistIterator:"); ListIterator<Integer> listIterator = list.listIterator(); while(listIterator.hasNext()){ System.out.print(listIterator.next() + " "); } System.out.println(); while(listIterator.hasPrevious()){ System.out.print(listIterator.previous() + " "); } } }
ArrayList詳解