书中自有黄金屋,书中自有颜如玉
————————————————————————————————————
本文在《码出高效:Java开发手册》书本讲解内容的基础上,将和大家一起对JDK1.8版本中的ArrayList源代码进行分析及拓展,争取将ArrayList周边的知识点做一个全面的复习回顾。
上一个图,我们今天看红色框List的ArrayList部分:
其中 红色代表接口 蓝色代表抽象类 绿色代表并发包中的类,灰色(Vector,Stack)代表早期线程安全的类(基本已经弃用)。
单从ArrayList的继承和实现接口关系来讲,下图会更为清晰
List 集合是线性数据结构的主要实现,集合元素通常存在明确的上一个和下一个
元素,也存在明确的第一个元素和最后一个元素(所以我们List家族中的实现类都是有序的)。 并且List 集合的遍历结果是稳定的。该体系最常用的是 ArrayList ,LinkedList 两个集合类。
ArrayList 是容量可以改变的非线程安全集合。内部实现使用数组进行存储,集合扩容时会创建更大的数组空间,把原有数据复制到新数组中。 ArrayList 支持对元素的快速随机访问,但是插入与删除时速度通常很慢,因为这个过程很有可能需要移动其他元素。
——————————————————————————
上边是对ArrayList特点的一个总结,下来我们开始源码学习
/**
* Default initial capacity.
* 默认初始容量为10
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
* 空的数组。
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
* 同样是空的数组,此空数组对象用来判别何时第一个元素加入。
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
* 这就是我们存储 ArrayList 真正数据的数组
* transient 关键字我们后边再聊
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
* 数组的大小,也可以理解为数组中存储数据单元的个数。
*/
private int size;
ArrayList的内部属性值没什么需要特别说明的,下来我们看一下构造方法:
/**
* Constructs an empty list with an initial capacity of ten.
* 构造初始容量为10的空列表
* 在1.8之前,默认的无参构造容量为10,在1.8后默认的构造容量为0,在第一次add一个元素时会对容量进行一个分配,容量为默认值10,后边会详细说明。
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
* 初始带容量的构造方法,简单易懂
* 当你传的值大于0,则就按照你穿的值设定数组的初始容量
* 当你传的值等于0,则使用默认的空数组 EMPTY_ELEMENTDATA
* 当你传的值小于0,抛出非法参数异常
*/
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);
}
}
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
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;
}
}
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
最后这个构造方法其实没多少代码,但我觉得需要稍微强调一下,我也是看书和查资料才理解了,主要想说明的是这段注释的缘由
// c.toArray might (incorrectly) not return Object[] (see 6260652)
很多小伙伴看到这块代码可能和我第一次看见一样有点懵,这是个嘛玩意,查了一下发现,这是个JAVA官方的bug,附上一个查bug的信息的网站
Java Bug Database
see 6260652 就是他的bug id,据说在1.9中已经被解决了,这里1.8的解决方法就是在后边加了一个判断,如下边代码,需要判断数组类型是否是Object,不是得话就需要把他转换为Object类型,再拷贝赋值。
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
然后这个bug的问题就如注释中说到的,因为c.toArray()这个方法返回的不一定是Object数组,所以有问题,下边我们详细看一下,
首先确定我们ArrayList集合里边存数据的数组对象elementData是一个Object数组,而java中Object数组对象引用是可以指向非Object数组的,问题就在这,我们看代码
// 首先我们创建一个类myList继承自ArrayList,并重写toArray()方法
// 使得场景先满足第一个条件,toArray()方法返回的不是Object数组
public class MyList<E> extends ArrayList<E> {
private static final long serialVersionUID = 8068784835731821475L;
@Override
public String[] toArray() {
// 这里把返回值先写死,方便
return new String[] {"1"};
}
}
// 接下来看这一段
public static void main(String[] args) {
// 创建一个MyList
MyList<String> mylist = new MyList<>();
// 模拟我们的elementData Object数据
// 并将返回的String[] 给到 elementData对象。
// 这里不会有问题,并且elementData看起来还是像一个存储Object对象类型的数组
Object[] elementData = mylist.toArray();
// 接下来我们给这个看起来像Object数组的elementData赋一个Integer的值
elementData[0] = new Integer(1);
// 最后会报一个错
// Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
// 原因就是这里的elementData对象虽然看起来像是一个Object数组,但是他底层
// 指向的对象已经是一个String类型的数组了,无法再加入String类型以外的对象
// 所以集合中我们需要保持elementData数组对象指向的是一个Object数组
// 而这个数组能加入什么类型的元素让泛型去决定,保持数组元素类型的一致性。
}
构造方法看完了,然后我们来看一下为什么大家都是在说ArrayList集合的增删速度慢,我们这边一步一步来,先说明一下Arrays类,引用书中的内容
Arrays 是针对数组对象进行操作的工具类,包括数组的排序、查找、对比、拷贝
等操作。尤其是排序,在多个 JDK 版本中在不断地进化,比如原来的归并排序改成
Timsort ,明显地改善了集合的排序性能。另外,通过这个工具类也可以把数组转成集合。
因为ArrayList的底层是数组,所以我们在ArrayList的源代码中经常能找到Arrays工具类的身影。我们先来看一下最简单的新增一个单独对象的无参方法add(),这里我把它用到的相关的方法也一起列出来了,直接在备注上进行说明
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return true (as specified by {@link Collection#add})
* add中主要的方法就是ensureCapacityInternal()
* 除了这个方法就直接赋值了,所以我们主要看这个方法干了啥
* 这方法里边其实还调了几个方法,我们把他们直接进行编号①→②→③→④
*/
public boolean add(E e) {
// 给①传递的参数是当前存储的元素个数加1
// 这里强调一下,这个size就是elementData数组当前存储元素的个数
// 和elementData.length是不一样的,length是表示数组长度,就是最多能存多少元素
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
// ①
// 【ensure→确保 Capacity→容量 Internal→内部】 :确保内部容量,方法没有返回值
// 也就是说假如你之前数组已经存满了,如果没调用这个方法,
// 再往里边加数据 elementData[size++] = e;的话,就加不进去了吧,而且会下表越界
// 所以这里这个方法,里边肯定会有对容量的判断及修改。我们先看③
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// ②
// 【ensure→确保 Explicit→明确的 Capacity→容量】 :确保明确的,正确的容量,方法没有返回值
// 参数就是②返回的容量数
private void ensureExplicitCapacity(int minCapacity) {
// 这个是父类的属性,用来记录修改次数,和这里的逻辑没有关系,暂时忽略
modCount++;
// overflow-conscious code
// ②方法为我们返回了最新的容量,也就是现在数组中需要存储的元素个数
// 但是我们得确保元素在数组中能否放的下,先简单重复一下,
// 我们java中的数组类型,初始化后容量无法改变,我们知道ArrayList的底层是使用数组的,而ArrayList集合的数量大小又是可以改变的,是怎么做到的呢
// 答案就是使用Arrays工具类,创建一个扩容后大的数组,把原数组内容拷贝过去,就是生成了一个新的数组,就是扩容拷贝这一步导致了在某些新增时间段ArrayList的速度慢。
if (minCapacity - elementData.length > 0)
// 这里判断最新容量是否大过了数组长度,如果超过了,就进行扩容拷贝
// 如果没有超过,就直接在数组的对应位置上赋值就可以了,这个新增速度还是可以的
// 具体的扩容方法在grow()方法中,我们接下来就去看④
grow(minCapacity);
}
// ③
//【calculate→计算 Capacity→容量】 :计算容量,静态方法,有返回值
// 入力参数为我们【存数据的数组elementData】和【最新的追加一个元素之后的元素个数】。
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 这里先判断elementData数组是不是空数组,当我们用默认空参数的构造方法构建时,elementData就是空数组。
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 如果是空数组,使用传进来的最新元素个数和默认值为10的定值进行比较,返回较大值
// 这里就是我们经常说的默认数组容量的分配地点了
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 如果不是空数组就把最新的容量值返回
// 我们再看②
return minCapacity;
// 方法的最终目的就是返回新增元素后的容量大小,默认最小容量为10。
}
/**
* ④
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
* 增加容量以确保它至少可以容纳由最小容量参数指定的元素数。
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
// 这里先保留了我们当前数据的容量长度,保留干啥呢,接着往下看
int oldCapacity = elementData.length;
// 扩容,获得最新的容量值newCapacity,其值为旧的数组长度加上一个值,我附上一段书上的备注:JDK6之前扩容50%或50%-1,但是取ceil,而之后版本取floor
// 这里(oldCapacity >> 1)的值可以理解为oldCapacity的一半
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 正数带符号右移的值肯定是正值,所以oldCapacity+(oldCapacity >> 1)的结果可能超过int可以表示的最大值,反而有可能比参数minCapacity更小
// 所以我们需要判断newCapacity和参数minCapacity的大小
if (newCapacity - minCapacity < 0)
// 如果newCapacity大小确实超过了int可以表示的最大值
// 反而此时比minCapacity更小,则此时容量值就直接设置为minCapacity的值
newCapacity = minCapacity;
// 再下来判断此时的新容量newCapacity是否超过了数组最大长度
// → MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
if (newCapacity - MAX_ARRAY_SIZE > 0)
// 如果超过了就调用hugeCapacity方法来得到更大的容量
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
// 翻译:最小容量通常接近大小,所以这是一个胜利
// 这里就是我们上边所说的进行扩容拷贝了,返回值是Object[]
elementData = Arrays.copyOf(elementData, newCapacity);
}
总结一下:
其实ArrayList的无参插入方法速度大部分时间还行,因为这里的的插入都是在数组最后边插入,除过要扩容的时间点,大部分时间不需要拷贝数据到另一个数组,跨数组拷贝数据才是慢的根本所在,试想一下,ArrayList使用无参构造时,默认大小为 10,也就是说在第一次 add 的时候分配为 10 的容量,后续的每次扩容都会调用 Arrays.copyOf 方法,创建新数组再复制。可以想象,假如需要将 1000 个元素放置在ArrayList中,采用默认构造方法,一个一个加入,则需要被动扩容 13 次才可以完成存储。反之,如果在初始化时便指定了容量 new ArrayList(1000), 那么在初始化ArrayList对象的时候就直接分配 1000 个存储空间,从而避免被动扩容和数组复制的额外开销。最后,进一步设想,如果这个值达到更大量级, 没有注意初始的容量分配问题,那么无形中造成的性能损耗是非常大的,甚至导致 OOM 的风险。(out of memory 内存溢出)
另外就是强调一下,有参的add(int index, E element)方法,这个效率是比上边无参的还要慢的,因为你在中间加入元素,必定会先将数组一分为二,当前加入的元素后方的数组中的元素就都需要向后一个位置拷贝了,这就慢了。代码如下
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
// 主要是这一步数组拷贝导致速度下降
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
至于addAll()方法我们就不继续看了,和add()方法差不多,无非就是加入的数据量不一致罢了,实现都是同样的套路,有兴趣的童鞋可以下去自己瞅瞅。
嗯,我们已经看完了插入方法,下来看删除方法↓
删除方法我们还是只看单个元素删除的,删除多个的原理都差不多,
这里有列出三个方法,①remove(int index),②remove(Object o),以及被这两个方法使用的内部私有方法 ③fastRemove(int index),下边直接看代码(过长的原有注释被我删除了)
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
* ①
* 参数是要删除的元素的下标
*/
public E remove(int index) {
// 这里rangeCheck方法是判断下标是否越界的
rangeCheck(index);
modCount++;
// remove方法最后会返回被删除掉的元素,就是在这里获取的
E oldValue = elementData(index);
// 这个numMoved变量就是在我们删除元素之后需要移动的元素数量
// 写为size - (index + 1)可能比较好理解一点,假如你集合中现在有十个元素
// 你现在要删除第九个元素,下标为8,通过计算得出numMoved为1,也就是需要
// 向前移动一个元素,就是原先集合的第十个元素。
int numMoved = size - index - 1;
// 这里什么情况下不需要移动元素呢,就是当你删除的是最后一个元素时。
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 这里删除方法就比较残暴,直接指向null,让垃圾回收器去回收已经删除的元素。
elementData[--size] = null; // clear to let GC do its work
// 这里我们可以发现的就是,ArrayList无论是插入还是删除,只要涉及到元素的拷贝
// 就会慢,当你删除或者插入元素在ArrayList尾部的时候,其实速度还是可以的。
return oldValue;
}
/**
* @param o element to be removed from this list, if present
* @return true if this list contained the specified element
* ②
* 参数就是要删除的元素的对象
*/
public boolean remove(Object o) {
// 首先我们ArrayList是能存null的,当然也能删除null
// 把这个判断单独提出来就是为了防止空指针,因为判断元素相等是需要比较的
if (o == null) {
// 至于为什么要循环,因为我们删除数组元素还是需要他的下标的,这里速度就已经有慢的隐患了
for (int index = 0; index < size; index++)
// 这里其实只能删除掉第一个值为null的元素
// fastRemove在下边再看
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
// 这里就是删除一个不是null值元素的地方了
if (o.equals(elementData[index])) {
// 主要就一个fastRemove方法,我们看下边
fastRemove(index);
return true;
}
}
return false;
}
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
* ③
* 参数为删除元素的下标
*/
private void fastRemove(int index) {
// 惊不惊喜,意不意外,这里fastRemove和remove(int index)方法几乎一模一样,唯一的区别就是fastRemove方法里不需要下标越界的判断了。。。
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
这里我们最后发现,上边两个remove方法,都只能一次删除掉一个元素,即使第二个remove方法参数传递的是一个Object,如果我们这边需要删除掉集合中多个一致的值的时候怎么办呢,答案就是使用removeAll(Collection> c)方法,他会删除掉所有和你传进来的数组中值一致的元素。看一下测试代码:
List<String> list1 = new ArrayList<String>();
list1.add("aaa");
list1.add(null);
list1.add("bbb");
list1.add(null);
System.out.println(list1.size());
System.out.println(list1);
List<String> list2 = new ArrayList<String>();
list2.add(null);
list1.removeAll(list2);
System.out.println(list1.size());
System.out.println(list1);
输出结果如下
4
[aaa, null, bbb, null]
2
[aaa, bbb]
这里源码基本上读完了,下来进入我们的扩展内容,依旧和ArrayList有关,主要是数组和集合的关系
数组与集合都是用来存储对象的容器,前者性质单一,方便易用,后者类型安全,功能强大,且两者之间必然有互相转换的方式。毕竟它们的性格迥异,在转换过程中,如果不注意转换背后的实现方式,很容易产生意料之外的问题。
下来我们就聊一下数组与集合的相互转换
首先是第一种情况,数组转集合,以 Arrays.asList()为例,它把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/remove/clear 方法会抛出UnsupportedOperationException 异常。示例代码如下:
String[] stringArray = new String[3];
stringArray[0] = "aaa";
stringArray[1] = "bbb";
stringArray[2] = "ccc";
List<String> list1 = Arrays.asList(stringArray);
list1.set(0, "uzi out");
System.out.println(list1.get(0));
// 以下三行编译正确,执行都会报错
list1.add("ddd");
list1.remove(1);
list1.clear();
执行结果为:
uzi out
Exception in thread "main" java.lang.UnsupportedOperationException
这里set方法是没有问题的,那是为什么不能使用add/remove/clear方法呢,因为Arrays.asList 体现的是适配器模式,后台的数据仍是原有数组,set()方法即间接对数组进行值的修改操作。asList 的返回对象是一个Arrays 的内部类,它并没有实现集合个数的相关修改方法,这也正是抛出异常的原因。
Arrays.asList 的源码如下:
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
返回的明明是 ArrayList 对象,怎么就不可以随心所欲地对此集合进行修改呢?
注意此 ArrayList 非彼 ArrayList ,虽然 Arrays 和ArrayList 同属于一个包,但是在
Arrays 类中还定义了一个 ArrayList 的内部类(或许命名为 InnerArrayList 更容易识别),根据作用域就近原则,此处的 ArrayList 是李鬼,即这是个内部类。此李鬼相对于1.6,1.7已经追加了很多实现方法了,但是依旧没有add/remove/clear方法的实现,大都是一些遍历取值改值的方法实现,代码如下:
/**
* @serial include
*/
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
private static final long serialVersionUID = -2764017481108945198L;
private final E[] a;
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
@Override
public int size() {
return a.length;
}
@Override
public Object[] toArray() {
return a.clone();
}
@Override
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
int size = size();
if (a.length < size)
return Arrays.copyOf(this.a, size,
(Class<? extends T[]>) a.getClass());
System.arraycopy(this.a, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
@Override
public E get(int index) {
return a[index];
}
@Override
public E set(int index, E element) {
E oldValue = a[index];
a[index] = element;
return oldValue;
}
@Override
public int indexOf(Object o) {
E[] a = this.a;
if (o == null) {
for (int i = 0; i < a.length; i++)
if (a[i] == null)
return i;
} else {
for (int i = 0; i < a.length; i++)
if (o.equals(a[i]))
return i;
}
return -1;
}
@Override
public boolean contains(Object o) {
return indexOf(o) != -1;
}
@Override
public Spliterator<E> spliterator() {
return Spliterators.spliterator(a, Spliterator.ORDERED);
}
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
for (E e : a) {
action.accept(e);
}
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
E[] a = this.a;
for (int i = 0; i < a.length; i++) {
a[i] = operator.apply(a[i]);
}
}
@Override
public void sort(Comparator<? super E> c) {
Arrays.sort(a, c);
}
}
数组上的final关键字使得数组引用始终被强制指向原有数组,并且你会发现他没有实现add/remove/clear方法,至于这个UnsupportedOperationException异常,是其父类AbstractList报出来的
所以,如果你只是查看集合内容,那这么转换是没有问题的,如果你要修改转换后的集合,那请使用如下代码:
List<Object> objectList = new java.util.ArrayList<Object>(Arrays.asList(数组));
下来我们看一下集合转数组,
其实集合转数组更加简单可控,但是还是要注意一些问题,我们看代码
List<String> list = new ArrayList<String>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
// 第一处
// 泛型丢失,无法使用String[]来接受返回的结果,因为toArray()方法返回的是object[]
Object[] arrayl = list.toArray();
// 第二处
// 数组长度小于集合元素个数
String[] array2 = new String[2] ;
list.toArray(array2);
System.out.println(Arrays.asList(array2)) ;
// 第三处
// 数组长度等于集合元素个数
String[] array3 = new String[3];
list.toArray(array3);
System.out.println(Arrays.asList(array3));
执行结果如下
[null, null]
[aaa, bbb, ccc]
第一处好理解,不要使用toArray()无参方法将集合转化为数组,这样会导致泛型丢失。第二处,编译没错,运行也没错,结果输出null,第三处,成功将集合元素复制到了数组中,2和3的区别就在于即将复制进去的数组容量是否足够。如果容量不够,则弃用此数组,另起炉灶,关于此方法的源码如下:
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
所以第二处会成功复制集合元素到一个数组中,并且返回这个数组,只不过此时就和你传进来的那个容量不足的数组没有关系了。
到这了,我们再聊一下transient关键字,transient Object[ ] elementData,我们真正存数据的这个集合就是被transient修饰的,这个关键字表示的是此字段修饰的对象在类的序列化时将被忽略。因为集合序列化时系统会调用 writeObject 写入流中,在网络客户端反序列化的 readObject 时,会重新赋值到新对象的 elementData中。为什么多此一举,因为 elementData 容量经常会大于实际存储元素的数 ,所以只需发送真正有实际值的数组元素即可。
还有,我们测试一下三种情况,分别为入参数组容量不够时、入参数组容量刚好时,以及入参数组容量超过集合大小时,并记录其执行时间,这里我直接截图了,就不敲了
所以,当我们集合转数组时,如果不会再往数组里边加入元素,请尽量给到一个和集合大小一样的数组,这样效率会最高。
最后再提一嘴subList
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
这个方法返回的也是一个内部类,并且注意他没有实现序列化接口,小心踩坑
最后总结一下
ArrayList 是最常用的 List 实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要将已经有数组的数据复制到新的存储空间中。当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。
ArrayList的内容大概就到这里了,非常感谢《码出高效:Java开发手册》这本书及写这本书的阿里大佬,活到老学到老,我们下次再见。