深入理解JAVA集合系列四:ArrayList源码解读
在开始本章内容之前,这里先简单介绍下List的相关内容。
List的简单介绍
有序的collection,用户可以对列表中每个元素的插入位置进行精确的控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。列表通常允许重复的元素,且允许null元素的存放。
ArrayList的简单介绍
JDK中这样定义ArrayList:List接口的大小可变数据的实现。
主要有以下特点:
1、有序
2、线程不安全
3、元素可以重复
4、可以存放null值
顾名思义,取名ArrayList是因为其内部是用一个大小可变的数组存储元素,也就是说底层是一个动态数组。
add方法解读
List<String> li = new ArrayList<String>(); li.add("33");
先看这个构造函数:
public ArrayList() { this(10); }
1 public ArrayList(int initialCapacity) { 2 super(); 3 if (initialCapacity < 0) 4 throw new IllegalArgumentException("Illegal Capacity: "+ 5 initialCapacity); 6 this.elementData = new Object[initialCapacity]; 7 }
这里先定义默认的初始容量为10,然后构建了一个容量为10的数组elementData;这个elementData数组就是ArrayList底层存放元素的数组,且元素是Object类型。该数组的源码定义也了解下:
private transient Object[] elementData;
再来看ADD方法:
1 public void add(int index, E element) { 2 if (index > size || index < 0) 3 throw new IndexOutOfBoundsException( 4 "Index: "+index+", Size: "+size); 5 6 ensureCapacity(size+1); // Increments modCount!! 7 System.arraycopy(elementData, index, elementData, index + 1, 8 size - index); 9 elementData[index] = element; 10 size++; 11 }
1、第6行的扩容部分后面再解释,其余的比较简单,我们直接看第7行。调用的是System.arraycopy的方法。
2、第一次看到这个方法有点搞不懂,先来解释下:System.arraycopy(src, srcPos, dest, destPos, length);
第一个参数表示的是要被复制的数组;第二个参数表示数组中从第几个元素开始复制。
第三个参数表示复制到哪个数组;第四个参数表示复制到那个数组从哪个元素开始;第五个参数表示要复制的长度
3、解释完这个方法就很清楚了,add方法就是将数组中待添加元素的位置后面所有的元素(包括该位置),通过copy的复制方法,全部后移一个位置。然后将待添加的元素放到指 定的位置上。当然这个数组可能是原数组,也可能是经过第6行代码扩容之后新的数组。
扩容
扩容,动态扩容,也就是前面所提到的ArrayList的底层是由一个动态变化的数组实现的。
1 public void ensureCapacity(int minCapacity) { 2 modCount++; 3 int oldCapacity = elementData.length; 4 if (minCapacity > oldCapacity) { 5 Object oldData[] = elementData; 6 int newCapacity = (oldCapacity * 3)/2 + 1; 7 if (newCapacity < minCapacity) 8 newCapacity = minCapacity; 9 // minCapacity is usually close to size, so this is a win: 10 elementData = Arrays.copyOf(elementData, newCapacity); 11 } 12 }
1、我们看扩容的算法,也就是在旧容量的基础上,扩大1.5倍再加上1。至于为什么是这样一个算法,我也没明白。但是JDK开发人员肯定是在这方面做过思考的:不能一次性扩容太大,会造成内存空间的浪费;也不能扩容得不够,否则下一次扩容的操作会很快到来。我猜想应该是基于这样的考虑才设计了这样一个算法。
2、扩容结束后,就是将旧数组里面的元素全部复制到新的数组里。并且新数组还是通过elementData变量来引用。
remove方法解读
我们先来看下该方法的源码:
1 public E remove(int index) { 2 RangeCheck(index); 3 4 modCount++; 5 E oldValue = (E) elementData[index]; 6 7 int numMoved = size - index - 1; 8 if (numMoved > 0) 9 System.arraycopy(elementData, index+1, elementData, index, 10 numMoved); 11 elementData[--size] = null; // Let gc do its work 12 13 return oldValue; 14 }
其实这个remove的方法处理的过程也比较简单:
1、第2行代码是用来检测数组下标是否越界。
2、然后取出该位置上的数组元素。
3、通过arryCopy方法将待删除元素之后的所有元素往前一个位置复制。(注意,这个动作是先复制,然后粘贴)。
4、这个时候最后一个位置上的元素就是多余的,所以才有了第11行的处理,将其置为null。
ArrayList的优缺点
1、底层是由数组实现的,便于随机访问。
2、顺序添加元素性能比较高,相当于每次在数组最后添加一个元素、
3、插入元素、删除元素的时候,会涉及到元素的复制,即调用arrayCopy方法,如果复制的元素比较多,则这样就比较消耗性能。
总结来说:ArrayList的优势是随机访问、顺序添加。