Java源码阅读-ArrayList

ArrayList

ArrayList是由数组支持实现的,内部封装了一个数组,下面介绍一下ArrayList内部的一些属性(其中包含三个数组,若暂时不理解可以继续向下看构造函数的实现,会有助于理解这三个数组的用途)

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

/**
* 长度为0的空数组,用于对该集合中elementData初始化
*/
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
* 长度为0的空数组,用于对该集合中elementData初始化。
* 该空数组与EMPTY_ELEMENTDATA的区别是,如果用这个数组初始化,那么在第一次添加数据的时* 候,数组长度会被扩展到10
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
* 用于存放集合元素的数组
* 非private权限,便于嵌套类访问
*/
transient Object[] elementData;

/**
* 集合中的元素个数
*/
private int size;

之前说过,Collection集合必须包含两个类型的构造函数(参考java集合类),ArrayList中有三种构造器,我们来分别看一下这三种构造器的内部实现

/**
* 第一个构造器
* 初始化一个指定长度的数组,用于以后存放集合元素,若指定的长度为0,则用
* EMPTY_ELEMENTDATA去初始化,创建一个长度为0的数组
*/
public ArrayList(int initialCapacity) {
   if (initialCapacity > 0) {
       this.elementData = new Object[initialCapacity];
   } else if (initialCapacity == 0) {
       this.elementData = EMPTY_ELEMENTDATA;
   } else {
       throw new IllegalArgumentException("Illegal Capacity: "+
                                          initialCapacity);
   }
}

/**
* 第二个构造器
* 初始化一个长度为默认长度(10)的数组,这里用DEFAULTCAPACITY_EMPTY_ELEMENTDATA去初* 始化,上面有介绍,这个数组也是一个长度为0的空数组,但是当存入第一个元素时,长度会被自动* 扩展到默认长度(10),这也是和EMPTY_ELEMENTDATA的主要区别
*/
public ArrayList() {
   this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

/**
* 第三个构造器
* 此构造器用于复制一个集合
*/
public ArrayList(Collection<? extends E> c) {
   elementData = c.toArray();
   if ((size = elementData.length) != 0) {
       if (elementData.getClass() != Object[].class)
           elementData = Arrays.copyOf(elementData, size, Object[].class);
   } else { 
       this.elementData = EMPTY_ELEMENTDATA;
   }
}

看完了三种构造器的源码,应该对ArrayList中的属性有了进一步的了解,终于明白了定义的这三个空数组的用处了。
接下来我们继续看一下ArrayList的扩容操作,虽然我们平时很少用到或没有用到过,但是理解一下这个方法会更好的理解ArrayList这个类

/**
* 此方法在向集合中添加大量元素前使用,以确保容量
* 此处需配合add源码来理解,因为进行add操作时,会调用ensureCapacityInternal来保证容量* 足够插入数据,由于默认大小是10,所以当大批量插入数据时会有频繁的扩容操作,所以我们需要 * 调用此方法来一次性扩容到我们需要的那么大的容量,避免扩容操作过于频繁,提高性能
* 
* minCapacity:所需的最小容量
*/
public void ensureCapacity(int minCapacity) {
   int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)? 0
   : DEFAULT_CAPACITY;
   if (minCapacity > minExpand) {
       ensureExplicitCapacity(minCapacity);
   }
}

/**
* 此操作在ArrayList的add*()操作中会被默认调用,以保证集合内数组的容量
* 
* minCapacity:所需的最小容量
*/
private void ensureCapacityInternal(int minCapacity) {
   if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
       minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
   }
   ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
   modCount++;//这个属性稍后会做介绍
   if (minCapacity - elementData.length > 0)
       grow(minCapacity);
}

/**
* 此属性用来表示要分配的数组的最大大小。
* 一些虚拟机会在数组中存一些头部关键字,所以尝试分配较大数组可能会导致内存溢出
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

/**
* 扩容容量计算
*/
private void grow(int minCapacity) {
   int oldCapacity = elementData.length;
   int newCapacity = oldCapacity + (oldCapacity >> 1);
   if (newCapacity - minCapacity < 0)
       newCapacity = minCapacity;
   if (newCapacity - MAX_ARRAY_SIZE > 0)
       newCapacity = hugeCapacity(minCapacity);
   elementData = Arrays.copyOf(elementData, newCapacity);
}

/*
*扩容操作
*/
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0)
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

上述介绍了两种扩容操作ensureCapacity和ensureCapacityInternal,前者是我们在进行大量插入元素操作时使用(提升性能),后者是ArrayList执行添加操作时默认调用,上面的介绍还留了一个关键点没有讲,就是modCount的来源和用途
modCount:此属性定义在AbstractList抽象类中,顾名思义,就是修改次数的意思,任何对集合的修改都会使这个值加1,用在非线程安全的方法中。集合都是用迭代器(iterator)迭代访问的,那么迭代器初始化的时候会得到这个值,在迭代访问过程中,如果集合中的这个值和迭代器初始化时得到的不一样,那么就会立刻抛出ConcurrentModificationException,避免其他线程对其修改造成的影响对此线程不可见,这也就是所谓的快速失败(fail-fast)机制,所以建议大家在使用非线程安全的集合类的时候,使用迭代器访问
下面我们来看一下”克隆”这个操作:

public Object clone() {
   try {
       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(e);
   }
}

这里用到了super.clone(),这个是Object类中的方法,所以这里的clone是浅拷贝,有以下特点:

public static void main(String[] args) {
    ArrayList collection = new ArrayList<>();
    collection.add(null);
    collection.add("1");
    collection.add("3");
    collection.add("2");
    collection.add("2");
    Object collection_1 = collection.clone();
    System.out.println(collection == collection_1);
    System.out.println(collection.getClass()== collection_1.getClass());
    System.out.println(collection.equals(collection_1));
}

此处输出的结果是false,true,true
下面看一下比较重要的一个操作,就是取元素,这里有两个方法

@SuppressWarnings("unchecked")
E elementData(int index) {
    return (E) elementData[index];
}

public E get(int index) {
    rangeCheck(index);
    return elementData(index);
}

....

private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

private String outOfBoundsMsg(int index) {
    return "Index: "+index+", Size: "+size;
}

get()方法用的其实就是elementData方法,不过前面加上了一个数组边界的判断,细心的人可以发现,rangeCheck中只有一个条件:if (index >= size),此条件可以避免输入的参数超过数组长度,那么如果输入参数小于0呢?这个会由数组Arrays类去判断下标小于0,则会抛出java.lang.ArrayIndexOutOfBoundsException: -xxx
接下来是一个常用的操作,添加元素

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

public void add(int index, E element) {
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1);
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

可以发现,无论是直接插入,还是在指定位置插入(先将要插入位置及后面的元素向后移动,再用新值覆盖要插入的位置),都会先执行以下ensureCapacityInternal,在前面有提到过这个操作的作用和用途,这里不再赘述,可以看上面关于扩容操作的介绍
CRUD操作我们看完了增和查,下面来看一下改和删

public E set(int index, E element) {
    rangeCheck(index);

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

修改操作没什么可说的,它也会进行边界判断,修改后会将原值返回

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; 

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

private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null;
}

删除操作常用的有以上两个,一个是按索引删除,一个是按元素删除。按索引删除会返回被删除元素的值,按元素删除用到了另一个删除方法fastRemove,两种删除操作都用到modCount这个属性,来保证线程安全,并且删除之后,数组元素都会移动位置
ArrayList中有一个内部类subList,该类实际上只是父类list的一个视图,任何对该类元素的修改都会影响父类中的元素,同时,由于快速失败机制,也不允许对已经形成视图(sublist)的list进行add和remove操作
举个例子:

List list = new ArrayList();
list.add("1");
list.add("2");
list.add("3");
list.add("2");
System.out.println(list);
List subList = list.subList(1,3);//前面包含、后面不包含
System.out.println(subList);
//list.add("10");
list.remove(3);
System.out.println(subList);
System.out.println(list);

输出结果是:
[1, 2, 3, 2]
[2, 3]
同时会抛出java.util.ConcurrentModificationException
但是可以进行set操作

List list = new ArrayList();
list.add("1");
list.add("2");
list.add("3");
list.add("2");
System.out.println(list);
List subList = list.subList(1,3);//前面包含、后面不包含
System.out.println(subList);
list.set(1,10);
System.out.println(subList);
System.out.println(list);

输出结果是:
[1, 2, 3, 2]
[2, 3]
[10, 3]
[1, 10, 3, 2]

均为个人理解,欢迎批评指正,谢谢

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值