ArrayList源码解析(jdk1.7)

1.什么是ArrayList

  • 使用动态数组Object[]保存元素,允许null值
  • 实现List接口,允许使用下标操作元素
  • 非线程安全,线程安全参见Vector
  • 适用于频繁访问的场景,不合适频繁插入或者删除(参见LinkedList)
  • 支持批量操作

2.ArrayList的数据结构

2.1 类定义

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

  • 继承AbstractList:实现了List,对List中重要操作的实现,包括add,remove等
  • 实现 RandmoAccess 接口:支持快速随机访问,即通过元素的序号快速获取元素对象
  • 实现Cloneable:重写clone(),能被克隆(浅拷贝)
  • 实现Serializable:支持序列化

2.2 重要的全局变量

可以思考一下,如果让我们来设计ArrayList,需要哪些全局变量和函数?

  • Object[] 数组,用来存放元素
  • size:数组中元素个数
  • MaxSize:数组的最大长度
  • add方法(考虑扩容),get方法,remove方法(考虑减容),size()方法,isEmpty()方法

jdk的实现,全局变量包括:

>//Default initial capacity.
    private static final int DEFAULT_CAPACITY = 10;
//Shared empty array instance used for empty instances.
    private static final Object[] EMPTY_ELEMENTDATA = {};
// 存放元素的数组,空的ArrayList,即elementData = EMPTY_ELEMENTDATA,在添加第一个元素时,会扩容到DEFAULT_CAPACITY 。transient 标记序列化时不需要序列化该字段。
    transient Object[] elementData;
// 元素个数size
    private int size;
//父类AbstractList中的成员变量,记录集合被修改的次数,用于迭代时的异常检查(下文会详细讲解)
  protected transient int modCount = 0;

2.3 重要的函数

2.3.1 add方法

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

添加一个元素到集合尾部,首先对数组大小进行检查,需要扩容的进行扩容,然后将元素添加到尾部下标所在位置,并将size加1。

private void ensureCapacityInternal(int minCapacity) {
      // 如果数组为空,则选择DEFAULT_CAPACITY与当前需要的总容量的较大者,作为扩容的参数
        if (elementData == EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        //若需要的容量比数组的长度还大,则需要进行扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    // 新的参考容量为旧容量的1.5倍(向上取整),和hashmap的2倍不一样
    //等同于 (int)Math.floor(oldCapacity*1.5) 
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)//若新的参考容量比需要的容量小,则直接使用需要的容量
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            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 < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

2.3.2 remove()方法

删除指定位置的元素

   public E remove(int index) {
        if (index >= size)//检查下标是否合法
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        modCount++;//修改modCount
        E oldValue = (E) 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;
    }

2.3.3 sort()方法

    public void sort(Comparator<? super E> c) {
        final int expectedModCount = modCount;
        Arrays.sort((E[]) elementData, 0, size, c);
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }

注意:ArrayList的排序采用的是折半插入排序。

     * Sorts the specified portion of the specified array using a binary
     * insertion sort.  This is the best method for sorting small numbers
     * of elements.  It requires O(n log n) compares, but O(n^2) data
     * movement (worst case).
* @param a the array in which a range is to be sorted
     * @param lo the index of the first element in the range to be sorted
     * @param hi the index after the last element in the range to be sorted
     * @param start the index of the first element in the range that is
     *        not already known to be sorted ({@code lo <= start <= hi})
     */
    @SuppressWarnings({"fallthrough", "rawtypes", "unchecked"})
    private static void binarySort(Object[] a, int lo, int hi, int start) {
        assert lo <= start && start <= hi;
        if (start == lo)
            start++;
        for ( ; start < hi; start++) {
            Comparable pivot = (Comparable) a[start];

            // Set left (and right) to the index where a[start] (pivot) belongs
            int left = lo;
            int right = start;
            assert left <= right;
            /*
             * Invariants:
             *   pivot >= all in [lo, left).
             *   pivot <  all in [right, start).
             */
            while (left < right) {
                int mid = (left + right) >>> 1;
                if (pivot.compareTo(a[mid]) < 0)
                    right = mid;
                else
                    left = mid + 1;
            }
            assert left == right;

            /*
             * The invariants still hold: pivot >= all in [lo, left) and
             * pivot < all in [left, start), so pivot belongs at left.  Note
             * that if there are elements equal to pivot, left points to the
             * first slot after them -- that's why this sort is stable.
             * Slide elements over to make room for pivot.
             */
            int n = start - left;  // The number of elements to move
            // Switch is just an optimization for arraycopy in default case
            switch (n) {
                case 2:  a[left + 2] = a[left + 1];
                case 1:  a[left + 1] = a[left];
                         break;
                default: System.arraycopy(a, left, a, left + 1, n);
            }
            a[left] = pivot;
        }
    }

2.3.4 writeObject()方法

    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();
        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);
        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }
       if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();

这里不直接使用s.writeObject(elementData)的方法,是因为考虑到数组有一些空余的空间不需要序列化。

2.3.5 两个toArray方法

  • 1
public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }
  • 2
public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

如果toArray()需要向下转型(例如将Object[]转换为的Integer[]),则需要使用第2种方式,传入一个子类数组(如下代码段)。否则直接使用第1种方法。由于java不支持向下转型,第1种方式会抛java.lang.ClassCastException异常。

public static Integer[] toArray(ArrayList<Integer> arrayList) {
    Integer[] newArrayList = (Integer[])arrayList.toArray(new Integer[0]);
    return newArrayList;
}

2.4 内部类

ArrayList的内部类
  • 静态内部类:ArrayListSpliterator可分割的迭代器,用于并行遍历集合。(jdk1.8才有)

静态内部类的使用场景是: 1 当外部类需要是用内部类,而内部类无需外部类的资源,并且内部类可以单独创建的时候,会考虑使用静态内部类。2 内部类与外部类关系密切的,且不依赖外部类实例的,都可以使用静态内部类。
例如builder模式中的builder类;hashmap的static class HashMapEntry<K,V>类。

  • private迭代器:Itr和ListItr
  • private SubList:集合的子集

2.5 其它

2.5.1 modCount与Fail-Fast机制

modCount记录数组被修改的次数,作为线程不安全的集合类的成员变量,主要用在迭代器以及序列化writeObject()等场景。由下面迭代器代码可以看到,每次获取元素之前都会检查当前modCount与该迭代器对象初始化时的modCount是否一致,如果不一致,则抛出异常。这就是Fail-Fast机制。对于线程不安全的集合类,通过modCount域快速检查集合内容是否有变化。JDK为了提示开发者将非线程安全的类使用到并发的场景下时,抛出一个异常,尽早发现代码中的问题。当然,这种机制并不一定有效。首先,jdk1.7中取消了modCount的volatitle修饰符,失去了各个线程直接对modCount的可见性。
注意:
在使用迭代器对象时,是可以修改数据的。即一下代码是合法的。

while (iterator.hasNext()) {    
  if(iterator.next().equals("c")) {        
    iterator.remove("c");    
  }
}

public boolean hasNext() {
            return cursor < limit;
        }

原因在于:hasNext()方法并没有使用modCount进行判断,而是比较下一个元素的位置与数组总长度。

    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();
        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);
        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }
       if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
private class Itr implements Iterator<E> {
        // The "limit" of this iterator. This is the size of the list at the time the
        // iterator was created. Adding & removing elements will invalidate the iteration
        // anyway (and cause next() to throw) so saving this value will guarantee that the
        // value of hasNext() remains stable and won't flap between true and false when elements
        // are added and removed from the list.
        protected int limit = ArrayList.this.size;

        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 < limit;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            int i = cursor;
            if (i >= limit)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }
        ……
}

2.5.2 System.arrayCopy() Arrays.copyOf()的区别

1 public static native void arraycopy(Object src, int srcPos,Object dest, int destPos, int length);
参数依次对应为:源数组,源数组copy的起始位置,目标数组,目标数组存放的起始位置,需要copy的数组长度

2 public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType)
参数依次对应为:源数组,新数组长度,新数组元素类型

实际上, Arrays.copyOf()里面还是通过调用System.arrayCopy()实现最终的数组元素拷贝。所以,Arrays.copyOf()实际上是受限的System.arrayCopy()
区别主要为:

  • 1 System方法需要传入新的数组,Arrays方法不用(会在内部自己新建一个数组并返回)。
  • 2 System方法没有返回值,Arrays方法返回新的数组
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容