04.ArrayList

200 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情

概要

记录一下ArrayList中比较有意思的设计

ArrayList的扩容

面试常问题之一,但属实没啥技术含量,但为什么说他有意思呢?

因为ArrayList给了一个DEFAULT_LIST和一个EMPTY_LIST来记录ArrayList空状态. DEFAULT_LIST表示用户使用默认构造器初始化, 这种情况下第一次扩容会把数组扩容至DEFAULT_CAPACITY,也就是10. 但如果是现实指定初始化容量为0,那么就会使用默认策略一点一点的扩容.

扩容策略:

  1. 确认本次最小扩容数量为min.
  2. 记录当前容量为old.
  3. 当old=0且ArrayList使用的空构造器初始化,扩容数组的大小为old+Math.max(DEFAULT_CAPACITY, min),DEFAULT_CAPACITY默认为10.
  4. 0 < new = Math.max(min,0.5*old) + old < Integer.MAX_VAULE - 8,扩容数组大小为new.
  5. old + min <= Integer.MAX_VAULE - 81.5 * old < 0||1.5 * old> Integer.MAX_VAULE - 8 , 扩容数组大小为Integer.MAX_VAULE - 8.
  6. 抛出 OutOfMemoryError.

ArrayList的Collection参数构造器

这个参数构造器从1.8开始优化了至少三版了,下面分别是jdk11和jdk17的实现,jdk8较早版本的实现我本地没有. 但是从网上的参考资料可以得出:最早的constructor是会直接复用c.toArray()返回的数组的.

那么为什么需要加上一个数组类型校验呢? 虽然Collection中规定的是toArray返回一个新的数组, 但是并没有强制类型为Object[], 所以当使用Arrays.asList("1","2","3").toArray()返回的是一个String数组,假如这时候再往新list中插入一个object类型的元素就会抛出异常(ArrayStoreException). 这个异常往往是超出预期的, 所以变成了jdk11的这个版本. 但又为什么会变成jdk17这个版本呢.我猜的的话是因为有一些实现不遵循接口定义的规范,toArray依旧可能返回一些不安全的数组,所以直接拒接外来array,自己拷贝一份更安全.

// jdk11
public ArrayList(Collection<? extends E> c) {  
    elementData = c.toArray();  
    if ((size = elementData.length) != 0) {  
        // defend against c.toArray (incorrectly) not returning Object[]  
 // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652) 
	if (elementData.getClass() != Object[].class)  
            elementData = Arrays.copyOf(elementData, size, Object[].class);  
    } else {  
        // replace with empty array.  
 this.elementData = EMPTY_ELEMENTDATA;  
    }  
}

// jdk17
public ArrayList(Collection<? extends E> c) {  
    Object[] a = c.toArray();  
    if ((size = a.length) != 0) {  
        if (c.getClass() == ArrayList.class) {  
            elementData = a;  
        } else {  
            elementData = Arrays.copyOf(a, size, Object[].class);  
        }  
    } else {  
        // replace with empty array.  
 elementData = EMPTY_ELEMENTDATA;  
    }  
}

indexOfRange

无他,优秀的代码,记录一下.

int indexOfRange(Object o, int start, int end) {  
    Object[] es = elementData;  
    if (o == null) {  
        for (int i = start; i < end; i++) {  
            if (es[i] == null) {  
                return i;  
            }  
        }  
    } else {  
        for (int i = start; i < end; i++) {  
            if (o.equals(es[i])) {  
                return i;  
            }  
        }  
    }  
    return -1;  
}

ArrayList clone之后还是原来的那个它吗

很明显是的,不过变的更年轻了(modCount归零)

public Object clone() {  
    try {  
        ArrayList<?> v = (ArrayList<?>) super.clone();  
        v.elementData = Arrays.copyOf(elementData, size);  
        v.modCount = 0;  
        return v;  
    } catch (CloneNotSupportedException e) {  
        // this shouldn't happen, since we are Cloneable  
 throw new InternalError(e);  
    }  
}

equals

  1. 只跟list实现类做对比,非list实现返回false
  2. 如果对比对象是ArrayList(这里是==,不是 instanceOf)对象,则直接使用elementData数组根据索引做对比.
  3. 如果对比对象不是ArrayList,则使用迭代器加数组对比.

batchRemove/replaceAllRange和removeIf

把这三个方法列出来的原因有两个: 一是实现都非常优秀,可以记录学习一下.二是他们的行为不统一,需要警醒一下,后期有时间的话我得去查查这三个方法是出自一人之手不. batchRemove和replaceAllRange方法如果出现异常会保留部分修改操作,而removeIf会等确认好所有需要删除的元素再进行全量删除.

boolean batchRemove(Collection<?> c, boolean complement,  
                    final int from, final int end) {  
    Objects.requireNonNull(c);  
    final Object[] es = elementData;  
    int r;  
    // Optimize for initial run of survivors  
	for (r = from;; r++) {  
        if (r == end)  
            return false;  
        if (c.contains(es[r]) != complement)  
            break;  
    }  
    int w = r++;  
    try {  
        for (Object e; r < end; r++)  
            if (c.contains(e = es[r]) == complement)  
                es[w++] = e;  
    } catch (Throwable ex) {  
        // Preserve behavioral compatibility with AbstractCollection,  
 // even if c.contains() throws. System.arraycopy(es, r, es, w, end - r);  
        w += end - r;  
        throw ex;  
    } finally {  
        modCount += end - w;  
        shiftTailOverGap(es, w, end);  
    }  
    return true;  
}

  
private void replaceAllRange(UnaryOperator<E> operator, int i, int end) {  
    Objects.requireNonNull(operator);  
    final int expectedModCount = modCount;  
    final Object[] es = elementData;  
    for (; modCount == expectedModCount && i < end; i++)  
        es[i] = operator.apply(elementAt(es, i));  
    if (modCount != expectedModCount)  
        throw new ConcurrentModificationException();  
}

boolean removeIf(Predicate<? super E> filter, int i, final int end) {  
    Objects.requireNonNull(filter);  
    int expectedModCount = modCount;  
    final Object[] es = elementData;  
    // Optimize for initial run of survivors  
	for (; i < end && !filter.test(elementAt(es, i)); i++)  
        ;  
    // Tolerate predicates that reentrantly access the collection for  
	// read (but writers still get CME), so traverse once to find // elements to delete, a second pass to physically expunge. if (i < end) {  
        final int beg = i;  
        final long[] deathRow = nBits(end - beg);  
        deathRow[0] = 1L;   // set bit 0  
	for (i = beg + 1; i < end; i++) 
        if (filter.test(elementAt(es, i)))  
            setBit(deathRow, i - beg);  
        if (modCount != expectedModCount)  
            throw new ConcurrentModificationException();  
        modCount++;  
        int w = beg;  
        for (i = beg; i < end; i++)  
            if (isClear(deathRow, i - beg))  
                es[w++] = es[i];  
        shiftTailOverGap(es, w, end);  
        return true;  
    } else {  
        if (modCount != expectedModCount)  
            throw new ConcurrentModificationException();  
        return false;  
    }  
}

经过writeObject和readObject之后还是自己吗

由于扩容机制的原因elementData.length往往可能大出size非常多,所以ArrayList重写了writeObject和readObject方法,经过反序列化之后的elementData.length == size.