java ArrayList源码分析

研究ArrayList源码有一段时间了,说实话收益还是很大的。主要当方法的实现比较清楚时,用起来也比较放心。并且集合相关的源码对理解数据结构还是有点帮助的。话不多说,开始分析。

(1)ArrayList实现了List接口,List接口中定义了list中通用的很多方法。

public class ArrayList<E> extends AbstractList<E>
	implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
	...<span style="font-family: Arial, Helvetica, sans-serif;">}</span>
(2)ArrayList里的属性。其中elementData就是底层用来存储ArrayList中元素的具体数组。
private static final long serialVersionUID = 8683452581122892189L;
    //默认初始化容量
    private static final int DEFAULT_CAPACITY = 10;
    //当不指定ArrayList大小时,elementdata<span style="font-family: Arial, Helvetica, sans-serif;">默认</span>初始化<span style="font-family: Arial, Helvetica, sans-serif;">是一个空的数组</span>
    private static final Object[] EMPTY_ELEMENTDATA = {};
    //存储ArrayList里元素的数组<span style="white-space:pre">	</span>
    private transient Object[] elementData;
    //包含元素的数量
    private int size;
    //数组最大容量
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

(3)构造方法

相比于之前的ArrayList的实现,之前当不指定ArrayList初始化容量时,默认在调用构造方法时,初始化elementData容量为DEFAULT_CAPACITY(即10)。现在只会在调用add方法第一次添加元素时,会初始化容量。起到一点节约内存的作用。

public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }


    public ArrayList() {
        super();
        this.elementData = EMPTY_ELEMENTDATA;//当不传入大小时,默认是一个空的数组
    }
	//根据其他集合构造List
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        if (elementData.getClass() != Object[].class)//toArray返回的可能不是Object[]类型
            elementData = Arrays.copyOf(elementData, size, Object[].class);//[1]调用该方法返回Object[]数组
    }
以上ArrayList(Collection<? extends E> c)这个构造方法根据传入的集合创建ArrayList。当传入的集合调用toArray方法返回的数组类型不是Object[]数组时,在[1]处调用Arrays工具类中的copyOf方法返回一个Object[]类型的数组并赋给elementData,由此达到创建ArrayList的目的。
Arrays类中的copyOf方法

  [1].Arrays类中的copyOf方法
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }
(4)boolean add(E e)方法:该方法用于向list末尾添加一个指定类型的元素。每次添加元素时,需要调用ensuerCapacityInternal方法来确保elementData数组的内部容量足够存储新添加的元素e。
public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!![2]
        elementData[size++] = e;//添加元素e,随后size+1
        return true;
    }

ensureCapacityInternal方法如下,该方法只会对elementData数组容量为0时(即无参构造方法中this.elementData = EMPTY_ELEMENTDATA;),指定elementData的最小容量为DEFAULT_CAPACITY或add中传入的大小中的较大者。如果elementData数组大小不为0,则会继续调用ensureExplicitCapacity方法(确保数组的明确容量)。

[2]处调用ensureCapacityInternal(size+1)保证每次添加元素时数组容量够用
   private void ensureCapacityInternal(int minCapacity) {
        if (elementData == EMPTY_ELEMENTDATA) {//如果elementData是空数组
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//最小容量是size+1或默认容量(10)中的最大值
        }
        ensureExplicitCapacity(minCapacity);//[3]
    }   

ensureExplicitCapacity方法中,当发现要存储元素的容量(size+1)大于elementData的长度时,则调用grow方法对数组扩容。

[3]处调用ensureExplicitCapacity(minCapacity)方法  
   private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)//如果指定的容量超出了elementData数组的现有容量,要扩容
            grow(minCapacity);//[4]
    }        
千呼万唤始出来,grow方法设置数组新容量为旧的数组容量+旧的数组容量右移一位。和size+1(这是能容纳add进来的元素的最小容量minCapacity)比较,如果小于,则使用minCapacity。如果newCapacity超出了MAX_ARRAY_SIZE,还需要调用hugeCapacity方法进一步处理。

[4]处调用了grow(minCapcity)实现扩容
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);//新容量为旧的数组容量+旧的数组容量右移一位
        if (newCapacity - minCapacity < 0)//和minCapacity比较,至少也要能容纳新添加进来的元素
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);//[5]
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);//数组扩容
    }  

当容量超出Integer.MAX_VALUE时(即minCapacity<0),内存溢出。

[5]处调用hugeCapacity(minCapacity)
   private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow.容量超出Integer最大值(即为负数),内存溢出
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?//最大容量为Integer.MAX_VALUE或 MAX_ARRAY_SIZE
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    } 

通过以上几个繁复的方法后,终于可以保证add方法容量够用了,此时add方法在elementData下标size+1处出入元素e即可。

(5)addAll方法

boolean addAll(Collection<? extends E> c):将集合c中的所有元素添加到elementData的末尾。

将要添加的集合调用toArray()转换为数组,并得到数组长度numNew.然后确保elementData的容量(依然调用ensureCapacityInternal方法)足够添加新的集合.。
将要添加的集合中的所有元素复制到elementData从size位置往后的位置,修改size.

 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;//如果要添加的元素中没有元素,返回false
    }

boolean addAll(int index,Collection<? extends E>)重载的方法。从elementData指定下标位置开始添加集合c中的元素。判断需要移动元素的数量。再调用arraycopy具体复制。

public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);//下标检查

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  //确保容量
		//从index及其以后的所有元素都需要移动,以便插入新的集合元素
        int numMoved = size - index;
        //如果需要移动,将index及其以后的所有元素移动到从index+numNew及其以后的位置处
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);
		//将要添加的集合复制进来
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

看完所有集合关于增add,addAll的操作。总结起来,涉及向ArrayList中添加元素之前,要先调用ensureCapacityInternal方法确保内部容量。然后可能会出现接下来的方法调用。

ensureCapacityInternal->ensureExplicitCapacity->grow->hugeCapacity,均是为了保证分配合适的容量用来添加元素。如果涉及根据指定下标添加元素时,需要调用rangeCheckForAdd方法来保证下标的正确性。紧接着,就是向elementData数组中添加元素的相关操作了。如果添加一个元素,直接在相应位置添加。如果添加多个元素,使用System.arraycopy方法或Arrays.copyof方法等进行操作。

(6)看完增(add),接下来再看删(remove)的相关方法。

E remove(int index):list中独有的根据指定下标来移除元素的方法。

先调用rangeCheck方法确保要移除元素的下标的正确性。由于要删除该index下标处的元素,所以index往后的所有元素响应的要向前移动一位。使用System.arraycopy方法,将elementData从index+1到最后的所有元素复制到index开始往后。最后将之前elementData中size-1位置的元素(list中的最后一个元素)置为null。返回移除的值。

public E remove(int index) {
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);
	//需要移动的元素的数量
    int numMoved = size - index - 1;
    if (numMoved > 0)//如果数量大于0,移动
    	//将从index+1以及之后的所有元素向前移动
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;//返回值为移除的值
rangeCheck方法如下

调用rangeCheck方法,检查下标,若下标越界,抛异常    
 private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }   
boolean remove(Object o):该方法在集合中移除指定的元素。在遍历集合过程中,如果找到指定元素,则调用fastRemove方法移除该元素。

remove(Object o):该方法根据指定的元素在集合中移除该元素
  public boolean remove(Object o) {
  		//如果要移除的元素为null,遍历集合找到为null的元素移除
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);//[8]
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
调用fastRemove来移除元素,该方法和remove(int index)方法相比,就少调用了rangeCheck(index)而已,
因为上面的remove(Object o)就是遍历下标,根据下标进行移除操作,所以不会出现下标越界情况,无需判断;

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
    }    


clear方法相比较于remove一个个的移除元素而言,clear方法则是清空集合中的所有元素。

clear
将集合中所有元素的引用设置为null,让垃圾回收器去解决吧.别忘了把size设置为0,代表现在list中没有元素
 public void clear() {
        modCount++;
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        size = 0;
    }
(7)看完了增(add,addAll)删(remove,clear)操作,再来看看改(set)相关的方法。

set(int index,E element):该方法用于在指定下标处替换原来的元素,返回原来的元素。涉及下标相关的都要进行rangeCheck操作。

public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }   
(8)增删改都介绍完了,下面介绍查(get)相关的方法。

get(int index)

get:该方法根据指定下标返回集合中的元素
 public E get(int index) {
        rangeCheck(index);//[7]
        return elementData(index);
    }

indexOf():该方法返回集合中要查询元素第一次出现的下标。

 public int indexOf(Object o) {
    if (o == null) {//如果要查询的元素为null
        for (int i = 0; i < size; i++)
        //直接找到elementData中元素为null的下标并返回即可
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
        //遍历集合,如果equals,则返回下标
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;//查无,则返回-1
}   

boolean contains(Object o):

public boolean contains(Object o) {
 //由于indexOf方法在查询不到指定元素时返回-1,查询到时返回下标,所以通过>=0即可判断是否包含指定元素o
        return indexOf(o) >= 0;
    }
int lastIndexOf(Object o):返回该元素最后一次出现的下标。

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;
}   
(9)toArray方法:该方法将elementData从下标0开始size长度的元素(即所有存储在集合中的元素)复制并返回。

 public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }
T[] toArray(T[] a)重载的方法,需要指定一个类型的数组,并返回该类型的数组。如果指定的数组大小小于size,则返回一个包含ArrayList中所有元素的新数组,否则复制并将size下标处的元素设置为null

public <T> T[] toArray(T[] a)
 public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;//a数组size下标处置为null
        return a;
    }
举个例子。

@Test
	public void testToArray(){
		List<Integer> list=new ArrayList<Integer>();
		for(int i=0;i<5;i++){
			list.add(i);
		}
		Integer[] arr=list.toArray(new Integer[]{11,22,33,44,55,66,77,88});
		System.out.println(Arrays.toString(arr));//[0, 1, 2, 3, 4, null, 77, 88]
	}
就说到这里,写了好几个小时,准备了好久,有不足之处和错误之处还望指正。







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FlyingZCC

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值