ArrayList内存增长策略和 removeIf

290 阅读2分钟

ArrayList内存增长策略和 removeIf

文章目录

  1. 增长策略
  2. removeIf(Predicate<? super E> filter)

增长策略

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

    ensureExplicitCapacity(minCapacity);
}

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

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

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);
    // minCapacity is usually close to size, so this is a win:
    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;
}

定义

intX // 传给构造函数的初始大小,int 值

collectionX // 传给构造函数的 collection

oldCapacity // 现有空间大小

minCapacity // oldCapacity + 要增加的元素个数

newCapacity // oldCapacity + (oldCapacity >> 1)

MAX_ARRAY_SIZE // 最大 int 值 - 8,- 8 是为了给一些 VM 预留 header words

ArrayList 从新建开始的增长大小如下表所示:

增长策略
new ArrayList() 0
new ArrayList(intX) intX
new ArrayList(collectionX) collectionX.size()
minCapacity <= 10 10
newCapacity < minCapacity minCapacity
newCapacity < MAX_ARRAY_SIZE newCapacity
minCapacity <= MAX_ARRAY_SIZE MAX_ARRAY_SIZE
minCapacity > MAX_ARRAY_SIZE Integer.MAX_VALUE
minCapacity > Integer.MAX_VALUE OutOfMemoryError

removeIf(Predicate<? super E> filter)

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;
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        @SuppressWarnings("unchecked")
        final E element = (E) elementData[i];
        if (filter.test(element)) {
            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++) {
            i = removeSet.nextClearBit(i);
            elementData[j] = elementData[i];
        }
        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;
}

这个方法是值得一说的。removeIf 这个需求,一听上去应该是遍历一次就可以的,而代码中先遍历一次用 bitSet 记录要改的位置,然后再一次遍历修改 List 中的数组。牺牲一次遍历的时间是为了防止 filter 条件执行时有异常,这样就会让 list 处于一个 inconsistent 的状态,所以这里采用两次遍历配合 bitSet 来保证数据的integrity

这次选读就到这里,有不对或者讲得不清楚的地方欢迎大家提出。