集合之ArrayList源码分析

ArrayList是内部使用数组实现的一个集合,它可以自动扩容,因此,我们可以将ArrayList看作是一个动态数组。因为内部使用的是数组,ArrayList做遍历查询的时候速度会比较快,但是添加、移除数据的时候都需要移动元素,特别是添加操作并且需要扩容的时候,会对数组进行拷贝,因此效率比较差。此外,ArrayList不是线程安全的,所以建议在多线程中不要使用它,如果需要使用一定要做好同步,当然也可以去选择CopyOnWriteArrayList或者其它的线程安全的集合。本文基于Android-23源码分析。

源码解析

先来看看ArrayList的继承和实现关系

public class ArrayList<E> extends AbstractList<E> implements Cloneable, Serializable, RandomAccess {
}

在这里可以看到ArrayList是支持泛型的,它实现了Cloneable,Serializable, RandomAccess接口,并且继承了 AbstractList<E>抽象类(实现了List集合)。
AbstractList<E>:提供了add,remove,clear等方法给到ArrayList去实现;
Cloneable:通过实现clone()方法,能够实现克隆对象;
Serializable:ArrayList支持序列化,和反序列化,实现Serializble接口之后能够进行序列化传输;
RandomAccess:它是一个标记接口,接口内没有定义任何内容,它支持快速随机访问,具体什么意思,后面会讲到。

先来看看ArrayList的成员变量以及构造函数:

    /**
     * 数组最小的扩容大小
     */
    private static final int MIN_CAPACITY_INCREMENT = 12;
    /**
     * ArrayList内部数组的大小
     */
    int size;
    /**
     * ArrayList内部使用的数组
     */
    transient Object[] array;

    public ArrayList(int capacity) {
        if (capacity < 0) {
            throw new IllegalArgumentException("capacity < 0: " + capacity);
        }
        array = (capacity == 0 ? EmptyArray.OBJECT : new Object[capacity]);
    }

    public ArrayList() {
        array = EmptyArray.OBJECT;
    }

    public ArrayList(Collection<? extends E> collection) {
        if (collection == null) {
            throw new NullPointerException("collection == null");
        }

        Object[] a = collection.toArray();
        if (a.getClass() != Object[].class) {
            Object[] newArray = new Object[a.length];
            //
            System.arraycopy(a, 0, newArray, 0, a.length);
            a = newArray;
        }
        array = a;
        size = a.length;
    }

以上代码就是ArrayList的成员变量和构造函数,比较简单,需要注意的是,array数组是利用transient来修饰的,transient的作用是被标记为transient的属性在对象被序列化的时候不会被保存。此外,ArrayList中是利用System.arraycopy()方式来拷贝数组的,这是一种浅拷贝,它调用的方法是System中的native方法。

public static native void arraycopy(Object src, int srcPos,
        Object dst, int dstPos, int length);

ArrayList添加

现在我们来看看add()方法:

 @Override public boolean add(E object) {
        Object[] a = array;
        int s = size;
        if (s == a.length) {//空间不够的时候需要扩容
            Object[] newArray = new Object[s +
                    (s < (MIN_CAPACITY_INCREMENT / 2) ?
                     MIN_CAPACITY_INCREMENT : s >> 1)];
            System.arraycopy(a, 0, newArray, 0, s);
            array = a = newArray;
        }
        a[s] = object;
        size = s + 1;
        modCount++;
        return true;
    }

add()方法中,如果数组的空间不够添加的时候,需要进行扩容:

 Object[] newArray = new Object[s +
                    (s < (MIN_CAPACITY_INCREMENT / 2) ?
                     MIN_CAPACITY_INCREMENT : s >> 1)];

此外有一个modCount变量,这个变量是继承父类AbstractList的,它的作用是记录对数组元素进行添加,删除的一个变量,最终的作用是进行Iterator操作的时候判断是否抛出ConcurrentModificationException,这就是fast-fail机制。
添加集合的方法也差不多:

   @Override public boolean addAll(Collection<? extends E> collection) {
        Object[] newPart = collection.toArray();
        int newPartSize = newPart.length;
        if (newPartSize == 0) {
            return false;
        }
        Object[] a = array;
        int s = size;
        int newSize = s + newPartSize; // If add overflows, arraycopy will fail
        if (newSize > a.length) {
            int newCapacity = newCapacity(newSize - 1);  // ~33% growth room
            Object[] newArray = new Object[newCapacity];
            System.arraycopy(a, 0, newArray, 0, s);
            array = a = newArray;
        }
        System.arraycopy(newPart, 0, a, s, newPartSize);
        size = newSize;
        modCount++;
        return true;
    }

    @Override
    public boolean addAll(int index, Collection<? extends E> collection) {
        int s = size;
        if (index > s || index < 0) {
            throwIndexOutOfBoundsException(index, s);
        }
        Object[] newPart = collection.toArray();
        int newPartSize = newPart.length;
        if (newPartSize == 0) {
            return false;
        }
        Object[] a = array;
        int newSize = s + newPartSize; // If add overflows, arraycopy will fail
        if (newSize <= a.length) {
             System.arraycopy(a, index, a, index + newPartSize, s - index);
        } else {
            int newCapacity = newCapacity(newSize - 1);  // ~33% growth room
            Object[] newArray = new Object[newCapacity];
            System.arraycopy(a, 0, newArray, 0, index);
            System.arraycopy(a, index, newArray, index + newPartSize, s-index);
            array = a = newArray;
        }
        System.arraycopy(newPart, 0, a, index, newPartSize);
        size = newSize;
        modCount++;
        return true;
    }

addAll(Collection<? extends E> collection) 方法先去将传入的集合转换为数组,然后判断数组的大小是否为0,数组不为空的时候定义一个表示新数组大小的变量,判断是否需要扩容,然后拷贝数组,最后修改modCount的变量值并返回修改成功。addAll(int index, Collection<? extends E> collection)方法与之比较类似就不一一说明了。

ArrayList移除

   @Override public E remove(int index) {
        Object[] a = array;
        int s = size;
        if (index >= s) {//判断数组下标是否越界
            throwIndexOutOfBoundsException(index, s);
        }
        @SuppressWarnings("unchecked") E result = (E) a[index];
        System.arraycopy(a, index + 1, a, index, --s - index);
        a[s] = null;  // Prevent memory leak
        size = s;
        modCount++;
        return result;
    }

    @Override public boolean remove(Object object) {
        Object[] a = array;
        int s = size;
        if (object != null) {
            for (int i = 0; i < s; i++) {
                if (object.equals(a[i])) {
                    System.arraycopy(a, i + 1, a, i, --s - i);
                    a[s] = null;  // Prevent memory leak
                    size = s;
                    modCount++;
                    return true;
                }
            }
        } else {
            for (int i = 0; i < s; i++) {
                if (a[i] == null) {
                    System.arraycopy(a, i + 1, a, i, --s - i);
                    a[s] = null;  // Prevent memory leak
                    size = s;
                    modCount++;
                    return true;
                }
            }
        }
        return false;
    }

    @Override protected void removeRange(int fromIndex, int toIndex) {
        if (fromIndex == toIndex) {
            return;
        }
        Object[] a = array;
        int s = size;
        if (fromIndex >= s) {
            throw new IndexOutOfBoundsException("fromIndex " + fromIndex
                    + " >= size " + size);
        }
        if (toIndex > s) {
            throw new IndexOutOfBoundsException("toIndex " + toIndex
                    + " > size " + size);
        }
        if (fromIndex > toIndex) {
            throw new IndexOutOfBoundsException("fromIndex " + fromIndex
                    + " > toIndex " + toIndex);
        }

        System.arraycopy(a, toIndex, a, fromIndex, s - toIndex);
        int rangeSize = toIndex - fromIndex;
        Arrays.fill(a, s - rangeSize, s, null);
        size = s - rangeSize;
        modCount++;
    }

remove()方法也需要和add()方法一样,在修改成功的时候,也需要将modCount值给修改。此外,removeRange(int fromIndex,int toIndex)方法将两个下标之间的元素给移除。在上面的代码中我们看到,remove掉单个元素的时候是直接将这个元素设置为null,如a[s] = null;而removeRange方法中的移除调用的是Arrays.fill(a, s - rangeSize, s, null)方法:

public static void fill(Object[] array, int start, int end, Object value) {
        Arrays.checkStartAndEnd(array.length, start, end);
        for (int i = start; i < end; i++) {
            array[i] = value;
        }
    }

因为传递的Object是null,所以也是通过循环将数组里面需要移除的元素赋值为null。

ArrayList查找

通过get()方法去拿到指定小标的元素:

    @SuppressWarnings("unchecked") @Override public E get(int index) {
        if (index >= size) {
            throwIndexOutOfBoundsException(index, size);
        }
        return (E) array[index];
    }

查询是否数组中是否包含某个元素,通过代码我们发现,ArrayList是可以保存null的值:

    @Override public boolean contains(Object object) {
        Object[] a = array;
        int s = size;
        if (object != null) {
            for (int i = 0; i < s; i++) {
                if (object.equals(a[i])) {
                    return true;
                }
            }
        } else {
            for (int i = 0; i < s; i++) {
                if (a[i] == null) {
                    return true;
                }
            }
        }
        return false;
    }

返回指定元素的下标,这里有两种方法实现:indexOf()和lastIndexOf(),同样的,我们通过这两个函数的代码发现,可以ArrayList可以保存相同的元素值。

 @Override public int indexOf(Object object) {
        Object[] a = array;
        int s = size;
        if (object != null) {
            for (int i = 0; i < s; i++) {
                if (object.equals(a[i])) {
                    return i;
                }
            }
        } else {
            for (int i = 0; i < s; i++) {
                if (a[i] == null) {
                    return i;
                }
            }
        }
        return -1;
    }

 @Override public int lastIndexOf(Object object) {
        Object[] a = array;
        if (object != null) {
            for (int i = size - 1; i >= 0; i--) {
                if (object.equals(a[i])) {
                    return i;
                }
            }
        } else {
            for (int i = size - 1; i >= 0; i--) {
                if (a[i] == null) {
                    return i;
                }
            }
        }
        return -1;
    }

除了,上面获取元素的方法,我们还可以通过ArrayList的内部类ArrayListIterator来迭代获取元素,删除元素。

ArrayListIterator

先来看看ArrayList中iterator的定义:

  @Override public Iterator<E> iterator() {
        return new ArrayListIterator();
    }

    private class ArrayListIterator implements Iterator<E> {
        /** Number of elements remaining in this iteration */
        private int remaining = size;
        /** Index of element that remove() would remove, or -1 if no such elt */
        private int removalIndex = -1;
        /** The expected modCount value */
        private int expectedModCount = modCount;
        public boolean hasNext() {
            return remaining != 0;
        }
        @SuppressWarnings("unchecked") public E next() {
            ArrayList<E> ourList = ArrayList.this;
            int rem = remaining;
            if (ourList.modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
            if (rem == 0) {
                throw new NoSuchElementException();
            }
            remaining = rem - 1;
            return (E) ourList.array[removalIndex = ourList.size - rem];
        }

        public void remove() {
            Object[] a = array;
            int removalIdx = removalIndex;
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
            if (removalIdx < 0) {
                throw new IllegalStateException();
            }
            System.arraycopy(a, removalIdx + 1, a, removalIdx, remaining);
            a[--size] = null;  // Prevent memory leak
            removalIndex = -1;
            expectedModCount = ++modCount;
        }
    }

在上面的代码我们可以看到,ArrayListIterator是继承与Iterator的,在ArrayListIterator内部定义了几个局部变量:remaining表示迭代器中元素的大小,removalIndex表示将要被删除的元素下标,expectedModCount迭代器中修改统计。
我们看到ArrayListIterator中有两个方法next()和remove():
next()方法中,先会去判断expectedModCount是否等于ArrayList中的modCount,如果不一致就会抛出ConcurrentModifcationException异常,接下来会去判断数组是否为空,如果为空将会抛出NoSuchElementException异常。如果没有抛出异常,则返回ArrayList对应下标的元素。
remove也有next()方法中的判断,如果没有异常抛出,接下来会进行数组拷贝,设置移除的元素为null,重置removalIndex变量,修改expectedModCount变量对应的值。
在这里我们看到这里expectedModCount != modCount之后会抛出ConcurrentModifcationException异常,这种并发修改异常有没有什么方法规避?

1,在使用iterator迭代的时候使用synchronized或者Lock进行同步;
2,使用CopyOnWriteArrayList代替ArrayList。
3,不要在循环中删除,添加数组元素,采用标记方式。

看完了ArrayListIterator之后,我们再来看看equels()方法:

@Override public boolean equals(Object o) {
        if (o == this) {
            return true;
        }
        if (!(o instanceof List)) {
            return false;
        }
        List<?> that = (List<?>) o;
        int s = size;
        if (that.size() != s) {
            return false;
        }
        Object[] a = array;
        if (that instanceof RandomAccess) {
            for (int i = 0; i < s; i++) {
                Object eThis = a[i];
                Object ethat = that.get(i);
                if (eThis == null ? ethat != null : !eThis.equals(ethat)) {
                    return false;
                }
            }
        } else {  // Argument list is not random access; use its iterator
            Iterator<?> it = that.iterator();
            for (int i = 0; i < s; i++) {
                Object eThis = a[i];
                Object eThat = it.next();
                if (eThis == null ? eThat != null : !eThis.equals(eThat)) {
                    return false;
                }
            }
        }
        return true;
    }

我们看到equals方法中,如果传入的是标记接口RandomAccess,将会使用ArrayList中的get()方法获取元素,如果不是RandomAccess类型将会走iterator,通过next去获取元素。这有什么区别?
首先,我们都知道foreach这个语法糖内部是iterator实现的,顺序访问的时候效率更高,而实现RandmoAccess接口的类实例,随机访问效率较高。

其它

set():

@Override public E set(int index, E object) {
        Object[] a = array;
        if (index >= size) {
            throwIndexOutOfBoundsException(index, size);
        }
        @SuppressWarnings("unchecked") E result = (E) a[index];
        a[index] = object;
        return result;
    }

hashCode():

   @Override public int hashCode() {
        Object[] a = array;
        int hashCode = 1;
        for (int i = 0, s = size; i < s; i++) {
            Object e = a[i];
            hashCode = 31 * hashCode + (e == null ? 0 : e.hashCode());
        }
        return hashCode;
    }

ensureCapacity():

    public void ensureCapacity(int minimumCapacity) {
        Object[] a = array;
        if (a.length < minimumCapacity) {
            Object[] newArray = new Object[minimumCapacity];
            System.arraycopy(a, 0, newArray, 0, size);
            array = newArray;
            modCount++;
        }
    }

ensureCapacity(int minimumCapacity),这个方法可以对ArrayList低层的数组进行扩容,如果明确知道ArrayList中的数组需要多大,ensureCapacity()会比较有优势,特别是大一些数据的时候,因为它只需要一次扩容,而默认的扩容方法会进行多次扩容操作。
clear():

 @Override public void clear() {
        if (size != 0) {
            Arrays.fill(array, 0, size, null);
            size = 0;
            modCount++;
        }
    }

在这里我们看到,clear()方法也会将所有的元素值设置为null,并且将ArrayList的size设置为0,最后把modCount的值修改。
到这里我们基本上将ArrayList的源码分析完了。

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

推荐阅读更多精彩内容

  • ArrayList 源码分析 不知道各位朋友,还记得开工前制定的学习目标么? 有没有一直为了那个目标废寝忘食呢?继...
    醒着的码者阅读 1,469评论 6 11
  • 一、基本数据类型 注释 单行注释:// 区域注释:/* */ 文档注释:/** */ 数值 对于byte类型而言...
    龙猫小爷阅读 4,261评论 0 16
  • ArrayList是在Java中最常用的集合之一,其本质上可以当做是一个可扩容的数组,可以添加重复的数据,也支持随...
    ShawnIsACoder阅读 572评论 4 7
  • 概念 ArrayList是我们常用的集合类,是基于数组实现的。不同于数组的是ArrayList可以动态扩容。 类结...
    特立独行的猪手阅读 895评论 8 33
  • 这是第二次坐火车从杨陵出发了,一个人背着黑色小包,叫个滴滴,到杨陵火车站后网络取票,等待检票,然后随着人流进站,上...
    雨中草zl阅读 114评论 0 0