zoukankan      html  css  js  c++  java
  • 【Java集合】试读ArrayList源码

    ArrayList简介

    ArrayList 是一个数组队列,相当于 动态数组。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。

    ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
    ArrayList 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。稍后,我们会比较List的“快速随机访问”和“通过Iterator迭代器访问”的效率。

    ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。

    ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。

    (前面都是复制粘贴的图和文字,大家大概理解一下,下面进入正题)

    首先我们要明白一点,ArrayList的本质就是数组

    所以我们的源码,首先从ArrayList中维护的两个数组变量开始,它们是ArrayList的核心:

    //该数组缓存者集合中的元素,集合的容量就是该数组的长度,elementData用transient修饰,说明在序列化时,数组elementData不在序列化范围内。
    private transient Object[] elementData;
    
    //集合的大小 (集合中元素的实际数量)
    private int size;

    接下来看一下ArrayList的构造器:

    //ArrayList带容量大小的构造函数,容量大于0,则生成对应容量的数组,容量等于0,则把空数组EMPTY_ELEMENTDATA赋给它,容量小于0则报错
        public ArrayList(int initialCapacity) {
            if (initialCapacity > 0) {
                this.elementData = new Object[initialCapacity];
            } else if (initialCapacity == 0) {
                this.elementData = EMPTY_ELEMENTDATA;
            } else {
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
            }
        }
    
        // ArrayList无参构造函数,则把空数组DEFAULTCAPACIT_EMPTY_ELEMENTDATA赋给它,但是这个空数组拥有一个默认的数组容量
        public ArrayList() {
            this.elementData = DEFAULTCAPACIT_EMPTY_ELEMENTDATA;
        }
    
        // 创建一个包含传入collection的ArrayList,若这个Collection为空,则把空数组EMPTY_ELEMENTDATA赋给它;若不为空,通过Collection的toArray()方法生成一个数组
        public ArrayList(Collection<? extends E> c) {
            elementData = c.toArray();
            if ((size = elementData.length) != 0) {
                // c.toArray might (incorrectly) not return Object[] (see 6260652)
                if (elementData.getClass() != Object[].class)
                    elementData = Arrays.copyOf(elementData, size, Object[].class);
            } else {
                // replace with empty array.7
                this.elementData = EMPTY_ELEMENTDATA;
            }
        }

    这时候问题出现了,里面的EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA是什么鬼?都是空数组有什么区别呢?

    //默认数组容量10
    private static final int DEFAULT_CAPACITY = 10;
    
    //空的数组,数组大小为0
    private static final Object[] EMPTY_ELEMENTDATA = {};
    
    //空的数组,但是数组大小为默认数组大小10
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    这个所谓的数组容量有什么用呢?

    我们接着看下常用的get() 、set()和add()这几个方法:

    //封装的获取index位置的元素值方法,避免强转
        @SuppressWarnings("unchecked")
        E elementData(int index) {
            return (E) elementData[index];
        }
    
        // 获取index位置的元素值
        public E get(int index) {
            rangeCheck(index);
    
            return elementData(index);
        }
    
        // 设置index位置的值为element
        public E set(int index, E element) {
            rangeCheck(index);
    
            E oldValue = elementData(index);
            elementData[index] = element;
            return oldValue;
        }
    
        // 将e添加到ArrayList中
        public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
    
         // 将e添加到ArrayList的指定位置
        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++;
        }

    get()方法和set()方法比较简单,都是判断一下是否越界,然后根据数组索引来找值。判断越界(包含add方法的越界判断)的代码如下:

       //判断是否index是否过界
        private void rangeCheck(int index) {
            if (index >= size)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    
        //判断是否index是否过界,用于add和addAll方法
        private void rangeCheckForAdd(int index) {
            if (index > size || index < 0)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }

    add()方法中有一个System.arraycopy(),主要用于数组内的数的复制。由于源代码不是Java写的,我这边只稍微写一下用法:
    System提供了一个静态方法arraycopy(),我们可以使用它来实现数组之间的复制。其函数原型是:
    
    public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length)
    
    src:源数组;    
    srcPos:源数组要复制的起始位置;
    dest:目的数组;    
    destPos:目的数组放置的起始位置;    
    length:复制的长度。
    注意:src and dest都必须是同类型或者可以进行转换类型的数组.
    有趣的是这个函数可以实现自己到自己复制,比如:
    int[] fun ={0,1,2,3,4,5,6}; 
    System.arraycopy(fun,0,fun,3,3);
    则结果为:{0,1,2,0,1,2,6};

    而之前构造器中的Arrays.copyOf()方法,也有调用这个system.arraycopy方法,从而实现数组的扩容(当然还有Arrays.copyOf()方法还有其他功能,此处姑且不谈):

    System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));

    我们同时发现发现add方法中有一个ensureCapacityInternal()方法,这个就涉及到了我们刚刚说到的容量,我们接下来看一下源码: // 确定ArrarList的容量。

     //确定ArrarList的容量。
        private void ensureCapacityInternal(int minCapacity) {
            //若数组为无参构造形成的数组,则minCapacity为传入值和默认值中的最大值
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
            }
    
            ensureExplicitCapacity(minCapacity);
        }
    
        private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
            //若传入值比当前的数组长度大,则要增加数组长度
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
    
        //不出现OutOfMemory的最大数组容量
        private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    
        //增加数组容量
        private void grow(int minCapacity) {
            int oldCapacity = elementData.length;
            //新的容量为原来的1.5倍
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            //把数组容量变大
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    
        private static int hugeCapacity(int minCapacity) {
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
        }

    ensureCapacityInternal()方法就是确认ArrayList的容量是否满足要求。

    若当前数组是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,则取传入的容量和默认容量中的较大值,来进行后面的增加容量操作。
    也就时说,默认容量较大时,把默认容量认为是当前数组的容量。

    同样,有另外一个对外的公共方法ensureCapacity(),也是确认ArrayList容量的功能,在是否为默认空间的时候都进行了一次判断,然后才调用ensureCapacity()方法:

      public void ensureCapacity(int minCapacity) {
            int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                //数组不是默认的容量
                ? 0
                //数组容量比默认的大
                : DEFAULT_CAPACITY;
    
            if (minCapacity > minExpand) {
                ensureExplicitCapacity(minCapacity);
            }
        }

    最后增加数据的部分看一下和addAll方法的源码:

      // 将集合c追加到ArrayList中,把容量新增一个collection的长度,然后在后面新增collection中的元素
        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;
        }
    
        // 把容量新增一个collection的长度,从index位置开始,将集合c添加到ArrayList
        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指定位置的元素
        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;
        }
    
        // 删除ArrayList的指定元素
        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;
        }
    
        // 快速删除第index个元素
        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
        }
    
        // 清空ArrayList,将全部的元素设为null
        public void clear() {
            modCount++;
    
            // clear to let GC do its work
            for (int i = 0; i < size; i++)
                elementData[i] = null;
    
            size = 0;
        }

    可以看到,每在中间删除或者增加一个元素,旁边的元素都要跟着移动一遍, 非常麻烦。

    然后看元素的搜索:

    // 获取index位置的元素值
        public E get(int index) {
            rangeCheck(index);
    
            return elementData(index);
        }
    
      // 返回ArrayList是否包含Object(o)
        public boolean contains(Object o) {
            return indexOf(o) >= 0;
        }
    
        // 正向查找,返回元素的索引值
        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;
        }
    
        // 反向查找,返回元素的索引值
        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;
        }

    接下来看一些里面剩下来的简单常用的方法:

            // 返回ArrayList的实际大小
        public int size() {
            return size;
        }
    
        // 返回ArrayList是否为空                 
        public boolean isEmpty() {
            return size == 0;
        }
    
             // 将当前容量值设为实际元素个数
        public void trimToSize() {
            modCount++;
            if (size < elementData.length) {
                elementData = (size == 0)
                  ? EMPTY_ELEMENTDATA
                  : Arrays.copyOf(elementData, size);
            }
        }
    
           // 克隆函数
        public Object clone() {
            try {
                ArrayList<?> v = (ArrayList<?>) super.clone();
                v.elementData = Arrays.copyOf(elementData, size);
                v.modCount = 0;
                return v;
            } catch (CloneNotSupportedException e) {
                // this shouldn't happen, since we are Cloneable
                throw new InternalError(e);
            }
        }

    最后稍微看一下里面的迭代器方法,代码如下:

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

    里面的具体方法不具体展开讲,我们重点关注一下checkForComodification()方法。

    眼尖的朋友可能已经发现了,在前面的add(),addAll(),clear等改变数组值的方法中,都有一个操作

    modCount++;

    这个modCount是什么?这个操作又代表着什么呢?

    事实上,这个数是定义在ArrayList的父类AbstractList中的:

    protected transient int modCount = 0;

    这个数的用处我们结合checkForComodification()方法来看,:

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

    这个方法就是判断,当modCount不等于expectedModCount,则抛出异常。

    从Itr类中,我们知道 expectedModCount 在创建Itr对象时,被赋值为 modCount。通过Itr,我们知道:expectedModCount不可能被修改为不等于 modCount。所以,当且仅当modCount被修改时,会抛出异常。

    这种异常的出现,源自于java集合(Collection)中的一种错误机制:

    fail-fast 机制

    当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。
    例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

      

    具体到代码中:

    当多个线程对同一个集合进行操作的时候,某线程访问集合的过程中,该集合的内容被其他线程所改变(即其它线程通过add、remove、clear等方法,改变了modCount的值);这时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

    所以这个modCount就是用来判断集合的内容是否在iterator遍历过程中被改变的一个参数。

    总结一下:

    1.ArrayList是用数组实现的,所以查找元素时直接通过Index查找,比较快;可是新增或者删除元素就慢了,要大量的移动数组里的数。
    2.ArrayList新增数据的时候,可能会引起容量的扩充,但是删除数据却不会造成容量的减小,只能用trimToSize()方法来把数组容量减小为实际元素个数。
    3.扩充容量的方法ensureExplicitCapacity。新增操作或者是客户端主动调用ensureCapacity()方法时,如果容量不足了,就设置新的容量为旧的容量的1.5倍,如果设置后的新容量还不够,则直接新容量设置为传入的参数,而后用Arrays.copyof()方法将元素拷贝到新的数组。容量经常变化,导致元素多次拷贝的话,非常耗时

    4.modCount就是用来判断集合的内容是否在iterator遍历过程中被改变,若被改变了会产生fail-fast事件。

  • 相关阅读:
    《反恐精英》VS《使命召唤》
    CSS中expression简介实现对象批量控制
    asp无组件上传文件超过200k就提示错误的解决方法
    IE和Firefox的js兼容性整理
    国外优秀的屏幕录象专家
    陆游和唐婉
    机器智能将会在2029年达到人类的水平
    WinXP中有趣的特殊文件夹
    二行代码解决全部网页木马(含iframe/script木马)
    Javascript 获取页面高度(多种浏览器)
  • 原文地址:https://www.cnblogs.com/jing-daye/p/7146280.html
Copyright © 2011-2022 走看看