經常使用集合源碼解析(一),ArrayList

ArrayList主要用做動態數組,是增強版的Array,能夠在添加元素時無需擔憂容量不夠,也支持經過index獲得元素。java

ArrayList定義數組

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

能夠得出ArrayList支持泛型。app

先來分析ArrayList的父類和接口:dom

1. AbstractList:提供List接口的默認實現,個別爲抽象方法;ide

2.List定義List(即列表)必須實現的方法,咱們一般定義一個ArrayList是這樣:List list = new ArrayList(),而非這樣ArrayList list = new ArrayList(),也正是因爲這點,這樣還能夠實現多態,來下降耦合度;測試

3.RandomAccess:這是一個標記接口,具體使用請參照RandomAccess接口的使用,不過值得關注的是這段話:ui

JDK中推薦的是對List集合儘可能要實現RandomAccess接口
若是集合類是RandomAccess的實現,則儘可能用for(int i = 0; i < size; i++) 來遍歷而不要用Iterator迭代器來遍歷,在效率上要差一些。反過來,若是List是Sequence List,則最好用迭代器來進行迭代。

4.Cloneable:這也是一個標記接口,表示實現該接口的類,能夠經過Object的clone方法獲得一個該對象的淺拷貝(僅僅拷貝該對象中的基本類型與該對象,而該對象中全部的對其餘對象的引用仍然指向原來的對象,沒有拷貝),例子:this

public class Person implements Cloneable {

    
    String name;
    int age;
    User user;
    
    
    
    public Person(){}
    
    public Person(String name,int age,User user){
        this.name = name;
        this.age = age;    
        this.user = user;
    }
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    
    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public String toString()
    {
        return "this is person:"+"name:"+this.name+"——age:"+this.age;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    
    
}

public class User {

    
    String name;
    String password;
    int age;
    
    public User() {
        
    }

    public User(String name,String password,int age)
    {
        this.name = name;
        this.password = password;
        this.age = age;
    }
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String toString()
    {
        return "this is user:"+"name:"+this.name+"——password:"+this.password+"——age:"+this.age;
    }
}

public class CloneTest {
    
    
    public static void main(String[] args) throws Exception {
        User user = new User("user","kk",20);
        Person a = new Person("person",20, user);
        Person b = (Person) a.clone();
        
        System.out.println("Person b:" + b.name + " Person b:user:" + b.user.name);
        System.out.println("Person a:" + a.name + " Person a:user:" + a.user.name);
        
        b.name = "personnamechange";
        b.user.name = "usernamechange";
        
        System.out.println("Person b:" + b.name + " Person b:user:" + b.user.name);
        System.out.println("Person a:" + a.name + " Person a:user:" + a.user.name);
    }

}

輸出爲:(能夠看到將b中的user的name修改後a中user的name也會改變,可得二者中的user引用的同一個對象)spa

Person b:person Person b:user:user
Person a:person Person a:user:user
Person b:personnamechange Person b:user:usernamechange
Person a:person Person a:user:usernamechange.net

5.Serializable:標記接口,實現該接口表示該類的對象可進行序列化。Java的對象序列化是指將那些實現了Serializable接口的對象轉換成一個字符序列,並可以在之後將這個字節序列徹底恢復爲原來的對象。例子:

import java.io.Serializable;

public class User implements Serializable {

    
    private String name;
    private String password;
    private int age;
    
    public User() {
        
    }

    public User(String name,String password,int age)
    {
        this.name = name;
        this.password = password;
        this.age = age;
    }
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String toString()
    {
        return "this is user:"+"name:"+this.name+"——password:"+this.password+"——age:"+this.age;
    }
}

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerialTest {

public static void main(String[] args) {
        
        User p1 = (User) deSerialByte(serialByte(new User("user","1234",15)));
        
        
        System.out.println("p1:"+p1.toString());
        
    }
    
    //序列化一個對象(能夠存儲到一個文件也能夠存儲到字節數組)這裏存儲到字節數組
    public static byte[] serialByte(Object obj)
    {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos;
        try {
            oos = new ObjectOutputStream(baos);
            oos.writeObject(obj);
            oos.close();
            return baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    
    //反序列化一個對象
    public static Object deSerialByte(byte[] by)
    {
        ObjectInputStream ois;
        try {
            ois = new ObjectInputStream(new ByteArrayInputStream(by));
            return ois.readObject();
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }
}

若User未實現Serializable接口,那麼writeObject時將會報NotSerializableException

成員變量:

private static final long serialVersionUID = 8683452581122892189L;

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer.
     */
    private transient Object[] elementData;

    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;

1.serialVersionUID:序列化ID,用於序列化

2.elementData:對象數組,存儲ArrayList中的元素,至於transient表示該成員變量在序列化時將不被歸入序列化範圍內,那麼這個數組該如何被序列化呢,能夠看到ArrayList自身實現了對elementData的序列化與反序列化

/**
     * Save the state of the <tt>ArrayList</tt> instance to a stream (that
     * is, serialize it).
     *
     * @serialData The length of the array backing the <tt>ArrayList</tt>
     *             instance is emitted (int), followed by all of its elements
     *             (each an <tt>Object</tt>) in the proper order.
     */
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out array length
        s.writeInt(elementData.length);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++)
            s.writeObject(elementData[i]);

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }

    }

    /**
     * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
     * deserialize it).
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in array length and allocate array
        int arrayLength = s.readInt();
        Object[] a = elementData = new Object[arrayLength];

        // Read in all elements in the proper order.
        for (int i=0; i<size; i++)
            a[i] = s.readObject();
    }

3.size:ArrayList包含元素的數量

構造方法:

/**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this(10);
    }

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }

該三個構造方法第一個可經過參數指定elementData的容量大小,第二個無參方法能夠看到若未指定容量那麼elementData初始大小爲10,第三個可將其餘集合轉成數組返回給elementData(返回若不是Object[]將調用Arrays.copyOf方法將其轉爲Object[])。

成員方法:列舉經常使用

add方法

/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    /**
     * Inserts the specified element at the specified position in this
     * list. Shifts the element currently at that position (if any) and
     * any subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
private void ensureCapacityInternal(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);
    }

第一個add方法:直接在尾部添加元素,並在添加前確保容量足夠(ensureCapacityInternal),若不夠從新指定容量後,使用Arrays.copyOf從新建立數組,並將原來的elementData拷貝進去。

第二個add方法:可指定下標進行元素添加,首先判斷指定位置index是否超出elementData的界限,再確保容量足夠,並使用System.arraycopy將index後的元素總體後移一位,將index位置空出,以後插入元素。

這裏有一個變量modCount須要注意,

The number of times this list has been structurally modified.

    這是對modCount的解釋,意爲記錄list結構被改變的次數(觀察源碼能夠發現每次調用ensureCapacoty方法,modCount的值都將增長,但未必數組結構會改變,因此感受對modCount的解釋不是很到位)。

addAll方法

/**
     * Appends all of the elements in the specified collection to the end of
     * this list, in the order that they are returned by the
     * specified collection's Iterator.  The behavior of this operation is
     * undefined if the specified collection is modified while the operation
     * is in progress.  (This implies that the behavior of this call is
     * undefined if the specified collection is this list, and this
     * list is nonempty.)
     *
     * @param c collection containing elements to be added to this list
     * @return <tt>true</tt> if this list changed as a result of the call
     * @throws NullPointerException if the specified collection is null
     */
    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

    /**
     * Inserts all of the elements in the specified collection into this
     * list, starting at the specified position.  Shifts the element
     * currently at that position (if any) and any subsequent elements to
     * the right (increases their indices).  The new elements will appear
     * in the list in the order that they are returned by the
     * specified collection's iterator.
     *
     * @param index index at which to insert the first element from the
     *              specified collection
     * @param c collection containing elements to be added to this list
     * @return <tt>true</tt> if this list changed as a result of the call
     * @throws IndexOutOfBoundsException {@inheritDoc}
     * @throws NullPointerException if the specified collection is null
     */
    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;
    }

第一個方法:將一個集合轉成數組,確保容量足夠,添加到ArrayList尾部,只要集合c的大小不爲空,即轉換後的數組長度不爲0則返回true,此處有一疑問,即咱們在構造方法中知道在使用toArray方法還需對返回值實際類型是不是Object數組進行判斷,若不是則要將實際類型轉爲Object數組,而在addAll方法卻沒有此操做,爲何?

第二個方法:將一個集合轉成數組,確保容量足夠,與add相似經過System.arraycopy將index後的元素後移c.size位,確保這個集合中元素能插入當中。

clear方法:

 

/**
     * Removes all of the elements from this list.  The list will
     * be empty after this call returns.
     */
    public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

 

將全部元素置成null,並未對數組容量進行釋放。

indexOf、lastIndexOf方法:

/**
     * Returns the index of the first occurrence of the specified element
     * in this list, or -1 if this list does not contain the element.
     * More formally, returns the lowest index <tt>i</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
     * or -1 if there is no such index.
     */
    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;
    }

    /**
     * Returns the index of the last occurrence of the specified element
     * in this list, or -1 if this list does not contain the element.
     * More formally, returns the highest index <tt>i</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
     * or -1 if there is no such index.
     */
    public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

能夠看出這兩個方法都是遍歷elementData數組,一個正向遍歷一個反向遍歷,indexOf返回正向查找到的第一個元素的下標,lastIndexOf返回反向查找到的第一個元素的下標。

get、set方法:

/**
     * Returns the element at the specified position in this list.
     *
     * @param  index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

    /**
     * Replaces the element at the specified position in this list with
     * the specified element.
     *
     * @param index index of the element to replace
     * @param element element to be stored at the specified position
     * @return the element previously at the specified position
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

/**
     * Checks if the given index is in range.  If not, throws an appropriate
     * runtime exception.  This method does *not* check if the index is
     * negative: It is always used immediately prior to an array access,
     * which throws an ArrayIndexOutOfBoundsException if index is negative.
     */
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

get方法先檢查查找的index是否越界,再直接經過下標返回元素;

set方法一樣先檢查index是否越界,在直接經過賦值將以前的元素替換成指定元素,並返回以前元素。

isEmpty方法

無腦判斷size是否爲0,並返回。

remove方法

/**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     *
     * @param index the index of the element to be removed
     * @return the element that was removed from the list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    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; // clear to let GC do its work

        return oldValue;
    }

    /**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If the list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * <tt>i</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
     * (if such an element exists).  Returns <tt>true</tt> if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return <tt>true</tt> if this list contained the specified element
     */
    public boolean remove(Object o) {
        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;
    }

    /*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    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
    }

第一個根據下標進行元素刪除,首先檢查index是否越界,再將該元素取出,並將elementData中在index後的元素總體前移一位,返回該元素;

第二個是根據元素進行刪除,先正向遍歷elementData數組,如有相同元素,獲得該元素下標,進行如第一個方法的刪除操做,返回true,若未遍歷到該元素則返回false,值得注意的是,這個remove只刪除正向遍歷找到的第一個相同元素,而不是刪除全部與參數相同的元素。

trimToSize方法

/**
     * Trims the capacity of this <tt>ArrayList</tt> instance to be the
     * list's current size.  An application can use this operation to minimize
     * the storage of an <tt>ArrayList</tt> instance.
     */
    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

這個方法用來釋放elementData多餘的容量,使用Arrays.copyOf將elementData容量重置爲size,正好能夠存下當前ArrayList中的元素。

clone方法

 /**
     * Returns a shallow copy of this <tt>ArrayList</tt> instance.  (The
     * elements themselves are not copied.)
     *
     * @return a clone of this <tt>ArrayList</tt> instance
     */
    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);
        }
    }

該方法返回此 ArrayList 實例的淺表副本。(不復制這些元素自己,而是直接引用這些元素),由此也能夠得出Array.copyOf方法返回的數組中元素是原數組元素的引用。

 

疑問解決:在addAll方法那裏,我留下了一個疑問,在此給出解答,首先解答爲什麼在構造方法中進行類型判斷並強轉,註釋上寫了這麼一句話

// c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);

c.toArray方法可能不是返回的Object方法,因而我進行了一個測試:

@Test
    public void test(){
        List<String> strList = Arrays.asList("test", "hello");
        Object[] a = strList.toArray();
        System.out.println(a.getClass());
    }

控制檯打出:

那麼爲什麼要進行實際類型轉換呢?在Java中,容許子類向上轉型(Object是全部類的父類),因此能夠這樣使用:Object[] a = strList.toArray();

可是若是進行以下操做:

@Test
    public void test(){
        List<String> strList = Arrays.asList("test", "hello");
        Object[] a = strList.toArray();
        
        a[0] = new Object();
        System.out.println(a[0]);
    }

會拋出異常:

java.lang.ArrayStoreException: java.lang.Object;

這也就是說假如咱們有1個Object[]數組,並不表明着咱們能夠將Object對象存進去,這取決於數組中元素實際的類型,那麼爲了將Object存進去,就要對a進行實際類型轉換,以下

@Test
    public void test(){
        List<String> strList = Arrays.asList("test", "hello");
        Object[] a = strList.toArray();
        System.out.println(a.getClass());
        a = Arrays.copyOf(a, strList.size(), Object[].class);
        
        a[0] = new Object();
        System.out.println(a.getClass());
    }

控制檯打出:

強轉以後將Object對象存入便不會報錯了。由於a的實際類型已經是Object[],此時將任意類型的對象存入該數組都再也不報錯,如此轉成Object[]就達到了ArrayList的目的,在未指定具體元素類型時,任何類型均可以存入其中:例子:

@Test
    public void test(){
        List<String> strList = Arrays.asList("test", "hello");
        Object[] a = strList.toArray();
        System.out.println(a.getClass());
        a = Arrays.copyOf(a, strList.size(), Object[].class);
        
        a[0] = new Person();
        a[1] = new User();
        System.out.println(a[0].getClass() + " " + a[1].getClass());
    }

控制檯打出:

那麼爲什麼addAll沒有對c進行Object[]強轉呢?ArrayList可沒有保證這個c也要像ArrayList同樣,在未指定元素具體類型時什麼類型都能存,ArrayList只需保證本身有這個厲害的特質就好了,其餘的就原封不動的裝進去就好

ArrayList的三個構造方法都有elementData = new Object[]這一操做,他們已經讓elementData引用了一個Object[](已經是Object[]),因此以後在add操做上,因爲容許子類向上轉型,因而全部的類型便均可以存入其中了。

 測試一下:

@Test
    public void test2(){
        List list = new ArrayList();
        list.add("hello");
        list.add(new User());
        List<Integer> objList = Arrays.asList(1,2,3);
        list.addAll(objList);
        System.out.println(list);
    }

控制檯:

收工

相關文章
相關標籤/搜索