Java ArrayList

Java ArrayList,本文将介绍Java中真正的动态数组容器类ArrayList。

基本用法

ArrayList是一个泛型容器,新建ArrayList需要实例化泛型参数,比如:

ArrayList<Integer> intList = new ArrayList<Integer>();
ArrayList<String> strList = new ArrayList<String>();

ArrayList的主要方法有:

public boolean add(E e) //添加元素到末尾
public boolean isEmpty() //判断是否为空
public int size() //获取长度
public E get(int index) //访问指定位置的元素
public int indexOf(Object o) //查找元素, 如果找到,返回索引位置,否则返回-1
public int lastIndexOf(Object o) //从后往前找
public boolean contains(Object o) //是否包含指定元素,依据是equals方法的返回值
public E remove(int index) //删除指定位置的元素, 返回值为被删对象
//删除指定对象,只删除第一个相同的对象,返回值表示是否删除了元素
//如果o为null,则删除值为null的元素
public boolean remove(Object o)
public void clear() //删除所有元素
//在指定位置插入元素,index为0表示插入最前面,index为ArrayList的长度表示插到最后面
public void add(int index, E element)
public E set(int index, E element) //修改指定位置的元素内容

这些方法简单直接,就不多解释了,我们看个简单示例:

ArrayList<String> strList = new ArrayList<String>();
strList.add("酷客");
strList.add("教程");
for(int i=0; i<strList.size(); i++){
    System.out.println(strList.get(i));
}

输出结果如下:
Java ArrayList

基本原理

Array-List的基本原理与DynaArray类似,内部有一个数组elementData,一般会有一些预留的空间,有一个整数size记录实际的元素个数(基于Java 7),如下所示:

private transient Object[] elementData;
private int size;

我们暂时可以忽略transient这个关键字。各种public方法内部操作的基本都是这个数组和这个整数,elementData会随着实际元素个数的增多而重新分配,而size则始终记录实际的元素个数。

下面,我们具体来看下add和remove方法的实现。add方法的主要代码为:

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

它首先调用ensureCapacityInternal确保数组容量是够的,ensureCapacityInternal的代码是:

private void ensureCapacityInternal(int minCapacity) {
    if(elementData == EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}

它先判断数组是不是空的,如果是空的,则首次至少要分配的大小为DEFAULT_CAPACITY, DEFAULT_CAPACITY的值为10,接下来调用ensureExplicitCapacity,主要代码为:

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    if(minCapacity - elementData.length > 0)
        grow(minCapacity);
}

modCount++是什么意思呢?modCount表示内部的修改次数,modCount++当然就是增加修改次数,为什么要记录修改次数呢?我们待会解释。

如果需要的长度大于当前数组的长度,则调用grow方法,其主要代码为:

private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    //右移一位相当于除2,所以,newCapacity相当于oldCapacity的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //如果扩展1.5倍还是小于minCapacity,就扩展为minCapacity
    if(newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    elementData = Arrays.copyOf(elementData, newCapacity);
}

代码中已有注释说明,不再赘述。我们再来看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; //将size减1,同时释放引用以便原对象被垃圾回收
    return oldValue;
}

它也增加了modCount,然后计算要移动的元素个数,从index往后的元素都往前移动一位,实际调用System.arraycopy方法移动元素。elementData[--size] = null;这行代码将size减1,同时将最后一个位置设为null,设为null后不再引用原来对象,如果原来对象也不再被其他对象引用,就可以被垃圾回收。

上面的代码中,为便于理解,我们删减了一些边界情况处理的代码,完整代码要晦涩复杂一些,但接口一般都是简单直接的,这就是使用容器类的好处,这也是计算机程序中的基本思维方式,封装复杂操作,提供简化接口。

ArrayList特点

对于ArrayList,它的特点是内部采用动态数组实现,这决定了以下几点。

  • 可以随机访问,按照索引位置进行访问效率很高,用算法描述中的术语,效率是O(1),简单说就是可以一步到位。
  • 除非数组已排序,否则按照内容查找元素效率比较低,具体是O(N), N为数组内容长度,也就是说,性能与数组长度成正比。
  • 添加元素的效率还可以,重新分配和复制数组的开销被平摊了,具体来说,添加N个元素的效率为O(N)
  • 插入和删除元素的效率比较低,因为需要移动元素,具体为O(N)

酷客教程相关文章:

赞(0)

评论 抢沙发

评论前必须登录!