List实现类的数据结构
- Arraylist: Object数组
- Vector: Object数组
- LinkedList:
双向链表(JDK1.6之前为循环链表,JDK1.7取消了循环)
Arraylist 与 LinkedList 的区别
- 是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
- 底层数据结构: Arraylist 底层使用的是 Object 数组;LinkedList 底层使用的是 双向链表 数据结构(JDK1.6之前为循环链表,JDK1.7取消了循环);
- 插入和删除是否受元素位置的影响:
① ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e)
方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element) )
时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。
② LinkedList 采用链表存储,所以对于add(E e)
方法的插入,删除元素时间复杂度不受元素位置的影响,近似 O(1),如果是要在指定位置i插入和删除元素的话((add(int index, E element))
时间复杂度近似为O(n)),因为需要先移动到指定位置再插入。 - 是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index) 方法)。
- 内存空间占用: ArrayList的空间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)
List的遍历方式选择
- 实现了 RandomAccess 接口的list,优先选择普通 for 循环 ,其次 foreach,
- 未实现 RandomAccess接口的list,优先选择iterator遍历(foreach遍历底层也是通过iterator实现的)大size的数据,千万不要使用普通for循环
ArrayList与Vector的区别
- Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间。
- Arraylist不是同步的,所以在不需要保证线程安全时建议使用Arraylist。
ArrayList扩容分析
成员变量
//Default initial capacity.
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.
*/
transient Object[] elementData;
//The size of the ArrayList (the number of elements it contains).
private int size; //基本数据类型,初始值为0
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
空参构造方法
/**
* Constructs an empty list with an initial capacity of ten.
* 构建一个空的ArrayList对象
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
让成员变量elementData
指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA
,即一个空的Object[]
数组,在我们没有添加元素之前数组为空,则说明在第一次添加元素的时候,就会发生扩容
add()方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
在执行ensureCapacityInternal(size + 1)
之后,将数组:elementData[size]
的size
位置赋值为当前要添加的元素,然后size++
返回true
,可见向ArrayList中添加元素,就是不断的在给Object[] elementData
数组进行赋值操作,那么在赋值之前,首先就要保证的是这个数组有空位置来装下当前元素,这个过程就是通过执行ensureCapacityInternal()
方法来保证的
ensureCapacityInternal()——保证内部容量
// 参数 minCapacity = size + 1
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
calculateCapacity()——计算容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
首先判断elementData是不是DEFAULTCAPACITY_EMPTY_ELEMENTDATA
,在空参的构造方法中让elementData指向了DEFAULTCAPACITY_EMPTY_ELEMENTDATA
,所以这个判断为true,返回DEFAULT_CAPACITY
和minCapacity
的较大值,此时添加元素时需要的最小容量minCapacity
为size+1 = 1
,而DEFAULT_CAPACITY = 10
,所以返回10
ensureExplicitCapacity()方法——确保明确的容量
// minCapacity = 10
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code 10 - 0 > 0
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
modCount++后面解释,此时minCapacity - elementData.length
(10 - 0 > 0)明显大于0,if 判断为true,执行grow()方法
grow()方法——扩容方法
//参数minCapacity = 10
private void grow(int minCapacity) {
// overflow-conscious code,第一次为 0
int oldCapacity = elementData.length;
// 新容量 = 旧容量 + 旧容量*0.5
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 1、如果:新的容量 - 最小容量 < 0,则新容量 = 最小容量
if (newCapacity - minCapacity < 0){
newCapacity = minCapacity;
}
// 2、如果新容量大于MAX_ARRAY_SIZE,则新容量 = hugeCapacity()的返回值
if (newCapacity - MAX_ARRAY_SIZE > 0){
newCapacity = hugeCapacity(minCapacity);
}
// 3、将原数组复制到新数组中,原数组指向新的数组,容量为:newCapacity
elementData = Arrays.copyOf(elementData, newCapacity);
}
(1) 当新容量大于最小容量时,即当向空的 List 集合中添加第一个元素的时候,oldCapacity = elementData.length = 0
,执行
int newCapacity = oldCapacity + (oldCapacity >> 1);
即:newCapacity = 0,所以newCapacity - minCapacity = 0 - 10 < 0 成立,第一个 if 判断为true,新容量newCapacity = minCapacity = 10,有时候说的默认初始容量为10,是在添加的时候容量为10,在没有添加元素,刚创建出来的时候,初始容量为0 (Object[]
空数组而已)
(2) 当扩容后的容量大于MAX_ARRAY_SIZE 的时候,执行hugeCapacity()
方法
(3)Arrays.copyOf(elementData, newCapacity)
将elementData复制到一个新数组中,新数组长度为newCapacity(此时为10),原数组中的前10个(包含第10个,下标为9)拷贝到新数组中,不够10个,用0替代,然后让elementData 指向这个新的数组,就实现对原数组的拷贝和扩容,此时elementData 的长度就为10
当再次添加第2个,第3个…元素的时候,calculateCapacity()
的第二个参数:minCapacity = 1+1,2+1,…,
此时 if 判断elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
为false,所以在添加第11个元素之前,该方法都返回的是传递进来的minCapacity
ensureExplicitCapacity()
方法的参数就是calculateCapacity()
的返回值,在添加第11个元素之前,方法中 if 判断minCapacity - elementData.length < 0
总是成立,此时就不会执行grow()方法,直到添加到第11个元素时,minCapacity = 11,会再次执行grow()方法,此时新容量为旧容量的1.5倍为15,grow()中两个if判断都不为true,继续将旧数组拷贝到新数组中,新容量为15
hugeCapacity()方法
private static int hugeCapacity(int minCapacity) {
// overflow
if (minCapacity < 0){
throw new OutOfMemoryError();
}
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
在这个方法中比较了minCapacity
和MAX_ARRAY_SIZE
的大小,如果前者大,则返回Integer.MAX_VALUE
,否则返回MAX_ARRAY_SIZE
,赋值给newCapacity
System.arraycopy()和Arrays.copyOf()
public static void main(String[] args) {
int[] arr1 = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int[] arr2 = new int[5];
System.arraycopy(arr1,0,arr2,0,5);
System.out.println(Arrays.toString(arr2));
//[1, 2, 3, 4, 5]
}
System.arraycopy()参数说明:
- 1、Object src : 原数组
- 2、int srcPos : 从元数据的起始位置开始
- 3、Object dest : 目标数组
- 4、int destPos : 目标数组的开始起始位置
- 5、int length : 要copy的数组的长度
public static void main(String[] args) {
int[] arr1 = {1, 2, 3, 4, 5};
int[] arr2 = Arrays.copyOf(arr1, 10);
int[] arr3 = Arrays.copyOf(arr1, 3);
//[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]
System.out.println(Arrays.toString(arr2));
//[1, 2, 3]
System.out.println(Arrays.toString(arr3));
}
ArrayList中的ensureCapacity()方法
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<>();
final int N = 10000000;
long startTime = System.currentTimeMillis();
for (int i = 0; i < N; i++) {
list.add(i);
}
long endTime = System.currentTimeMillis();
System.out.println("使用ensureCapacity方法前:"+(endTime - startTime));
list = new ArrayList<>();
long startTime1 = System.currentTimeMillis();
list.ensureCapacity(N);
for (int i = 0; i < N; i++) {
list.add(i);
}
long endTime1 = System.currentTimeMillis();
System.out.println("使用ensureCapacity方法后:"+(endTime1 - startTime1));
}
使用ensureCapacity方法前:2277
使用ensureCapacity方法后:375
Process finished with exit code 0
通过运行结果,我们可以很明显的看出向 ArrayList 添加大量元素之前最好先使用ensureCapacity 方法,以减少增量重新分配的次数
LinkedList
LinkedList是一个实现了List接口和Deque接口的双端链表。 LinkedList底层的链表结构使它支持高效的插入和删除操作,另外它实现了Deque接口,使得LinkedList类也具有队列的特性; LinkedList不是线程安全的
私有静态内部类
private static class Node<E> {
E item;//节点值
Node<E> next;//后继节点
Node<E> prev;//前驱节点
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}