java集合系列之ArrayList源码分析(基于jdk1.8)

  ArrayList简介

  ArrayList时List接口的一个非常重要的实现子类,它的底层是通过动态数组实现的,因此它具备查询速度快,增删速度慢的特点。另外数组拥有索引,因此可通过索引直接访问集合中的元素,ArrayList集合中允许存放重复的元素。

  下面将对ArrayList集合中的重要方法的底层实现做一下简单的介绍,如有错误,请指正。

  • ArrayList的成员变量:
  //序列化id
private static final long serialVersionUID = 8683452581122892189L;
//默认初始化容量
private static final int DEFAULT_CAPACITY = 10;/**
* 一个空数组,如果使用带参构造方法创建ArrayList对象,并且传入的参数为0,直接将elementData = EMPTY_ELEMENTDATA。
*/
private static final Object[] EMPTY_ELEMENTDATA = {}; /**
* 另一个空数组,如果使用无参构造方法创建ArrayList对象,直接将elementData = EMPTY_ELEMENTDATA。
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //实际数据存放的数组
transient Object[] elementData; // non-private to simplify nested class access //数组中包含元素的数量
private int size;

  需要注意的是ArrayList从父类那里继承了一个非常重要的成员变量modCount,用来记录数组被修改的次数(增、删、清空数组该变量都会自增),这个变量在并发修改异常时会再次讲解

protected transient int modCount = 0;
  • ArrayList的构造方法:
//带参构造
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {//传入的参数大于0,创建数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {//长度为0,赋值为空数组常量,此时add,初始化容量为1
this.elementData = EMPTY_ELEMENTDATA;
} else {//小于0,抛出非法参数异常,并将该参数输出
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* 空参构造,此时add元素时,初始化容量变为10
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 将一个Collection对象转换成数组,然后将这个数组的引用赋给elementData,前提这个Collection对象中存放的是E或者E的子类
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)这个地方是jdk的一个bug不用考虑
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 如果elementData为空,则指向空数组EMPTY_ELEMENTDATA
this.elementData = EMPTY_ELEMENTDATA;
}
}
  • ArrayList的添加元素
   /**
* 添加元素(添加到数组最后):
* 1.确保数组已使用长度+1之后能存放下一个数据(ensureCapacityInternal())
* 2.在size位置处添加元素,并将size自增(元素个数+1)
* 3.返回true
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
} /**
* 确保数组的长度能够放得下下一个元素
* 1.如果elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA,即ArrayList对象是通过空参构造创建的,则将最小容量设为10,否则设为还是size+1
* @param minCapacity
*/
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
} /**
* 修改次数自增1
* 判断数组是否需要扩容,条件为所需的最小容量 > 数组的长度*/
private void ensureExplicitCapacity(int minCapacity) {
modCount++; // overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
} /**
* 最大数组长度
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /**
* 1. 将原数组的长度扩充为原来的1.5倍,如果此时数组的长度超过了MAX_ARRAY_SIZE,则调用hugeCapacity进行判断
* 2. 数组copy
*
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
} /**
* 如果minCapacity < 0,抛出OutOfMemoryError
* 否则返回Integer的最大值
*/
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
/**
* 指定索引处添加元素:
* 1. 检查索引必须 0 <= index <=size
* 2. 确保数组已使用长度+1之后能存放下一个数据
* 3. 将该索引及其之后的元素全部后移一位。
* 4. 将新元素添加到该索引处
* 5. 元素数量自增
*/
public void add(int index, E element) {
rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!!
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));
/**
* 将集合中的元素全部添加到ArrayList对象中:
* 1.将集合转变成数组
* 2.确保当前数组的长度能够放的下size+所添加数组的长度
* 3.数组copy
* 4.size增加
* 5.返回添加是否成功
*/
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
} /**
*该方法与之前的添加方法类似,将集合放入指定索引处,现将该索引及其之后的元素后移,然后再要添加的数组放到相应位置
*/
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index); Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved); System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
  • ArrayList删除元素
 /**
* 删除指定索引处的元素,并将该元素返回
* 1. 检查所删除元素的索引是否越界,即保证删除元素的存在,如果不存在抛出IndexOutOfBoundsException
* 2. 修改次数自增
* 3. 如果删除的不是最后一个元素,要将该索引之后的所有元素左移一位
* 4. 将数组最后一个元素设为null,便于垃圾回收,并将数组中的元素数量减1
* 5. 返回所删除的元素(在数组移动之前已经保存在临时变量中)
*/
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);
elementData[--size] = null; // clear to let GC do its work return oldValue;
}   private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
} /**
* 删除包含指定数据的元素:
* 1.如果o==null,则使用==比较,否则用equals比较,如果找到返回true
* 2.如果没有找到返回false
*/
public boolean remove(Object o) {
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;
} /**
* 1. 修改次数自增
* 2. 如果删除的不是最后一个元素,要将该索引之后的所有元素左移一位
* 3. 将数组最后一个元素设为null,便于垃圾回收,并将数组元素数量减1
*/
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. 修改次数自增
* 2. 将所有的元素设为null,便于垃圾回收
* 3. size设为0
*/
public void clear() {
modCount++; // clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null; size = 0;
}
  • ArrayList元素迭代的ConcurrentModificationException
/**
* 返回一个迭代器对象new Itr(),该对象为一个内部类
*/
public Iterator<E> iterator() {
return new Itr();
} /**
* An optimized version of AbstractList.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
int expectedModCount = modCount;//将集合的修改次数赋值给expectedModCount(期望修改次数) public boolean hasNext() {
return cursor != size;
} /**
* 在迭代遍历集合时,调用该集合的next()方法,首先会检查该集合首先调用checkForComodification()检查是否在迭代过程中发生了修改,
* 如果被修改,则会抛出并发修改异常
*/
@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];
} /**
* 用迭代器的remove()方法之所以不会抛出并发修改异常的原因在于,它在删除元素之后,又重新将modCount赋值给expectedModCount,
* 所以在下次调用next()方法进行checkForComodification时,expectedModCount = modCount仍然成立
*/
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification(); try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;//又重新将modCount赋值给expectedModCount
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
/**
* 如果modCount != expectedModCount,则抛出并发修改异常
* 即实际修改的数量不等于并发修改的数量
*/
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
  • 其他方法
    /**
* 查看集合是否包含对象o,注意参数为Object类型,不是泛型
* 判断元素在数组中第一次出现的索引,并与0比较
*/
public boolean contains(Object o) {
return indexOf(o) >= 0;
} /**
* 判断该元素在数组中第一次出现的索引
* 1. 如果o为null,则遍历数组用==比较
* 2. 如果不为null,则遍历数组用equals比较
* 3.如果都没找到,返回-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;
} /**
* 该元素在数组中最后出现的位置
* 实现方式与indexOf(Object o)方法相同,仅仅需要倒序遍历数组即可
*/
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}

java集合系列之ArrayList源码分析的更多相关文章

  1. Java集合系列[1]----ArrayList源码分析

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

  2. java集合系列之LinkedList源码分析

    java集合系列之LinkedList源码分析 LinkedList数据结构简介 LinkedList底层是通过双端双向链表实现的,其基本数据结构如下,每一个节点类为Node对象,每个Node节点包含 ...

  3. Java集合系列[4]----LinkedHashMap源码分析

    这篇文章我们开始分析LinkedHashMap的源码,LinkedHashMap继承了HashMap,也就是说LinkedHashMap是在HashMap的基础上扩展而来的,因此在看LinkedHas ...

  4. Java集合系列:-----------03ArrayList源码分析

    上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具体实现类进行讲解:首先,讲解List,而List中ArrayList又最为常用.因此,本章我们讲解ArrayLi ...

  5. Java集合系列[3]----HashMap源码分析

    前面我们已经分析了ArrayList和LinkedList这两个集合,我们知道ArrayList是基于数组实现的,LinkedList是基于链表实现的.它们各自有自己的优劣势,例如ArrayList在 ...

  6. Java集合系列[2]----LinkedList源码分析

    上篇我们分析了ArrayList的底层实现,知道了ArrayList底层是基于数组实现的,因此具有查找修改快而插入删除慢的特点.本篇介绍的LinkedList是List接口的另一种实现,它的底层是基于 ...

  7. java多线程系列(九)---ArrayBlockingQueue源码分析

    java多线程系列(九)---ArrayBlockingQueue源码分析 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 j ...

  8. Java并发系列[2]----AbstractQueuedSynchronizer源码分析之独占模式

    在上一篇<Java并发系列[1]----AbstractQueuedSynchronizer源码分析之概要分析>中我们介绍了AbstractQueuedSynchronizer基本的一些概 ...

  9. Java并发系列[3]----AbstractQueuedSynchronizer源码分析之共享模式

    通过上一篇的分析,我们知道了独占模式获取锁有三种方式,分别是不响应线程中断获取,响应线程中断获取,设置超时时间获取.在共享模式下获取锁的方式也是这三种,而且基本上都是大同小异,我们搞清楚了一种就能很快 ...

随机推荐

  1. shell脚本,100以内的质数有哪些?

    [root@localhost wyb]# cat 9zhishu.sh #!/bin/bash ` do ;j<=i-;j++)) do [ $((i%j)) -eq ] && ...

  2. 使用Xcode连接开源中国

    故事背景: 今天加入一个新的项目组,其实也就是包括我在内就两个人,由于对方在开源中国上建的项目我没法使用. 所以由我接手第一个任务:就是在开源中国上搭建git项目组 前提条件:xcode和 Git(h ...

  3. C# IsNullOrEmpty与IsNullOrWhiteSpace

    IsNullOrEmpty:非空非NULL判断 IsNullOrWhiteSpace:非空非NULL非空格判断 后者优于前者 if (!string.IsNullOrWhiteSpace(valueE ...

  4. JS数组专题2️⃣ ➖ 数组去重

    距离上次发文,已经有一段时间了,最近工作比较忙,这不眼看快双十一了,就相当于给大家一些福利吧! 一.什么是数组去重 简单说就是把数组中重复的项删除掉,你 GET 到了吗 ?下面我将简单介绍下几种基本的 ...

  5. UVa-1368-DNA序列

    这题的话,我们每次统计的话,是以列为外层循环,以行为内层循环,逐一按列进行比较. 统计完了之后,题目中要求说到要hamming值最小的,那我们就选用该列最多的字母就可以了,如果有数目相等的字母,那就按 ...

  6. Windows10安装MariaDB

    截至写这篇博客为止,MariaDB官方的稳定版本为,详情访问官方地址:https://downloads.mariadb.org/ 安装之前先简单说一下MariaDB:         MariaDB ...

  7. python--内置函数, 匿名函数

    一 . 内置函数 什么是内置函数? 就是python给你提供的. 拿来直接⽤的函数, 比如print., input等等. 字符串类型代码的执⾏ eval() 执⾏字符串类型的代码. 并返回最终结果( ...

  8. Python之写入文件(1)

    一.写入文件 保存数据也是在各个编程语言中常用的操作,将数据写入到文件中是常用的操作,你可以将程序运行中的一些临时输出保存至文件中,以便后续打开文件查看,也可以把这些文件读入程序中来操作其中的数据. ...

  9. LeetCode(103) Binary Tree Zigzag Level Order Traversal

    题目 Given a binary tree, return the zigzag level order traversal of its nodes' values. (ie, from left ...

  10. The North American Invitational Programming Contest 2018 E. Prefix Free Code

    Consider nn initial strings of lower case letters, where no initial string is a prefix of any other ...