Java 集合学习笔记:ArrayList

UML

在这里插入图片描述
从类图可见ArrayList 并不是直接实现 List
ArrayList 是继承 AbstractList 再进行扩展的。
至于实现List接口的行为,听说是失误,反正删不删也不影响,所以就一直没动。

简介

ArrayList 是 List 接口的大小可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。(此类大致上等同于 Vector 类,除了此类是非线程安全的。)

List数组实现。

  1. 因为底层数据解构是数组,所以特点是连续内存空间,根据索引操作。
  2. 优势在于,频繁写操作相对吃亏。有两个性能消耗点:
    2.1. 插入/删除时,当前元素之后的所有元素需要整体平移。
    2.2. 空间不足时的反复自动扩容。

阅读源码

方法说明
boolean add(E e)在列表末尾添加新元素。
void add(int index, E element)在指定位置添加元素,原 index 到末尾的所有元素,整体后移一位。
boolean addAll(Collection<? extends E> c)将指定集合添加到当前列表的末尾
boolean addAll(int index, Collection<? extends E> c)将指定集合插入当前列表的指定位置
  • public boolean add(E element)
    public boolean add(E e) {
    	// 当前长度 + 1 = 所需长度。检测并按需扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 在末尾插入新元素,并更新 size
        elementData[size++] = e;
        // 成功返回 true
        return true;
    }
  • public void add(int index, E element)
    public void add(int index, E element) {
		// 检查 index 有没有超出合理范围,插入的合理索引范围是: 0 到 size
        rangeCheckForAdd(index);
		// 当前长度 + 新增长度1 = 所需长度。检测并按需扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // elementData 是当前 list 底层的数组容器。
        // 将 index 后的所有元素整体向右移一位,让出空间,插入新元素。
        System.arraycopy(elementData, index, elementData, index + 1, size - index);
        // 更新 index 位置元素的值
        elementData[index] = element;
        // 更新当前 list 大小
        size++;
    }
  • public boolean addAll(Collection<? extends E> c)
    public boolean addAll(Collection<? extends E> c) {
	    // 取出 c 的底层数组
        Object[] a = c.toArray();
        // 获取将会新增多少个元素(也就是 a 的长度)
        int numNew = a.length;
        // 当前长度 + 新增长度 = 所需长度。检测并按需扩容
        ensureCapacityInternal(size + numNew);  // Increments modCount
        // 从数组 a 的第 0 个索引开始,复制 numNew 个元素,到 elementData 的末尾(size)。
        System.arraycopy(a, 0, elementData, size, numNew);
        // 更新当前 list 的大小(+上新增个数)
        size += numNew;
        // 如果新增个数不为 0 表示插入成功,返回 true 
        return numNew != 0;
    }
  • public boolean addAll(int index, Collection<? extends E> c)
	// 在当前 list 的 index 处插入集合 c (c保持其迭代器的顺序,整个拷进来)
    public boolean addAll(int index, Collection<? extends E> c) {
    	// 检查 index 有没有超出合理范围,插入的合理索引范围是: 0 到 size
        rangeCheckForAdd(index); 
		// 取出 c 的底层数组
        Object[] a = c.toArray();
        // 获取将会新增多少个元素(也就是 a 的长度)
        int numNew = a.length;
        // 当前长度 + 新增长度 = 所需长度。检测并按需扩容
        ensureCapacityInternal(size + numNew);  // Increments modCount
		// numMoved 表示:在当前 list 中的 index 处插入 c 时 index 后有多少个元素要向右平稳让出空位。
        int numMoved = size - index;
        // numMoved 大于 0 表示不在末尾,肯定有元素需要右移。
        // 调用 native 方法,利用复制实现平稳。(原来的位置数据还在,但不用管,插入新元素会覆盖它们)
        // elementData 是当前 list 底层的数组容器。
        // 从 index 位置开始,复制 numMoved 个元素到 index + numNew 处。
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew, numMoved);
		// 从数组 a 的第 0 个索引开始,复制 numNew 个元素,到 elementData 的 index 处。
        System.arraycopy(a, 0, elementData, index, numNew);
        // 更新当前 list 的大小(+上新增个数)
        size += numNew;
        // 如果新增个数不为 0 表示插入成功,返回 true 
        return numNew != 0;
    }

方法说明
void clear()清空列表。
E remove(int index)移除指定索引上的元素。右侧所有元素整体向左平移一位。
boolean remove(Object o)移除指定元素。如果有多个,每次只会移除第一个。
boolean removeAll(Collection<?> c)删除指定集合中包含的元素。
boolean removeIf(Predicate<? super E> filter)删除所有复合条件的元素。(filter 是一个返回布尔型的 Lambda)
boolean retainAll(Collection<?> c)保留当前列表目标集合都存在的元素。也就是,从当前列表中删除指定集合中不存在的所有元素。
  • public boolean removeAll(Collection<?> c)
    public boolean removeAll(Collection<?> c) {
    	// 如果要删除的集合 c 为 null 直接抛锅。
        Objects.requireNonNull(c);
        // 否则调用批量删除方法。(注意这里给的值 false)
        return batchRemove(c, false);
    }
	// c :给定的集合
	// complement : true ? 跳过 : 删除 (以下注释以 false 为例)
    private boolean batchRemove(Collection<?> c, boolean complement) {
    	// 取当前 list 底层数组容器
        final Object[] elementData = this.elementData;
        // 双指针删除算法。(本质是把不需要删除的元素从 r 复制到 w 保留下来)
        // 可以把 r 想象成原数组中的指针,把 w 想象成结果数组中的指针。
        int r = 0, w = 0;
        // 设定修改标记,表示未修改。
        boolean modified = false;
        try {
            // 本质就是从原数组向结果数据复制元素,遇到要删除的就跳过。
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // 保持与AbstractCollection的行为兼容性,即使c.contains()抛出。
            // r != size 表示遍历没走完,中途抛异常了。
            if (r != size) {
            	// 把 r 后所有元素,向前移到 w 位置。(后续元素虽然没处理,但也不能弄丢了)
                System.arraycopy(elementData, r, elementData, w, size - r);
                // 更新结果指针的值
                w += size - r;
            }
            // 如果结果指针 w 与 size 不一样,说明有元素被删除了。
            if (w != size) {
                // 把 w 之后的所有索引上的内容都清理掉。(这些元素都已经被移走了,这里是残留的垃圾)
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w; // 更新修改计数
                size = w; // 更新 size
                modified = true; // 更新修改标记
            }
        }
        return modified;  // 返回修改标记
    }
  • 双指针删除算法 JS 演示

本质就是从原数组向结果数据复制元素,遇到要删除的就跳过。结果数组自然就没有要删除的元素了。
分开声明 arr1arr2 是为了便于理解,懂了之后不需要arr2全都用arr1,效果是一样的。

var arr1 = [1,2,3,4,5]; // 原数组
var arr2 = arr1;		// 结果数组
var removeArr = [2, 4];	// 要删除的元素
var r = 0; // 指向原数组
var w = 0; // 指向结果数组
for(;r < arr1.length; r++){
	// 该元素不需要删除,我们就执行:从原数组拷贝到结果数组
	if (removeArr.includes(arr1[r]) == false)
    	arr2[w++] = arr1[r];
}
// 抛弃末尾的无效元素
arr2.length = arr1.length - removeArr.length;
console.info(arr2);

方法说明
E set(int index, E element)替换指定位置的元素
void replaceAll(UnaryOperator operator)遍历列表执行特定的操作,实现替换。list.replaceAll(x -> x * 2);
void sort(Comparator<? super E> c)传入一个比较器。list.sort((a,b) -> b-a);

方法说明
size()获取列表大小。
boolean isEmpty()判断列表是否为空。
E get(int index) 按索引获取元素。
boolean contains(Object o)判断列表是否包含指定元素。
int indexOf(Object o)返回指定元素在列表中的索引位置 。
int lastIndexOf(Object o)返回指定元素最后一次出现的位置。

手动扩容/缩容

方法说明
void ensureCapacity(int minCapacity) 如有必要,增加此 ArrayList 实例的容量,以确保它至少能够容纳最小容量参数所指定的元素数。
void trimToSize()将此 ArrayList 实例的容量调整为列表的当前大小。

迭代

方法说明
void forEach(Consumer<? super E> action)遍历列表执行消费者。
Iterator iterator()返回迭代器
Spliterator spliterator()返回可拆分迭代器。
ListIterator listIterator()返回双向迭代器。
ListIterator listIterator(int index)从指定的位置开始,返回双向迭代器。
List subList(int fromIndex, int toIndex)返回指定范围的subList
Object[] toArray()返回 Object 数组
<T> T[] toArray(T[] a)返回指定类型数组。 list.toArray(new Integer[0]);

内部类

说明
class Itr implements Iterator实现了 Iterator 接口,iterator() 返回的就是它。
class ListItr extends Itr implements ListIterator双向迭代器。listIterator() 返回的就是它。
private class SubList extends AbstractList implements RandomAccess私有内部类。subList() 返回的就是它。

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

Itr

定义迭代器,用于在调用 iterator() 时,返回当前列表的迭代器

private class Itr implements Iterator<E> {
	int cursor;       // 光标指向:下一次(要返回的)元素的索引。 int 类型默认值 0
	int lastRet = -1; // 最近一次(返回的)元素的索引; 调用 remove 后会重置为-1,表示没有指向。
	int expectedModCount = modCount; // 预期的结构修改的次数 = 此列表被结构修改的次数

	Itr() {}

	// 如果下一次(要返回的)元素,不等于最后一个。返回 true 
	public boolean hasNext() {
		return cursor != size;
	}

	// 
	@SuppressWarnings("unchecked")
	public E next() {
		// 检测并发冲突
		checkForComodification();
		// 申明变量 i 缓存下一个(要返回的)元素的索引。
		int i = cursor;
		// 如果 i 比最后一个元素(size-1)还大,则抛锅。
		if (i >= size)
			throw new NoSuchElementException();
		// 取列表底层数组容器。如果 i 大于底层数组的长度。抛锅。
		Object[] elementData = ArrayList.this.elementData;
		// i 大于等于数组长度,说明数组被别人动了,抛并发异常锅。
		if (i >= elementData.length)
			throw new ConcurrentModificationException();
		else{
			// 否则更新下一个(要返回的)元素的索引 +1
			cursor = i + 1;
		}
		// 然后从底层数组容器中获取索引 i 对应的元素。
		// 同时更新最后一次操作的元素 lastRet 等于这个返回的元素索引。(下面 remove 就要用到 lastRet )
		return (E) elementData[lastRet = i];
	}
	
	// 删除 next() 返回的索引,每次 next() 后才可以 remove 一次。
	// Itr 只有这一个方法会修改列表。
	// 最后一次(操作的)元素的索引小于 0,等于 -1 时表示没有。抛锅。
	// 检查并发冲突。
	// 执行删除操作
	public void remove() {
		if (lastRet < 0)
			throw new IllegalStateException();
		checkForComodification();

		try {
			// 上一次 next() 的最后更新了 lastRet,移除它。
			ArrayList.this.remove(lastRet);
			// 更新下一个(要返回的)元素的索引等于 lastRet。
			// 比如 lastRet = 0 时 cursor = 1。当上面执行删除索引后,后面所有元素向前移到,next() 应该返回的还是索引 0 对应的元素。
			cursor = lastRet;
			// 上一次操作的元素已经删除。重置 lastRet 为 -1 实现一次 next() 对应一个 remove()
			lastRet = -1;
			// 同步预期修改次数
			expectedModCount = modCount;
		} catch (IndexOutOfBoundsException ex) {
			throw new ConcurrentModificationException();
		}
	}

	@Override
	@SuppressWarnings("unchecked")
	/** 遍历余下的所有元素,执行消费者 */
	public void forEachRemaining(Consumer<? super E> consumer) {
		// 非空检测,为空抛锅。
		Objects.requireNonNull(consumer);
		// 取当前列表的 size
		final int size = ArrayList.this.size;
		// 申明变量 i 缓存下一个(要返回的)元素的索引。
		// 如果 i 大于列表尺寸,抛锅。
		int i = cursor;
		if (i >= size) { return; }
		// 取列表底层数组容器。如果 i 大于底层数组的长度。抛锅。
		final Object[] elementData = ArrayList.this.elementData;
		// i 大于等于数组长度,说明数组被别人动了,抛并发异常锅。
		if (i >= elementData.length) {
			throw new ConcurrentModificationException();
		}
		// 从下一个(要返回的)元素开始,一直到最后一个元素。逐个执行消费者。
		// 每次都要同步 modCount == expectedModCount
		while (i != size && modCount == expectedModCount) {
			consumer.accept((E) elementData[i++]);
		}
		// 下一个(要返回的)元素索引更新为 i(最后一个元素的后一位)
		cursor = i;
		// 更新最后一次(操作作的)元素的索引
		lastRet = i - 1;
		// 检查并发冲突。
		checkForComodification();
	}
	
	// 检查并发冲突。Itr 的 remove 会同步 expectedModCount = modCount;
	// 如果这两个变量不相等,说明当前列表被其他未知的神秘力量修改过了。
	final void checkForComodification() {
		if (modCount != expectedModCount)
			throw new ConcurrentModificationException();
	}
}
ListItr

定义双向迭代器,用于在调用 listIterator() 时,返回当前列表的双向迭代器

/** 扩展 Itr 实现 ListIterator 接口定义的方法。*/
private class ListItr extends Itr implements ListIterator<E> {
	// 传入光标指向的索引。初始化双向迭代器。
	ListItr(int index) {
		super();
		cursor = index;
	}
	// 如果下一个(要返回的)元素的索引不等于 0 返回 true
	public boolean hasPrevious() {
		return cursor != 0;
	}
	// 返回下一个(要返回的)元素的索引
	public int nextIndex() {
		return cursor;
	}
	// 返回上一个(要返回的)元素的索引。
	public int previousIndex() {
		return cursor - 1;
	}

	
	/** 返回上一个元素。*/
	@SuppressWarnings("unchecked")
	public E previous() {
		// 检查并发冲突。
		checkForComodification();
		
		// 取上一个元素索引。小于 0 就抛锅。
		int i = previousIndex();
		if (i < 0) 
			throw new NoSuchElementException();
			
		// 取列表底层数组容器。如果 i 大于底层数组的长度。抛锅。
		Object[] elementData = ArrayList.this.elementData;
		// i 大于等于数组长度,说明数组被别人动了,抛并发异常锅。
		if (i >= elementData.length)
			throw new ConcurrentModificationException();
		
		// 更新光标指向上一个元素
		cursor = i;
		// 更新最后一次操作的元素的索引
		// 返回这个元素
		return (E) elementData[lastRet = i];
	}

	/** 用指定元素替换 next 或 previous 返回的最后一个元素(可选操作)。*/
	public void set(E e) {
		// 最后一次(操作的)元素的索引小于 0,等于 -1 时表示没有。抛锅。
		// 检查并发冲突。
		if (lastRet < 0)
			throw new IllegalStateException();
		checkForComodification();

		try {
			// 更新最后一次操作的元素。
			ArrayList.this.set(lastRet, e);
		} catch (IndexOutOfBoundsException ex) {
			throw new ConcurrentModificationException();
		}
	}
	
	/** 将指定的元素插入列表(可选操作)。 */
	public void add(E e) {
		// 检查并发冲突
		checkForComodification();

		try {
			int i = cursor;
			ArrayList.this.add(i, e);
			
			// 光标指向,向后+1
			cursor = i + 1;
			
			// 清空最后一个操作元素的指针。
			lastRet = -1;
			
			// 每次都要同步 modCount == expectedModCount
			expectedModCount = modCount;
		} catch (IndexOutOfBoundsException ex) {
			throw new ConcurrentModificationException();
		}
	}
}

静态内部类

说明
static final class ArrayListSpliterator<E> implements Spliterator<E>实现了可拆分迭代器接口。

在这里插入图片描述

自动扩容逻辑

以下方法会触发自动扩容。(从JDK8中数的)

// 手动扩容
public void ensureCapacity(int minCapacity)
// 末尾追加
public boolean add(E e)
// 插入指定位置(ListItr、SubList 会间接调用此方法)
public void add(int index, E element)
// 末尾追加给定集合
public boolean addAll(Collection<? extends E> c)
// 指定位置插入给定集合(SubList 会间接调用此方法)
public boolean addAll(int index, Collection<? extends E> c)

没有自动缩容,觉得自己拿捏的可以手动缩容。

// 将此 ArrayList 实例的容量调整为列表的当前大小。
public void trimToSize() 

Java7

说明
Object[] elementData;被封装的数组对象ArrayList内部真正储存数据的容器。
自动扩容其实就是在折腾elementData
int size;元素个数: ArrayList中实际存储元素的个数。
DEFAULT_CAPACITY = 10;默认初始容量
Object[] EMPTY_ELEMENTDATA = {};初始化容器 :所有ArrayList都用它初始化,避免创建无数个空数组浪费。
默认扩容因子oldCapacity + (oldCapacity >> 1): 原容量 x 1.5
扩容方式1. 调用 ensureCapacity(int minCapacity) 手动扩容。
2. add 时自动检测并按需扩容。
3. addAll 时自动检测并按需扩容。
4. readObject(ObjectInputStream s) 时自动检测并按需扩容。

minCapacity:最小所需容积
newCapacity:新容积

  1. 计算新容积newCapacity
    1.1. 默认扩容1.5倍。
    1.2. 如果1.5不够,则扩容到所需大小minCapacity
    1.3. 如果新容积newCapacity > MAX_ARRAY_SIZE则进一步判断:(minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
  2. 底层调用Arrays.copyOf(elementData, newCapacity)创建新数组,再将结果赋给原容器elementData
    2.1. Arrays.copyOf的底层是native原生方法System.arraycopy

Java8

-说明
int DEFAULT_CAPACITY = 10;默认初始容量。(容量:指底层数组实际大小)
Object[] EMPTY_ELEMENTDATA = {};列表大小为 0 时共享此空数组。避免每个空列表都创建一个空数组,浪费。
Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};1. 初始列表时,没给默认大小,用此空数组。
2. 扩容时用于作为判断条件之一。
Object[] elementData;内部容器,底层实际用于保存数据的数组。
size列表长度。(容器当前存了几个元素)。
默认扩容因子新容量 = 旧容量 + (旧容量 >> 1) = 原容量 x 1.5

具体的直接看源码吧。。。

扩容 - 核心代码

/**
 * 在此列表中的指定位置插入指定元素。
 * 将当前位于该位置的元素(如果有)和任何后续元素向右移动(将其索引加一)。
 */
public void add(int index, E element) {
	// 检查 index 如果越界就抛异常。
	rangeCheckForAdd(index);
	// 判断内部数组 elementData 大小,如果有需要就扩容。同时列表修改次数 modCount++
	ensureCapacityInternal(size + 1);
	// 将目标索引后面的所有元素整体向后移一位。用的原生方法 System.arraycopy
	System.arraycopy(elementData, index, elementData, index + 1, size - index);
	// 当前索引位置赋值
	elementData[index] = element;
	// 列表大小+1
	size++;
}
/**
 * 计算内部数组 elementData 最小所需容量
 */
private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
	// 判断内部数组 elementData 大小,如果有需要就扩容。
    ensureExplicitCapacity(minCapacity);
}
/**
 * 判断内部数组 elementData 大小,如果有需要就扩容。
 */
private void ensureExplicitCapacity(int minCapacity) {
	// 列表修改次数 modCount++
	modCount++;

    // 最小所需容量 > 数组原长度则扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
/**
 * 扩容,以确保至少要能下 minCapacity 个元素。
 */
private void grow(int minCapacity) {
    // 原容量
    int oldCapacity = elementData.length;
    // 新容量 = 原容量 + (原容量 / 2)
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 新容量够用就用新容量,不够就按 minCapacity 来。
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 新容量大于最大限制,就使用最大限制。
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 原数组 = 复制原数组中的数据,并按 newCapacity 创建的新数组。
    elementData = Arrays.copyOf(elementData, newCapacity);
}

移除 - 核心代码

  • E remove(int index)
public E remove(int index) {
    rangeCheck(index); // 检查索引,越界就抛异常

    modCount++; // 列表修改次数 +1
    E oldValue = elementData(index); // 缓存当前索引值,删除完成后 return 出去
	// 利用 arraycopy 将索引后的全部元素整体前移一位,覆盖当前索引值,达到删除目的。
    int numMoved = size - index - 1; // 计算需要整体向左移动的元素个数
    // 如果有,就复制(以复制实现移动效果)
    // 将数组 elementData 中 index+1 位置开始的元素,复制到 index 处。(也就是向前移一位)
    // 需要复制的元素个数就是 numMoved (也就是被删除的元素右侧的所有元素)
    if (numMoved > 0) 
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    // 所有数据复制后向左移一位粘贴,最右侧一格现在就需要销毁了。
    elementData[--size] = null; // --size 实现长度减1。同时也将最后一个元素=null 以便垃圾回收。
	// 返回被删除的值
    return oldValue;
}
  • boolean remove(Object o)
// 遍历数组逐个比对,找到第一个复合的元素就移除。
// 唯一区别就在于 null 和普通对象做了区别处理
public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
        	// null 直接使用 == 对比
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
        	// 其他对象使用 equals 对比
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}
// 在 remove(int index) 基础上做了简化,省去了对【索引的校验】和【删除值的返回】。
// 因为这里是通过对比对象得出的 index 绝对是存在的。
private void fastRemove(int index) {
    modCount++;
    // 计算删除 index 位置元素后,它后面有多少个元素需要前移一位。
    // 要使用的元素个数 = 总个数 - 删除的索引 - 1
    int numMoved = size - index - 1;
    if (numMoved > 0)
    	// 将被删除元素后紧接着的 numMoved 个元素向前移支一位。
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    // 最后一个位置清空,待GC清理。同时总个数 - 1
    elementData[--size] = null; // clear to let GC do its work
}

根据 remove 我们可以看到,每次执行只会移除第一个匹配到的元素。

ArrayList<Integer> list = new ArrayList<>(Arrays.asList(1, 2, null, 3, 4, 5, null));
list.remove(null);
System.out.println(list);
// [1, 2, 3, 4, 5, null]
  • boolean removeAll(Collection<?> c)
public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c); // 如果 c 为 null 抛异常
    return batchRemove(c, false); //  
}
/**
 * 批量移除
 * @param c collection 从当前列表中的元素,如果 c 也有,那么根据 complement 决定去留。
 * @param complement 判断对 c 中包含的元素 true=保留,false=移除
 */
private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;
    // r 表示被检测的索引。用于遍历原数组,从 0 一直加到 size-1,逐个检测元素。
    // w 表示用于保存的位置。配合 r 每判定保留(移动的元素就是要保留下来的)一个元素 +1。最终 w 也就是最后一个留存元素的索引。
    int r = 0, w = 0;
    boolean modified = false;
    try {
    	// 本质上是两层循环:第一层 for 遍历 elementData
    	// 第二层用 c.contains 进行了简化。判断 c 中是否包含 elementData的当前元素。
    	// 如果不包含,就是要保留的元素。将元素从 r 移动到 w。然后 w 向后移一位。 
    	// 否则遇到需要移除的元素时,只需要跳过移动操作。r++ 但 w 没动,下个元素移过就实现了。
    	// 假设第一个元素就是要移除的,那么移动就是:1到0, 2到1,3到2,以此类推,从第二个元素开始整体向前平移一位。
        for (; r < size; r++)
            if (c.contains(elementData[r]) == complement)
            	// 将要保留的元素从 r 移到 w
                elementData[w++] = elementData[r];
    } finally {
        // r 没走到 size 只有一种可能就是异常了。(暂时还没想到什么情况能触发异常)
        // 后面的不处理的,没移动的都一把移过来。
        // 最后更新 w  = w + 刚才这波移动的个数
        if (r != size) {
            System.arraycopy(elementData, r, elementData, w, size - r);
            w += size - r;
        }
        // w 之后都是要移除的,全部置空,等GC回收
        if (w != size) {
            // clear to let GC do its work
            for (int i = w; i < size; i++)
                elementData[i] = null;
            modCount += size - w;
            size = w;
            modified = true;
        }
    }
    return modified;
}

学习总结

  1. 插入:如果插入多个连续的元素,可以使用addAll优化 。只需要整体平移一次。

  2. 扩容:如果能提前确定所需容积,可以手动指定大小来实现优化。省掉多次扩容浪费时间。

  3. 删除:如果是删除连续的几个元素可以使用removeRange(int fromIndex, int toIndex) ,底层只需要 arraycopy 整体平移一次。
    3.1. 从当前列表中移除 fromIndex(包括)到 toIndex(不包括)之间的所有元素。向左平移所有后续元素。
    3.2. 当然这个方法没有直接开放给用户使用。可以list.subList(3, 5).clear();间接调它。
    3.3. subList.clear() > List.clear() > AbstractList.clear() >> AbstractList.removeRange() >ArrayList.removeRange() 大概就是这么兜了一圈,实现的。

  4. 遍历删除:遍历中要删除元素的场景,请用迭代器或Java8的 removeIf。详情见:笑虾:forEach 遍历中 remove 的BUG,及 Java8 的新推荐。

  5. 效率:底层的数据结构为数组,基于索引操作。相对来说:查询效率高,增删效率低。

参考资料

ArrayList

笑虾:Java 集合学习笔记:Iterator
笑虾:Java 集合学习笔记:Collection

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笑虾

多情黯叹痴情癫。情癫苦笑多情难

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值