本篇文章主要是学习后的知识记录,存在不足,或许不够深入,还请谅解。

ArrayList源码解析

ArrayList中的变量



通过上图可以看到,ArrayList中总共有7个变量,下面看下每个变量的作用:

    /**
* 序列化
*/
private static final long serialVersionUID = 8683452581122892189L;
/**
* 默认的初始容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 用于在构造函数中,初始化一个空的数组
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 用于无参构造函数中,给一个空的数组
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 这个是真正存储元素的数组
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* size,是用来记录arrayList中,即elementData里的元素的大小,数组中共有多少个元素
* @serial
*/
private int size;
/**
* 这个参数,是数组所允许的最大长度,要是超出了,可能会报OutOfMemoryError异常
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

ArrayList构造函数

ArrayList中有三个构造函数:

    /**
* 指定一个具有初始容量的空数组,可以自己指定容量的大小
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException 指定的初始容量值若是负值,会抛出这个异常
*/
public ArrayList(int initialCapacity) {
//容量大于0,就把elementData初始化一个具有initialCapacity大小的数组
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
//要是指定的容量值是0,就初始化一个空数组
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
//指定的容量值小于0.抛出不合法参数异常
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
} /**
* 无参构造函数,会指定一个初始容量为10的数组
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
} /**
* 构造一个包含指定元素的列表集合,按照集合返回它们的顺序
*
* @param c 传入的集合,会把集合中的元素放入数组中
* @throws NullPointerException if the specified collection is null
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
//数组中元素的长度不为0,就把c的元素copy到elementData中
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 {
//要是元素个数是0个,就初始化一个空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}

ArrayList中的add方法

    /**
* add方法,会把一个元素添加到数组的末尾
*
* @param e 传入的元素
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
//size是目前数组存的元素的个数,传入size+1,即需要的数组长度
//需要的数组长度,是可以再去容纳一个元素
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
 //这里传入的是size+1
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//这个方法是判断数组是否是一个空数组,要是使用无参构造函数初始化的arrayList,那么返回值就是10
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//要是elementData是一个空的数组
//判断需要的数组长度和默认容量值哪个大,返回最大的那个
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//要是elementData数组中有元素,直接返回minCapacity
return minCapacity;
}
//这里是,判断数组是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
//记录数组被修改的次数
modCount++; //要是需要的数组长度大于目前数组的长度,就需要扩容了(即数组的长度是否可以存入下一个元素)
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
 /**
* 扩容方法
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
//旧的数组的长度
int oldCapacity = elementData.length;
//需要扩容的数组的长度,即10*1.5=15
int newCapacity = oldCapacity + (oldCapacity >> 1);
//要是扩容后的数组的长度还是小于需要的最小容量,那么就把需要的最小容量给newCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//要是扩容后的数组长度比最大的数组容量还大,就需要控制了
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//把扩容之后的数组copy到长度为newCapacity的数组中
elementData = Arrays.copyOf(elementData, newCapacity);
} private static int hugeCapacity(int minCapacity) {
//参数小于0.抛异常
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//判断需要的数组容量和数组最大容量哪个大,
//需要的数组容量比数组最大容量还大,就返回int的最大值
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

图式:

ArrayList中的add(插入指定位置)方法

先看下 System.arraycopy:

    // src 源数组
// srcPos 源数组要复制的起始位置
// dest 要赋值到的目标数组
// destPos 目标数组放置的起始位置
// length 复制的长度
System.arraycopy(Object src, int srcPos, Object dest, int destPos,int length);

继续add方法:

    /**
* 将元素插入数组中指定的位置
* @param index 指定的索引值
* @param element 需要插入的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
//参数校验,判断要插入的下标是否越界
rangeCheckForAdd(index);
//这个和add(E e)是一样的,判断数组是否需要扩容等
ensureCapacityInternal(size + 1); // Increments modCount!!
//把插入index位置的原有元素以及该元素后面的元素,向右移动
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//把元素插入到index的位置
elementData[index] = element;
//数组元素的个数加1
size++;
}
    /**
* A version of rangeCheck used by add and addAll.
*/
private void rangeCheckForAdd(int index) {
//要是插入的位置比数组中元素的个数大,或者插入的位置值小于0,就抛出下标越界异常
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
} /**
* Constructs an IndexOutOfBoundsException detail message.
* Of the many possible refactorings of the error handling code,
* this "outlining" performs best with both server and client VMs.
*/
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}

ArrayList中的get方法

get方法相对而言就比较简单些:

 /**
* 根据下标获取指定位置的元素
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
//参数校验,若index比数组中元素的size还大,就抛异常
rangeCheck(index);
//返回对应的元素
return elementData(index);
}
 /**
* 在获取数组元素之前,需要进行数据校验
* 若传入的参数不在指定的数组索引范围内,就抛异常
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

ArrayList中的remove(int index)方法

/**
* 移除列表中指定位置的元素,然后把移除元素后面的元素向左移动
*
* @param index 需要移除元素的索引值
* @return 移除的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
//参数校验,校验index的值是否在索引所允许的范围内
rangeCheck(index);
//列表的修改次数加一
modCount++;
//先查出对应index位置出的元素,赋值给oldValue
E oldValue = elementData(index);
//把移除的元素后面的所有元素向左移动
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//把数组最后一个索引位置数据设置为null
elementData[--size] = null; // clear to let GC do its work
//返回移除的元素数据
return oldValue;
}

ArrayList中的remove(Object o)方法

    /**
*
* 从列表中删除指定元素的第一个匹配项
*
* @param o 需要移除的元素
* @return <tt>true</tt> if this list contained the specified element
*/
public boolean remove(Object o) {
//若元素是null,移除第一个匹配为null的元素
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
//若元素不为null,就移除第一个匹配到的元素
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
    /*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
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
}

注意:由于ArrayList是线程不安全的,所以不要在遍历中去对ArrayList做修改,否则会出现错误

ArrayList中的clear方法

/**
* 清除列表中的所有元素
*/
public void clear() {
//列表修改次数加一
modCount++;
// clear to let GC do its work 清除所有元素,垃圾回收
//通过遍历把所有元素设置为Null
for (int i = 0; i < size; i++)
elementData[i] = null; size = 0;
}

CopyOnWriteArrayList源码解析



通过上图可以看到copyOnWrite的实现方式,这种方式适用于读极多,写极少的情况,而且如果数据量巨大,在copy之后的一瞬间,内存占用增加,也会引发问题。CopyOnWriteArrayList是线程安全的。

CopyOnWriteArrayList变量

    /** 可重入锁 */
final transient ReentrantLock lock = new ReentrantLock(); /**
* 数组,只能通过getArray和setArray操作
*/
private transient volatile Object[] array; /**
* Gets the array. Non-private so as to also be accessible
* from CopyOnWriteArraySet class.
*/
final Object[] getArray() {
return array;
} /**
* Sets the array.
*/
final void setArray(Object[] a) {
array = a;
}

CopyOnWriteArrayList的构造函数

 /**
* 创建一个空的列表
*/
public CopyOnWriteArrayList() {
setArray(new Object[0]);
} /**
* 构造一个包含指定元素的列表集合,按照集合返回它们的顺序
*
* @param c 传入的集合
* @throws NullPointerException if the specified collection is null
*/
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
if (c.getClass() == CopyOnWriteArrayList.class)
elements = ((CopyOnWriteArrayList<?>)c).getArray();
else {
elements = c.toArray();
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elements.getClass() != Object[].class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
}
setArray(elements);
} /**
* 创建包含给定数组副本的列表
*
* @param toCopyIn the array (a copy of this array is used as the
* internal array)
* @throws NullPointerException if the specified array is null
*/
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}

CopyOnWriteArrayList的两个add方法

    /**
* 添加一个元素到列表的最后面
*
* @param e 需要添加到列表中的元素
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
//初始化锁
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
//获取数组中的元素
Object[] elements = getArray();
//获取数组的长度
int len = elements.length;
//把elements数组copy到长度为len + 1的newElements数组中,即新的数组长度增加1
Object[] newElements = Arrays.copyOf(elements, len + 1);
//然后把元素加到数组的末尾
newElements[len] = e;
//set数组的元素为添加之后的数组
setArray(newElements);
return true;
} finally {
//解锁
lock.unlock();
}
} /**
* 向指定的索引位置加入元素,加入位置后面的元素需要向右移位
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
//初始化一个锁
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
//获取数组中的元素
Object[] elements = getArray();
//获取数组的长度
int len = elements.length;
//如果传入的索引值不在数组所允许的范围内,就抛异常
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;
int numMoved = len - index;
//如果插入的索引大小和数组长度一样,那么直接插入到数组末尾
if (numMoved == 0)
newElements = Arrays.copyOf(elements, len + 1);
else {
//设置新数组的长度比之前的数组长度大一位,即可以插入一个元素
newElements = new Object[len + 1];
//先copy elements中index索引之前的元素到newElements
System.arraycopy(elements, 0, newElements, 0, index);
//再把elements中index之后的元素已经index中的元素copy到index右边,即右移
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
//把元素放入到指定的索引处
newElements[index] = element;
//设置数组的引用为新的数组
setArray(newElements);
} finally {
//解锁
lock.unlock();
}
}

向指定索引处插入元素图解:

CopyOnWriteArrayList的两个get方法

get方法比较简单,不做赘述。

private E get(Object[] a, int index) {
return (E) a[index];
} /**
* {@inheritDoc}
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
return get(getArray(), index);
}

CopyOnWriteArrayList的remove方法

    /**
* 移除列表中指定索引位置的元素,并把后续的元素向左移动
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
//初始化锁
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
//获取数组中的元素
Object[] elements = getArray();
//获取数组的长度
int len = elements.length;
//获取索引处原先的旧值
E oldValue = get(elements, index);
int numMoved = len - index - 1;
//如果要移除的是最后一位直接移除
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
//新的数组,长度比旧的数组少一位
Object[] newElements = new Object[len - 1];
//同样的,//先copy elements中index索引之前的元素到newElements
System.arraycopy(elements, 0, newElements, 0, index);
//再把elements中index+1之后的元素向左移位
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
//返回移除后的元素
return oldValue;
} finally {
//解锁
lock.unlock();
}
}

在CopyOnWriteArrayList,移除的方法还有另外两个,实现的方法也都大同小异,都是先copy一份列表,然后加锁去操作,移除掉元素,然后再把数组的引用指向移除后的数组即可。

到此,arrayList和CopyOnWriteArrayList源码就结束了,上面的解释以及注释可能有错误或者不足的地方,希望指正,共同进步,多谢!

ArrayList、CopyOnWriteArrayList源码解析(JDK1.8)的更多相关文章

  1. Java集合-ArrayList源码解析-JDK1.8

    ◆ ArrayList简介 ◆ ArrayList 是一个数组队列,相当于 动态数组.与Java中的数组相比,它的容量能动态增长.它继承于AbstractList,实现了List, RandomAcc ...

  2. CopyOnWriteArrayList源码解析(1)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 注:在看这篇文章之前,如果对ArrayList底层不清楚的话,建议先去看看ArrayList源码解析. ht ...

  3. 第三章 CopyOnWriteArrayList源码解析

    注:在看这篇文章之前,如果对ArrayList底层不清楚的话,建议先去看看ArrayList源码解析. http://www.cnblogs.com/java-zhao/p/5102342.html ...

  4. CopyOnWriteArrayList源码解析

    Java并发包提供了很多线程安全的集合,有了他们的存在,使得我们在多线程开发下,可以和单线程一样去编写代码,大大简化了多线程开发的难度,但是如果不知道其中的原理,可能会引发意想不到的问题,所以知道其中 ...

  5. (一)ArrayList集合源码解析

    一.ArrayList的集合特点 问题 结      论 ArrayList是否允许空 允许 ArrayList是否允许重复数据 允许 ArrayList是否有序 有序 ArrayList是否线程安全 ...

  6. 源码解析JDK1.8-HashMap链表成环的问题解决方案

    前言 上篇文章详解介绍了HashMap在JDK1.7版本中链表成环的原因,今天介绍下JDK1.8针对HashMap线程安全问题的解决方案. jdk1.8 扩容源码解析 public class Has ...

  7. ArrayList LinkedList源码解析

    在java中,集合这一数据结构应用广泛,应用最多的莫过于List接口下面的ArrayList和LinkedList; 我们先说List, public interface List<E> ...

  8. ArrayList类源码解析——ArrayList动态数组的实现细节(基于JDK8)

    一.基本概念 ArrayList是一个可以添加对象元素,并进行元素的修改查询删除等操作的容器类.ArrayList底层是由数组实现的,所以和数组一样可以根据索引对容器对象所包含的元素进行快速随机的查询 ...

  9. CopyOnWriteArrayList源码解析(2)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 5.删除元素 public boolean remove(Object o) 使用方法: list.remo ...

随机推荐

  1. JDK源码阅读-DirectByteBuffer

    本文转载自JDK源码阅读-DirectByteBuffer 导语 在文章JDK源码阅读-ByteBuffer中,我们学习了ByteBuffer的设计.但是他是一个抽象类,真正的实现分为两类:HeapB ...

  2. Winform 判断打印机是否可用,实现设置默认打印机功能

    Winform 判断打印机是否可用,实现设置默认打印机功能 http://www.cnblogs.com/zfanlong1314/p/3878563.html

  3. Google单元测试框架gtest之官方sample笔记1--简单用例

    1.0 通用部分 和常见的测试工具一样,gtest提供了单体测试常见的工具和组件.比如判断各种类型的值相等,大于,小于等,管理多个测试的测试组如testsuit下辖testcase,为了方便处理初始化 ...

  4. Django框架的forms组件与一些补充

    目录 一.多对多的三种创建方式 1. 全自动 2. 纯手撸(了解) 3. 半自动(强烈推荐) 二.forms组件 1. 如何使用forms组件 2. 使用forms组件校验数据 3. 使用forms组 ...

  5. 使用gitlab构建基于docker的持续集成(二)

    使用gitlab构建基于docker的持续集成(二) gitlab docker aspnetcore Centos配置gitlab镜像并且启动 Centos配置防火墙 windows上访问gitla ...

  6. 后端程序员之路 10、gbdt(Gradient Boosting Decision Tree)

    1.GbdtModelGNode,含fea_idx.val.left.right.missing(指向left或right之一,本身不分配空间)load,从model文件加载模型,xgboost输出的 ...

  7. WPF绑定资源文件错误(error in binding resource string with a view in wpf)

    报错:无法将"***Properties.Resources.***"StaticExtension 值解析为枚举.静态字段或静态属性 解决办法:尝试右键单击在Visual Stu ...

  8. 如何进BAT,有了这个篇面试秘籍,成功率高达80%!!(附资料)

    多年前自己刚来北京找工作的时候,面了一个星期 面了七八家公司才拿到一个offer.而上次跳槽面了不到10家公司基本全过而且都给到了期望的薪资,本来自己在面试前没想到能够这么顺利,回想起来还是自己准备的 ...

  9. iOS之CoreBluetooth

    思路 手机与设备间的通讯方式CoreBluetooth是比较常见且通用的.在iOS开发中需明晰以下几点 蓝牙4.0最多可联机7个设备,iPhone6以上都是蓝牙4.0 两台iPhone并不直接通过蓝牙 ...

  10. POJ-2752(KMP算法+前缀数组的应用)

    Seek the Name, Seek the Fame POJ-2752 本题使用的算法还是KMP 最主要的片段就是前缀数组pi的理解,这里要求解的纸盒pi[n-1]有关,但是还是需要使用一个循环来 ...