基本概念
在开发过程中,数组一旦初始化后,长度就确定了,存储数据对象不能达到动态扩展,其次数组存储元素不便于对数组进行添加、修改、删除操作,而且数组可以存储重复元素。相比之下,Java集合就像是一个容器,可以存储任何类型的数据,也可以结合泛型来存储具体的类型对象。在程序运行过程中,Java集合可以动态的进行扩展。因此,Java集合类更加适合于现代开发需求。
-
从集合接口结构可以看出Java集合主要由2大体系构成,分别
Collection
体系和Map
体系,其中Collection
和Map
分别是2大体系中的顶层接口。 -
Collection
主要有三个子接口,分别为List(列表)
、Set(集)
、Queue(队列)
。其中,List、Queue
中的元素有序可重复,而Set
中的元素无序不可重复。 -
List
中有ArrayList
、LinkedList
以及Vector
三个实现类。
我将从下面几个方面简单概括一下三个实现类的实现原理和区别:
存储结构
ArrayList和Vector的底层都是通过数组实现的,因此和数组类似,可以通过下标访问,默认初始容量为10,可以动态扩容;
LinkedList的底层则是使用含头结点的双向链表实现。
线程安全性
三者中,只有Vector是线程安全的类,因为它的大部分方法都包含关键字synchronized,但如果是开发中没有线程同步的需求,推荐优先使用ArrayList。因此其内部没有synchronized关键字,执行效率会比Vector快很多;如果需要在并发环境下使用ArrayList和LinkedList类,可以通过调用Collections类中的静态方法synchronizedList()来保证线程安全;
扩容机制
从内部实现机制来讲,ArrayList和Vector都是使用Object的数组形式来存储的,当向这两种类型中增加元素的时候,若容量不够,需要进行扩容。
在这里需要说一下可变长度数组的原理:当元素个数超过数组的长度时,会产生一个新的数组,将原数组的数据复制到新数组,再将新的元素添加到新数组中。
ArrayList在JDK1.8中的扩容过程:
/**
ArrayList扩容后的容量是之前的1.5倍,然后把之前的数据拷贝到新建的数组中去。
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//表明最大的数组容量是Integer.MAX_VALUE-8,对于空出的8位,官方解释是:1.存储Headerwords; 2.避免一些机器内存的溢出,减少出错率; 3.最大还是能支持到Integer.MAX_VALUE.
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);//可以看出扩容后的容量是扩容前的1.5倍
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类中不同的是,Vector可以设置容量增量,而ArrayList则不行。在Vector中,有容量增量参数capacityIncrement:当大小大于其容量时,容量自动增加的量。如果在创建Vector时,指定了capacityIncrement的大小,则Vector中动态数组容量需要增加时,如果容量的增量大于0,则增加的是大小是capacityIncrement,如果增量小于0,则增大为之前的2倍。
Vector在JDK1.8中的扩容过程:
/**
Vector扩容后的容量是之前的2倍,然后把之前的数据拷贝到新建的数组中去。
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
操作效率
ArrayList和Vector效率差别不大,区别在于使用场景,因为Vector类是线程安全的,所以适用于多线程场景。重点比较ArrayList和LinkedList的操作效率:
ArrayList | LinkedList |
---|---|
底层是线性表(数组) | 底层是链表操作 |
get()直接读取某个元素,复杂度O(1) | get()获取某个元素,需要依次遍历,复杂度O(n) |
add(x),直接在尾部添加,复杂度O(1) | add(x), 直接在尾部添加,复杂度O(1) |
add(index,E)添加元素,后面的元素需要向后移动,复杂度O(n) | add(index,E)添加元素,需要先查找到元素位置,然后直接指针指向操作,复杂度O(n) |
remove()删除元素,后面的元素需要逐个移动,复杂度O(n) | remove()删除元素,直接指针指向操作,复杂度O(1) |