从源代码角度来分析ArrayList、LinkedList、Vector的区别

目录

一、ArrayList

1、结论

ArrayList是List接口的实现类,底层数据结构是数组,查询快,添加删除慢,不是线程安全的,如果不设置初始化容量,那么初始化容量是0,并且将在初次执行添加方法的时候会将容量增加到10;如果设置初始化容量,无论设置的初始化容量是否大于10,都不会在添加的时候将容量增加到10;按照数组容量的1.5倍进行扩容,如果知道ArrayList的大致容量,最好设置初始化容量,毕竟扩容是通过数组复制来完成的,这还是很耗费资源的

2、证明

注意:以下代码来自于ArrayList类的源码;

2.1、ArrayList是List接口的实现类
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
2.2、底层数据结构是数组
transient Object[] elementData;
2.3、查询快
public E get(int index) {
	// 检查是否会出现索引越界异常IndexOutOfBoundsException
	rangeCheck(index);
	// 根据索引获取对应值
	return elementData(index);
}
E elementData(int index) {
	// 根据索引去索取值,时间复杂度是O(1)
	return (E) elementData[index];
}
2.4、添加删除慢

添加我们等会还会说到,现在只说一下删除方法(remove(int index)和remove(Object o)都是类似操作,只分析一个);

public E remove(int index) {
	rangeCheck(index);

	modCount++;
	// 取出需要删除的数据,用来最后return返回
	E oldValue = elementData(index);
	// 计算数组中该索引之后还有几个非空值
	int numMoved = size - index - 1;
	// 如果非空值的数目大于0
	if (numMoved > 0)
		// 调用native方法来执行移动操作,也就是将需要删除的值之后的所有值往前面移动一位,这个移动还是浪费时间的
		System.arraycopy(elementData, index+1, elementData, index,
						 numMoved);
	// 将数组中的最后一个非空值设置为null,如果这个值就是被删除的值,那就正合我意;如果这个值不是被删除的值,那这个值已经被移动到前面一位了,所以这一位没用了
	elementData[--size] = null; // clear to let GC do its work
	// 返回被删除值
	return oldValue;
}
2.5、不是线程安全的

里面的方法都不是synchronized同步方法,如下:
在这里插入图片描述

2.6、如果不设置初始化容量,那么初始化容量是0,并且将在初次执行添加方法的时候会将容量增加到10
注意:本次分析使用ArrayList无参构造方法创建对象,然后初次往集合中添加数据的前提下

// int类型的成员变量初始化容量是0
private int size;

// 默认容量
private static final int DEFAULT_CAPACITY = 10;

// 该值换成数字是2147483639
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

// 数组初始值
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 由于将数组赋值为{},那么初始化容量是0
public ArrayList() {
	this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

// 假设初次执行添加方法
public boolean add(E e) {
	// 确保数组容量充足,size是默认值0,那么 size + 1 就是 1
	ensureCapacityInternal(size + 1);  // Increments modCount!!
	// 往数组中添加值
	elementData[size++] = e;
	return true;
}

// 确保数组容量充足,minCapacity是1
private void ensureCapacityInternal(int minCapacity) {
	// DEFAULTCAPACITY_EMPTY_ELEMENTDATA是{},在调用无参构造的时候就是将DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值给了elementData
	if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
		// DEFAULT_CAPACITY等于10,而minCapacity等于1,那么minCapacity等于10
		minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
	}
	
	// 确保清晰的容量,minCapacity是10
	ensureExplicitCapacity(minCapacity);
}

// 确保清晰的容量,minCapacity是10
private void ensureExplicitCapacity(int minCapacity) {
	// modCount用来计算集合结构被改变的次数,和本次分析无关,所以不再解释
	// 该字段源码注释:The number of times this list has been <i>structurally modified</i>.
	modCount++;

	// 由于elementData是{},所以elementData.length是0,而minCapacity是10,所以大于0
	if (minCapacity - elementData.length > 0)
		// 扩大数组容量,minCapacity是10
		grow(minCapacity);
}

// 扩大数组容量,minCapacity是10
private void grow(int minCapacity) {
	// 由于elementData是{},所以elementData.length是0,所以oldCapacity是0
	int oldCapacity = elementData.length;
	// 结果依然是0,由于oldCapacity >> 1代表oldCapacity除以2,
	// 因此以下代码相当于int newCapacity = 1.5 * oldCapacity
	int newCapacity = oldCapacity + (oldCapacity >> 1);
	// newCapacity是0,minCapacity是10,所以小于0,符合要求
	if (newCapacity - minCapacity < 0)
		// newCapacity被赋值为10
		newCapacity = minCapacity;
	// newCapacity是10,MAX_ARRAY_SIZE是2147483639,所以小于0,不符合要求
	if (newCapacity - MAX_ARRAY_SIZE > 0)
		newCapacity = hugeCapacity(minCapacity);
	// 通过Arrays工具类的copyOf方法来扩大elementData数组的容量到newCapacity,内部是数组元素的复制,这是很浪费性能的
	elementData = Arrays.copyOf(elementData, newCapacity);
}
2.7、如果设置初始化容量,无论设置的初始化容量是否大于10,都不会在添加的时候将容量增加到10
注意:本次分析使用ArrayList有参构造方法创建对象,然后初次往集合中添加数据的前提下

// int类型的成员变量初始化容量是0
private int size;

// 默认容量
private static final int DEFAULT_CAPACITY = 10;

// 该值换成数字是2147483639
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

// 数组初始值
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 假设初始化容量是1,那么initialCapacity就是1,即使容量大于10也是一样的分析方法
public ArrayList(int initialCapacity) {
	// initialCapacity是1,所以大于0
	if (initialCapacity > 0) {
		// 创建容量为1的数组
		this.elementData = new Object[initialCapacity];
	} else if (initialCapacity == 0) {
		this.elementData = EMPTY_ELEMENTDATA;
	} else {
		throw new IllegalArgumentException("Illegal Capacity: "+
										   initialCapacity);
	}
}

// 初次往集合中添加数据
public boolean add(E e) {
	// 确保容量够用,size是0,那么size + 1 是 1
	ensureCapacityInternal(size + 1);  // Increments modCount!!
	elementData[size++] = e;
	return true;
}

// 确保容量够用,minCapacity是1
private void ensureCapacityInternal(int minCapacity) {
	// DEFAULTCAPACITY_EMPTY_ELEMENTDATA是{},而elementData是容量为1的数组,所以不相等
	if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
		minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
	}
	// 确保容量清晰,minCapacity是1
	ensureExplicitCapacity(minCapacity);
}

// 确保容量清晰,minCapacity是1
private void ensureExplicitCapacity(int minCapacity) {
	// modCount用来计算集合结构被改变的次数,和本次分析无关,所以不再解释
	// 该字段源码注释:The number of times this list has been <i>structurally modified</i>.
	modCount++;

	// minCapacity是1,而elementData是容量为1的数组,所以elementData.length也是1,因此相减结果是0,所以不符合要求
	if (minCapacity - elementData.length > 0)
		grow(minCapacity);
}
2.8、按照数组容量的1.5倍进行扩容
// 扩大数组容量
private void grow(int minCapacity) {
	int oldCapacity = elementData.length;
	// 由于oldCapacity >> 1代表oldCapacity除以2,
	// 因此以下代码相当于int newCapacity = 1.5 * oldCapacity
	int newCapacity = oldCapacity + (oldCapacity >> 1);
	…………
}

如果从上往下看的,你一定能看到grow()方法,所以代码注释你也能看懂

2.9、如果你知道ArrayList的大致容量,最好设置初始化容量,毕竟扩容是通过数组复制来完成的,这还是很耗费CPU性能的
private void grow(int minCapacity) {
	…………
	// 通过Arrays工具类的copyOf方法来扩大elementData数组的容量到newCapacity,内部是数组元素的复制,这是很浪费性能的
	elementData = Arrays.copyOf(elementData, newCapacity);
}

二、LinkedList

1、结论

LinkedList是List接口的实现类,底层数据结构是双向链表,查询慢,添加删除快,不是线程安全的,没有初始化大小,也没有扩容机制

2、证明

注意:以下代码来自于LinkedList类的源码;

2.1、LinkedList是List接口的实现类
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
2.2、底层数据结构是双向链表
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;
	}
}
2.3、查询慢
public E get(int index) {
	// 检查该索引是否对应值
	checkElementIndex(index);
	// 找到该索引对应的Node节点中的item值
	return node(index).item;
}

// 找到该index索引对应的Node节点
Node<E> node(int index) {
	// 采用中分思想,找到该index索引的节点在链表前半部分还是后半部分,里面使用的是for循环遍历,所以还是速度比较慢
	if (index < (size >> 1)) {
		// 遍历链表前半部分,找出对应的Node节点
		Node<E> x = first;
		for (int i = 0; i < index; i++)
			x = x.next;
		return x;
	} else {
		// 遍历链表后半部分,找出对应的Node节点
		Node<E> x = last;
		for (int i = size - 1; i > index; i--)
			x = x.prev;
		return x;
	}
}
2.4、添加删除快
2.4.1、先来说一下添加方法
注意:本次分析使用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) {
	// l是null
	final Node<E> l = last;
	// 返回一个新节点,新节点的next是null,而prev也是l,那也是null
	final Node<E> newNode = new Node<>(l, e, null);
	// 将新节点赋值给last
	last = newNode;
	// 因为l是null,所以相等
	if (l == null)
		// 将新节点赋值给first
		first = newNode;
	else
		l.next = newNode;
	size++;
	modCount++;
}

最终LinkedList对象如下图:
在这里插入图片描述

2.4.2、再来说一下删除方法(remove(int index)和remove(Object o)都是类似操作,只分析一个)
// 删除方法
public boolean remove(Object o) {
	// 假设o不是null
	if (o == null) {
		for (Node<E> x = first; x != null; x = x.next) {
			if (x.item == null) {
				unlink(x);
				return true;
			}
		}
	} else {
		// 通过遍历进行找到该节点,然后解除该节点和其他节点之间的关系
		for (Node<E> x = first; x != null; x = x.next) {
			if (o.equals(x.item)) {
				// 解除该节点和其他节点之间的关系
				unlink(x);
				return true;
			}
		}
	}
	return false;
}

// 解除该节点和其他节点之间的关系
E unlink(Node<E> x) {
	// assert x != null;
	final E element = x.item;
	// 当前节点的下一个节点
	final Node<E> next = x.next;
	// 当前节点的上一个节点
	final Node<E> prev = x.prev;

	if (prev == null) {
		// 如果prev是null,说明删除的是头结点,而first就等于头结点,first = next是让first等于头结点的下一个节点
		first = next;
	} else {
		// 如果prev不是null,说明删除的不是头结点,那么将该结点上一个节点的next指向该节点的下一个节点
		prev.next = next;
		// 断掉该节点和上一个节点的联系
		x.prev = null;
	}

	if (next == null) {
		// 如果next是null,说明删除的是尾结点,而last就等于尾结点,last = prev是让last等于尾结点的上一个节点
		last = prev;
	} else {
		// 如果next不是null,说明删除的不是尾结点,那么将该结点下一个节点的prev指向该节点的上一个节点
		next.prev = prev;
		// 断掉该节点和下一个节点的联系
		x.next = null;
	}

	// 将该节点中的值变成null,方便GC回收x节点
	x.item = null;
	// 链表中的节点少了一个,所以减减
	size--;
	// 操作步骤多了一次,所以加加
	modCount++;
	// 返回被删除的节点
	return element;
}
2.5、不是线程安全的

在这里插入图片描述

2.6、没有初始化大小
// 链表就是节点连起来的链子,不需要初始化
public LinkedList() {
}
2.7、也没有扩容机制

添加方法我也带着大家看过了,里面没有看到任何扩容的信息,毕竟链表就是节点组成的链子,需要添加的时候直接把把节点往链表尾部加上就是了

三、Vector

1、结论

Vector是List接口的实现类,底层数据结构是数组,查询快、添加删除慢,是线程安全的,默认初始化容量是10,当然还可以设置初始化容量,默认按照原来的2倍扩容,当然也可以按照你设置的扩容值扩容,如果容量有大致范围,建议设置一下,避免扩容,毕竟数组扩容很耗费性能

2、证明

注意:以下代码来自于Vector类的源码;

2.1、Vector是List接口的实现类
public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
2.2、底层数据结构是数组
protected Object[] elementData;
2.3、查询快
public synchronized E get(int index) {
	// 判断是否索引越界
	if (index >= elementCount)
		throw new ArrayIndexOutOfBoundsException(index);
	// 调用相应方法根据index索引从数组中取出相应的值
	return elementData(index);
}

// 调用相应方法根据index索引从数组中取出相应的值
E elementData(int index) {
	return (E) elementData[index];
}
2.4、添加删除慢
2.4.1、先来说一下添加方法
注意:本次分析使用Vector无参构造方法创建对象,然后初次往集合中添加数据的前提下

protected Object[] elementData;

protected int capacityIncrement;

protected int elementCount;

public Vector() {
	// 初始化容量是10
	this(10);
}

public Vector(int initialCapacity) {
	// 容量自增变量是0
	this(initialCapacity, 0);
}

public Vector(int initialCapacity, int capacityIncrement) {
	super();
	if (initialCapacity < 0)
		throw new IllegalArgumentException("Illegal Capacity: "+
										   initialCapacity);
	// 数组容量是10
	this.elementData = new Object[initialCapacity];
	// 扩容容量是0
	this.capacityIncrement = capacityIncrement;
}

// 添加方法
public synchronized boolean add(E e) {
	// modCount用来计算集合结构被改变的次数,和本次分析无关,所以不再解释
	// 该字段源码注释:The number of times this list has been <i>structurally modified</i>.
	modCount++;
	// elementCount是0,那么elementCount + 1 是 1
	ensureCapacityHelper(elementCount + 1);
	elementData[elementCount++] = e;
	return true;
}

// 确保容量充足,minCapacity是1
private void ensureCapacityHelper(int minCapacity) {
	// minCapacity是1,elementData的length是10,所以小于0,不符合要求
	if (minCapacity - elementData.length > 0)
		grow(minCapacity);
}

// 增加容量,虽然上面不需要执行grow()方法,但是该方法还是有必要说一下
private void grow(int minCapacity) {
	// 获取elementData的数组长度
	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);
	// 如果需要扩大容量,那就使用复制方法来扩大容量,正是由于这个原因,我才说Vector添加慢
	elementData = Arrays.copyOf(elementData, newCapacity);
}
2.4.2、再来说一下删除方法(remove(int index)和remove(Object o)都是类似操作,只分析一个)
// 删除方法
public synchronized E remove(int index) {
		// modCount用来计算集合结构被改变的次数,和本次分析无关,所以不再解释
		// 该字段源码注释:The number of times this list has been <i>structurally modified</i>.
        modCount++;
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);
        E oldValue = elementData(index);
		// 需要移动被删除元素之后的那几个元素
        int numMoved = elementCount - index - 1;
        if (numMoved > 0)
        	// 移动元素调用的是native底层方法,里面涉及元素的复制等等操作,比较浪费时间
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        // 如果被删除元素就是末尾元素,直接置为null,否则末尾元素已经被移动到前面一个位置上了,所以可以直接置空
        elementData[--elementCount] = null; // Let gc do its work
		// 返回被删除的元素
        return oldValue;
    }
2.5、是线程安全的

在这里插入图片描述

2.6、默认初始化容量是10,当然还可以设置初始化容量
public Vector() {
	// 初始化容量是10
	this(10);
}

public Vector(int initialCapacity) {
	// 初始化容量是10,容量自增变量是0
	this(initialCapacity, 0);
}

public Vector(int initialCapacity, int capacityIncrement) {
	super();
	if (initialCapacity < 0)
		throw new IllegalArgumentException("Illegal Capacity: "+
										   initialCapacity);
	// 设置数组容量
	this.elementData = new Object[initialCapacity];
	// 设置扩容容量
	this.capacityIncrement = capacityIncrement;
}
2.7、默认按照原来的2倍扩容,当然也可以按照你设置的扩容值扩容
// 扩容
private void grow(int minCapacity) {
	// 获取elementData的数组长度
	int oldCapacity = elementData.length;
	// 如果设置增长容量,那就使用增长容量,否则使用数组长度来作为增长值
	int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
									 capacityIncrement : oldCapacity);
	…………
}
2.8、如果容量有大致范围,建议设置一下,避免扩容,毕竟数组扩容很耗费性能
// 扩容
private void grow(int minCapacity) {
	…………
	// 如果需要扩大容量,那就使用复制方法来扩大容量,正是由于这个原因才需要设置初始化容量,避免扩容造成性能耗费
	elementData = Arrays.copyOf(elementData, newCapacity);
}

四、类图

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值