码迷,mamicode.com
首页 > 编程语言 > 详细

JAVA常用集合源码解析系列-ArrayList源码解析(基于JDK8)

时间:2017-04-23 18:54:14      阅读:501      评论:0      收藏:0      [点我收藏+]

标签:保存   ror   bsp   length   font   ring   地方   机制   data   

文章系作者原创,如有转载请注明出处,如有雷同,那就雷同吧~(who care!)

一、写在前面

这是源码分析计划的第一篇,博主准备把一些常用的集合源码过一遍,比如:ArrayList、HashMap及其对应的线程安全实现,此文章作为自己相关学习的一个小结,记录学习成果的同时,也希望对有缘的朋友提供些许帮助。

当然,能力所限,难免有纰漏,希望发现的朋友能够予以指出,不胜感激,以免误导了大家!

二、稳扎稳打过源码

首先,是源码内部的成员变量定义以及构造方法:

技术分享
 1 /**
 2      * Default initial capacity.
 3     (默认初始化长度)ps:实际是“延时初始化”(lazy init),后文详解
 4      */
 5     private static final int DEFAULT_CAPACITY = 10;
 6 
 7     /**
 8      * Shared empty array instance used for empty instances.
 9     (共享空数组,为了追求效率)
10      */
11     private static final Object[] EMPTY_ELEMENTDATA = {};
12 
13     /**
14      * Shared empty array instance used for default sized empty instances. We
15      * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
16      * first element is added.
17     (区别于EMPTY_ELEMENTDATA,使用默认构造方法时,默认使用此空数组,再配合DEFAULT_CAPACITY共同实现lazy init)
18      */
19     private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
20 
21     /**
22      * The array buffer into which the elements of the ArrayList are stored.
23      * The capacity of the ArrayList is the length of this array buffer. Any
24      * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
25      * will be expanded to DEFAULT_CAPACITY when the first element is added.
26     (集合数据真正存放的地方,所以对于ArrayList我们可以理解为提供了一组高效操作方法的数组。注意注释后半段:当集合的首个元素被添加时,把空集合DEFAULTCAPACITY_EMPTY_ELEMENTDATA扩展为DEFAULT_CAPACITY大小的集合,这就是lazy init,使用时才分配内存空间,目的是防止空间的浪费。)ps:transient 表示此变量不参与序列化
27      */
28     transient Object[] elementData; // non-private to simplify nested class access
29 
30     /**
31      * The size of the ArrayList (the number of elements it contains).
32      *
33      * @serial
34     (数组大小)
35      */
36     private int size;
参数项
技术分享
 1 /**
 2      * Constructs an empty list with the specified initial capacity.
 3      *
 4      * @param  initialCapacity  the initial capacity of the list
 5      * @throws IllegalArgumentException if the specified initial capacity
 6      *         is negative
 7 (没什么可说,初始化参数>0创建该长度数组,=0使用共享空数组,<0报错)
 8      */
 9     public ArrayList(int initialCapacity) {
10         if (initialCapacity > 0) {
11             this.elementData = new Object[initialCapacity];
12         } else if (initialCapacity == 0) {
13             this.elementData = EMPTY_ELEMENTDATA;
14         } else {
15             throw new IllegalArgumentException("Illegal Capacity: "+
16                                                initialCapacity);
17         }
18     }
19 
20     /**
21      * Constructs an empty list with an initial capacity of ten.
22 (使用默认空数组,lazy init,添加元素时数组扩展至默认容量DEFAULT_CAPACITY)
23      */
24     public ArrayList() {
25         this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
26     }
27 
28     /**
29      * Constructs a list containing the elements of the specified
30      * collection, in the order they are returned by the collection‘s
31      * iterator.
32      *
33      * @param c the collection whose elements are to be placed into this list
34      * @throws NullPointerException if the specified collection is null
35      */
36     public ArrayList(Collection<? extends E> c) {
37         elementData = c.toArray();
38         if ((size = elementData.length) != 0) {
39             // c.toArray might (incorrectly) not return Object[] (see 6260652)
40 (准确的说这是个bug,从下面链接可以看到会在jdk9修复,bug产生原因后面详解。附上链接http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6260652)
41             if (elementData.getClass() != Object[].class)
42                 elementData = Arrays.copyOf(elementData, size, Object[].class);
43         } else {
44             // replace with empty array.
45             this.elementData = EMPTY_ELEMENTDATA;
46         }
47     }
构造方法

 进阶分析,集合常用操作,先从简单入手:

技术分享
 1 /**
 2 思路很简单:
 3 1.判断index是否越界,越界则异常
 4 2.直接取出数组elementData相应位置index的元素并强转为E
 5 **/
 6 public E get(int index) {
 7         rangeCheck(index);
 8 
 9         return elementData(index);
10     }
11 private void rangeCheck(int index) {
12         if (index >= size)
13             throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
14     }
15  E elementData(int index) {
16         return (E) elementData[index];
17     }
按位次获取单个集合元素
技术分享
 1 /**
 2 同样很简单:
 3 1.判断index是否越界,越界则异常
 4 2.取到index位置的值作为原值
 5 3.设置该位置值为修改后的值
 6 4.return原值
 7 **/
 8 public E set(int index, E element) {
 9         rangeCheck(index);
10 
11         E oldValue = elementData(index);
12         elementData[index] = element;
13         return oldValue;
14     }
按位次修改单个集合元素

  如前所述,单个元素按位次获取操作很简单,只要你了解到arrayList内部是数组存放数据,那上述操作完全可以想得到。

  下面继续,集合的添加和删除操作,没看过源码的朋友可以思考下,添加和删除操作同上述两个操作主要的区别在哪里?为什么会复杂些?

  因为添加元素的时候可能位置不够嘛,那怎么办?位置不够就加位置,也就是所谓的“扩容”!我们自己思考下实现思路,然后同作者的思路对比下:

  1.上面我们提到,默认构造方法使用的lazy_init模式,添加元素时才init。那第一步判断是否需要init,需要则init。

  2.判断是否需要扩容(通俗点说就是当前数组有没有空位置?)需要就扩容(创建个容量更大的新数组),但是扩容也不是无限的,超过上限就报错(Integer.Max_Value),并把原数据copy到新数组,否则什么都不做

  3.在指定位置添加元素,并size++

技术分享
 1 /**
 2 看下作者是如何思考的,和自己的思考做下对比:
 3 1.判断是否需要lazy init,如果需要则init,然后执行步骤2
 4 2.判断是否需要扩容,如果需要则执行步骤3,否则执行步骤5
 5 3.扩容,新数组长度=当前数组长度*1.5,并判断扩容后长度是否满足目标容量,不满足,则新数组长度=目标容量。接着判断新数组长度是否超过阈值MAX_ARRAY_SIZE,超过则执行步骤4
 6 4.目标数组长度=如果目标数组长度>MAX_ARRAY_SIZE?Integer.MAX_VALUE:MAX_ARRAY_SIZE
 7 5.扩容结束后,执行数据copy,从原数组copy数据到新数组
 8 6.在指定的位置添加元素,并使长度增加
 9 **/
10 public boolean add(E e) {
11         ensureCapacityInternal(size + 1);  // Increments modCount!!
12         elementData[size++] = e;
13         return true;
14     }
15 private void ensureCapacityInternal(int minCapacity) {
16         if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
17             minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
18         }
19 
20         ensureExplicitCapacity(minCapacity);
21     }
22 private void ensureExplicitCapacity(int minCapacity) {
23         modCount++;
24 
25         // overflow-conscious code
26         if (minCapacity - elementData.length > 0)
27             grow(minCapacity);
28     }
29 private void grow(int minCapacity) {
30         // overflow-conscious code
31         int oldCapacity = elementData.length;
32         int newCapacity = oldCapacity + (oldCapacity >> 1);
33         if (newCapacity - minCapacity < 0)
34             newCapacity = minCapacity;
35         if (newCapacity - MAX_ARRAY_SIZE > 0)
36             newCapacity = hugeCapacity(minCapacity);
37         // minCapacity is usually close to size, so this is a win:
38         elementData = Arrays.copyOf(elementData, newCapacity);
39     }
40 private static int hugeCapacity(int minCapacity) {
41         if (minCapacity < 0) // overflow
42             throw new OutOfMemoryError();
43         return (minCapacity > MAX_ARRAY_SIZE) ?
44             Integer.MAX_VALUE :
45             MAX_ARRAY_SIZE;
46     }
单个添加集合元素

  对比后发现,总体思路是没问题的,但是作者为什么要加入下面这样的容量上限处理逻辑?朋友们可以思考下,ArrayList的容量上限到底是Integer.MAX_VALUE还是MAX_ARRAY_SIZE?

  答案是:Integer.MAX_VALUE,依然还是它,并不是源码中作者定义的MAX_ARRAY_SIZE,那就引申出一个问题了-MAX_ARRAY_SIZE存在的意义。

  我的理解是,这个上限的存在,减小了内存溢出的概率。首先注释中也提到了“Some VMs reserve some header words in an array”,意思就是有些虚拟机把头信息保留在数组中,毫无疑问保存信息是要占空间的,也就是我实际数组空间可能是不足Integer.MAX_VALUE的,作者应该是这样的思路(我猜的~)“我可以基本确定实际空间不会小于MAX_ARRAY_SIZE(可能是分析了主流虚拟机的实现而得出的结论),因此设置个阈值,来确保不会内存溢出,但如果这个容量还是不够,那把剩下的风险很高的8也给你好了,至于是不是会溢出,天知道,反正我已经尽力了!

if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
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 /**
 2 比较简单,一笔带过,按位置删除
 3 思路:
 4 1.越界检测
 5 2.取出当前位置的元素
 6 3.当前位置之后的所有元素整体前移一位置
 7 4.最后位置置空,size--,返回删除的元素值。
 8 **/
 9 public E remove(int index) {
10         rangeCheck(index);
11 
12         modCount++;
13         E oldValue = elementData(index);
14 
15         int numMoved = size - index - 1;
16         if (numMoved > 0)
17             System.arraycopy(elementData, index+1, elementData, index,
18                              numMoved);
19         elementData[--size] = null; // clear to let GC do its work
20 
21         return oldValue;
22     }
23 /**
24 思路很简单,按元素删除(注意只删除从头开始第一个匹配值),遍历->匹配->删除
25 值得注意的是,单独对null做特殊处理,按地址比较
26 **/
27 public boolean remove(Object o) {
28         if (o == null) {
29             for (int index = 0; index < size; index++)
30                 if (elementData[index] == null) {
31                     fastRemove(index);
32                     return true;
33                 }
34         } else {
35             for (int index = 0; index < size; index++)
36                 if (o.equals(elementData[index])) {
37                     fastRemove(index);
38                     return true;
39                 }
40         }
41         return false;
42     }
43 
44 单个删除集合元素
单个删除元素
技术分享
 1 public boolean removeAll(Collection<?> c) {
 2         Objects.requireNonNull(c);
 3         return batchRemove(c, false);
 4     }
 5 
 6     private boolean batchRemove(Collection<?> c, boolean complement) {
 7         final Object[] elementData = this.elementData;
 8         int r = 0, w = 0;
 9         boolean modified = false;
10         try {
11 //遍历当前集合所有元素
12             for (; r < size; r++)
13                 if (c.contains(elementData[r]) == complement)
14 //如果指定集合不包含该元素(即不应删除的,需要保留的),把当前元素移动到头部w位置(原头部元素因不符合条件,直接删除掉,这里覆盖也即删除),并把w标记移到下一位
15                     elementData[w++] = elementData[r];
16         } finally {
17             // Preserve behavioral compatibility with AbstractCollection,
18             // even if c.contains() throws.
19 //这儿两种情况:
20 //无异常r==size 不会进入这个if
21 //有异常,则把因异常而未来得及比较的所有元素整体copy到w位置,并把w标记移位size - r(可以理解为还未比较的数量)
22             if (r != size) {
23                 System.arraycopy(elementData, r,
24                                  elementData, w,
25                                  size - r);
26                 w += size - r;
27             }
28 //这儿很好理解,w位置(该位置之前都是比较或者异常而需要保留的)之后的所有都是应该删除的。
29             if (w != size) {
30                 // clear to let GC do its work
31                 for (int i = w; i < size; i++)
32                     elementData[i] = null;
33                 modCount += size - w;
34                 size = w;
35                 modified = true;
36             }
37         }
38         return modified;
39     }
批量删除
技术分享
 1 /**
 2 看起来比较简单,但仔细分析就会发现还是有些深度的,有兴趣可以看下defaultReadObject的实现,这里就暂时忽略,以后有机会详细分析下java的序列化与反序列化。
 3 实现思路(以序列化为例,反序列化同理)
 4 1.调用默认序列化
 5 2.写入size
 6 3.遍历数组,写入所有集合元素
 7 **/
 8 private void writeObject(java.io.ObjectOutputStream s)
 9         throws java.io.IOException{
10         // Write out element count, and any hidden stuff
11         int expectedModCount = modCount;
12         s.defaultWriteObject();
13 
14         // Write out size as capacity for behavioural compatibility with clone()
15         s.writeInt(size);
16 
17         // Write out all elements in the proper order.
18         for (int i=0; i<size; i++) {
19             s.writeObject(elementData[i]);
20         }
21 
22         if (modCount != expectedModCount) {
23             throw new ConcurrentModificationException();
24         }
25     }
26 
27     /**
28      * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
29      * deserialize it).
30      */
31     private void readObject(java.io.ObjectInputStream s)
32         throws java.io.IOException, ClassNotFoundException {
33         elementData = EMPTY_ELEMENTDATA;
34 
35         // Read in size, and any hidden stuff
36         s.defaultReadObject();
37 
38         // Read in capacity
39         s.readInt(); // ignored
40 
41         if (size > 0) {
42             // be like clone(), allocate array based upon size not capacity
43             ensureCapacityInternal(size);
44 
45             Object[] a = elementData;
46             // Read in all elements in the proper order.
47             for (int i=0; i<size; i++) {
48                 a[i] = s.readObject();
49             }
50         }
51     }
序列化与反序列化
技术分享
/**
迭代器
1.了解快速失败机制即可
2.关注下forEachRemaining的实现,可扩展研究下lambda表达式
**/
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;

        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;
                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();
        }
    }
迭代器

三、 ArrayList使用建议

  1.当你需要确定长度的ArrayList时,推荐使用该长度初始化,理由是防止频繁扩容,扩容相对而言是对性能影响相当大的操作(请注意是相对而言,相对于常规的增删改查等操作)。

  2.谨慎使用clone、toArray,对于源对象和方法返回对象,数据修改操作会相互影响。他们最终都执行了System.arrayCopy(),都是“浅拷贝”:只copy了引用,所有对于其中一个引用的修改操作会传递到其他引用上。java的copy应该都是“浅拷贝”,测试如下:

技术分享
/**
测试代码,随手为之,仅供参考。
**/
class TestObj {

            TestObj(Integer i, String s) {
                this.i = i;
                this.s = s;
            }

            Integer i;

            String s;

            public String toString() {

                return "i=" + i + "," + "s=" + s;
            }
        }
        ArrayList<TestObj> arrayList = new ArrayList<>();

        int size = 10;


        for (Integer i = 0; i < size; i++) {

            arrayList.add(new TestObj(i, "test" + i));

        }


        ArrayList<TestObj> cloneArrayList = (ArrayList<TestObj>) arrayList.clone();

        cloneArrayList.add(new TestObj(101, "test" + 101));

        System.out.println("arrayList size:" + arrayList.size());
        System.out.println("cloneArrayList size:" + cloneArrayList.size());


        System.out.println("arrayList index 0:" + arrayList.get(0).toString());
        System.out.println("cloneArrayList index 0:" + cloneArrayList.get(0).toString());

        //修改cloneArrayList index 0
        TestObj testInteger = cloneArrayList.get(0);
        testInteger.i = 1000111;

        System.out.println("修改cloneArrayList index=0对象后,ps:我没修改arrayList哦");


        System.out.println("arrayList index 0:" + arrayList.get(0).toString());
        System.out.println("cloneArrayList index 0:" + cloneArrayList.get(0).toString());
测试结果:
arrayList size:10
cloneArrayList size:11
arrayList index 0:i=0,s=test0
cloneArrayList index 0:i=0,s=test0
修改cloneArrayList index=0对象后,ps:我没修改arrayList哦
arrayList index 0:i=1000111,s=test0
cloneArrayList index 0:i=1000111,s=test0
测试clone

  3.谨慎使用subList,深入分析后会发现,本质原因同上面类似,都是相同的引用,所以数据修改操作会相互影响,如下例子:

技术分享
 1 public static void testSubList() {
 2 
 3         Integer index = 10;
 4 
 5         List<Integer> myList = new ArrayList<>(index);
 6 
 7         for (int i = 0; i < index; i++) {
 8             myList.add(i);
 9         }
10 
11         List<Integer> mySubList = myList.subList(3, 5);
12 
13 
14         System.out.println("打印myList:");
15         myList.forEach(System.out::println);
16 
17         System.out.println("对mySubList增加个元素,注意我没对myList做任何操作哦");
18         mySubList.add(100);
19 
20 
21         System.out.println("打印mySubList:");
22         mySubList.forEach(System.out::println);
23 
24         System.out.println("再次打印myList:");
25         myList.forEach(System.out::println);
26 
27 
28     }
29 运行结果:
30 打印myList:
31 0
32 1
33 2
34 3
35 4
36 5
37 6
38 7
39 8
40 9
41 对mySubList增加个元素,注意我没对myList做任何操作哦
42 打印mySubList:
43 3
44 4
45 100
46 再次打印myList:
47 0
48 1
49 2
50 3
51 4
52 100
53 5
54 6
55 7
56 8
57 9
测试subList

  4.细心的朋友可能发现了,arrayList没有提供对数组的构造方法,但是我们知道array->arrayList是比较常见的需求,那如何做呢?办法不止一种,选择你喜欢的即可,如下:

技术分享
1 int[] ints = {1, 2, 3, 4};
2         List<Integer> myIntListByStream = Arrays.stream(ints).boxed().collect(Collectors.toList());
3 
4 
5         Integer[] integers = {1, 2, 3, 4};
6         List<Integer> myListByAsList = Arrays.asList(integers);   //ps:此方式得到的数组不可修改,这里的修改指的是所有更改List数据的行为
7         List<Integer> myListByNewArrayListAsList = new ArrayList<>(Arrays.asList(integers));
8         List<Integer> myListByStream = Arrays.stream(integers).collect(Collectors.toList());
arrayToList

四、源码相关扩展(我能想到的可以深入思考的一些地方)

  1.快速失败(fail fast)机制:

  我们知道,ArrayList不是线程安全的集合类。意味着在并发操作时,很容易发生错误。按照常规思路,发现结果出错,抛出异常即可。但在实际应用中,我们并不满足于此,有没有一种检测机制,在并发操作前进行校验,提前告诉我们可能发生的错误呢?如你所料,就是我们提到的快速失败机制。它是如何实现的呢?其实很简单,我们定义一个计数器modCount,这个计数器记录所有集合结构变动的次数,比如增加两个元素就modCount+=2,再比如删除3个元素就modCount+=3,这个计数器只能增加不能减少,然后我们在执行一些需要快速失败的操作时(比如:迭代器、序列化等等),执行之前记录下当前modCount为expectedModCount,在适当的时候判断expectedModCount、modCount是否相等就可以判断这期间是否有过集合结构的变动。

  2.lambda表达式:读源码我们发现,ArrayList中加入了许多支持lambda的方法,作为JDK8的亮点之一,应该能够熟练使用,然后再详细分析lambda的实现机制我觉得也很有意思。

  3.jdk中常用JNI方法的实现:上文我们在对clone方法做分析的时候,最终只分析到Object的native方法,我觉得有机会去看下常用的一些native方法的实现也是很有意思的,待研究。

五、总结一下

老实说,分析完ArrayList发现,所花的时间大出我预计,我一度认为我已经理解的很透彻了,但是在写这篇文章的途中我又把差不多一半的源码回顾了一遍(这真是个悲伤的故事),不过不管怎么说,这是个好的开始,后面一篇解析hashMap。

 

JAVA常用集合源码解析系列-ArrayList源码解析(基于JDK8)

标签:保存   ror   bsp   length   font   ring   地方   机制   data   

原文地址:http://www.cnblogs.com/moranqingqiu/p/6725240.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!