ArrayList类的申明

ArrayList是一个支持泛型的,底层通过数组实现的一个可以存任意类型的数据结构,源码中的定义如下:

 public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{}

ArrayList类继承了AbstractList抽象类,AbstractList提供了List接口的默认实现

ArrayList实现了以下几个接口:

List<E>接口:约定List的操作规范,提供了一系列操作方法约定

RandomAccess接口:该接口约定其实现类支持随机访问,即可以通过下标的方式访问其中的元素

Cloneable接口:约定其实现类实例是可以被克隆的,通过调用Object.clone方法返回该对象的浅拷贝

Serializable接口:约定其实现类实例可以被序列化和反序列化

ArrayList主要字段、属性说明

    // 版本号
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;
// 实际元素大小,默认为0
private int size;
// 最大数组容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

其中有个重要的属性elementData其作用是存放集合元素,这说明了ArrayList内部其实是通过数组实现的。其修饰符transient 表明这个字字段在序列化时被忽略不序列化。

ArrayList部分方法分析

构造函数

  • 无参构造函数:初始化一个长度为0的空数组
 public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
} private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
  • ArrayList(int) 构造函数:初始化一个指定长度的数组
 public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//初始化一个容量为initialCapacity的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//初始化空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
//如果尝试初始化一个容量小于0的数组,则直接抛异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
  • ArrayList(Collection<? extends E>)构造函数:初始化一个数组,并将参数集合中的元素复制到数组中
 public ArrayList(Collection<? extends E> c) {
//将参数集合转化为数组,赋值到ArrayList内部存储属性上
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
//如果elementData数组类型不是Object[],则重新将elementData中元素转为Object复制到elementData中
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//为空则返回空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}

上面的代码中为什么要再次判断?Collection类本身的toArray方法是返回Object[]类型数组,但是Java中如果子类如果继承Collection并重写了toArray方法,则返回的可能并不是Object[]类型数值,比如String[]等其他类型

Add(E e)、add(int index, E element)、addAll(Collection<? extends E> c) 、addAll(int index, Collection<? extends E> c)

ArrayList提供了这两个add操作方法,Add(E e)直接向素组末尾添加元素,add(int index, E element)向指定index索引处添加元素

 //直接向素组末尾添加元素
public boolean add(E e) {
//判断数组容量是否还可以添加,不够添加则扩充数组容量
ensureCapacityInternal(size + 1); // Increments modCount!!
//将元素添加到数组末尾
elementData[size++] = e;
return true;
} private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
} private void ensureExplicitCapacity(int minCapacity) {
//用于迭代器
modCount++; //期望的最小数组容量大于当前数组容量,则扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
} //计算期望最小的素组容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// DEFAULT_CAPACITY=10,也就是说如果此时最小返回一个长度为10的数组
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
} //扩容
private void grow(int minCapacity) {
//当前数组容量
int oldCapacity = elementData.length;
  //计算新素组容量,为当前数组容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
  //判断新数组容量与期望数组容量大小,取值大的一方
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
  // MAX_ARRAY_SIZE =Integer.MAX_VALUE – 8= 2147483639
  //如果新数组容量大于2147483639,则使用扩展到最大Integer.MAX_VALUE
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//将原数组中的元素拷贝到新素组中
elementData = Arrays.copyOf(elementData, newCapacity);
}

上面的代码注释已经写的很清楚了,add方法的逻辑是首选检查数组容量是否够用,如果容量不足,则进行扩容,扩容策略是如果原素组为空,则返回一个长度为10的数组,否则数组容量扩充到原素组的1.5倍,最终数组容量最大为Integer.Max_VALUE=2147483647

 //向指定索引处添加元素
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++;
}

add(int index, E element)内部多了一个验证指定索引合法性逻辑,其他与add(E element)实现逻辑基本一致。

 //添加集合
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;
}

addAll方法实现逻辑与add方法基本相同

get(int index)

get方法返回此列表中指定位置的元素,内部实现首先判断一下索引是否越界(居然没有判断小于0,实际上小于0时,数组读取也会抛异常),然后取出对应索引位置处的元素,另外由于ArrayList内部是用Object[]实现存储的,get(int index)返回泛型E,实际上elementData(index)内部实现将Object转为E

 public E get(int index) {
//验证索引是否越界
rangeCheck(index); return elementData(index);
} private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

set(int index,E element)

set方法的功能是将指定索引处的元素修改为element值,并返回该位置原来的值

public E set(int index, E element) {
//验证索引合法性
rangeCheck(index);
//读取原来的值
E oldValue = elementData(index);
  //替换为目标值
elementData[index] = element;
return oldValue;
}

remove(int index)、remove(Object o)、removeAll(Collection<?> c)、removeIf(Predicate<? super E> filter)、removeRange(int fromIndex, int toIndex)

ArrayList提供了一系列删除元素的方法,下面分析一个基础的remove(int index):

 //删除指定索引处的元素
public E remove(int index) {
  //校验索引合法性
rangeCheck(index); //删除操作影响数组列表结构,所以modCount自增1
modCount++;
  //读取将要删除的元素
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;
}

IndexOf(Object o) 、lastIndexOf(Object o)

如果我们需要检查列表中某个元素的位置,则可以使用indexOf方法,此方法返回被检查元素在列表中第一次出现的下标,如果未找到该元素,则返回-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;
}
  //不存在返回-1
return -1;
}

lastIndexOf(Object o)返回指定元素在数组中最后一次出现的下标

iterator()、listIterator()、listIterator(int index)

  • iterator()方法: 返回一个ArrayList中元素的迭代器,实现代码如下:
 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
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;
//返回索引值为i处的元素,并将i赋值给lastRet:代表最后返回元素的索引
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();
}
}

迭代器的实际应用:

  1. 使用迭代器iterator遍历:

     Iterator<Integer> list= array.iterator();
while(list.hasNext()){
//array.add(4); add() 和remove()会导致modCount发生变化,从而导致迭代过程中抛出异常
int value = it.next();
       //使用迭代器提供的remove()方法避免抛异常,原因:迭代器的remove方法在删除元素之后对将ArrayList的modCount覆盖了迭代器类的expectedModCount
it.remove();   }

  2.使用forEach遍历:反编译class文件可以发现其本质还是使用了iterator迭代器

  for(Integer item : array){
//item.add()和item.remove()都将报错
System.out.println(value);
}
  • listIterator()方法:返回返回ArrayList元素的列表迭代器,与Iterator迭代器相比,它还提供了向前遍历,增加元素,修改元素的操作,其实现代码如下
 public ListIterator<E> listIterator() {
return new ListItr(0);
} private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
super();
    //初始化游标
cursor = index;
}   //判断是否存在上 一个元素
public boolean hasPrevious() {
return cursor != 0;
}   //返回下一个将遍历的元素索引
public int nextIndex() {
return cursor;
}   //返回前一个元素索引
public int previousIndex() {
return cursor - 1;
}   //向前遍历:返回当前索引的上一个元素
@SuppressWarnings("unchecked")
public E previous() {
      //校验,在迭代器进行元素遍历期间如果修改数组长度,则抛出异常
checkForComodification();
      //计算前一个元素索引
int i = cursor - 1;
if (i < 0)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
      //游标指向前一个元素
cursor = i;
41       //返回前一个元素
return (E) elementData[lastRet = i];
}     //修改当前位置的元素
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification(); try {
ArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
  //当前位置新增元素
public void add(E e) {
checkForComodification(); try {
int i = cursor;
ArrayList.this.add(i, e);
cursor = i + 1;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}

最后:ArrayList其实就是一个动态的Array,并且提供了一些便携的操作方法而已。

JDK源码分析 – ArrayList的更多相关文章

  1. 【JDK】JDK源码分析-ArrayList

    概述 ArrayList 是 List 接口的一个实现类,也是 Java 中最常用的容器实现类之一,可以把它理解为「可变数组」. 我们知道,Java 中的数组初始化时需要指定长度,而且指定后不能改变. ...

  2. JDK源码分析-ArrayList

    ArrayList 储存结构 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; transient Objec ...

  3. 【JDK】JDK源码分析-Vector

    概述 上文「JDK源码分析-ArrayList」主要分析了 ArrayList 的实现原理.本文分析 List 接口的另一个实现类:Vector. Vector 的内部实现与 ArrayList 类似 ...

  4. JDK源码分析(2)LinkedList

    JDK版本 LinkedList简介 LinkedList 是一个继承于AbstractSequentialList的双向链表.它也可以被当作堆栈.队列或双端队列进行操作. LinkedList 实现 ...

  5. 【JDK】JDK源码分析-List, Iterator, ListIterator

    List 是最常用的容器之一.之前提到过,分析源码时,优先分析接口的源码,因此这里先从 List 接口分析.List 方法列表如下: 由于上文「JDK源码分析-Collection」已对 Collec ...

  6. 【JDK】JDK源码分析-CountDownLatch

    概述 CountDownLatch 是并发包中的一个工具类,它的典型应用场景为:一个线程等待几个线程执行,待这几个线程结束后,该线程再继续执行. 简单起见,可以把它理解为一个倒数的计数器:初始值为线程 ...

  7. JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue

    JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue 目的:本文通过分析JDK源码来对比ArrayBlockingQueue 和LinkedBlocki ...

  8. JDK 源码分析(4)—— HashMap/LinkedHashMap/Hashtable

    JDK 源码分析(4)-- HashMap/LinkedHashMap/Hashtable HashMap HashMap采用的是哈希算法+链表冲突解决,table的大小永远为2次幂,因为在初始化的时 ...

  9. JDK源码分析(三)—— LinkedList

    参考文档 JDK源码分析(4)之 LinkedList 相关

随机推荐

  1. Git简单配置ssh秘钥

    执行以下命令: git config --global user.name "demo" git config --global user.email "demo@dem ...

  2. PHP 使用GD库合成带二维码的海报步骤以及源码实现

    PHP 使用GD库合成带二维码的海报步骤以及源码实现 在做微信项目开发过程中,经常会遇到图片合成的问题,比如将用户的二维码合成到宣传海报中,那么,遇到这种情况,利用PHP的GD库也是很容易实现的,实现 ...

  3. 第6章 AOP与全局异常处理6.1-6.4 慕课网微信小程序开发学习笔记

    第6章 AOP与全局异常处理 https://coding.imooc.com/learn/list/97.html 目录: 第6章 AOP与全局异常处理6-1 正确理解异常处理流程 13:236-2 ...

  4. java synchronized 关键字原理

    Synchronized 关键字是解决并发问题常用解决方案,有以下三种使用方式: 同步普通方法,锁的是当前对象.同步静态方法,锁的是当前 Class 对象.同步块,锁的是 {} 中的对象. 实现原理: ...

  5. React 源码中的依赖注入方法

    一.前言 依赖注入(Dependency Injection)这个概念的兴起已经有很长时间了,把这个概念融入到框架中达到出神入化境地的,非Spring莫属.然而在前端领域,似乎很少会提到这个概念,难道 ...

  6. win10每次重新启动,eclipse不能打开,要重新配jdk环境的解决办法

    在后面加上反斜杠就好,也不知道是什么原因,知道的同学希望可以在下面的评论告诉我.

  7. (数据科学学习手札45)Scala基础知识

    一.简介 由于Spark主要是由Scala编写的,虽然Python和R也各自有对Spark的支撑包,但支持程度远不及Scala,所以要想更好的学习Spark,就必须熟练掌握Scala编程语言,Scal ...

  8. 简单R语言爬虫

    R爬虫实验 R爬虫实验 PeRl 简单的R语言爬虫实验,因为比较懒,在处理javascript翻页上用了取巧的办法. 主要用到的网页相关的R包是: {rvest}. 其余的R包都是常用包. libra ...

  9. spark2.2 从入门到精通 视频教程 百度云网盘下载地址

    spark2.2 从入门到精通 视频教程 百度云网盘下载地址 链接:https://pan.baidu.com/s/1sm2Jdmt 密码:rdea

  10. 成都Uber优步司机奖励政策(1月29日)

    滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...