本文选取 List 接口实现子类中最常用的三个类 ArrayList、Vector 和 LinkedList 进行讲解
1. ArrayList
1.1 扩容机制
-
ArrayList 中维护了一个Object类型的数组 elementData,该数组用于存放数据
transient Object[] elementData;
-
当 ArrayList 创建对象时,如果使用的是无参构造器,则初始 elementData 容量为0。第一次添加元素到数组中,则扩容 elementData为10。如果需要再次扩容,则扩容 elementData 为当前容量的1.5倍
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//无参构造器创建的数组为空
private int size;//默认为0
private static final int DEFAULT_CAPACITY = 10;//默认容量大小
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//数组添加元素方法,其中含有数组扩容机制
public boolean add(E e) {
ensureCapacityInternal(size + 1);//检查是否需要扩容
elementData[size++] = e;
return true;
}
//
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果传入的数组为空
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//返回传入的 minCapacity (此处为size + 1) 和 10 中的最大值
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//不为空返回 size + 1
return minCapacity;
}
//
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//如果此时 minCapacity 的大小比数组长度要大,则扩容
//比如是无参构造器创建的数组,此时 10 - 0 > 0
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//扩容方法
private void grow(int minCapacity) {
int oldCapacity = elementData.length;//数组大小
//新数组大小 = 旧数组大小 + (旧数组大小按二进制形式右移1位)
//此处如果 oldCapacity = 10,则 (oldCapacity >> 1) = 5,即扩展为原数组1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)//一般不会出现该情况
newCapacity = hugeCapacity(minCapacity);
//将数据拷贝至具有 newCapacity 长度的新数组 elementData
elementData = Arrays.copyOf(elementData, newCapacity);
}
- 如果使用的是指定大小的构造器,则初始 elementData 容量为指定大小,扩容机制同上。
private static final Object[] EMPTY_ELEMENTDATA = {};
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
2.Vector
2.1 线程安全
Vector 是线程同步的,即线程安全,Vector 类的操作方法带有 synchronized。
在开发中,需要线程同步安全时,考虑使用Vector。
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
2.2 扩容机制
- Vector 底层也是一个对象数组;
protected Object[] elementData;
- 如果调用无参构造器,默认数组大小是10,填满数组后,则按2倍扩容;
- 如果使用的是指定大小的构造器,则每次直接按2倍扩容。
//add方法与ArrayList的方法基本一致,也是带有扩容机制
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);//检查是否需要扩容
elementData[elementCount++] = e;
return true;
}
private void ensureCapacityHelper(int minCapacity) {
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//扩容机制
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//新数组大小 = 2 * 旧数组大小
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);
}
2.3 ArrayList 和 Vector比较
底层结构 | 版本 | 线程安全(同步)效率 | 扩容倍数 | |
---|---|---|---|---|
ArrayList | transient Object[] elementData; | jdk 1.2 | 不安全,效率高 | 如果是无参构造器,则初始数组容量为0。第一次添加元素到数组中,则扩容数组为10。如果需要再次扩容,则扩容数组为当前容量的1.5倍; 如果是有参构造器,则初始数组容量为指定大小,扩容机制同上。 |
Vector | protected Object[] elementData; | jdk 1.0 | 安全,效率不高 | 如果调用无参构造器,默认数组大小是10,填满数组后,则按2倍扩容; 如果是有参构造器构造器,则每次直接按2倍扩容。 |
3. LinkedList
由于 LinkedList 底层实现了双向链表和双端队列特点,此处首先需要对双向链表进行基本介绍。
3.1 双向链表
首先我们需要介绍Node结点这一基本概念。Node结点是构成双向链表的基本元素。
//Node 对象表示双向链表的一个结点
class Node{
public Object item;
public Node next;//指向后一个结点
public Node pre;//指向前一个结点
public Node(Object item) {
this.item = item;
}
}
而双向链表则是在Node结点的基础上,将各个结点连接起来的表。下面是一个简单双向链表的实例:
public class LinkedList01 {
public static void main(String[] args) {
//简单双向链表
Node jack = new Node("jack");
Node tom = new Node("tom");
Node red = new Node("red");
//链接结点
jack.next = tom;
tom.next = red;
tom.pre = jack;
red.pre = tom;
Node first = jack;//让first引用指向jack,就是链表的头结点
Node last = red;//让last引用指向red,就是链表的尾结点
//演示从到头尾遍历
while (true){
if (first == null){
break;
}
System.out.println(first);
first = first.next;
}
3.2 LinkedList操作机制
- LinkedList 底层维护了一个双向链表;
- LinkedList 中维护了两个属性 first 和 last ,分别指向首结点和尾结点;
- 每个结点(Node对象)里面维护了 prev、next、item 三个属性,其中 prev 指向前一个结点, next 指向后一个结点,item 存放元素,最终实现双向链表;
- LinkedList 的元素的添加和删除,不是通过数组完成的,相对来说效率较高;
- LinkedList 可以添加任意元素(元素可以重复),包括null;
- 线程不安全,没有实现同步。
3.3 扩容机制
与前面的 ArrayList 和 Vector 不同,LinkedList 的扩容机制实际上就是双向链表增加结点的操作。
transient Node<E> first;
transient Node<E> last;
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;
}
}
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;//l 指向尾结点
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;//last 指向新结点
if (l == null)//尾结点为空,即链表为空时
first = newNode;//first 指向新结点
else
l.next = newNode;//否则,在尾结点后方插入新结点
size++;
modCount++;
}
3.4 ArrayList 和 LinkedList 比较
底层结构 | 增删效率 | 改查效率 | |
---|---|---|---|
ArrayList | 可变数组 | 较低| 数组扩容 | 较高 |
LinkedList | 双向链表 | 较高 通过链表追加 | 较低 |
在实际情况中,如何选择 ArrayList 和 LinkedList :
- 如果我们改查的操作多,选择 ArrayList ;
- 如果我们增删的操作多,选择 LinkedList ;
- 在一个项目中,根据业务灵活选择,也可能一个模块使用的是 ArrayList ,另外一个模块是 LinkedList 。