ArrayList源码分析

一、类的定义

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

类的继承关系:

二、ArrayList的属性


//数据存储在当前数组中,其中  ArrayList的容量就是当前数组的容量,
//默认长度是 DEFAULT_CAPACITY.private transient Object[] elementData;

private int size;//ArrayList的容量,表示 elementData数组中包含的元素的个数。

private static final int DEFAULT_CAPACITY = 10;//默认的长度。

private static final Object[] EMPTY_ELEMENTDATA = {};//默认的空元素

关键是 Object[] ,所有的元素被存储在当前的Obejct的数组中。同时size中记录了当前ArrayList的容量。所有的操作最终都会转变为对这两个对象的操作。这两个对象就是当前类的状态变量。

 

三、ArrrayList的内部类

//这个是一个私有的内部类

private class Itr implements Iterator<E> {
    int cursor;        // 下一个返回元素的index。
    int lastRet = -1;  // 上一个返回元素的index。-1表示没有返回顾。
    int expectedModCount = modCount; //这个 modCount是在AbstractList中定义的,表示修改的数量。

    public boolean hasNext() {
        return cursor != size; //后面是否还有元素
    }

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification(); //当前数组的元素是否被修改过,否者就报错。
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;//将游标+1
        return (E) elementData[lastRet = i];//返回对应的元素。
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();//看下元素是否被修改过了

        try {
            ArrayList.this.remove(lastRet);//删除对应的元素,其中 modCount会被修改
            cursor = lastRet;//cursor就需要变为上一个返回的元素。
            lastRet = -1; //上一个返回的元素被删除了。
            expectedModCount = modCount;//修改一下变更的元素。
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

//检查一下当前的modCount是否被变过了,如果已经被改变了直接抛异常。
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

ListItr:提供了一个能够双向访问ArrayList内部元素的内部类。这个类在  Itr的基础上扩展了向前访问的功能。

//ListTtr:提供了一个能够对当前元素进行修改的对象的实现。
private class ListItr extends Itr implements ListIterator<E> {
    ListItr(int index) {
        super();
        cursor = index;//可以从当前的数组的中间开始。往前,往后遍历,Itr只能往后。
    }

    public boolean hasPrevious() {//还有前驱元素
        return cursor != 0;
    }

    public int nextIndex() {//返回下一个元素的下表。
        return cursor;
    }

    public int previousIndex() {//返回签约元素的下标
        return cursor - 1;
    }

    @SuppressWarnings("unchecked")
    public E previous() {//返回前面的一个元素。
        checkForComodification();
        int i = cursor - 1;
        if (i < 0)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i;
        return (E) elementData[lastRet = i];
    }

    public void set(E e) {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.set(lastRet, e);
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    public void add(E e) {
        checkForComodification();

        try {
            int i = cursor;
            ArrayList.this.add(i, e);
            cursor = i + 1;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
}

 

 

 

四、构造函数

//初始容量的构造函数:创建对应大小的数组:
public ArrayList(int initialCapacity) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    this.elementData = new Object[initialCapacity];
}

 

无参构造函数是一个空的数组,这个和之前会构造一个 长度为10的数组的实现是不一样的。

public ArrayList() {
    super();
    this.elementData = EMPTY_ELEMENTDATA;
}

 

public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();//首先转变为一个Array。
    size = elementData.length;//直接转变。
    // c.toArray might (incorrectly) not return Object[] (see 6260652)
    if (elementData.getClass() != Object[].class)//如果转变的过程中失败了,就创建一个新的Object[],然后将对应的参数copy进去。
     //Arrays方法中使用到了很多native方法,这些方法得到了虚拟机的额外的支持。包括 newInstance,和arraycopy 方法。
        elementData = Arrays.copyOf(elementData, size, Object[].class);
}

 

四、ArrayList的  关键方法

1) 写入数据

写入数据主要是添加元素到当前Object[]队列当中,这个过程中有一个关键是当添加的数据太多的时候,是如何扩容的。

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}
首先调用了一个ensureCapacityInternal(size  +1 );确保容量是够的。
看下代码:
private void ensureCapacityInternal(int minCapacity) {
    if (elementData == EMPTY_ELEMENTDATA) {//如果当前的队列是一次
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);//根据minCapacity来扩展当前的 数组的长度
}
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;//先修改  modCount

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)//如果需要的长度比 数组的长度大,就需要调用grow的方法。
        grow(minCapacity);
}
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);//首先是尝试 扩大 到1.5倍。
    if (newCapacity - minCapacity < 0) //如果还不够,就使用  指定的大小
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)//如果当前的大小比  最大的MAX_ARRAY_SIZE还大,那么就使用 Integer.MAX最大的。
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);//然后开始cop数据
}

如何插入一个元素,首先是进行容量,如果当前的ArrayList是刚开始创建的话,就创建一个Math.max(DEFAULT_CAPACITY, minCapacity);容量的数组。然后开始判断容量。如果当前数组能够放得下对应的元素话,就返回,首先尝试一下1.5倍够不够,不够就按照需要的空间来扩。然后看下 ARRAY_MAX_SIZE哪个更大,如果不够,那么就使用Integer.MAX来扩容。

 

//将element添加到指定的  位置index上。
public void add(int index, E element) {
    rangeCheckForAdd(index);//判断Index。在size以内

    ensureCapacityInternal(size + 1);  // Increments modCount!!  确定是否需要扩容。
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);//将所有的数据往后copy一个。
    elementData[index] = element;//写入数据。
    size++;//将array的大小增加一个。
}

 

//添加所有的 数组
把 容器 c  写到  index后面的数组中,
public boolean addAll(int index, Collection<? extends E> c) {//将Collection  添加到  当前数组中。
    rangeCheckForAdd(index);//手下判断当前de
    boolean modified = false;
    for (E e : c) { //依次将当前序列中的数据写到  新的队列中来。
        add(index++, e);
        modified = true;
    }
    return modified;
}

2)读取数据

//获取  index位置上的 元素
public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}

3)遍历数据

// 返回一个Iterator实例Itr,这个实例是当前对象的匿名内部类。
//能够通过当前的 Iterator来访问。这个当中涉及到内部类和 迭代器模式的内容,后期单独讲。
public Iterator<E> iterator() {
    return new Itr();
}

 

//将当前 List转变为一个数组。这个过程中,如果接收的数组,就会创建一个新的数组,将当前内存中的数组copy进去。
//如果接受的数组的大小大于 List的大小, 需要将接受数组中,array大小的下一位的值设置为null值。
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;
    return a;
}

如果  数组a 空间不够,创建一个新的数组来放数据。如果空间够的话,就将当前的数据拷贝到 a中,将最后一个位置设置为null。后面的数据不变。

例如  

ArrayList list  = {"0","1"}; 
String[5]  a = {"a","b","c","d","e"};  
list.toArray(a).
结果, a= {"0","1",null,"d","e"}

 

4)删除数据

//删除所有的内容
public void clear() {
    modCount++;

    // clear to let GC do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;//将当前的对象中的所有的字段都变为  null,当前List中的值变为 null。

    size = 0;
}

 

//删除index位置上的某个参数

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;
}

首先找出需要移动位置的数据的个数,然后将index后的所有数据往前移动一个,最后一步,将size减一,

然后把最后一个位置上的值 设置为null。这一步很关键,避免内存泄露。

 

//删除某个值,这个当中需要对null和非null进行分开处理。

public boolean remove(Object o) {
    if (o == null) {//如果这个是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])) {//如果当前的对象不为null使用  equals更加的合适。
                fastRemove(index);
                return true;
            }
    }
    return false;//当前系统中根本就不存在当前的对象。
}

 

//批量删除
public boolean retainAll(Collection<?> c) {
    return batchRemove(c, true);//调用本地的批量删除。
}

//看下到底是如何批量删除的。
private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;//当前的数组中的对象
    int r = 0, w = 0;//
    boolean modified = false;
    try {
        for (; r < size; r++)
            if (c.contains(elementData[r]) == complement)//这里为真表示当前的elementData[r]是需要留下来的元素。
                elementData[w++] = elementData[r]; // 表示elementData[w] 中是所有不会被删除的元素,这些元素都会被移动元素的最左边。和GC中的标记-整理算法的实现相类似。
    } finally {
        // Preserve behavioral compatibility with AbstractCollection,
        // even if c.contains() throws.
        if (r != size) {
            System.arraycopy(elementData, r,
                             elementData, w,
                             size - r);
            w += size - r;
        }
        if (w != size) {//如果有留下的参数和原有的长度不相等,说明有参数被删除,这个时候需要将多余的位置给清空。

            // clear to let GC do its work
            for (int i = w; i < size; i++)//清空对应的位置。
                elementData[i] = null;
            modCount += size - w;//表示变动了数据的个数
            size = w;
            modified = true;
        }
    }
    return modified;
}

批量删除和GC中的标记整理算法有点相似,在gc中 标记和清理是分为两个步骤做的,但是在这里一步做了。

 

5)其他方法

/**

*找出当前元素在ArrayList中的位置。

*其中null值和  非null值需要分开处理。

**/

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中相关的  remove中相关的方法,一旦某一个位置上的元素被删除后,这个时候需要将对应的位置 设置为  elementData[index] = null.这个是最典型的防止内存泄露的方式。

转载于:https://my.oschina.net/u/3388174/blog/875674

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值