ArrayList概述

ArrayList是一个的可变数组的实现,实现了所有可选列表操作,并允许包括 null 在内的所有元素。每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向ArrayList中不断添加元素,其容量也自动增长。自动增长会带来数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造ArrayList时指定其容量。在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量。 
注意,此实现不是同步的。如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。

ArrayList的源码阅读:

  • 底层容器

  ArrayList是一个Object的数组,还有一个size属性来记录当前容器的容量。

transient Object[] elementData; // non-private to simplify nested class access
private int size;
  • 构造函数

ArrayList提供了三种方式的构造器,可以构造一个默认初始容量为0的空列表、构造一个指定初始容量的空列表以及构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回它们的顺序排列的。

    private static final Object[] EMPTY_ELEMENTDATA = {};

   private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

   
  // 传入初始容量的构造函数
  public ArrayList(int initialCapacity) {
if (initialCapacity > ) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == ) {
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) != ) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
          //判断引用的数组类型, 并将引用转换成Object数组引用
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
  • 添加元素

ArrayList有四种添加元素的方法:add(E e),add(int index, E element),addAll(Collection<? extends E> c),addAll(int index, Collection<? extends E> c)

    // 添加一个元素
  public boolean add(E e) {
    //添加前先检查是否需要拓展数组, 此时数组长度最小为size+1
ensureCapacityInternal(size + ); // Increments modCount!!
    //将元素添加到数组末尾
elementData[size++] = e;
return true;
}
  // 插入一个元素
public void add(int index, E element) {
     //插入位置范围检查
rangeCheckForAdd(index);
    //检查是否需要扩容
ensureCapacityInternal(size + ); // Increments modCount!!
    //挪动插入位置后面的元素
System.arraycopy(elementData, index, elementData, index + ,
size - index);
    //在要插入的位置赋上新值
elementData[index] = element;
    //将容器的容量+1
size++;
}

  // 添加一个集合数据
public boolean addAll(Collection<? extends E> c) {
    // 把Collection中的数据按照迭代器的排列复制到临时数组a中
Object[] a = c.toArray();
int numNew = a.length;
    // 检查是否需要扩容
ensureCapacityInternal(size + numNew); // Increments modCount
    // 将数据插入到容器的末尾
System.arraycopy(a, , elementData, size, numNew);
    // 更新容器的容量
size += numNew;
return numNew != ;
}
  // 插入一个集合数据
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 > )
       // 在elementData的index位置开始,往后移动numNew个位置
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
    // 将数据插入的index处
System.arraycopy(a, , elementData, index, numNew);
size += numNew;
return numNew != ;
}

细读里面上的代码:

  1. System.arraycopy

  System.arraycopy(elementData, index, elementData, index + numNew,numMoved);

  

这时的index=1 numNew=3,numMoved=4,那么执行上面的代码后:

然后执行

System.arraycopy(a, 0, elementData, index, numNew);

  2. ensureCapacityInternal

  //集合最大容量    
  private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - ; private void ensureCapacityInternal(int minCapacity) {
    // 如果是空数组(默认初始化时的数组)
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
       // 容量不能小于默认容量10
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
    
ensureExplicitCapacity(minCapacity);
} private void ensureExplicitCapacity(int minCapacity) {
    // 更新容器被修改的次数
modCount++; // overflow-conscious code
    //如果最小容量大于数组长度就扩增数组
if (minCapacity - elementData.length > )
grow(minCapacity);
}

  //增加数组的长度
private void grow(int minCapacity) {
// overflow-conscious code
    //获取数组原先的容量
int oldCapacity = elementData.length;
    // 新数组容量,在原来的基础上增加1.5倍
int newCapacity = oldCapacity + (oldCapacity >> );
    //检查新的容量是否小于最小容量
if (newCapacity - minCapacity < )
newCapacity = minCapacity;
    //检查新的容量是否超过最大数组容量
if (newCapacity - MAX_ARRAY_SIZE > )
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
    // 增加新容量的数组,并把原先的数据拷贝到新容量的数组
elementData = Arrays.copyOf(elementData, newCapacity);
} private static int hugeCapacity(int minCapacity) {
if (minCapacity < ) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

每次添加元素前会调用ensureCapacityInternal这个方法进行集合容量检查。在这个方法内部会检查当前集合的内部数组是否还是个空数组,如果是就新建默认大小为10的Object数组。如果不是则证明当前集合已经被初始化过,那么就调用ensureExplicitCapacity方法检查当前数组的容量是否满足这个最小所需容量,不满足的话就调用grow方法进行扩容。

在grow方法内部可以看到,每次扩容都是增加原来数组长度的一半,扩容实际上是新建一个容量更大的数组,将原先数组的元素全部复制到新的数组上,然后再抛弃原先的数组转而使用新的数组。

  • 删除元素
   // 删除指定下标的元素
public E remove(int index) {
    //index不能大于size
rangeCheck(index);
    //更新容器被修改的次数
modCount++;
E oldValue = elementData(index); int numMoved = size - index - ;
if (numMoved > )
       //将index后面的值往前移一位
System.arraycopy(elementData, index+, elementData, index,
numMoved);
     //清空最后一个元素
elementData[--size] = null; // clear to let GC do its work return oldValue;
} // 删除对象元素
public boolean remove(Object o) {
if (o == null) {
for (int index = ; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = ; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
} private void fastRemove(int index) {
modCount++;
int numMoved = size - index - ;
if (numMoved > )
System.arraycopy(elementData, index+, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
  • 修改元素
    public E set(int index, E element) {
rangeCheck(index); E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
  • 查询元素
    public E get(int index) {
rangeCheck(index); return elementData(index);
}
  • 增删改查总结

增(添加):仅是将这个元素添加到末尾。操作快速。如果有扩容的情况,会涉及数组的复制,操作较慢。

增(插入):由于需要移动插入位置后面的元素,并且涉及数组的复制,所以操作较慢。

删:由于需要将删除位置后面的元素向前挪动,也会设计数组复制,所以操作较慢。

改:直接对指定位置元素进行修改,不涉及元素挪动和数组复制,操作快速。

查:直接返回指定下标的数组元素,操作快速。

  • Fail-Fast机制

ArrayList也采用了快速失败的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。在看下面iterator()方法的源代码时会发现,再通过迭代器操作ArrayList时都会调用checkForComodification方法,如果modCount被修改了会抛出ConcurrentModificationException.

 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 = -; // index of last element returned; -1 if no such
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 + ;
return (E) elementData[lastRet = i];
} public void remove() {
if (lastRet < )
throw new IllegalStateException();
checkForComodification(); try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -;
          // 把modCount重新赋值,所以我们可以使用iterator的remove方法来删除ArrayList里的元素,而不会导致ConcurrentModificationException.
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 - ;
checkForComodification();
} final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}

源码阅读之ArrayList(JDK8)的更多相关文章

  1. jdk源码阅读笔记-ArrayList

    一.ArrayList概述 首先我们来说一下ArrayList是什么?它解决了什么问题?ArrayList其实是一个数组,但是有区别于一般的数组,它是一个可以动态改变大小的动态数组.ArrayList ...

  2. 源码阅读之HashMap(JDK8)

    概述 HashMap根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的. HashMap最多只允许一条记录的键为null,允许多条记录 ...

  3. Java源码阅读之ArrayList

    基于jdk1.8的ArrayList源码分析. 实现List接口最常见的大概就四种,ArrayList, LinkedList, Vector, Stack实现,今天就着重看一下ArrayList的源 ...

  4. JDK源码阅读(一) ArrayList

    基于JDK7.0 ArrayList<E>类继承了抽象类AbstractList<E> 实现了List<E> 接口,RandomAccess接口,Cloneable ...

  5. java1.7集合源码阅读:ArrayList

    ArrayList是jdk1.2开始新增的List实现,首先看看类定义: public class ArrayList<E> extends AbstractList<E> i ...

  6. java1.7集合源码阅读: Vector

    Vector是List接口的另一实现,有非常长的历史了,从jdk1.0开始就有Vector了,先于ArrayList出现,与ArrayList的最大区别是:Vector 是线程安全的,简单浏览一下Ve ...

  7. java8 ArrayList源码阅读

    转载自 java8 ArrayList源码阅读 本文基于jdk1.8 JavaCollection库中有三类:List,Queue,Set 其中List,有三个子实现类:ArrayList,Vecto ...

  8. 【JDK1.8】JDK1.8集合源码阅读——ArrayList

    一.前言 在前面几篇,我们已经学习了常见了Map,下面开始阅读实现Collection接口的常见的实现类.在有了之前源码的铺垫之后,我们后面的阅读之路将会变得简单很多,因为很多Collection的结 ...

  9. JDK 1.8源码阅读 ArrayList

    一,前言 ArrayList是Java开发中使用比较频繁的一个类,通过对源码的解读,可以了解ArrayList的内部结构以及实现方法,清楚它的优缺点,以便我们在编程时灵活运用. 二,ArrayList ...

随机推荐

  1. 九度oj 题目1062:分段函数

    题目1062:分段函数 时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:3874 解决:2278 题目描述: 编写程序,计算下列分段函数y=f(x)的值.y=-x+2.5; 0<=x& ...

  2. POJ 3518 (后缀自动机)

    POJ 3518 Boring Problem : 给一个串S,询问串S有多个子串出现至少两次且位置不重叠. Solution : 对S串建立后缀自动机,再建立后缀树,dfs一遍统计处每个结点的子树中 ...

  3. 【小记事】电脑命令行开WiFi

    1.设置WiFi名称和密码 在命令行输入: netsh wlan set hostednetwork mode=allow WiFi名称 key=密码 2.开启WiFi 在命令行输入: netsh w ...

  4. [bzoj3894]文理分科_网络流_最小割

    文理分科 bzoj-3894 题目大意:题目链接. 注释:略. 想法: 这种题也是一种套路. 我们新建一个点表示收益点. 然后把所有的收益都加一起,求最小割表示代价即可. Code: #include ...

  5. P1334 瑞瑞的木板 洛谷

    https://www.luogu.org/problem/show?pid=1334 题目描述 瑞瑞想要亲自修复在他的一个小牧场周围的围栏.他测量栅栏并发现他需要N(1≤N≤20,000)根木板,每 ...

  6. JSP中HTTP状态码

    以下内容引用自http://wiki.jikexueyuan.com/project/jsp/http-status-codes.html: HTTP请求格式和HTTP响应消息的格式一样,都有以下结构 ...

  7. dtrace-conf 2016

    https://www.joyent.com/about/events/2016/dtrace-conf

  8. 加上mvc:resources后controller访问404

    之前因为静态资源访问,404,于是加上了类似的代码 <mvc:resources location="/resources/" mapping="/resource ...

  9. flask应用的分页

    Flask-SQLAlchemy支持分页 https://www.jianshu.com/p/5e03cd202728

  10. 连接App.config

    ConfigurationManager.AppSettings["AdminName"]; 连接App.config的字符