public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L; //默认初始容量
private static final int DEFAULT_CAPACITY = 10; //空数组
private static final Object[] EMPTY_ELEMENTDATA = {}; //默认空数组,用来和EMPTY_ELEMENTDATA区分什么时候扩容多少
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //存储ArrayList元素的数组缓冲区,ArrayList的容量是此数组缓冲区的长度,添加第一个元素时,*将扩展为DEFAULT_CAPACITY
transient Object[] elementData; //ArrayList的大小,也就是包含的元素数
private int size; public ArrayList(int initialCapacity) {
if (initialCapacity > 0) { //初始容量大于0
this.elementData = new Object[initialCapacity]; //生成一个新的存储数组
} else if (initialCapacity == 0) { //初始容量等于0
this.elementData = EMPTY_ELEMENTDATA; //设置为空数组
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
} public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
} /**
* 构造一个包含指定元素的列表
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
}

从上述源码中,我们可以得到以下信息:源码基于JDK1.8

1、implements List<E>, RandomAccess(可以随机访问), Cloneable, java.io.Serializable

2、初始容量10,有看过大佬博客说,默认创建ArrayList的时候,调用默认构造器this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA,此时初始容量为0,而不是10

我的理解:

默认构造器注解:

  Constructs an empty list with an initial capacity of ten

  构造一个初始容量为10的空集合

elementData的注解如下:

  The capacity of the ArrayList is the length of this array buffer. Any empty ArrayList with elementData ==

DEFAULTCAPACITY_EMPTY_ELEMENTDATA will be expanded to DEFAULT_CAPACITY when the first element is added

  第一次添加元素,容量会变成DEFAULT_CAPACITY,也就是初始容量

  所以,只是容量为0,初始容量DEFAULT_CAPACITY是final类型,永远为10,和elementData具体等于什么没关系,容量Capacity才会变

3、并且通过transient Object[] elementData保存数据

add()添加数据

public boolean add(E e) {
ensureCapacityInternal(size + 1); //确保容量
elementData[size++] = e; //在数组尾部添加数据
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
} ensureExplicitCapacity(minCapacity);
} private void ensureExplicitCapacity(int minCapacity) {
modCount++; //修改次数+1
if (minCapacity - elementData.length > 0)  //如果最小容量大于数组的长度,进行扩容
grow(minCapacity);
}

grow()扩容

private void grow(int minCapacity) {
int oldCapacity = elementData.length;  //oldCapacity当前数组的长度
int newCapacity = oldCapacity + (oldCapacity >> 1);  //新容量=oldCapacity*1.5
if (newCapacity - minCapacity < 0)  //newCapacity < 最小容量,赋值
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)  //大于最大容量,newCapacity赋值为Integer最大值0x7fffffff
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);  //通过Arrays重新生成一个新容量的数组,包含之前数组的数据,赋值原数组
}

remove()删除数据

1、按照数组下标删除

public E remove(int index) {
rangeCheck(index);  //检查数组是否下标越界 modCount++;
E oldValue = elementData(index);  //取出index位置的数据 int numMoved = size - index - 1;  //
if (numMoved > 0)
     //将index后面的所有元素,都前移一位
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; //最后一位置为null return oldValue;  //返回原来的老数据
}

2、按照元素删除

public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {  //删除第一个null
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {  //删除第一个和object相同的数据
fastRemove(index);
return true;
}
}
return false;
}

都是找个第一个和参数匹配的数据,然后执行的代码和第一种方式相同

复制方法:public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

Object src : 原数组

int srcPos : 从元数据的起始位置开始

Object dest : 目标数组

int destPos : 目标数组的开始起始位置

int length : 要copy的数组的长度

set():

public E set(int index, E element) {
rangeCheck(index);  //检查是否下标越界 E oldValue = elementData(index);  //获取原值
elementData[index] = element;  //覆盖原值
return oldValue;  //返回原值
}

get():

public E get(int index) {
rangeCheck(index); return elementData(index);
}

indexOf():

public int indexOf(Object o) {  //和contain()一样,整体就是遍历是否包含该元素,然后返回下标,如果不包含,返回-1
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;
}

size():和elementData的长度没有关系,指的是实际包含元素的个数

public int size() {
return size;
}

isEmpty():是否为空

public boolean isEmpty() {
return size == 0;
}

trimToSize():缩减集合的容量为size,也就是剔除空节点

public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}

ArrayList的for循环: 

  for、foreach、Iterator,for效率最好,Iterator效率最差

  只有for循环中调用add()和remove(),才不会发生java.util.ConcurrentModificationException异常,其他两种都会,set()不会出现异常。

  因为add() remove()都会执行modCount++

  ConcurrentModificationException主要为了防止线程并发修改的,因为ArrayList是线程不安全的,做了实现fast-fail机制

public static void main(String[] args) throws IOException {
ArrayList<String> list = new ArrayList<>();
list.add("abc");
list.add("def");
list.forEach(s -> {
list.add(s);
list.remove(s);
});
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
String s = (String)iterator.next();
list.remove(iterator.next());
}
}
输出结果:java.util.ConcurrentModificationException

Itr:

public Iterator<E> iterator() {
return new Itr();
}

ArrayList实现了自定义的迭代器

private class Itr implements Iterator<E> {
int cursor; //游标,下个元素的索引
int lastRet = -1; //最后一个元素的索引,如果没有返回-1
int expectedModCount = modCount;//期望修改的次数 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;  //cursor指的是下个元素的索引,由于删除一个元素,所以还是等于当前位置
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();
}
}

ListIterator:

在之前对比Iterator和Iterable有讲过,支持双向遍历,既可以向前,又可以向后

序列化:

  ArrayList本身实现了序列化,transient Object[] elementData,用来保存数据的数组确实用transient修饰的,意味着不能该字段不能实现

序列化,那ArrayList到底如何实现的序列化的呢?

  通过重写writeObject()和readObject(),因为数组中容量Capacity一般都是大于size的,意味着有很多空节点,ArrayList为了节省网络资源

,自定义了序列化,只序列化非空的部分

  如果对序列化不够了解的话,可以参考:Java基础(十一)--Serializable和Externalizable接口实现序列化

到目前为止,我们对ArrayList有了基本的了解:

1、通过数组保存数据,可以随机访问数据,无论是get(i)、add(Object)还是set(int index, E element)效率都是比较高的

2、remove()和add(int index, Object object),都会发生System.arraycopy(),如果index后面的元素比较多的情况下,效率降低很多

3、线程不安全,如果需要保证安全性的话,建议使用CopyOnWriteArrayList

4、初始容量10,发生扩容为原来的1.5倍

总结:ArrayList适合顺序插入,按下标访问/修改,不适合删除,按下标插入

Java集合(四)--基于JDK1.8的ArrayList源码解读的更多相关文章

  1. Java集合(七)--基于jdk1.8的HashMap源码

    HashMap在开发中经常用,面试源码方面也会经常问到,在之前也多次了解过源码,今天算是复习一下,顺便好好总结一下,包括在后面有 相关面试题.本文不会对红黑树代码由太多深入研究,特别是删除方面太复杂, ...

  2. Java -- 基于JDK1.8的ArrayList源码分析

    1,前言 很久没有写博客了,很想念大家,18年都快过完了,才开始写第一篇,争取后面每周写点,权当是记录,因为最近在看JDK的Collection,而且ArrayList源码这一块也经常被面试官问道,所 ...

  3. Java集合基于JDK1.8的ArrayList源码分析

    本篇分析ArrayList的源码,在分析之前先跟大家谈一谈数组.数组可能是我们最早接触到的数据结构之一,它是在内存中划分出一块连续的地址空间用来进行元素的存储,由于它直接操作内存,所以数组的性能要比集 ...

  4. 基于jdk1.8的ArrayList源码分析

    前言ArrayList作为一个常用的集合类,这次我们简单的根据源码来看看AarryList是如何使用的. ArrayList拥有的成员变量 public class ArrayList<E> ...

  5. Java 集合系列07之 Stack详细介绍(源码解析)和使用示例

    概要 学完Vector了之后,接下来我们开始学习Stack.Stack很简单,它继承于Vector.学习方式还是和之前一样,先对Stack有个整体认识,然后再学习它的源码:最后再通过实例来学会使用它. ...

  6. Java 集合系列10之 HashMap详细介绍(源码解析)和使用示例

    概要 这一章,我们对HashMap进行学习.我们先对HashMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashMap.内容包括:第1部分 HashMap介绍第2部分 HashMa ...

  7. Java 集合系列11之 Hashtable详细介绍(源码解析)和使用示例

    概要 前一章,我们学习了HashMap.这一章,我们对Hashtable进行学习.我们先对Hashtable有个整体认识,然后再学习它的源码,最后再通过实例来学会使用Hashtable.第1部分 Ha ...

  8. 【转】Java 集合系列11之 Hashtable详细介绍(源码解析)和使用示例

    概要 前一章,我们学习了HashMap.这一章,我们对Hashtable进行学习.我们先对Hashtable有个整体认识,然后再学习它的源码,最后再通过实例来学会使用Hashtable.第1部分 Ha ...

  9. 【转】 Java 集合系列07之 Stack详细介绍(源码解析)和使用示例

    概要 学完Vector了之后,接下来我们开始学习Stack.Stack很简单,它继承于Vector.学习方式还是和之前一样,先对Stack有个整体认识,然后再学习它的源码:最后再通过实例来学会使用它. ...

随机推荐

  1. 收集几个Android CalendarView非常用属性

    android:dateTextAppearance 设置日历View在日历表格中的字体皮肤;android:firstDayOfWeek 指定日历第一个星期的第一天,在日历中横向所在位置,从右边向左 ...

  2. MySQL主从详细安装步骤

    网站: 程序在:web服务器192.168.1.100上面 数据库在:MySQL服务器192.168.1.123上面 实现目的:增加一台MySQL备份服务器(192.168.1.124),作为MySQ ...

  3. 使用putty连接虚拟机上的centos提示Network:connection refused

    转自:https://yeyuan.iteye.com/blog/1266484 今天早上开机之后,像往常一样使用putty连接linux的时候,突然提示Network:connection refu ...

  4. pythonchallenge 2

     pythonchallenge是一个很有意思的学习python的网站,通过用程序解开一个谜,可以进入到下一个level,总共有几十个level,网址是http://www.pythonchallen ...

  5. pl/sql developer中如何导出oracle数据库结构? 参考文章一

    本文作者来自csdn的xieyuooo地址为 : http://bbs.csdn.net/topics/340209135 进入PL/SQL后,使用如下图所示的操作步骤: 然后会弹出一个窗口,在弹出窗 ...

  6. Android Studio:layout-sw600dp文件夹中创建activity_main.xml

    1.右键res文件夹,新建Android resource directory文件夹 2.在resource type中选择layout  3.将Directory name命名为layout-sw6 ...

  7. Codeforces Round #422 (Div. 2)D. My pretty girl Noora(递推+数论)

    传送门 题意 对于n个女孩,每次分成x人/组,每组比较次数为\(\frac{x(x+1)}{2}\),直到剩余1人 计算\[\sum_{i=l}^{r}t^{i-l}f(i)\],其中f(i)代表i个 ...

  8. git 命令参考手册

    你的本地仓库由 git 维护的三棵“树”组成.第一个是你的 工作目录,它持有实际文件:第二个是 缓存区(Index),它像个缓存区域,临时保存你的改动:最后是 HEAD,指向你最近一次提交后的结果. ...

  9. 两年Java程序员面试经验分享,从简历制作到面试总结!

    前言 工作两年左右,实习一年左右,正式工作一年左右,其实挺尴尬的,高不成低不就.因此在面试许多公司,找到了目前最适合自己的公司之后.于是做一个关于面试的总结.希望能够给那些依旧在找工作的同学提供帮助. ...

  10. alternatives 命令学习

    最经在捣鼓Cloudera的cdh ,发现里面使用了alternatives命令,由于不懂这个命令,让我走了好多弯路. 现在mark一下 ubuntu 12.04 系统的命令为:update-alte ...