一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情。
概要
记录一下ArrayList中比较有意思的设计
ArrayList的扩容
面试常问题之一,但属实没啥技术含量,但为什么说他有意思呢?
因为ArrayList给了一个DEFAULT_LIST和一个EMPTY_LIST来记录ArrayList空状态. DEFAULT_LIST表示用户使用默认构造器初始化, 这种情况下第一次扩容会把数组扩容至DEFAULT_CAPACITY,也就是10. 但如果是现实指定初始化容量为0,那么就会使用默认策略一点一点的扩容.
扩容策略:
- 确认本次最小扩容数量为min.
- 记录当前容量为old.
- 当old=0且ArrayList使用的空构造器初始化,扩容数组的大小为
old+Math.max(DEFAULT_CAPACITY, min),DEFAULT_CAPACITY默认为10. - 当
0 < new = Math.max(min,0.5*old) + old < Integer.MAX_VAULE - 8,扩容数组大小为new. - 当
old + min <= Integer.MAX_VAULE - 8且1.5 * old < 0||1.5 * old> Integer.MAX_VAULE - 8, 扩容数组大小为Integer.MAX_VAULE - 8. - 抛出
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
- 只跟list实现类做对比,非list实现返回false
- 如果对比对象是ArrayList(这里是
==,不是 instanceOf)对象,则直接使用elementData数组根据索引做对比. - 如果对比对象不是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.