jdk ArrayList工作原理分析
An ordered collection (also known as a sequence). The user of this interface has precise control over where in the list each element is inserted. The user can access elements by their integer index (position in the list), and search for elements in the list.
list是一个有序的集合,通常也会被称为序列。使用list接口可以精确的控制每个元素的插入,可以通过索引访问list中的各个项,也可以在list中查询元素。
上面这段话摘自jdk的官方文档。
这里的源码分析基于jdk 1.8。
ArrayList源码分析
首先我们先分析list接口的实现类之一,也是最常用的,ArrayList的源码。
ArrayList的底层使用一个数组完成数据的添加、查询、修改、删除。这个数组就是下面字段中的elementdata。
字段
1 | //集合的默认容量 |
构造函数
1 | //带有指定集合容量参数的构造函数 |
方法
我们挑选几个重要的方法讲解一下
boolean add(E e) 方法
把元素添加到集合的最后面。
1 | public boolean add(E e) { |
下面通过一个例子讲解下ArrayList的扩容机制:
1 | //初始化一个容量为5的数组 |
上面2个白色区域就是扩容出来的,第6个元素被添加,最后一个元素还没被设置。
void add(int index, E element)
在指定位置插入元素,移动当前位置元素和右侧一连串的元素(索引加1)。
1 | public void add(int index, E element) { |
上图显示了要在容量为5的数组中的第4个位置插入6这个元素,会进行3个步骤:
1.容量为5的元素,再次加入元素,需要扩容,扩容出两个白色空间
2.扩容之后,4和5这两个元素拷贝到新数组索引+1的位置
3.拷贝之后,设置指定4的位置 为元素 6
E remove(int index)
移除指定位置的元素
1 | public E remove(int index) { |
比如要移除5个元素中的第3个元素,首先要把4和5这两个位置上的元素分别拷贝到3和4这两个位置,然后把最后一个位置也就是第5位置set为null。
boolean remove(Object o)
移除指定元素在数组中第一次出现的位置元素
1 | public boolean remove(Object o) { |
void clear()
移除list中的所有元素
1 | public void clear() { |
E set(int index, E element)
用element替换集合中指定位置的元素
1 | public E set(int index, E element) { |
E get(int index)
获取集合中指定位置的元素
1 | public E get(int index) { |
boolean addAll(Collection<? extends E> c)
在集合尾部添加指定集合中的所有元素
1 | public boolean addAll(Collection<? extends E> c) { |
Object[] toArray()
根据elementData数组拷贝一个新的数组
1 | public Object[] toArray() { |
ArrayList注意事项
1.ArrayList允许插入null元素
2.当数据量很大时,ArrayList中间添加或移除元素的时候,会拷贝元素到新的位置,所以效率较低。建议使用其他集合,如LinkedHashSet
3.ArrayList虽然可以自动扩容,但是数据量较大时,扩展的容量也较大,会造成空间浪费。建议如果预先知道要新增的容量大小,初始化ArrayList时就指定容量大小
三个有趣的示例
示例1
1 | int[] a={1,2,3}; |
问题:A的长度为什么是1呢?
我们看下asList的源码
1 | public static <T> List<T> asList(T... a) { |
发现asList接收一个泛型变长参数,而基本类型是不能被泛型化的,所以当传入int类型数组的时候,数组是一个对象,把int类型数组作为了T类型。则A长度为1。
示例2
1 | List<String> pets= Arrays.asList("cat","dog"); |
问题:为什么list不能使用add方法呢?
我们还是查看下源码:
1 | public class Arrays { |
发现静态内部类中,存储数组的a变量是final类型的,由此发现,这里是不能做任何内部元素的添加删除操作的。
内部类里面也没有add,remove方法,可以看下这个类继承的AbstractList类里面的这些方法实现:
1 | public void add(int index, E element) { |
ok,我们使用asList得到的对象add,remove操作时就是直接抛出异常。
如果要对asList的对象使用add,remove方法可以使用如下的解决办法
1 | List<String> pets= new ArrayList<String>(Arrays.asList("cat","dog")); |
示例3
1 | String[] test={"a","b","c","d"}; |
我们发现改变了list的顺序,但是数组的顺序也发生了变化。
解决办法:
1 | List<String> testList = new ArrayList<String>(Arrays.asList(test)); |
综上:
Arrays.asList()产生的List的对象会使用底层数组作为其物理实现,只要你执行操作就会修改这个List,并且你不想原来的数组被修改,那么你就应该在另一个容器中创建一个副本。