一、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);
}