ArrayList源码解析

List

ArrayList

重要属性
  1. 通过无参构造方法创建ArrayLit 的时候,默认的数组空间是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}, 在第一次添加元素的时候会扩容并初始化数组的大小 ,所以对于需要添加元素的ArrayList的初始化尽量指定数组容量,避免扩容
    /**
     * 数组默认容量
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 数组为空时,默认的对象值
     * 参考 new ArrayList(0);
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 用于提供默认大小的实例的共享空数组实例.
     * 参考 new ArrayList()
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 存储ArrayList 元素的数组,用transient修饰 序列化时并非序列化整个数组,而是逐个序列化数组中的元素
     */
    transient Object[] elementData; // non-private to simplify nested class access 不使用private修饰简化内部类的访问

    /**
     * ArrayList元素个数,可读取的数据个数
     *
     * @serial
     */
    private int size;

	/**
	* 继承自父类AbstractList, 用于记录集合的修改(添加,删除元素,清除集合)次数
	*/
	protected transient int modCount = 0;
  1. 为什么 elementData 被transient 修改?

    1. elementData里面不是所有的空间都有数据,因为容量的问题,elementData里面有一些元素是空的,这种是没有必要序列化的,可以避免序列化空的元素, 本质是代码性能优化问题
  2. ArrayList 如何支持序列化

    • ArrayList 实现了writeObject() 和 readObject() ,通过for 循环的方式逐个序列化或者反序列化数组中的元素

    • private void writeObject(java.io.ObjectOutputStream s)
             throws java.io.IOException{
             // Write out element count, and any hidden stuff
             int expectedModCount = modCount;
             s.defaultWriteObject();
       
             // Write out size as capacity for behavioural compatibility with clone()
             s.writeInt(size);
       
             // Write out all elements in the proper order.
             for (int i=0; i<size; i++) {
                 s.writeObject(elementData[i]);
             }
       		// 序列化的过程中元素发生变更(新增,删除,清除)抛出异常
             if (modCount != expectedModCount) {
                 throw new ConcurrentModificationException();
             }
         }
      
    • 反序列化,反序列化后会返回一个新对象

         private void readObject(java.io.ObjectInputStream s)
              throws java.io.IOException, ClassNotFoundException {
              elementData = EMPTY_ELEMENTDATA;
         // Read in size, and any hidden stuff
          s.defaultReadObject();
      
          // Read in capacity
          s.readInt(); // ignored
      
          if (size > 0) {
              // be like clone(), allocate array based upon size not capacity
              int capacity = calculateCapacity(elementData, size);
              SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
              ensureCapacityInternal(size);
      
              Object[] a = elementData;
              // Read in all elements in the proper order.
              for (int i=0; i<size; i++) {
                  a[i] = s.readObject();
              }
          }
      }
    
  3. modCount 的作用?

    • 通常在迭代器中校验集合修改次数和预计修改次数是否一致,如果不一致会抛出ConcurrentModificationExceptions
  4. 为什么会出现 ConcurrentModificationExceptions 异常?为什么集合的遍历更新操作不要直接使用ArrayList 提供的 add 和 remove 方法?

    • 迭代时直接通过集合的更新方法(remove,add)修改集合元素会导致modCount被修改,导致和迭代器中维护的预期的修改次数expectedModCount不一致,从而抛出异常

      • expectedModCount : Itr类里有一个成员变量expectedModCount,它的值为创建Itr对象的时候List的modCount值

      • final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
        
    • 迭代器通过游标cursor的方式记录当前下标,指向对应的元素, 当直接通过ArrayList 调用 add, remove 方法时,会导致size 的变更,从而导致数据越界或者读取不到对于的元素 操作前校验 fail-fast

          public E next() {
                  checkForComodification();
                  int i = cursor;
                  if (i >= size)
                      throw new NoSuchElementException();
                  Object[] elementData = ArrayList.this.elementData;
                  if (i >= elementData.length)
                      throw new ConcurrentModificationException();
                  cursor = i + 1;
                  return (E) elementData[lastRet = i];
              }
      
  5. 为什么ArrayList 的遍历更新要 通过 迭代器的 remove 和 add 方法

    • 迭代器更新元素的时候会同步更新迭代器的 游标 cursor 和 expectedModCount

    •     public void remove() {
                  if (lastRet < 0)
                      throw new IllegalStateException();
                  checkForComodification();
      
                  try {
                      ArrayList.this.remove(lastRet);
                      cursor = lastRet; 
                      lastRet = -1;
                      expectedModCount = modCount;
                  } catch (IndexOutOfBoundsException ex) {
                      throw new ConcurrentModificationException();
                  }
              }
      
主要api
插入方法 add
  1. 新增元素前会校验数组的长度,当数组长度小于新增后的长度时会进行扩容
  2. 扩容后的大小为原来的1.5倍,最大容量是Integer 的最大值,并且会考虑溢出
  3. 扩容其实是将旧数组复制到一个容量更大的新数组, elementData 指向新数组
 public boolean add(E e) {
     ensureCapacityInternal(size + 1);  // 修改modCount, 扩容
     elementData[size++] = e;
     return true;
 }

 private void ensureCapacityInternal(int minCapacity) {
     ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
 }

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // 当数组长度小于需要的长度时,ArrayList 进行扩容 考虑内存溢出
    if (minCapacity - elementData.length > 0)
        grow(minCapacity); // 扩容
}

 private void grow(int minCapacity) {
     // overflow-conscious code
     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);  // 超大容量, ArrayList 的最大容量是 Integer 的最大值
     // 此处可以看出,扩容其实是将旧数组复制到一个容量更大的新数组, elementData 指向新数组
     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;
}

删除元素 remove
  1. 移除实际是通过System.arrayCopy对原数组进行截取操作, 指定位置插入public void add(int index, E element)也是同理的操作
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;
}
截取子集合 subList
  1. 截取的子集合 和 ArrayList 共用同一个数组,SubList 只定义了重新指向该数组的偏移量,对于子集合元素的修改都会改变原集合内容
 public List<E> subList(int fromIndex, int toIndex) {
     subListRangeCheck(fromIndex, toIndex, size);
     return new SubList(this, 0, fromIndex, toIndex);  // SubList 为ArrayList 的一个内部类
 }

// subList 构造方法 只定义了重新指向该数组的偏移量
SubList(AbstractList<E> parent,
        int offset, int fromIndex, int toIndex) {
    this.parent = parent;
    this.parentOffset = fromIndex;
    this.offset = offset + fromIndex;
    this.size = toIndex - fromIndex;
    this.modCount = ArrayList.this.modCount;
}

// SubList 的 set 方法
public E set(int index, E e) {
    rangeCheck(index);
    checkForComodification();
    E oldValue = ArrayList.this.elementData(offset + index);
    ArrayList.this.elementData[offset + index] = e; // 更新外部类的数组元素
    return oldValue;
}

public E get(int index) {
    rangeCheck(index);
    checkForComodification();
    return ArrayList.this.elementData(offset + index); // 从外部类获取元素
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值