关于ArrayList的小知识
1. LinkedList与ArrayList的区别
-
首先两者的内部结构不同:ArrayList是基于在动态数组的顺序表数据结构。顺序表的存储地址是连续的。而LinkedList 是一个继承于AbstractSequentialList的双向链表,它是在链表的基础上实现的。它也可以被当作堆栈、队列或双端队列进行操作。链表的存储地址是不连续的,每一个存储地址都通过指针指向。
-
对于随机访问get和set操作,ArrayList比LinkedList的性能要高,因为LinkedList要移动指针。
-
对于新增和删除操作则相反,LinedList比ArrayList性能要高,因为ArrayList要移动数据,需要重新排放,而LinkedList只需要把指针重新指向就可以了
2. ArrayList的底层实现与扩容
ArrayList的底层实现:
ArrayList是List接口的可变数组的实现:
// An highlighted block
transient Object[] elementData;
private int size;
List<String> list = new ArrayList<String>(100);
list.add(“aaa”);
list.add(“bbb”);
list.add(“ccc”);
list.add(“ddd”);
list.remove(1);
移除操作,就是将指定索引位置以后的元素都向前挪了一位,(这里调用了System.arraycopy方法,该方法的优势在于它使用的是内存复制,省去了大量的数组寻址访问等时间),然后执行elementData[–size] = null;将size减1,并将最后一个元素置为空
// An highlighted block
public E remove(int index) {
rangeCheck(index);//检查索引是否在合理范围内
modCount++;
E oldValue = elementData(index);
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
return oldValue;
}
ArrayList的扩容
每次向数组中添加元素时,都要去检查元素的个数会不会超出当前数组的长度,如若长度超出,数组就会进行扩容,以满足添加数据的需求。数组扩容通过一个公开的方法ensureCapacity(int minCapacity)来实现, 而该方法最终又会调用一个私有的grow方法进行扩容。我们每扩容一次其容量约为原来的1.5倍,具体代码是将数组原来长度跟数组原来长度右移一位的值求和。以至于为什么用位操作,是因为对于计算机而言位操作的效率更高。
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
注意:
数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,这种操作的代价是很高的,因此在使用时,我们应该最大限度避免数组容量的扩张的次数。所以在初始化ArrayList的时候最好估算一下大概需要多少容量,也可以使用 ensureCapacity来手动添加ArrayList实例的容量,尽量减少频繁调整容量的开销和效率