Java 集合--ArrayList

ArrayList实现了List接口,是顺序容器,即元素存放的数据与放进去的顺序相同,允许放入null元素,底层通过数组实现。除该类未实现同步外,其余跟Vector大致相同。

每个ArrayList都有一个容量(capacity),表示底层数组的实际大小,容器内存储元素的个数不能多于当前容量。当向容器中添加元素时,如果容量不足,容器会自动增大底层数组的大小。size(), isEmpty(), get(), set()方法均能在常数时间内完成,add()方法的时间开销跟插入位置有关,addAll()方法的时间开销跟添加元素的个数成正比。其余方法大都是线性时间。

初始容量为10,按照1.5倍扩容。

ArrayList 有个 trimToSize() 方法用来缩小 elementData 数组的大小,这样可以节约内存。考虑这样一种情形,当某个应用需要,一个 ArrayList 扩容到比如 size=10000,之后经过一系列 remove 操作 size=15,在后面的很长一段时间内这个 ArrayList 的 size 一直保持在 < 100 以内,那么就造成了很大的空间浪费,这时候建议显式调用一下 trimToSize() 这个方法,以优化一下内存空间。或者在一个 ArrayList 中的容量已经固定,但是由于之前每次扩容都扩充 50%,所以有一定的空间浪费,可以调用 trimToSize() 消除这些空间上的浪费。

 

ArrayList的继承关系

java.lang.Object
   ↳     java.util.AbstractCollection<E>
         ↳     java.util.AbstractList<E>
               ↳     java.util.ArrayList<E>

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

ArrayList构造函数

// 默认构造函数
ArrayList()

// capacity是ArrayList的默认容量大小。当由于增加数据导致容量不足时,容量会添加上一次容量大小的一半。
ArrayList(int capacity)

// 创建一个包含collection的ArrayList
ArrayList(Collection<? extends E> collection)

ArrayList的API

// Collection中定义的API
boolean             add(E object)
boolean             addAll(Collection<? extends E> collection)
void                clear()
boolean             contains(Object object)
boolean             containsAll(Collection<?> collection)
boolean             equals(Object object)
int                 hashCode()
boolean             isEmpty()
Iterator<E>         iterator()
boolean             remove(Object object)
boolean             removeAll(Collection<?> collection)
boolean             retainAll(Collection<?> collection)
int                 size()
<T> T[]             toArray(T[] array)
Object[]            toArray()
// AbstractCollection中定义的API
void                add(int location, E object)
boolean             addAll(int location, Collection<? extends E> collection)
E                   get(int location)
int                 indexOf(Object object)
int                 lastIndexOf(Object object)
ListIterator<E>     listIterator(int location)
ListIterator<E>     listIterator()
E                   remove(int location)
E                   set(int location, E object)
List<E>             subList(int start, int end)
// ArrayList新增的API
Object               clone()
void                 ensureCapacity(int minimumCapacity)
void                 trimToSize()
void                 removeRange(int fromIndex, int toIndex)

 

ArrayList源码分析

基于jdk1.7

ArrayList类的属性:

private static final int DEFAULT_CAPACITY = 10; // 集合的默认容量
private static final Object[] EMPTY_ELEMENTDATA = {}; // 一个空集合数组,容量为0
private transient Object[] elementData; // 存储集合数据的数组,默认值为null
private int size; // ArrayList集合中数组的当前有效长度,比如数组的容量是5,size是1 表示容量为5的数组目前已经有1条记录了,其余4条记录还是为空

三个构造函数:

public ArrayList(int initialCapacity) { // 带有集合容量参数的构造函数
    // 调用父类AbstractList的方法构造函数
    super();
    if (initialCapacity < 0) // 如果集合的容量小于0,这明显是个错误数值,直接抛出异常
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    this.elementData = new Object[initialCapacity]; // 初始化elementData属性,确定容量
}

public ArrayList() { // 没有参数的构造函数
    super(); // 调用父类AbstractList的方法构造函数
    this.elementData = EMPTY_ELEMENTDATA; // 让elementData和ArrayList的EMPTY_ELEMENTDATA这个空数组使用同一个引用
}

public ArrayList(Collection<? extends E> c) { // 参数是一个集合的构造函数
    elementData = c.toArray(); // elementData直接使用参数集合内部的数组
    size = elementData.length; // 初始化数组当前有效长度
    // c.toArray方法可能不会返回一个Object[]结果,需要做一层判断。这个一个Java的bug,可以在http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6260652查看
    if (elementData.getClass() != Object[].class)
        elementData = Arrays.copyOf(elementData, size, Object[].class);
}

add(E e) 方法:

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 调用ensureCapacityInternal,参数是集合当前的长度。确保集合容量够大,不够的话需要扩容
    elementData[size++] = e; // 数组容量够的话,直接添加元素到数组最后一个位置即可,同时修改集合当前有效长度
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == EMPTY_ELEMENTDATA) { // 如果数组是个空数组,说明调用的是无参的构造函数
        // 如果调用的是无参构造函数,说明数组容量为0,那就需要使用默认容量
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}    

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // 如果集合需要的最小长度比数组容量要大,那么就需要扩容,已经放不下了
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

private void grow(int minCapacity) { // 扩容的实现
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 长度扩大1.5倍
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 将数组拷贝到新长度的数组中
    elementData = Arrays.copyOf(elementData, newCapacity);
}

add(int index, E element) 方法:

这个方法的作用是 在指定位置插入数据,该方法的缺点就是如果集合数据量很大,移动元素位置将会花费不少时间。

public void add(int index, E element) {
    rangeCheckForAdd(index); // 检查索引位置的正确的,不能小于0也不能大于数组有效长度
    ensureCapacityInternal(size + 1);  // 扩容检测
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index); // 移动数组位置,数据量很大的话,性能变差
    elementData[index] = element; // 指定的位置插入数据
    size++; // 数组有效长度+1
}

上图就表示要在容量为5的数组中的第4个位置插入6这个元素,会进行3个步骤:

  1. 容量为5,再次加入元素,需要扩容,扩容出2个白色的空间
  2. 扩容之后,5和4这2个元素都移到后面那个位置上
  3. 移动完毕之后空出了第4个位置,插入元素6

remove(int 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; // 清楚对应位置上的对象,让gc回收
  return oldValue;
}

比如要移除5个元素中的第3个元素,首先要把4和5这2个位置的元素分别set到3和4这2个位置上,set完之后最后一个位置也就是第5个位置set为null。

 

remove(Object o)

// 跟remove索引元素一样,这个方法是根据equals比较
public boolean remove(Object o) {
    if (o == null) {
        // ArrayList允许元素为null,所以对null值的删除在这个分支里进行
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        // 效率比较低,需要从第1个元素开始遍历直到找到equals相等的元素后才进行删除,删除同样需要移动元素
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

 

clear

清除list中的所有数据

public void clear() {
  modCount++;

  // 遍历集合数据,全部set为null
  for (int i = 0; i < size; i++)
      elementData[i] = null;

  size = 0; // 数组有效长度变成0
}

 

set(int index, E element)

用element值替换下标值为index的值

public E set(int index, E element) {
  rangeCheck(index); // 检查索引值是否合法

  E oldValue = elementData(index); 
  elementData[index] = element; // 直接替换
  return oldValue;
}

 

get(int index)

得到下标值为index的元素

public E get(int index) {
    rangeCheck(index); // 检查索引值是否合法

    return elementData(index); // 直接返回下标值
}

 

addAll

在列表的结尾添加一个Collection集合

public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // 扩容检测
    System.arraycopy(a, 0, elementData, size, numNew); // 直接在数组后面添加新的数组中的所有元素
    size += numNew; // 更新有效长度
    return numNew != 0;
}

 

toArray

根据elementData数组拷贝一份新的数组

public Object[] toArray() {
    return Arrays.copyOf(elementData, size);
}

 

 

ArrayList的注意点

  1. 当数据量很大的时候,ArrayList内部操作元素的时候会移动位置,很耗性能
  2. ArrayList虽然可以自动扩展长度,但是数据量一大,扩展的也多,会造成很多空间的浪费
  3. ArrayList有一个内部私有类,SubList。ArrayList提供一个subList方法用于构造这个SubList。这里需要注意的是SubList和ArrayList使用的数据引用是同一个对象,在SubList中操作数据和在ArrayList中操作数据都会影响双方。
  4. ArrayList允许加入null元素

 

posted @ 2016-06-08 21:26  Hesier  阅读(243)  评论(0编辑  收藏  举报