ArrayList 笔记整理

基于 Java 8

参考链接:https://lrh1993.gitbooks.io/android_interview_guide/content/java/basis/arraylist.html


一、概述

ArrayList 笔记整理_第1张图片

二、涉及的几个常量

// 默认的容量
DEFAULT_CAPACITY = 10

// 空的元素容器,当 new ArrayList(0) 时使用
Object[] EMPTY_ELEMENTDATA = {};

// 默认的为空的元素容器
Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
 * The maximum size of array to allocate.
 * Some VMs reserve some header words in an array.
 * Attempts to allocate larger arrays may result in
 * OutOfMemoryError: Requested array size exceeds VM limit
 */
 // 容器数组的最大容量,因为有些虚拟机会在数组中占用一些额外的头部内容,所以这里会减 8
MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

三、构造方法

// 用于存储元素的数组
transient Object[] elementData;

// 三个构造方法

//(1) 默认 elementDat 为空数组
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

// (2) 根据 initialCapacity 值来设置容器数组的长度
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);
    }
}

// (3) 根据传递的集合来设置 elementData
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

四、常用方法

(1)add(E e)

public boolean add(E e) {
	 //	先确认添加一个元素时,elementData 的大小是否足够
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 在对 elementData 的大小进行处理后(可能会扩容),把目标元素添加在末尾
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    	 // 如果 elementData 原本是空数组,
    	 // 则 minCapacity 重新赋值为 DEFAULT_CAPACITY 和 minCapacity 最大的那一个值
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    // 如果添加元素后的大小大于现在的,则扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

// minCapacity 为最小的可满足的容量大小
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    
    // newCapacity = oldCapacity + oldCapacity/2,即 1.5 倍 oldCapacity
    // 此时 newCapacity 可能大于等于 minCapacity,也可能小于 minCapacity
    // 比如 oldCapacity == 0,则 newCapacity 也会为 0,
    // 此时会小于 minCapacity(minCapacity 最小为 1) 
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    
    // 确保 newCapacity 肯定大于等于 minCapacity
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
        
    // 如果 newCapacity 大于 MAX_ARRAY_SIZE,
    // 则根据 minCapacity 和 hugeCapacity() 获取合适的值
    // 此时 newCapacity 为 MAX_ARRAY_SIZE 或者 Integer.MAX_VALUE
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
        
    // minCapacity is usually close to size, so this is a win:
    // 通过 Arrays.copyOf() 方法实现扩容,即创建新的大小为 newCapacity 的数组
    elementData = Arrays.copyOf(elementData, newCapacity);
}

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

在通过 add(E e) 添加元素的时候,会先判断原有的容器大小是否能够容下新增的元素,如果不能,则会对容器数组进行扩容。

扩容的规则在正常情况下是变为原来的 1.5 倍,当然,得到 1.5 倍之后的 newCapacity,不一定满足目标要求的最小的容量值,或者超过了 MAX_ARRAY_SIZE,都需要再进行处理。

扩容之后,会把要添加的元素添加到末尾。

(2)add(int index, E element)

public void add(int index, E element) {
	// 先判断指定位置是否合理,即 index 在 [0,size] 内(可以添加到 elementData[size] 的位置,
	// 即添加到末尾,此时不一定需要扩容)
    rangeCheckForAdd(index);
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 利用 System.arraycopy() 将 index 位置开始的元素往后挪动一位
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

(3)set()get()

public E set(int index, E element) {
    rangeCheck(index);
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

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

// rangeCheck() 是检查 index 是否在 [0, size) 的范围内,不包括 size
// 而 rangeCheckForAdd() 是 [0, size]
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

(4)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);
	// 把最后的位置 null,以便让 GC 回收
    elementData[--size] = null; // clear to let GC do its work
    return oldValue;
}

(5)removeIf(Predicate filter)

@Override
public boolean removeIf(Predicate<? super E> filter) {
    Objects.requireNonNull(filter);
    // figure out which elements are to be removed
    // any exception thrown from the filter predicate at this stage
    // will leave the collection unmodified
    int removeCount = 0;
    
    // 用于收集需要移除的元素的下标
    final BitSet removeSet = new BitSet(size);
    final int expectedModCount = modCount;
    final int size = this.size;
    
    // 通过 BitSet,相当于有一个大小为 size 的数组 X,
    // 其 X[index] 用于记录 elementData[index] 是否需要被去除
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        @SuppressWarnings("unchecked")
        final E element = (E) elementData[i];
        if (filter.test(element)) {
        	// 将指定索引处的位设置为 true,因此索引对应 true 时表示是要删除的
            removeSet.set(i);
            removeCount++;
        }
    }
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    // shift surviving elements left over the spaces left by removed elements
    final boolean anyToRemove = removeCount > 0;
    if (anyToRemove) {
        final int newSize = size - removeCount;
        for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
        	// 得到指定位置及其之后的第一个对应为 false 的索引(因为 true 表示要删除)
            i = removeSet.nextClearBit(i);
            elementData[j] = elementData[i];
        }
        // 将 newSize 及其之后的都置为 null
        for (int k=newSize; k < size; k++) {
            elementData[k] = null;  // Let gc do its work
        }
        this.size = newSize;
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }
    return anyToRemove;
}

有关于 BitSet:https://www.runoob.com/java/java-bitset-class.html


补充

(1)有关 modCount

/**
 * The number of times this list has been structurally modified.
 * Structural modifications are those that change the size of the
 * list, or otherwise perturb it in such a fashion that iterations in
 * progress may yield incorrect results.
 *
 * 

This field is used by the iterator and list iterator implementation * returned by the {@code iterator} and {@code listIterator} methods. * If the value of this field changes unexpectedly, the iterator (or list * iterator) will throw a {@code ConcurrentModificationException} in * response to the {@code next}, {@code remove}, {@code previous}, * {@code set} or {@code add} operations. This provides * fail-fast behavior, rather than non-deterministic behavior in * the face of concurrent modification during iteration. * *

Use of this field by subclasses is optional. If a subclass * wishes to provide fail-fast iterators (and list iterators), then it * merely has to increment this field in its {@code add(int, E)} and * {@code remove(int)} methods (and any other methods that it overrides * that result in structural modifications to the list). A single call to * {@code add(int, E)} or {@code remove(int)} must add no more than * one to this field, or the iterators (and list iterators) will throw * bogus {@code ConcurrentModificationExceptions}. If an implementation * does not wish to provide fail-fast iterators, this field may be * ignored. */ protected transient int modCount = 0;

modCount 继承自 AbstractList,是用来记录 ArrayList 的结构被改变的次数,即 add、remove、clear 等操作都会使改值加 1。

它允许列表的内部结构知道是否已经进行了可能导致当前操作给出不正确结果的结构修改。

该变量可以检测 同一个线程(而并非是一定是与多线程的同步有关)在遍历的时候有没有修改这个 ArrayList,进一步预防在使用迭代器进行遍历删除时造成的问题(ConcurrentModificationException)。

而如果是使用 for(i) 的下标遍历,则会引起另外一种问题,即在遍历删除的时候,导致下标与目标不匹配

附:面试题:Java中ArrayList循环遍历并删除元素的陷阱

你可能感兴趣的:(Java基础)