数组
- 连续的内存空间
- 相同类型的数据
- 通过索引即数组下标快速定位元素
- 需事先确定数组长度,不支持动态改变数组大小
链表
- 内存空间不连续,动态分配内存
- 动态增加或删除元素
- 链表节点包含两部分内容,一个是元素的值,另一个是指向下一个元素的指针
List
元素有序、可重复
ArrayList(重点)
- 线程不安全
- 支持快速随机访问,即通过索引快速获取元素,对应get(int index)
- 底层是Object[]数组
- 长度不够需要扩容时,默认扩容为原来的1.5倍
JDK1.8之前时,无参构造时底层直接构建一个长度为10的Object[]数组。
JDK1.8及之后时,无参构造时底层的Object[]数组先初始化为{},当需要添加第一个元素时才初始化一个长度为10的Object[]数组,并进行元素添加。延迟了数组的创建,在一定程度上节省了内存。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
...
}
插入时的时间复杂度:
- 最好情况,直接在尾部插入,时间复杂度为O(1);
- 最坏情况,在头部插入,所有元素都需要向后移动一位,时间复杂度为O(n)。
删除时的时间复杂度:
- 最好情况,直接删除末尾元素,时间复杂度为O(1);
- 最坏情况,删除首部元素,剩余元素都需要向前移动一位,时间复杂度为O(n)。
查找时的时间复杂度:
- 最好情况,首部元素即为所需元素,时间复杂度为O(1);
- 最坏情况,末尾元素才是所需元素或者元素不存在,时间复杂度为O(n)。
ArrayList可以存储null
public static void main(String[] args) {
ArrayList<String> array = new ArrayList<>();
array.add("hhh");
array.add(null);
//输出[hhh, null]
System.out.println(Arrays.toString(array.toArray()));
}
ArrayList的扩容机制
//将指定的元素追加到此列表的末尾
public boolean add(E e) {
//添加元素前先调用ensureCapacityInternal方法
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//进一步调用
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//判断数组是否为{},如果是则返回默认容量10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//如果不是,则返回minCapacity
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
//如果calculateCapacity返回的容量比数组elementData的长度大,则调用grow方法
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//右移一位相当于除以2,扩大为原来的1.5倍
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);
}
- 当add第1个元素时,elementData是个空数组,此时内部调用ensureCapacityInternal(0+1),然后调用calculateCapacity(elementData, 1),返回默认容量10,接着调用ensureExplicitCapacity(10),由于10-elementData.length > 0,则调用grow(10),确定数组容量为10,使用Arrays.copyOf方法进行数组复制。
- 当add第2个元素时,此时内部调用ensureCapacityInternal(1+1),然后调用calculateCapacity(elementData, 2),返回2,由于第一次添加元素时elementData扩容为10了,所以2-elementData.length > 0不成立,不会调用grow(2)。
- 当add第3、4、... 、10个元素时,同理,都不会调用grow方法。
- 当add第11个元素时,此时内部调用ensureCapacityInternal(10+1),然后调用calculateCapacity(elementData, 11),返回11,由于elementData的容量为10,所以11-elementData.length > 0成立,则调用grow(11),在原容量的基础上数组容量扩容了1.5倍,即newCapacity=15,然后使用Arrays.copyOf方法进行数组复制。
LinkedList
- 线程不安全
- 底层是双向链表(JDK1.6之前是双向循环链表,JDK1.7之后取消了循环)
- 两个指针,一个指向头结点,一个指向尾结点
插入时的时间复杂度:
- 直接在头部或尾部插入,时间复杂度为O(1);
- 在指定位置插入,需要先移动到该位置,再进行插入操作,时间复杂度为O(n)。
删除时的时间复杂度:
- 直接删除头结点或尾结点,时间复杂度为O(1);
- 在指定位置插入,需要先移动到该位置,再进行删除操作,时间复杂度为O(n)。
查找时的时间复杂度:
- 查找的是头结点或尾结点对应的值,时间复杂度为O(1);
- 查找的是其他结点的值或者所查找的元素不存在,需要移动进行比较,时间复杂度为O(n)。
Vector
- 线程安全
- 底层是Object[]数组,容量不够时扩容为原来的2倍
- 基本不使用
ArrayList和数组的区别
- ArrayList能动态扩容,数组不能扩容;
- ArrayList创建时既可以自定义长度也可以不定义,而数组需要自定义长度;
- ArrayList可以使用泛型确定存储的数据类型,数组不是;
- ArrayList只存储对象,不存储基本数据类型,数组既可以存储基本数据类型也可以存储对象;
- ArrayList有一些方法可实现插入、删除等操作,而数组没有。
ArrayList和LinkedList的区别
- ArrayList和LinkedList都是线程不安全的;
- ArrayList底层是Object[]数组,LinkedList底层是双向链表;
- ArrayList支持快速随机访问,而LinkedList不支持;
- ArrayList的空间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间浪费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。