java arraylist详解

2022-08-25 13:59:27

1,ArrayList的秘密都在构造函数里

public ArrayList() {
  this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

2,接着 elementData 和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA

/**
 * The array buffer into which the elements of the ArrayList are stored.
 * The capacity of the ArrayList is the length of this array buffer. Any
 * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 * will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData;
    
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

翻译elementData的注释

  1. 存储ArrayList元素的数组缓冲区。

  2. ArrayList的容量是这个数组缓冲区的长度。

  3. 添加第一个元素时,任何elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList都将扩展为默认的DEFAULT_CAPACITY容量。

3,接着DEFAULT_CAPACITY

/**
 * Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;

默认容量为10

4,看下添加第一个元素时发生了什么

    /**    
     * Appends the specified element to the end of this list.
    */
    public boolean add(E e) {
       ensureCapacityInternal(size + 1);  // Increments modCount!!
       elementData[size++] = e;
       return true;
    }
  1. 确定内部容量,size作为成员变量private int size;,实例化时默认赋值为0,也就是用(0+1)1去确定内部容量。

  2. size 加 1 ,并给索引位置赋值。

  3. 返回true。

这样添加就完成了,是否扩容并在下标位置赋值。

5,进入ensureCapacityInternal方法

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

如果是空数组,用传入的容量和默认的容量 之中 大的那一个作为初始化容量。也就是10个元素以内容量一直是10;

6,上面是初始化,下面才是真正的确定容量。进入ensureExplicitCapacity方法。

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

修改次数 加1。这个参数用作并发修改控制。

如果需要的容量大于数组的长度,用需要的容量扩容。

7,扩容,grow()

private void grow(int minCapacity) {
        // overflow-conscious code
        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;
    }
  1. 数组的原容量oldCapacity

  2. 准备的容量oldCapacity + (oldCapacity >> 1);>>1向右位移1位,高位补0,即除以2,结果为1.5倍的原容量。

  3. 准备的容量不够,就用需要的容量。

  4. 准备的容量大于最大的数组容量,就取最小的大于需要的容量的容量。

  5. 原则就是实现最小扩容。

8,常问:从list里面移除元素

//从下标处移除元素,注意参数是int,如果是integer,会调用第二个方法。
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;
}

//移除指定元素,快速移除,跳过边界检查并且不返回已删除的值。
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;
}

第一个方法从下标处移除元素,注意参数是int,如果使用integer,会调用第二个方法。

第二个方法移除指定元素,移除的时候也是用的下标移除,但不是调的第一个方法,因为index一定是合法的而且移除的值是用户传进来的。也就是它跳过边界检查并且不返回已删除的值。

重点:list移除只需要调用remove方法,不需要使用循环遍历,迭代器,stream等实现。

9,ConcurrentModificationException

final void checkForComodification() {
   if (expectedModCount != ArrayList.this.modCount)
     throw new ConcurrentModificationException();
}
                
                
  @Override
    public void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        final int expectedModCount = modCount;
        @SuppressWarnings("unchecked")
        final E[] elementData = (E[]) this.elementData;
        final int size = this.size;
        for (int i=0; modCount == expectedModCount && i < size; i++) {
            action.accept(elementData[i]);
        }
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

foreach时,final int expectedModCount = modCount;

modCount已确定且不可修改,而添加移除等方法中会调用checkForComodification方法,所以抛出异常。

10,fail-fast 快速失败

<p><a name="fail-fast">
 * The iterators returned by this class's {@link #iterator() iterator} and
 * {@link #listIterator(int) listIterator} methods are <em>fail-fast</em>:</a>
 * if the list is structurally modified at any time after the iterator is
 * created, in any way except through the iterator's own
 * {@link ListIterator#remove() remove} or
 * {@link ListIterator#add(Object) add} methods, the iterator will throw a
 * {@link ConcurrentModificationException}.  Thus, in the face of
 * concurrent modification, the iterator fails quickly and cleanly, rather
 * than risking arbitrary, non-deterministic behavior at an undetermined
 * time in the future.

Arraylist类73行定义,翻译为

这个类的iterator和listierator方法是快速失败的:如果迭代器被创建,不管何时何地,只要不是调用迭代器自己的remove或add方法,迭代器将抛出一个ConcurrentModificationException。因此如果是并发修改,迭代器会迅速而彻底地失败。翻译的不行,可自行翻译。

11,序列化

transient Object[] elementData;

可见elementData被transient 修饰,也就是不参与序列化,但是如果这个参数都不参与序列化,那反序列化的时候得到的不就是一个空列表吗,所以肯定是参与序列化了,下面看看它怎么被序列化的。

更多内容请访问原文

转载申明:https://www.youfengxin.com/simpleapi/arraylist.html

  • 作者:aqiangzai
  • 原文链接:https://blog.csdn.net/qq_32379477/article/details/111373539
    更新时间:2022-08-25 13:59:27