JAVA ArrayList原理分析

作者: 0d25a2e2a50a | 来源:发表于2017-09-06 16:36 被阅读0次

    文章中用到的代码是基于JDK1.7.0_79

    ArrayList是有序、可以重复、可以为null的数据集合,底层是使用数组进行实现的,数组容量可以自动增长,除了实现List接口外,还提供了其他的一系列操作数组的方法。

    ArrayList实现了Serializable接口,因此它可以序列化,能够通过序列化传输,实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标序号进行快速访问,实现了Cloneable接口,能被克隆。

    ArrayList本质是数组

    transientObject[] elementData;

    ArrayList有提供三个构造方法

    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;

    }

    public ArrayList(Collection 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);

    }

    第一个构造方法,设置初始化大小,第二个构造方法,指定了数据为空数组,默认容量大小是10,第三个是构造一个包含指定collection的元素的列表。

    添加数据有以下几个方法

    //该方法是用指定的对象替换数组中指定位置的对象,并且返回原来的对象

    public E set(int index, E element) {

    rangeCheck(index);

    E oldValue = elementData(index);

    elementData[index] = element;

    return oldValue;

    }

    //该方法是在数组末尾添加一个对象

    public boolean add(E e) {

    ensureCapacityInternal(size + 1);  // Increments modCount!!

    elementData[size++] = e;

    return true;

    }

    //该方法是在数组中指定的位置插入一个对象,该位置后面的对象依次后移

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

    }

    //该方法是将指定的Collection对象插入到数组的末尾

    public boolean addAll(Collection 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;

    }

    //该方法是将指定的Collection对象插入到指定位置,如果指定位置后面还有对象,后面的对象依次后移

    public boolean addAll(int index, Collection 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;

    }

    读取数据有以下一个方法,返回指定位置的一个元素

    public E get(int index) {

    rangeCheck(index);

    return elementData(index);

    }

    E elementData(int index) {

    return (E) elementData[index];

    }

    删除一个对象有两个方法,分别可以根据数组下标和对象进行删除,如果是根据数组下标进行删除的话,会返回被删除的对象,如果是根据对象进行删除,返回的是boolean,因为ArrayList允许重复,一个实例中可能有多个一样的对象,如果是根据对象进行删除,只会删除数组中第一次出现的对象,删除一个对象之后,该对象所在位置后面如果还有对象,后面的对象都会向左移动一个位置。

    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;

    }

    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;

    }

    清空数据有2个方法,分别是clear()和removeAll(Collection c);

    public void clear() {

    modCount++;

    // clear to let GC do its work

    for (int i = 0; i < size; i++)

    elementData[i] = null;

    size = 0;

    }

    clear()在循环遍历ArrayList,并且将每一个元素都置为null,使它们在没有被外部引用的情况下可以被垃圾回收。

    public boolean removeAll(Collection c) {

    return batchRemove(c, false);

    }

    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++)

    //删除的话,complement=false,如果该对象不在指定的集合中,则将该对象放在数组前面,遍历完成之后,elementData数组中w后面的对象是跟前面重复了,可以直接删除

    if (c.contains(elementData[r]) == complement)

    elementData[w++] = elementData[r];

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

    }

    //如果w!=size,说明有对象被删除了,则从w开始到后面的元素都直接设置为null,等待gc回收

    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;

    }

    removeAll可以只删除部分数组,这个方法会检查数组中每个对象是否包含在特定的集合中。如果存在,则去掉。因为会用到contains方法,removeAll的复杂度是O(n^2)。所以在想要重置一个大的ArrayList时,这种方法是绝对不可取的。

    根据以上代码分析,如果是删除所有数据,使用clear()方法效率会更高。

    从上面的代码可以看出,每次在调用add()方法添加对象的时候,都会调用方法ensureCapacityInternal(size + 1)

    private void ensureCapacityInternal(int minCapacity) {

    //如果当期数组是空的,则设置容量长度取默认长度与传进来长度的最大值,默认长度是10

    if (elementData == EMPTY_ELEMENTDATA) {

    minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);

    }

    ensureExplicitCapacity(minCapacity);

    }

    private void ensureExplicitCapacity(int minCapacity) {

    modCount++;

    // overflow-conscious code

    //如果minCapacity的值大于add数据之前的大小,就调用grow方法,进行扩容,否则什么也不做。

    if (minCapacity - elementData.length > 0)

    grow(minCapacity);

    }

    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    private void grow(int minCapacity) {

    // overflow-conscious code

    int oldCapacity = elementData.length;

    //设置新的容量长度为原来长度的1.5倍(JDK1.6是1.5倍+1)

    int newCapacity = oldCapacity + (oldCapacity >> 1);

    //检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量,

    if (newCapacity - minCapacity < 0)

    newCapacity = minCapacity;

    //检查新容量是否超出了ArrayList所定义的最大容量,若超出了,则调用hugeCapacity()来比较minCapacity

    //和 MAX_ARRAY_SIZE,如果minCapacity大于最大容量,则新容量则为ArrayList定义的最大容量,

    //否则,新容量大小则为 minCapacity。

    if (newCapacity - MAX_ARRAY_SIZE > 0)

    newCapacity = hugeCapacity(minCapacity);

    // minCapacity is usually close to size, so this is a win:

    //数组拷贝,每次调用add(),都需要拷贝一次数组

    elementData = Arrays.copyOf(elementData, newCapacity);

    }

    每次调用add()方法后,数组容量就会扩大1.5倍,所以如果事先能够确定数组长度的话,就直接设置好长度,避免每次调用add的时候进行数组重新拷贝以及内存开销。

    ArrayList的clone()是浅拷贝,返回当前ArrayList实例的一个副本,对副本进行添加或者移除对象,不影响原来的ArrayList对象

    public Object clone() {

    try {

    @SuppressWarnings("unchecked")

    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();

    }

    }

    ArrayList a = new ArrayList();

    People p1 = new People(20,"aaa");

    People p2 = new People(30,"bbb");

    a.add(p1);

    a.add(p2);

    ArrayList b = (ArrayList) a.clone();

    b.remove(p2);

    System.out.println(a);

    System.out.println(b);

    [ArrayListTest$People@f0d1e0b, ArrayListTest$People@262f6be5]

    [ArrayListTest$People@f0d1e0b]

    如果有误,欢迎指正

    相关文章

      网友评论

        本文标题:JAVA ArrayList原理分析

        本文链接:https://www.haomeiwen.com/subject/pyagvttx.html