Java集合之ArrayList

670 阅读3分钟

一、概述

底层存储结构:数组

当数组存储的容量达到上限,则需要进行扩容,ArrayList扩容的规则是超出限制时增加当前容量的50%,即

newCapacity = oldCapacity + (oldCapacity >> 1);

ArrayList默认初始容量为10;

数组的优势在于查找和修改某值的效率高,因而ArrayList按数组下标访问第i个元素(get(i))或修改第i个元素(set(i, o))的效率高。


在数组末尾插入元素add(o)的效率也高;

但是基于下标插入(add(i, o))或删除某个元素(remove(i),remove(o)此处若ArrayList中对象为Integer类型,若为int,则移除对应的下标,若为Integer,则移除元素)

对于中间插入和删除的情况,则会使用System.arraycopy()来移动部分受影响的元素,性能就有所下降,则也是数组的数据结构的缺点。


二、add()方法

该方法的底层源码如下:

public boolean add(E e){
   ensureCapacityInternal(size + 1);
   elementData[size ++] = e;
   return true;
}

ensureCapacityInternal()方法是自动扩容机制的核心,先附上源码:

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(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;
    // 内存扩张,即oldCapacity + oldCapacity/2
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 如果扩为1.5倍还不满足需求,直接扩为需求值
    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);
}

通过让目前容量加1,与当前的最大容量比较,若超过则进行扩容,否则直接添加到集合中即可。

扩容的规则是目前容量加上目前容量的一半,为扩容后容量,即:

newCapacity = oldCapacity + (oldCapacity >> 1);

底层通过Arrays.copyOf()进行扩容。


对于add(int index, E element)方法,先附上其源码:

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

该方法是先将数组从index + 1起的元素使用System.arraycopy()方法向后移动移位。

注意这儿使用数组复制方法是System.arraycopy()方法。文章最后补充这两种方法的区别。


三、set和get方法

Array的set和get方法比较简单,先检测index是否越界,不越界则执行赋值或访问。

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

   return elementData(index);
}

public E set(int index, E element){
   rangeCheck(index);
   
   E oldValue = elementData(index);
   elementData[index] = element;
   return oldValue;
}

注意:set()方法返回值,是返回旧值,这儿需注意下。


四、remove方法

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

在该方法中注意将数组长度size最后一个置位null,释放内存空间。


补充:数组拷贝主要的四种方法:

分别是循环赋值、System.arraycopy()、Arrays.copyOf()(或者Arrays.copyOfRange)和close()方法。

  • 循环拷贝(速度相对比较慢),即for循环,可以进行深拷贝或者浅拷贝
  • System.arraycopy()(浅拷贝),这是系统提供的拷贝方法,也是我们推荐使用的拷贝方式,它是浅拷贝!!!

底层是通过C或者C++实现的,即native修饰的方法,因而速度比较快。

public static native void arraycopy(Object src, int sroPos, Object dest, int destPos, int length);

  • Arrays.copyOf()(浅拷贝)

底层是通过调用System.arraycopy()实现的

public static byte[] copyOfRange(byte[] original, int from, int to) {                   
   int newLength = to - from;
   if (newLength < 0)
       throw new IllegalArgumentException(from + " > " + to);
       byte[] copy = new byte[newLength];
       System.arraycopy(original, from, copy, 0,
              Math.min(original.length - from, newLength));
        return copy;
    }
}

  • Object.close(),close()比较特殊,对于对象而已,它是深拷贝,但是对于数组而言,它是浅拷贝。