1. 程式人生 > >ArrayList詳解

ArrayList詳解

ast close 部分 返回 initial mat set等 dog init

閱讀目錄

  • 什麽是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 Code

RandomAccess, 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詳解