閱讀ArrayList原始碼的一些記錄
阿新 • • 發佈:2018-12-20
ArrayList的底層是基於陣列實現的,但是我們知道陣列的長度一旦確定就不能夠再次變化,ArrayList的長度是可以變化的,其實就是在需要擴容的時候,重新生成一個數組,並把原陣列中的元素放到新的陣列中,用新的陣列替代就得陣列,就完成了ArrayList的擴容。
本文是基於JDK1.8的原始碼,同時也會提到一些和JDK1.6的一些差別
一、構造方法
1、給定初始大小的構造方法
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {// 如果大於0,就按照給定的大小來初始化陣列
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {// 如果等於0,則初始化為一個空陣列
this.elementData = EMPTY_ELEMENTDATA;
} else {// 如果小於0,則直接丟擲異常
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
2、無參構造方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
此處的無參構造方法是初始化一個空的陣列,相比於1.6來說,無參構造方法有一點變化,在1.6中,如果呼叫無參構造方法,會把elementData陣列的長度初始化為10
3、以傳入的Collection為基礎構建ArrayList
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;
}
}
這個構造方法沒什麼說的,就是把傳入的集合轉化為陣列,然後放到elementData中
下面按照增刪改查依次說明
二、增
1、在陣列尾部插入
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 確保長度夠用,否則就擴容
elementData[size++] = e;// 在陣列尾部新增元素
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//用無參構造方法生成物件的時候,elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);// 取其中比較大的值
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 是否需要擴容,如果需要就呼叫grow方法
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;// 原來的容量
// 正常的擴容原則 原長度 + 原長度的一半(只取整數部分)(eg:1、原長度是10則正常擴容長度為10 + (10 >> 1)=15 2.原長度為11,則正常擴容以後的長度為11 + (11 >> 1) = 16)
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果按照正常的擴容演算法擴容後的長度還不能達到要求,則按照傳進來的長度進行擴容
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)// 這種情況一般不會發生,除非你往List中添加了很多很多資料
newCapacity = hugeCapacity(minCapacity);
// 把原來的資料放到新的陣列中
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // 如果小於0就表示溢位了
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
JDK1.6的擴容原則和這個有一定的差別,沒有那麼複雜,可以看一下1.6的擴容原則
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;// 原長度
if (minCapacity > oldCapacity) {// 是否需要擴容
Object oldData[] = elementData;
// 正常的擴容演算法,原長度的3/2 + 1
int newCapacity = (oldCapacity * 3)/2 + 1;
if (newCapacity < minCapacity)// 不滿足要求,則把傳入的長度作為擴容後的長度
newCapacity = minCapacity;
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
2、在指定位置插入
public void add(int index, E element) {
rangeCheckForAdd(index);// 檢測index是否合法(0 < index < size)
ensureCapacityInternal(size + 1); // 是否要擴容
// 把index以及其後的元素往後移動,由此可以看出在特定位置插入資料的效率並不高
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}
3、把一個集合的元素插入到陣列的尾部
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();// 把集合轉為陣列
int numNew = a.length;
ensureCapacityInternal(size + numNew); // 是否需要擴容
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
4、把一個集合插入到陣列的特定位置
和插入一個元素的套路一樣,此處只貼一下程式碼
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
三、刪
1、按照索引刪除
public E remove(int index) {
rangeCheck(index);// 檢查index是否大於等於size,如果是會丟擲異常
modCount++;
E oldValue = elementData(index);// 獲取指定索引的上的值
int numMoved = size - index - 1;
if (numMoved > 0)
// 把index後面的元素前移,所以移除的效率不高
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
2、按照值來刪除
如果有此值,刪除成功返回true,否則返回false
// 此方法是從頭開始迴圈查詢,找到以後就刪除並返回,後面相同的值不會被刪除
public boolean remove(Object o) {
// 分是否為null兩種情況,然後迴圈查詢,如果找到就刪除
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;
}
// 方法相比於remove(int)來說,減少了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
}
3、清空
// 迴圈把陣列的每個元素都置為null
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
四、改
public E set(int index, E element) {
// 校驗index的合法性
rangeCheck(index);
// 獲取舊值,返回時用
E oldValue = elementData(index);
// 修改值
elementData[index] = element;
return oldValue;
}
五、查
public E get(int index) {
rangeCheck(index);// 校驗index的合法性
return elementData(index);// 獲取指定索引上的值
}
六、其他的一些方法
1、獲取List的長度
因為ArrayList維護了一個size成員變數來表示其長度,直接獲取size的長度即可
public int size() {
return size;
}
2、集合是否為空集合
長度為空就是空集合
public boolean isEmpty() {
return size == 0;
}
3、是否包含指定的元素
遍歷去比較
public boolean contains(Object o) {
return 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;
}
七、ArrayList的遍歷
以下都是個人的測試程式碼 三種遍歷方式
public void testArrayList(){
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
// 第一種方法
for(int i = 0; i < list.size(); i ++){
System.out.println(list.get(i));
}
// 第二種
for(String str : list){
System.out.println(str);
}
// 第三種,迭代器
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
下面我們看一種情形,就是刪除List中的所有與指定值相同的值 第一種(並不能達到我們預想的結果)
public void testFor(){
List<String> list = new ArrayList<>();
list.add("a");
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("a");
list.add("a");
list.add("a");
for(int i = 0; i < list.size(); i ++){
if("a".equals(list.get(i))){
list.remove(i);
}
}
// 經過刪除以後,list中的元素為a,b,c,d,a顯然沒有達到我們要刪除所有的a的目的
for(int i = 0; i < list.size(); i ++){
System.out.println(list.get(i));
}
}
第二種(迭代器方法 推薦使用)
public void testiter(){
List<String> list = new ArrayList<>();
list.add("a");
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("a");
list.add("a");
list.add("a");
Iterator<String> iterator2 = list.iterator();
while(iterator2.hasNext()){
String val = iterator2.next();
if("a".equals(val)){
iterator2.remove();
}
}
// 經過刪除後 list 中的元素為b,c,d 得到了我們所期望的結果
for(int i = 0; i < list.size(); i ++){
System.out.println(list.get(i));
}
}
第三種方法
public void testFor2(){
List<String> list = new ArrayList<>();
list.add("a");
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("a");
list.add("a");
list.add("a");
for(int i = 0; i < list.size(); i ++){
if("a".equals(list.get(i))){
list.remove(i);
i --;
}
}
// 經過刪除以後,list中的元素為b,c,d 同樣達到了我們的要求,不過不建議用這種方法,推薦用迭代器去刪除
for(int i = 0; i < list.size(); i ++){
System.out.println(list.get(i));
}
}
由於比較好奇foreach的實現就寫了兩個測試方法,程式碼如下
public void testForEach(){
List<String> list = new ArrayList<>();
for(String str : list){
System.out.println(str);
}
}
public void testIter(){
List<String> list = new ArrayList<>();
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String s = iterator.next();
System.out.println(s);
}
}
通過idea反編譯後