List接口
List 是一个接口,继承自Collection 接口,其下方还有ArrayList 、LinkedList 、Vertor 的实现类。
类图可参考:文章《Collection》
本文将记录List 下实现类的源码debug 流程,以及源码分析。
一、ArrayList??
1. 简介
在源码的头部注释中写道:实现自List 接口,是一个可调整数组的实现,允许所有元素包括null 的添加,除了实现List 接口内的方法之外,还包含一些方法用于操作内部数组的大小。这个类是类似于Vertor 的,但它是非同步的。当向内部添加元素的时候,它的容量可以自动增加。由于是非同步(非线程安全)的类,所以如果有多线程并发操作ArrayList 实例,则可能会抛出ConcurrentModificationException (并发修改异常)异常。
从以上内容中可以得知ArrayList 有几个特点:
- 内部由一个
Object 数组实现 - 可以添加
null 值,且可以重复 - 使用数组实现,随机查改效率高,增删效率低,因为设计了数组的扩容
- 会自动扩容
- 线程不安全:不可以并发操作此类
- 性能比
Vector 高,因为没有加同步锁
2. 类核心属性
3. 构造方法
ArrayList 有3个构造方法,分别为:
4. add() 方法
先说结论:当调用ArrayList 的add() 方法时
-
调用时,会首先调用判断内部的elementData 大小是否满足当前元素的放入,如果不满足的话,则会调用grow() 方法进行数组的扩容,扩容时会计算新数组的长度,计算时分为两种情况,第一次进入的时候原数组是个空的,所以会给一个默认的长度10 ,第二次及以后进入,才会 * 1.5,来计算,得出新数组长度后会进行Arrays.copyOf() 复制操作,将原数组赋值给新数组,并初始化至newCapacity 新的容量。
为什么是1.5 倍?
-
首先1.5 是由(oldCapacity >> 1 计算得出) -
因为1.5 可以充分利用移位操作,减少浮点数或者运算时间和运算次数。 -
其次扩容1.2 倍未免过于太小,而2 倍又太大,在权衡之下就采用1.5 倍的做法。
源码:
第一步先看add(E e) 方法
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
第二步ensureCapacityInternal() 方法
-
确保内部大小足够 -
参数minCapacity 为期望的最小容量
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
第三步ensureExplicitCapacity() 方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
第四步grow() 方法
- 数组扩容,确保至少可以容纳
minCapacity 个元素
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
第四步的->hugeCapacity() 方法
此处可能会抛出OOM 异常
在方法hugeCapacity(minCapacity) 内会判断,minCapacity 是否小于0,但是一步一步的往上查找,发现,minCapacity 的值是size + 1 ,那么正常情况下理解,这个参数应该是不会小于0的,那为什么要加这个判断呢?
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0)
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
这个地方有两个问题
二、LinkedList
1. 简介
LinkedList 是一个使用了双向链表实现的一个数据结构,和ArrayList 有着很大的区别,同时它也是一个线程不安全的集合,可能会抛出ConcurrentModificationException 。
- 使用双向链表进行实现
- 可以添加
null 值 - 使用双向链表实现,不涉及扩容,增删效率高
- 随机查找需要循环,效率慢
- 不可以并发修改
2. 类的核心属性
-
first 指向双向连表的头部节点
-
transient Node<E> first;
transient 修饰不需要被序列化
-
last 指向双向连表的尾部节点
-
transient Node<E> last;
transient 修饰不需要被序列化
-
Node<E> 内部结构
-
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;
}
}
3. 构造方法
LinkedList 共有2个构造方法
4. add() 方法
先说结论:当调用LinkedList 的add() 方法时:
默认会往双向链表的尾部添加一个元素,等价于addLast() 方法。如果last 属性等于null 的话,则代表是第一次添加,此时将会把first 属性赋值为添加的元素,否则就创建一个新节点,将last 节点的next 属性指向这个创建的新节点,完成双向连表的赋值和连接操作。
源码:
public boolean add(E e) {
linkLast(e);
return true;
}
linkLast() 方法
- 意思为连接尾部的意思,将新节点连接到当时尾部的
next 属性下
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
注:看到这个方法有没有想到,如果我添加一个null进来,头部节点会被覆盖掉吗?
答:并不会?,因为null 只是Node 中item 属性的值,所以l == null 并不成立,所以并不会被覆盖。😜
5. add(index) 方法
- 将元素插入到指定
index 的位置,如果该位置有元素的话就将其右移。
源码:
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
Node<E> node(int index) {
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
}
else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
6. linkBefore() 方法
- 意思为往头部插入元素,将内部
first 属性赋值给传入元素 - 方法备注:将元素
e 插入到,一定不为空的元素succ 之前
源码:
void linkBefore(E e, Node<E> succ) {
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
7. 备注
在调用iterator() 方法时,会首先去校验,调用时记录的modCount 是否等于自身当前的modCount ,如果不等于的话则会抛出并发修改异常ConcurrentModificationException()
三、Vector
1. 简介
Vector 是一个线程安全的集合类,你可以在使用iterator() 方法时,调用其他修改的方法(会等待锁释放),不会产生线程安全的问题,因为在Vector 的实现方法内,基本的都被加上了synchronzied 关键字修饰,iterator() 方法在执行的时候更是会锁住类,其他方法执行的时候都会等待锁的释放。
- 线程安全的集合类
- 效率没有
ArrayList 高,因为加了synchronzied 关键字,会使线程同步,因此会影响效率。 - 底层使用
Object 数组进行实现
2. 和ArrayList 的区别
3. add()方法
- 可以参考
ArrayList 的add() 方法,除了扩容机制不一样,其它基本一致。
四、集合类选型
三种集合特性对比
-
ArrayList (90%的应用场景,推荐指数:??????????)
- 随机查询,随机修改快,可以通过索引下标直接选中
- 线程不安全,并发场景不同时修改同一对象
- 添加效率低,需要进行扩容,效率较低
-
LinkedList (根据业务需求,进行灵活使用 推荐指数:??????)
-
Vector (多线程并发场景使用,并发场景推荐指数:??????????,其余场景不推荐使用)
- 修改效率最低,因为添加了
synchronzied ,进行线程同步 - 并发场景时使用
|