ArrayList、CopyOnWriteArrayList源码解析(JDK1.8)

时间:2023-02-21 16:30:48

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

ArrayList源码解析

ArrayList中的变量

ArrayList、CopyOnWriteArrayList源码解析(JDK1.8)

通过上图可以看到,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中有三个构造函数:

ArrayList、CopyOnWriteArrayList源码解析(JDK1.8)

    /**
* 指定一个具有初始容量的空数组,可以自己指定容量的大小
*
* @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、CopyOnWriteArrayList源码解析(JDK1.8)

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、CopyOnWriteArrayList源码解析(JDK1.8)

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、CopyOnWriteArrayList源码解析(JDK1.8)

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源码解析

ArrayList、CopyOnWriteArrayList源码解析(JDK1.8)

通过上图可以看到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();
}
}

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

ArrayList、CopyOnWriteArrayList源码解析(JDK1.8)

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&period;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. &lpar;一&rpar;ArrayList集合源码解析

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

  6. 源码解析JDK1&period;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. 几种常见的Shell

    Unix/Linux上常见的Shell脚本解释器有bash.sh.csh.ksh等,习惯上把它们称作一种Shell.我们常说有多少种Shell,其实说的是Shell脚本解释器. bash bash是L ...

  2. vue切换按钮(关闭消失型)

    弹窗: <div class="pop" v-show="isShow"> <i class="iconfont icon-icon ...

  3. setNeedsDisplay和setNeedsLayout

    1,UIView的setNeedsDisplay和setNeedsLayout方法 首先两个方法都是异步执行的.而setNeedsDisplay会调用自动调用drawRect方法,这样可以拿到  UI ...

  4. 如何用Matlab将cell数据写入文件

    我们知道,一般的文件读写函数是不接受直接将cell内容(非数值)直接写入文件的, 例如:dlmwrite('o.txt', C, 'delimiter', '\t');%C 为cell类型数据,会报错 ...

  5. APP顶号逻辑

    登录的接口login.do接口上需要记录关键的信息:userId.设备型号(Android|iPhone).登录时间.登录IP.设备唯一标识(UUID) 当在另外一台新设备上登录同一帐号时,将user ...

  6. springmvc前后端传值

    @pathvible 后端传值(rest风格) exp: @requestMapping("/view/{userId}") public String getiew(@Parth ...

  7. zookeeper&lbrack;6&rsqb; zookeeper FAQ(转)

    转自:http://www.cnblogs.com/zhengran/p/4601855.html 1. 如何处理CONNECTION_LOSS?在Zookeeper中,服务器和客户端之间维持一个长连 ...

  8. html入门1

    1.HTML超文本标记语言,(Hypertext Markup Language),通过一段内容定义为标题,段落或者图像等,从而让该内容具有结构以及含义 2.W3C:万维网联盟(World Wide ...

  9. javascript&amp&semi;&amp&semi;jquery编写插件模板

    javascrpt插件编写模板 这里不分享如何编写插件,只留一个框架模板,使用面向对象的形式进行编写,方便管理 ;(function(window,document){ function FnName ...

  10. cu命令

    选项: -b:仅显示行中指定直接范围的内容: -c:仅显示行中指定范围的字符: -d:指定字段的分隔符,默认的字段分隔符为“TAB”: -f:显示指定字段的内容: -n:与“-b”选项连用,不分割多字 ...