吃透Java集合系列三:ArrayList

文章首發(fā)csdn博客地址:https://blog.csdn.net/u013277209?viewmode=contents

前言

本篇作為吃透Java集合系列第三篇笛臣,我們來(lái)看一下ArrayList个曙,通過(guò)本篇我們要明白如下問(wèn)題:
1茬底、ArrayList擴(kuò)容機(jī)制
2钠龙、ArrayList迭代器實(shí)現(xiàn)
3吹缔、fail-fast機(jī)制
4捺信、ArrayList序列化反序列化機(jī)制
5岳瞭、ArrayList clone實(shí)現(xiàn)

ArrayList內(nèi)部是使用動(dòng)態(tài)數(shù)組實(shí)現(xiàn)的狂票,換句話說(shuō)候齿,ArrayList封裝了對(duì)內(nèi)部數(shù)組的操作,比如向數(shù)組中添加闺属、刪除慌盯、插入新的元素或者數(shù)據(jù)的擴(kuò)展和重定向。

  • 繼承了AbstractList,此類提供 List 接口的骨干實(shí)現(xiàn)掂器,以最大限度地減少實(shí)現(xiàn)”隨機(jī)訪問(wèn)”數(shù)據(jù)存儲(chǔ)(如數(shù)組)支持的該接口所需的工作.對(duì)于連續(xù)的訪問(wèn)數(shù)據(jù)(如鏈表)亚皂,應(yīng)優(yōu)先使用 AbstractSequentialList,而不是此類国瓮。
  • 實(shí)現(xiàn)了List接口,意味著ArrayList元素是有序的,可以重復(fù)的,可以有null元素的集合.
  • 實(shí)現(xiàn)了RandomAccess接口標(biāo)識(shí)著其支持隨機(jī)快速訪問(wèn),實(shí)際上,我們查看RandomAccess源碼可以看到,其實(shí)里面什么都沒(méi)有定義.因?yàn)锳rrayList底層是數(shù)組,那么隨機(jī)快速訪問(wèn)是理所當(dāng)然的,訪問(wèn)速度O(1)灭必。
  • 實(shí)現(xiàn)了Cloneable接口,標(biāo)識(shí)著可以它可以被復(fù)制.注意,ArrayList里面的clone()復(fù)制其實(shí)是淺復(fù)制匠楚。
  • 實(shí)現(xiàn)了Serializable 標(biāo)識(shí)著集合可被序列化。

一:ArrayList擴(kuò)容機(jī)制

初始化:
ArrayList提供了三個(gè)構(gòu)造函數(shù)來(lái)對(duì)elementData數(shù)組初始化:
無(wú)參構(gòu)造函數(shù):初始化一個(gè)空的數(shù)組厂财,添加元素時(shí)再對(duì)數(shù)組elementData擴(kuò)容芋簿。
指定容量的構(gòu)造函數(shù):直接初始化數(shù)組為指定的大小。
帶有一個(gè)集合參數(shù)的構(gòu)造函數(shù):把指定集合中的數(shù)據(jù)通過(guò)Arrays.copyOf拷貝到elementData中璃饱,容量和指定集合容量相同与斤。

private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
//無(wú)參構(gòu)造函數(shù)直接賦值一個(gè)空的數(shù)組
 public ArrayList() {
        super();
        this.elementData = EMPTY_ELEMENTDATA;
    }
//指定大小的構(gòu)造函數(shù)
public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }
//構(gòu)造一個(gè)包含指定*集合的元素的列表。
public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }

擴(kuò)容:
添加元素時(shí)使用 ensureCapacityInternal() 方法來(lái)保證容量足夠荚恶,如果不夠時(shí)撩穿,需要使用 grow() 方法進(jìn)行擴(kuò)容,新容量的大小為 oldCapacity + (oldCapacity >> 1)谒撼,也就是舊容量的 1.5 倍食寡。

擴(kuò)容操作需要調(diào)用 Arrays.copyOf() 把原數(shù)組整個(gè)復(fù)制到新數(shù)組中,這個(gè)操作代價(jià)很高廓潜,因此最好在創(chuàng)建 ArrayList 對(duì)象時(shí)就指定大概的容量大小抵皱,減少擴(kuò)容操作的次數(shù)。

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
private void ensureCapacityInternal(int minCapacity) {
        if (elementData == EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
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;
    }

二:ArrayList迭代器實(shí)現(xiàn)

如果對(duì)Iterable和Iterator接口不是很清楚的辩蛋,請(qǐng)先移步到第一篇文章:
吃透Java集合系列一:Iterable和Iterator
ArrayList通過(guò)內(nèi)部類實(shí)現(xiàn)Iterator接口來(lái)實(shí)例化迭代器類呻畸,通過(guò)Iterator我們可以實(shí)現(xiàn)對(duì)elementData中的元素迭代遍歷。而ArrayList又實(shí)現(xiàn)了一種功能更為強(qiáng)大的ListIterator來(lái)實(shí)現(xiàn)迭代遍歷悼院。ListIterator繼承于Iterator接口伤为,對(duì)Iterator接口做了擴(kuò)展,支持向前向后遍歷据途、迭代過(guò)程中去修改集合等

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

private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            super();
            cursor = index;
        }

        public boolean hasPrevious() {
            return cursor != 0;
        }

        public int nextIndex() {
            return cursor;
        }

        public int previousIndex() {
            return cursor - 1;
        }

        @SuppressWarnings("unchecked")
        public E previous() {
            checkForComodification();
            int i = cursor - 1;
            if (i < 0)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i;
            return (E) elementData[lastRet = i];
        }

        public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.set(lastRet, e);
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;
                ArrayList.this.add(i, e);
                cursor = i + 1;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }

三: Fail-Fast機(jī)制

modCount 用來(lái)記錄 ArrayList 結(jié)構(gòu)發(fā)生變化的次數(shù)绞愚。結(jié)構(gòu)發(fā)生變化是指添加或者刪除至少一個(gè)元素的所有操作,或者是調(diào)整內(nèi)部數(shù)組的大小颖医,僅僅只是設(shè)置元素的值不算結(jié)構(gòu)發(fā)生變化位衩。

在進(jìn)行序列化或者迭代等操作時(shí),需要比較操作前后 modCount 是否改變便脊,如果改變了需要拋出 ConcurrentModificationException蚂四。如下例子

public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
        Iterator<Integer> iterator = list.listIterator();
        while (iterator.hasNext()) {
            Integer i = iterator.next();
            if (i == 1) {
                list.remove(i);
            }
        }
    }

運(yùn)行后會(huì)拋出異常:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:886)
    at java.util.ArrayList$Itr.next(ArrayList.java:836)
    at MyTest.main(MyTest.java:12)

當(dāng)我們調(diào)用list.remove的方法來(lái)刪除元素后,此時(shí)modCount會(huì)+1哪痰,導(dǎo)致modCount和迭代器里面的expectedModCount 不相等遂赠,當(dāng)遍歷下一個(gè)元素調(diào)用next方法時(shí),會(huì)先調(diào)用checkForComodification()方法晌杰,當(dāng)expectedModCount跷睦!=modCount時(shí)會(huì)拋出ConcurrentModificationException,這就是Fail-Fast機(jī)制肋演。

那我們要如何避免此問(wèn)題呢抑诸?Iterator已經(jīng)為我們提供了remove方法烂琴,所以我們只需要調(diào)用迭代器里面的remove方法就可以了,Iterator中的remove方法移除元素后會(huì)把modCount重寫賦值給expectedModCount蜕乡,下一個(gè)循環(huán)時(shí)expectedModCount與modCount相等就避免此問(wèn)題奸绷。如下例子:

public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
        Iterator<Integer> iterator = list.listIterator();
        while (iterator.hasNext()) {
            Integer i = iterator.next();
            if (i == 1) {
                iterator.remove();
            }
        }
    }

四: ArrayList序列化機(jī)制

我們看到ArrayList實(shí)現(xiàn)了Serializable接口,那么證明可以是被序列化的层玲,但是elementData數(shù)組又被transient關(guān)鍵字修飾号醉,我們知道被transient修飾的成員屬性變量不被序列化,那么我們先看一個(gè)例子辛块,ArrayList是否能被序列化成功呢畔派?

public static void main(String[] args) throws IOException, ClassNotFoundException {
        List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(list);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        List<Integer> newList = (List<Integer>) objectInputStream.readObject();
        System.out.println(Arrays.toString(newList.toArray()));
    }

運(yùn)行輸出結(jié)果:

[1, 2, 3]

結(jié)果是序列化和反序列化成功?润绵?這是為什么呢线椰?
其實(shí)細(xì)心的我們?cè)诓榭丛创a時(shí)發(fā)現(xiàn),ArrayList重寫了readObject和writeObject來(lái)自定義的序列化和反序列化策略尘盼。什么是自定義序列化和反序列化呢憨愉?

  • 在序列化過(guò)程中,如果被序列化的類中定義了writeObject 和 readObject 方法悔叽,虛擬機(jī)會(huì)試圖調(diào)用對(duì)象類里的 writeObject 和 readObject 方法莱衩,進(jìn)行用戶自定義的序列化和反序列化爵嗅。
  • 如果沒(méi)有這樣的方法娇澎,則默認(rèn)調(diào)用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。
  • 用戶自定義的 writeObject 和 readObject 方法可以允許用戶控制序列化的過(guò)程睹晒,比如可以在序列化的過(guò)程中動(dòng)態(tài)改變序列化的數(shù)值趟庄。

那么我們來(lái)看一下ArrayList源碼是怎么來(lái)自定義序列化和反序列化的:

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 void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;

        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {
            // be like clone(), allocate array based upon size not capacity
            ensureCapacityInternal(size);

            Object[] a = elementData;
            // Read in all elements in the proper order.
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
    }

可以看到通過(guò)writeObject方法和readObject方法來(lái)遍歷elementData數(shù)組把數(shù)組中的元素寫入ObjectOutputStream ,ObjectInputStream 中的伪很。那么為什么ArrayList要用這種方式來(lái)實(shí)現(xiàn)序列化呢戚啥?

ArrayList實(shí)際上是動(dòng)態(tài)數(shù)組,每次在放滿以后自動(dòng)增長(zhǎng)設(shè)定的長(zhǎng)度值锉试,如果數(shù)組自動(dòng)增長(zhǎng)長(zhǎng)度設(shè)為100猫十,而實(shí)際只放了一個(gè)元素,那就會(huì)序列化99個(gè)null元素呆盖。為了保證在序列化的時(shí)候不會(huì)將這么多null同時(shí)進(jìn)行序列化拖云,ArrayList把元素?cái)?shù)組設(shè)置為transient。

五: ArrayList clone機(jī)制

ArrayList的clone實(shí)現(xiàn)应又,其實(shí)是通過(guò)數(shù)組元素拷貝來(lái)實(shí)現(xiàn)的淺拷貝宙项,很簡(jiǎn)單,我們看一下源碼就行了:

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);
        }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末株扛,一起剝皮案震驚了整個(gè)濱河市尤筐,隨后出現(xiàn)的幾起案子汇荐,更是在濱河造成了極大的恐慌,老刑警劉巖盆繁,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掀淘,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡油昂,警方通過(guò)查閱死者的電腦和手機(jī)繁疤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)秕狰,“玉大人稠腊,你說(shuō)我怎么就攤上這事∶В” “怎么了架忌?”我有些...
    開(kāi)封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)我衬。 經(jīng)常有香客問(wèn)我叹放,道長(zhǎng),這世上最難降的妖魔是什么挠羔? 我笑而不...
    開(kāi)封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任井仰,我火速辦了婚禮,結(jié)果婚禮上破加,老公的妹妹穿的比我還像新娘俱恶。我一直安慰自己,他們只是感情好范舀,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布合是。 她就那樣靜靜地躺著,像睡著了一般锭环。 火紅的嫁衣襯著肌膚如雪聪全。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天辅辩,我揣著相機(jī)與錄音难礼,去河邊找鬼。 笑死玫锋,一個(gè)胖子當(dāng)著我的面吹牛蛾茉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播景醇,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼臀稚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了三痰?” 一聲冷哼從身側(cè)響起吧寺,我...
    開(kāi)封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤窜管,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后稚机,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體幕帆,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年赖条,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了失乾。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡纬乍,死狀恐怖碱茁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情仿贬,我是刑警寧澤纽竣,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站茧泪,受9級(jí)特大地震影響蜓氨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜队伟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一穴吹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嗜侮,春花似錦港令、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至宜猜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間硝逢,已是汗流浹背姨拥。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留渠鸽,地道東北人叫乌。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像徽缚,于是被迫代替她去往敵國(guó)和親憨奸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 遙望青春時(shí)光凿试,何其溫暖排宰,一縷幽香 滿腔熱血似芝,幾許拼博努力,無(wú)限能量 驕陽(yáng)板甘,汗水党瓮,共赴歡笑與希望 一點(diǎn)淚水,幾道憂傷...
    不完美的低調(diào)閱讀 178評(píng)論 2 2
  • 暖清陽(yáng)閱讀 493評(píng)論 4 14
  • 七絕*思母 [詩(shī)前白話] 母親老大夫人盐类,諱雪花寞奸;她的名字同雪花一樣美麗而高雅。 我的母親離開(kāi)家已26年了在跳!每當(dāng)下雪...
    白丙之閱讀 383評(píng)論 6 15
  • 又是周五枪萄,又一次候場(chǎng)。你在教室里辛苦地作答猫妙,我在門口的走廊里繼續(xù)流浪呻引。說(shuō)不出今天為什么有點(diǎn)心緒不寧。想起上周五吐咳,同...
    隨遇而安SZ閱讀 336評(píng)論 5 2