上一篇:java集合:(一)认识集合的本质、java两大集合派系的关系梳理
ArrayList、LinkedList、Vector源码原理探究以及FailFast机制(JDK1.8)
一、ArrayList
ArrayList继承关系图
1. 源码解析
在看下面分析时,建议自己翻出源码对照着看。
1.1 类中属性
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
//数组默认初始化大小
private static final int DEFAULT_CAPACITY = 10;
//空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//空数组(和EMPTY_ELEMENTDATA的区别没仔细研究)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//最终存储元素的数组结构(重要)
transient Object[] elementData; // non-private to simplify nested class access
//elementData数组中存放元素的个数
private int size;
//elementData最大储存容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
1.2 构造函数
/**
* 无参构造函数
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 有参构造函数1
*/
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
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
总结:ArrayList的构造方法就做一件事情,就是初始化一下储存数据的容器,其实底层本质就是elementData数组。
1.3 核心方法
1.3.1 boolean add(E e)
默认直接在末尾添加元素
public boolean add(E e) {
//size是数组中数据的个数,因为要添加一个元素,所以size+1
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) {
/**
*elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA,
*即判断elementData是不是空的数组。然后找出默认容量(10)
*和参数容量中大的。
*可以得出ArrayList在实例化时是不初始化elementData的,
*在使用add方法添加元素时才开始初始化elementData数组。
*/
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//判断容量是否够用的方法,如果不够用则扩容
private void ensureExplicitCapacity(int minCapacity) {
/**
*modCount代表集合类内部结构的修改次数,即集合中的删除、添加
*等等操作都会增加该变量的数量。
*这个属性的作用很多,个人认为最主要的就是用来检测快速失败(后面会讲到)
*/
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
//将扩充前的elementData大小给oldCapacity
int oldCapacity = elementData.length;
/**
*oldCapacity >> 1 = oldCapacity ÷ 2
*即
*oldCapacity + (oldCapacity >> 1) = oldCapacity * 1.5
*所以这一步就相当于扩容1.5倍
*/
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
//如果newCapacity超过了最大的容量限制,就调用hugeCapacity,也就是将能给的最大值给newCapacity
newCapacity = hugeCapacity(minCapacity);
//新的容量大小已经确定好了,就copy数组,改变容量大小
elementData = Arrays.copyOf(elementData, newCapacity);
}
1.3.2 void add(int index, E element)
在特定位置添加元素,也就是插入元素
public void add(int index, E element) {
//检查index也就是插入的位置是否合理,即数组下标是否越界
rangeCheckForAdd(index);
//和上面add方法中的ensureCapacityInternal作用一致
ensureCapacityInternal(size + 1);
//这个方法就是用来在插入元素之后,要将index之后的元素都往后移一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
1.3.3 E remove(int index)
删除指定位置上的元素
public E remove(int index) {
//检测数组下标是否越界
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
//得出要移动元素个数
int numMoved = size - index - 1;
if (numMoved > 0)
//移动元素
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将最后一个元素置空,然后GC机制会回收该内存,并且ArrayList的长度-1
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
1.3.4 boolean remove(Object o)
删除指定数据的元素
public boolean remove(Object o) {
//可以看出arrayList是可以存放null的
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
1.3.5 int indexOf(Object o)
查找数组里面是否存在指定元素。遍历数组,找到第一个和指定元素相等的元素,返回对应的下标,如果找不到相同的则返回-1。
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
本文就不详细分析每个方法了,其余的核心方法自己去翻下源码看看:
void clear()
:将elementData中每个元素都赋值为null,等待垃圾回收将这个给回收掉
boolean removeAll(Collection<?> c)
:批量删除
boolean addAll(Collection<? extends E> c)
:批量添加
E get(int index)
:根据下标获得元素
2. 总结
1)arrayList可以存放null。
2)arrayList本质上就是一个elementData数组。
3)arrayList区别于数组的地方在于能够自动扩展大小,其中关键的方法就是gorw()方法。
4)arrayList中removeAll(collection c)和clear()的区别就是removeAll可以删除批量指定的元素,而clear是删除集合中的全部元素。
5)arrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除这些方面,性能下降很多,因为需要移动很多数据才能达到应有的效果
6)arrayList实现了RandomAccess,所以在遍历它的时候推荐使用for循环(RandomAccess相关介绍)。
二、LinkedList
LinkedList继承关系图
1. 源码解析
1.1 类中属性
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
//链表目前的容量
int size = 0;
//链表中的头节点
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;
}
}
}
1.2 构造方法
//无参构造方法
public LinkedList() {
}
//有参构造方法
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
1.3 核心方法
1.3.1 boolean add(E e)
添加元素时,是直接添加在链表的结尾
public boolean add(E e) {
//将e作为最后一个元素插入
linkLast(e);
return true;
}
void linkLast(E e) {
//取出当前最后一个节点
final Node<E> l = last;
//创建一个新节点,注意其前驱节点为l,后续节点为null
final Node<E> newNode = new Node<>(l, e, null);
//将新的节点记为最后一个节点
last = newNode;
if (l == null)
//如果最后一个节点为空,则表示链表为空,则将first节点也赋值为newNode
first = newNode;
else
//关联l的next节点,构成双向节点
l.next = newNode;
//元素总数加1
size++;
//修改次数自增
modCount++;
}
1.3.2 E get(int index)
根据下标获取元素。
在上篇文章中我们说过,链表不能像数组一样通过下标索引访问对应元素,linkedList
提供的E get(int index)
方法其实是根据类似二分的方法,判断是从first
节点遍历查找还算是从last
节点遍历查找 ,根据下面的源码就可得出。
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
Node<E> node(int index) {
// assert isElementIndex(index);
// index如果小于链表长度的1/2
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;
}
}
1.3.3 void add(int index, E element)
在特定位置添加元素
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
//上面已解释
linkLast(element);
else
//插入到指定元素前
linkBefore(element, node(index));
}
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
//创建新的节点 前驱节点为succ的前驱节点,后续节点为succ,则e元素就是插入在succ之前的
final Node<E> newNode = new Node<>(pred, e, succ);
//构建双向链表,succ的前驱节点为新节点
succ.prev = newNode;
//如果前驱节点为空,则first为newNode
if (pred == null)
first = newNode;
else
//构建双向链表
pred.next = newNode;
size++;
modCount++;
}
1.3.4 boolean addAll(Collection<? extends E> c)和boolean addAll(int index, Collection<? extends E> c)
批量添加(在链表末尾添加)
批量添加(随机某个位置添加)
这块逻辑不复杂,但是纯看代码不一定能理解,建议自己写个demo调试一下。
//这个addAll方法也是上面有参构造方法中的addAll方法
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
public boolean addAll(int index, Collection<? extends E> c) {
// 检查index是否越界
checkPositionIndex(index);
Object[] a = c.toArray();
int numNew = a.length;
// 如果插入集合无数据,则直接返回
if (numNew == 0)
return false;
Node<E> pred, succ;
// 如果index与size相同
if (index == size) {
// succ的前驱节点直接赋值为最后节点
// succ赋值为null,因为index在链表最后
succ = null;
pred = last;
} else {
//取出index上的节点
succ = node(index);
pred = succ.prev;
}
// 遍历插入集合
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
// 创建新节点 前驱节点为succ的前驱节点,后续节点为null
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
// succ的前驱节点为空,则表示succ为头,则重新赋值第一个结点
first = newNode;
else
// 构建双向链表
pred.next = newNode;
// 将前驱节点移动到新节点上,继续循环
pred = newNode;
}
// index位置上为空 赋值last节点为pred,因为通过上述的循环pred已经走到最后了
if (succ == null) {
last = pred;
} else {
// 构建双向链表
// 从这里可以看出插入集合是在succ[index位置上的节点]之前
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
其余的一些方法也没什么特别的,自己看源码即可。
2. 总结
1)LinkedList也是继承了List的接口,所以在LinkedList中存储的也是有序的,不唯一的数据。它采用的是链表式储存,所以比较适合用来执行插入,删除等功能
2) LinkedList总结
三、Vector
Vector底层源码基本和ArrayList是一致的,只不过Vector是线程同步的(Synchronized),ArrayList是线程不同步的。但是Vector基本上已经被大家慢慢放弃了,是一个过时的类。
Vector继承关系图
1. ArrayList、LinkedList、Vector对比
ArrayList
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程不安全,效率高
Vector
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程安全(不绝对
),效率低
LinkedList
优点: 底层数据结构是链表,查询慢,增删快。
缺点: 线程不安全,效率高
Vector为什么慢慢被遗弃
Vector中对每一个独立操作都实现了同步,这通常不是我们想要的做法。对单一操作实现同步通常不是线程安全的(举个例子,比如你想遍历一个Vector实例。你仍然需要申明一个锁来防止其他线程在同一时刻修改这个Vector实例。如果不添加锁的话通常会在遍历实例的这个线程中导致一个ConcurrentModificationException(快速失败机制,后面会讲)
)同时这个操作也是十分慢的(在创建了一个锁就已经足够的前提下,为什么还需要重复的创建锁)当然,即使你不需要同步,Vector也是有锁的资源开销的。总的来说,在大多数情况下,这种同步方法是存在很大缺陷的。
2. Collections.synchronizedList
要实现List的线程安全,建议使用Collections.synchronizedList方法。
Collections.synchronizedList使用同步代码块的方式,而Vector是同步方法。
同步代码块和同步方法的区别:
- 同步代码块在锁定的范围上可能比同步方法要小,一般来说锁的范围大小和性能是成反比的。
- 同步块可以更加精确的控制锁的作用域(锁的作用域就是从锁被获取到其被释放的时间),同步方法的锁的作用域就是整个方法。
- 静态代码块可以选择对哪个对象加锁,但是静态方法只能给this对象加锁。
//SynchronizedRandomAccessList也继承了SynchronizedList,所以我们看看SynchronizedList里面是怎么写的
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
/**
* 由源码可以看出add、get、remove等等很多方法都加上了同步代码块的锁
*/
static class SynchronizedList<E>
extends SynchronizedCollection<E>
implements List<E> {
private static final long serialVersionUID = -7754090372962971524L;
final List<E> list;
SynchronizedList(List<E> list) {
super(list);
this.list = list;
}
SynchronizedList(List<E> list, Object mutex) {
super(list, mutex);
this.list = list;
}
public boolean equals(Object o) {
if (this == o)
return true;
synchronized (mutex) {return list.equals(o);}
}
public int hashCode() {
synchronized (mutex) {return list.hashCode();}
}
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
public E set(int index, E element) {
synchronized (mutex) {return list.set(index, element);}
}
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}
public int indexOf(Object o) {
synchronized (mutex) {return list.indexOf(o);}
}
public int lastIndexOf(Object o) {
synchronized (mutex) {return list.lastIndexOf(o);}
}
public boolean addAll(int index, Collection<? extends E> c) {
synchronized (mutex) {return list.addAll(index, c);}
}
public ListIterator<E> listIterator() {
return list.listIterator(); // Must be manually synched by user
}
public ListIterator<E> listIterator(int index) {
return list.listIterator(index); // Must be manually synched by user
}
public List<E> subList(int fromIndex, int toIndex) {
synchronized (mutex) {
return new SynchronizedList<>(list.subList(fromIndex, toIndex),
mutex);
}
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
synchronized (mutex) {list.replaceAll(operator);}
}
@Override
public void sort(Comparator<? super E> c) {
synchronized (mutex) {list.sort(c);}
}
private Object readResolve() {
return (list instanceof RandomAccess
? new SynchronizedRandomAccessList<>(list)
: this);
}
}
使用SynchronizedList,在遍历时要手动进行同步处理
使用SynchronizedList的时候,进行遍历时要手动进行同步处理。可以根据上面的源码
看出执行add()等方法的时候是加了synchronized关键字的,但是listIterator(),
iterator()却没有加.所以在使用的时候需要加上synchronized,如下:
List<String> list = Collections.synchronizedList(new ArrayList<String>());
list.add("1");
list.add("2");
list.add("3");
// Must be in synchronized block
synchronized (list) {
Iterator i = list.iterator();
while (i.hasNext()) {
//foo(i.next());
System.out.println(i.next());
}
}
SynchronizedList和Vector最主要的区别:
- SynchronizedList有很好的扩展和兼容功能。他可以将所有的List的子类转成线程安全的类。
- 使用SynchronizedList的时候,进行遍历时要手动进行同步处理。
- SynchronizedList可以指定锁定的对象
四、FailFast机制
1. 测试快速失败机制
创建三个类
ThreadAdd:
使用线程休眠间接性的向list中添加数据
ThreadIterate:
使用线程休眠迭代list中的值
FailFastTest:
执行上面两个类
public class ThreadAdd extends Thread {
private List list;
public ThreadAdd(List list) {
this.list = list;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("add :" + i);
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add(i);
}
}
}
public class ThreadIterate extends Thread {
private List list;
public ThreadIterate(List list) {
this.list = list;
}
@Override
public void run() {
while (true) {
for (Iterator iteratorTmp = list.iterator(); iteratorTmp.hasNext(); ) {
System.out.println("loop" + iteratorTmp.next());
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class FailFastTest {
private static final List<String> list = new ArrayList<>();
public static void main(String[] args) {
new ThreadAdd(list).start();
new ThreadIterate(list).start();
}
}
运行结果:
运行该代码,抛出异常java.util.ConcurrentModificationException!即,产生fail-fast事件!
结果说明:
当某一个线程遍历list的过程中,list的内容被另外一个线程所改变了;就会抛出ConcurrentModificationException异常,产生fail-fast事件。
2. fail-fast解决办法
fail-fast机制,是一种错误检测机制。它只能被用来检测错误,因为JDK并不保证fail-fast机制一定会发生。若在多线程环境下使用fail-fast机制的集合,建议使用“java.util.concurrent包下的类”去取代“java.util包下的类”。
所以,本例中只需要将ArrayList替换成java.util.concurrent包下对应的类即可。
即,将代码
private static final List<String> list = new ArrayList<>();
替换为
private static final List<String> list = new CopyOnWriteArrayList<String>();
则可以解决该办法(虽然解决了,但是我是没有想到能应用到什么业务场景里)。
3. fail-fast原理
产生fail-fast事件,是通过抛出ConcurrentModificationException异常来触发的。
那么,ArrayList是如何抛出ConcurrentModificationException异常的呢?
ConcurrentModificationException是在操作Iterator时抛出的异常,所以我们看看iterator的相关源码。
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
//modCount记录了对象的修改次数,前面也讲过,集合在添加删除等等操作时都会使该值增加
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
可以发现在调用 next() 和 remove()时,都会执行checkForComodification()。若 “modCount 不等于 expectedModCount”,则抛出ConcurrentModificationException异常,产生fail-fast事件。
下一篇:java集合:(三)HashMap、ConcurrentHashMap源码分析以及与Hashtable、TreeMap的区别