JDK1.8源码之ArrayList

主要从以下几个方面介绍:

  1. ArrayList扩容机制
  2. ArrayList迭代器
  3. Spliterator多线程遍历

一.ArrayList扩容机制

  ArrayList实现List接口,底层为一个可以调节长度的数组,主要成员变量如下。

 1     //默认初始容量
 2     private static final int DEFAULT_CAPACITY = 10;
 3
 4     //空数组的实例,供需要空数组的地方调用,有实意
 5     private static final Object[] EMPTY_ELEMENTDATA = {};
 6
 7     //同样是一个空数组的实例,不同的是,使用默认构造器返回这个数组
 8     private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
 9
10     //存储数据
11     transient Object[] elementData; 12
13     //当前数组长度
14     private int size;

  ArrayList有三种构造方法:

  1.默认构造方法

public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

  可以看到返回一个空列表,elementData为一个空数组,长度为0,实际上第一次长度为10的扩容并不在此时完成,而是在第一次添加数据时。

  2.构建一个包含指定集合的列表

 1 public ArrayList(Collection<? extends E> c) {
 2         elementData = c.toArray();
 3         if ((size = elementData.length) != 0) {
 4             // c.toArray might (incorrectly) not return Object[] (see 6260652)
 5             if (elementData.getClass() != Object[].class)
 6                 elementData = Arrays.copyOf(elementData, size, Object[].class);
 7         } else {
 8             // replace with empty array.
 9             this.elementData = EMPTY_ELEMENTDATA;
10         }
11     }

  3.构建一个指定初始容量的空列表

 1 public ArrayList(int initialCapacity) {
 2         if (initialCapacity > 0) {
 3             this.elementData = new Object[initialCapacity];
 4         } else if (initialCapacity == 0) {
 5             this.elementData = EMPTY_ELEMENTDATA;
 6         } else {
 7             throw new IllegalArgumentException("Illegal Capacity: "+
 8                                                initialCapacity);
 9         }
10     }

  接下来通过add()方法分析ArrayList的扩容机制。

1 public boolean add(E e) {
2         ensureCapacityInternal(size + 1);  // Increments modCount!!
3         elementData[size++] = e;
4         return true;
5     }

  可以看到add()方法只有两步,先扩容,然后添加元素,所以ensureCapacityInternal()为扩容的核心方法,接下来进入ensureCapacityInternal()。

 1     private void ensureCapacityInternal(int minCapacity) {
 2         ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
 3     }
 4
 5     private static int calculateCapacity(Object[] elementData, int minCapacity) {
 6         if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
 7             return Math.max(DEFAULT_CAPACITY, minCapacity);
 8         }
 9         return minCapacity;
10     }
11
12     private void ensureExplicitCapacity(int minCapacity) {
13         modCount++;
14
15         // overflow-conscious code
16         if (minCapacity - elementData.length > 0)
17             grow(minCapacity);
18     }

  此处我调整了一下源码的顺序,可以更方便的看出方法之间的调用关系。其中minCapacity为添加元素后所需的容量,在calculateCapacity()方法里首先判断elementData是否为默认的空数组(即调用默认构造方法的结果),如果是,则返回DEFAULT_CAPACITY与minCapacity中较大的那个,DEFAULT_CAPACITY值为10,

  然后调用ensureExplicitCapacity()方法,modCount+1,这个值记录当前ArrayList对象修改次数,主要用于快速失败的判断,后文会提到。然后比较列表新的容量与当前容量,如果当前容量小于新的容量,则进行具体的扩容操作,进入grow()方法。

 1 private void grow(int minCapacity) {
 2         // overflow-conscious code
 3         int oldCapacity = elementData.length;
 4         int newCapacity = oldCapacity + (oldCapacity >> 1);
 5         if (newCapacity - minCapacity < 0)
 6             newCapacity = minCapacity;
 7         if (newCapacity - MAX_ARRAY_SIZE > 0)
 8             newCapacity = hugeCapacity(minCapacity);
 9         // minCapacity is usually close to size, so this is a win:
10         elementData = Arrays.copyOf(elementData, newCapacity);
11     }

  可以看到新容量为旧容量的1.5倍(oldCapacity + (oldCapacity >> 1)  旧容量值做右移位运算,右移一位相当于除2,即1.5倍),如果扩充1.5倍后的新容量仍然小于所需要的容量,则直接让新容量等于所需容量,最后判断新容量长度是否超过最大长度,最后拷贝数据。

  最后总结一下,使用默认构造方法返回的ArrayList对象首次添加单个元素时,列表长度扩容至10,若第一次是添加多个元素(addAll()方法等),扩容长度由添加的元素个数决定。

二.ArrayList迭代器

  迭代器在开发中算是比较常用的东西,这部分主要想说一下ArrayList中的快速失败机制.

  快速失败指的是在集合类的迭代过程中,集合内容被修改,如果被修改则抛出ConcurrentModificationException异常,产生fail-fast事件。

  Iterator接口定义了以下几个方法

  • boolean hasNext()
  • E next()
  • void remove()
  • void forEachRemaining(Consumer<? super E> action)

  在ArrayList中Itr实现了Iterator接口,源码如下:

 1 private class Itr implements Iterator<E> {
 2         int cursor;       // 下一个元素的索引
 3         int lastRet = -1; // 当前最后一次返回的元素的索引
 4         int expectedModCount = modCount;  //expectedModCount是快速失败机制中关键的锁,初始值当前列表对象的修改次数modCount
 5
 6         Itr() {}
 7
 8         public boolean hasNext() {
 9             return cursor != size;
10         }
11
12         @SuppressWarnings("unchecked")
13         public E next() {
14             checkForComodification();
15             int i = cursor;
16             if (i >= size)
17                 throw new NoSuchElementException();
18             Object[] elementData = ArrayList.this.elementData;
19             if (i >= elementData.length)
20                 throw new ConcurrentModificationException();
21             cursor = i + 1;
22             return (E) elementData[lastRet = i];
23         }
24
25         public void remove() {
26             if (lastRet < 0)
27                 throw new IllegalStateException();
28             checkForComodification();
29
30             try {
31                 ArrayList.this.remove(lastRet);
32                 cursor = lastRet;
33                 lastRet = -1;
34                 expectedModCount = modCount;
35             } catch (IndexOutOfBoundsException ex) {
36                 throw new ConcurrentModificationException();
37             }
38         }
39
40         @Override
41         @SuppressWarnings("unchecked")
42         public void forEachRemaining(Consumer<? super E> consumer) {
43             Objects.requireNonNull(consumer);
44             final int size = ArrayList.this.size;
45             int i = cursor;
46             if (i >= size) {
47                 return;
48             }
49             final Object[] elementData = ArrayList.this.elementData;
50             if (i >= elementData.length) {
51                 throw new ConcurrentModificationException();
52             }
53             while (i != size && modCount == expectedModCount) {
54                 consumer.accept((E) elementData[i++]);
55             }
56             // update once at end of iteration to reduce heap write traffic
57             cursor = i;
58             lastRet = i - 1;
59             checkForComodification();
60         }
61
62         final void checkForComodification() {
63             if (modCount != expectedModCount)
64                 throw new ConcurrentModificationException();
65         }
66     }

  在next()方法中,调用checkForComodification检查当前集合是否被其他线程修改过,可以看一下源码。

1 final void checkForComodification() {
2             if (modCount != expectedModCount)
3                 throw new ConcurrentModificationException();
4         }

  可以看到,这里判断modCount与expectedModCount是否相等,如果不等,则说明在迭代过程中,可能有其他线程修改了集合结构或是迭代元素时调用了集合自身的新增或删除操作,集合不同步,抛出ConcurrentModificationException异常。

  但是需要强调一点,快速失败机制只是一种错误检测机制,fail-fast事件并不是一定会产生,触发时机是在迭代过程中,集合的结构发生了变化而此时迭代器并不知道或者还没来得及反应时便会产生fail-fast事件,这种行为无法得到保证,因为ArrayList是线程不安全的,迭代器只会尽最大努力抛出 ConcurrentModificationException,所以在并发环境下,靠快速失败机制确保集合数据的一致性是不明智的。

  PS. 解决办法:在多线程情况下遍历集合,使用“java.util.concurrent包下的类”去取代“java.util包下的类,比如将ArrayList替换为CopyOnWriteArrayList,对于CopyOnWrite容器这里就不做过多介绍,中心思想就是,对一个集合进行操作时,不直接操作该集合,而是操作该集合的一个拷贝,操作结束后,将原容器的引用指向新容器,是一种读写分离的思想。

三.Spliterator

  Spliterator是一个可分割的迭代器,JDK1.8中默认的数据结构都实现了Spliterator,主要目的是提高并行处理能力。

  主要作用就是把集合分成了好几段,每个线程执行一段,因此是线程安全的。基于这个原理,以及modCount的快速失败机制,如果迭代过程中集合元素被修改,会抛出异常。

  接口主要方法

 1 //单个对元素执行给定的动作,如果有剩下元素未处理返回true,否则返回false
 2 boolean tryAdvance(Consumer<? super T> action);
 3
 4 //对每个剩余元素执行给定的动作,依次处理,直到所有元素已被处理或被异常终止。默认方法调用tryAdvance方法
 5 default void forEachRemaining(Consumer<? super T> action) {
 6    do { } while (tryAdvance(action));
 7 }
 8
 9 //对任务分割,返回一个新的Spliterator迭代器
10 Spliterator<T> trySplit();
11
12 //用于估算还剩下多少个元素需要遍历
13 long estimateSize();
14
15 //当迭代器拥有SIZED特征时,返回剩余元素个数;否则返回-1
16 default long getExactSizeIfKnown() {
17    return (characteristics() & SIZED) == 0 ? -1L : estimateSize();
18 }
19
20  //返回当前对象有哪些特征值
21 int characteristics();
22
23 //是否具有当前特征值
24 default boolean hasCharacteristics(int characteristics) {
25    return (characteristics() & characteristics) == characteristics;
26 }
27 //如果Spliterator的list是通过Comparator排序的,则返回Comparator
28 //如果Spliterator的list是自然排序的 ,则返回null
29 //其他情况下抛错
30 default Comparator<? super T> getComparator() {
31     throw new IllegalStateException();
32 }

针对这一部分为我设计了一个测试用例:创建长度为10的list,多线程求和。

public class ArrayListTest {

    static int count = 0;
    static List<Integer> list = getList();
    static Spliterator spliterator = list.spliterator();

    public static void main(String[] args) {

        for (int i = 0 ; i < 4; i++) new MyThread().start();

        try {
            Thread.sleep(15000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(count);
    }

    static List<Integer> getList(){
        List<Integer> list = new ArrayList<>();
        for (int i = 1;i <= 10;i++ ){

            list.add(i * 10);
        }
        return list;
    }

    static class MyThread extends Thread{

        @Override
        public void run(){

            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + "ing...");

            spliterator.trySplit().forEachRemaining(x->{
                System.out.println(x + threadName);
                count += (int)x;
                try {
                    Thread.sleep(1500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            System.out.println(threadName + "end!!!");
        }
    }
}

输出结果:

可以看到使用spliterator,多线程遍历各自的分段后集合进行求和运算,运算结果正确没有受到影响,

此时可以做一下对比,将spliterator.trySplit().forEachRemaining替换为list.forEach,可以看到输出结果:

没有截取完整的输出结果,但是可以直观的看到,四个线程各自对集合进行完整的遍历,结果为450*4=2200。

参考博客:

https://blog.csdn.net/starexplode/article/details/80499255

https://blog.csdn.net/u010890358/article/details/80515284

https://www.cnblogs.com/nevermorewang/p/9368431.html

原文地址:https://www.cnblogs.com/youtang/p/11182749.html

时间: 2024-10-16 23:28:53

JDK1.8源码之ArrayList的相关文章

JDK1.8源码学习-ArrayList

JDK1.8源码学习-ArrayList 目录 一.ArrayList简介 为了弥补普通数组无法自动扩容的不足,Java提供了集合类,其中ArrayList对数组进行了封装,使其可以自动的扩容或缩小长度,相当于动态数组. ArrayList封装了一个动态的可以重新分配的Object[]数组,其中每一个类的对象都有一个capacity属性,表示了它们所封装的Object[]数组的长度,当向ArrayList中添加元素的时候,该属性会自动的添加.如果想要添加大量元素的时候,可以使用ensureCap

jdk1.8.0_45源码解读——ArrayList的实现

jdk1.8.0_45源码解读——ArrayList的实现 一.ArrayList概述 ArrayList是List接口的可变数组的实现.实现了所有可选列表操作,并允许包括 null 在内的所有元素.除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小. 每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小.它总是至少等于列表的大小.随着向ArrayList中不断添加元素,其容量也自动增长.自动增长会带来数据向新数组的重新拷贝,因此,如果可预

集合之HashSet(含JDK1.8源码分析)

一.前言 我们已经分析了List接口下的ArrayList和LinkedList,以及Map接口下的HashMap.LinkedHashMap.TreeMap,接下来看的是Set接口下HashSet和LinkedHashSet,其实在分析完了HashMap.LinkedHashMap之后,再来看HashSet和LinkedHashSet就会非常简单. 二.hashSet的数据结构 因为hashSet的底层是基于hashMap或linkedHashMap的(new hashSet的时候可以指定),

【源码】ArrayList源码剖析

//-------------------------------------------------------------------- 转载请注明出处:http://blog.csdn.net/chdjj by Rowandjj 2014/8/7 //-------------------------------------------------------------------- 从这篇文章开始,我将对java集合框架中的一些比较重要且常用的类进行分析.这篇文章主要介绍的是Array

JDK源码学习----ArrayList

                                                                         JDK源码学习----ArrayList 1.ArrayList简介 ArrayList是基于Object[] 数组的,也就是我们常说的动态数组.它能很方便的实现数组的增加删除等操作. public class ArrayList<E> extends AbstractList<E> implements List<E>, R

JDK1.8源码学习之 HashMap.java

///JDK1.8源码学习之HashMap.java package java.util; import java.io.IOException; import java.io.InvalidObjectException; import java.io.Serializable; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.function.BiConsu

由JDK源码学习ArrayList

ArrayList是实现了List接口的动态数组.与java中的数组相比,它的容量能动态增长.ArrayList的三大特点: ① 底层采用数组结构 ② 有序 ③ 非同步 下面我们从ArrayList的增加元素.获取元素.删除元素三个方面来学习ArrayList. ArrayList添加元素 因为ArrayList是采用数组实现的,其源代码比较简单.首先我们来看ArrayList的add(E e).以下代码版本是jdk7. public boolean add(E e) { // 检查数组容量 e

Java集合源码剖析——ArrayList源码剖析

ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的CopyOnWriteArrayList类. ArrayList实现了Serializable接口,因此它支持序列化,能够通过

集合之LinkedHashSet(含JDK1.8源码分析)

一.前言 上篇已经分析了Set接口下HashSet,我们发现其操作都是基于hashMap的,接下来看LinkedHashSet,其底层实现都是基于linkedHashMap的. 二.linkedHashSet的数据结构 因为linkedHashSet的底层是基于linkedHashMap实现的,所以linkedHashSet的数据结构就是linkedHashMap的数据结构,因为前面已经分析过了linkedHashMap的数据结构,这里不再赘述.集合之LinkedHashMap(含JDK1.8源