Java ArrayList源码学习

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

一 自动扩容机制

Resizable-array implementation of the List interface.
capacity: 存储list元素的底层数组的大小;
list size:存储的list元素的个数;

  1. 初始化
    不指明容量时,底层数组默认是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,即{}
    指明容量为0时,底层数组为EMPTY_ELEMENTDATA,即{}
    指明容量>0时, this.elementData = new Object[initialCapacity];

    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);
           }
       }
     // Constructs an empty list with an initial capacity of ten.
    public ArrayList() { 
         this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
  2. add

        public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // 确保底层数组的容量
            elementData[size++] = e;
            return true;
        }
    

    先计算需要的容量,然后进行分配;

        private void ensureCapacityInternal(int minCapacity) {
            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
        }
    

    计算所需的最小容量
    如果开始没有指定容量大小,最低容量设为Math.max(10, minCapacity);
    为什么没有指定容量时,不直接设为10呢?反序列化时也会调用,minCapacity可能>10;
    如果开始制定了容量为0,最低容量是0;

    private static int calculateCapacity(Object[] elementData, int minCapacity) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            }
            return minCapacity;
    }
    

    底层数组容量是否改变,均执行modCount++; 表示list结构性变化;

    private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
    
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
    

    实际的数组扩增函数:1.5倍扩增

        private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
                                       //实际上小于MAX_ARRAY_SIZE,就报错OutOfMemoryError
        /**
         * Increases the capacity to ensure that it can hold at least the
         * number of elements specified by the minimum capacity argument.
         *
         * @param minCapacity the desired minimum capacity
         */
        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);
        }
    

    ArrayList<Integer> list =new ArrayList<Integer>();
    list.add(1); 默认数组分配容量为10

  3. remove
    remove函数并不会调节底层数组的大小;

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

    clear函数也并不会调节底层数组的大小;

        /**
         * Removes all of the elements from this list.  The list will
         * be empty after this call returns.
         */
        public void clear() {
            modCount++;
    
            // clear to let GC do its work
            for (int i = 0; i < size; i++)
                elementData[i] = null;
    
            size = 0;
        }
    

    真正减少底层数组容量的函数调用 trimToSize()

        /**
         * Trims the capacity of this <tt>ArrayList</tt> instance to be the
         * list's current size.  An application can use this operation to minimize
         * the storage of an <tt>ArrayList</tt> instance.
         */
        public void trimToSize() {
            modCount++;
            if (size < elementData.length) {
                elementData = (size == 0)
                  ? EMPTY_ELEMENTDATA
                  : Arrays.copyOf(elementData, size);
            }
        }
    

二: 线程不安全

This class is roughly equivalent to Vector, except that it is unsynchronized.
推荐方式:List list = Collections.synchronizedList(new ArrayList(...));


三: Fail-Fast 机制,ConcurrentModificationException

The iterators returned by this class's iterator methods are fail-fast:
if the list is structurally modified at any time after the iterator is created, in any way except through the iterator's own ListIterator.remove() methods, the iterator will throw a ConcurrentModificationException

  1. A structural modification is any operation that adds or deletes one or more elements, or explicitly resizes the backing array; merely setting the value of an element is not a structural modification. (内部int值modCount负责记录更改次数)

  2. 一旦创建了Iterator后,Iterator会记录当前list的modCount,并保存为expectedModCount;
    此后外部list发生结构性更改,外部modCount更改,而内部expectedModCount不变,再次调用Iterator函数时无法通过检查,报错;

    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();
                }
            }
    
            final void checkForComodification() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
            }
        }
    

    Iterator.remove是删除前一次返回的值;
    Iterator不能连续remove的原因,remove后 lastRet = -1;

  3. 解决方式:
    3.1)Iterator创建之后,不能用ArrayList增删元素。 只能用Iterator删除元素;
    因为Iterator.remove会同步更新list的modCount以及expectedModCount;
    subList 和Iterator 一样,创建后list就不能结构性修改了;list的增删不要Iterator的操作混在一起
    3.2)java1.8后,貌似removeIf函数式编程也可以避免,待研究;
    注意
    ArrayList线程不安全,改为Vector或者Collections.synchronizedList(list);并不能避免ConcurrentModificationException。
    因为线程安全是多线程排队访问list,不同时访问。仍然允许一个创建了Iterator,另一个修改,只要不是同时就没问题。


四: 总结

  1. ArrayList默认扩增方式为1.5倍;
  2. ArrayList随机访问get,set是0(1)时间开销,add有可能扩容,移动数组元素0(n)时间开销;删除移动数组元素0(n)时间开销;Iterator.remove移动数组元素0(n)时间开销;
    数组优势,数组劣势
  3. list转array
      List<String> v = new ArrayList<>();
      return v.toArray(new String[v.size()]);
    

@梦工厂 2018.3.6

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

推荐阅读更多精彩内容