ArrayList中elementData為什么被transient修飾

在閱讀ArrayList源碼時(shí)帖渠,發(fā)現(xiàn)保存元素的數(shù)組 elementData 使用 transient 修飾谒亦,該關(guān)鍵字聲明數(shù)組默認(rèn)不會(huì)被序列化。

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    // Android-note: Also accessed from java.util.Collections
    transient Object[] elementData; // non-private to simplify nested class access

那么在序列化后空郊,ArrayList里面的元素?cái)?shù)組保存的數(shù)據(jù)不就完全丟失了嗎份招?
深入研究代碼后發(fā)現(xiàn)事實(shí)上,并不會(huì)狞甚,ArrayList提供了兩個(gè)用于序列化和反序列化的方法锁摔,
readObject和writeObject

    /**
     * Save the state of the <tt>ArrayList</tt> instance to a stream (that
     * is, serialize it).
     *
     * @serialData The length of the array backing the <tt>ArrayList</tt>
     *             instance is emitted (int), followed by all of its elements
     *             (each an <tt>Object</tt>) in the proper order.
     */
    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();
        }
    }

    /**
     * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
     * deserialize it).
     */
    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();
            }
        }
    }

ArrayList在序列化的時(shí)候會(huì)調(diào)用writeObject,直接將size和element寫入ObjectOutputStream哼审;
反序列化時(shí)調(diào)用readObject谐腰,從ObjectInputStream獲取size和element,再恢復(fù)到elementData涩盾。

為什么不直接用elementData來(lái)序列化十气,而采用上面的方式來(lái)實(shí)現(xiàn)序列化呢?

原因在于elementData是一個(gè)緩存數(shù)組旁赊,默認(rèn)size為10,對(duì)ArrayList進(jìn)行add操作當(dāng)空間不足時(shí)桦踊,
會(huì)對(duì)ArrayList進(jìn)行擴(kuò)容。通常擴(kuò)容的倍數(shù)為1.5倍终畅。

    /**
     * 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);
    }

所以elementData數(shù)組會(huì)預(yù)留一些容量籍胯,等容量不足時(shí)再擴(kuò)充容量,那么有些空間可能就沒(méi)有實(shí)際存儲(chǔ)元素离福,采用上面的方式來(lái)實(shí)現(xiàn)序列化時(shí)杖狼,就可以保證只序列化實(shí)際存儲(chǔ)的那些元素,而不是整個(gè)數(shù)組妖爷,從而節(jié)省空間和時(shí)間蝶涩。

序列化的時(shí)候是怎么調(diào)用writeObject和readObject的

奇怪了?盡管writeObject和readObject被外部類調(diào)用但事實(shí)上這是兩個(gè)private的方法絮识。并且它們既不存在于java.lang.Object绿聘,也沒(méi)有在Serializable中聲明。那么ObjectOutputStream如何使用它們的呢次舌?

序列化時(shí)需要使用 ObjectOutputStream 的 writeObject() 將對(duì)象轉(zhuǎn)換為字節(jié)流并輸出熄攘。而 writeObject() 方法在傳入的對(duì)象存在 writeObject() 的時(shí)候會(huì)去反射調(diào)用該對(duì)象的 writeObject() 來(lái)實(shí)現(xiàn)序列化。反序列化使用的是 ObjectInputStream 的 readObject() 方法彼念,原理類似挪圾。

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(list);

我們以O(shè)bjectInputStream為例浅萧,大體梳理一下調(diào)用流程,感興趣的同學(xué)可以跟著讀一下源碼

首先哲思,反序列化時(shí)會(huì)調(diào)用readObject() -> Object obj = readObject0(false) -> readObject0 -> return checkResolve(readOrdinaryObject(unshared)) -> readOrdinaryObject -> readSerialData(obj, desc);

然后readSerialData會(huì)調(diào)用slotDesc.invokeReadObject(obj, this)
這里調(diào)用ObjectStreamClass的invokeReadObject(Object obj, ObjectInputStream in)
里面的readObjectMethod.invoke(obj, new Object[]{ in });

這顯然是一個(gè)通過(guò)反射進(jìn)行的方法調(diào)用洼畅,那么readObjectMethod是什么方法?
readObjectMethod = getPrivateMethod(cl, "readObject",new Class<?>[] { ObjectInputStream.class },Void.TYPE); writeObjectMethod = getPrivateMethod(cl, "writeObject",new Class<?>[] { ObjectOutputStream.class },Void.TYPE);
可以看到writeObjectMethod也在這里
getPrivateMethod方法如下:

    /**
     * Returns non-static private method with given signature defined by given
     * class, or null if none found.  Access checks are disabled on the
     * returned method (if any).
     */
    private static Method getPrivateMethod(Class<?> cl, String name,
                                           Class<?>[] argTypes,
                                           Class<?> returnType)
    {
        try {
            Method meth = cl.getDeclaredMethod(name, argTypes);
            meth.setAccessible(true);
            int mods = meth.getModifiers();
            return ((meth.getReturnType() == returnType) &&
                    ((mods & Modifier.STATIC) == 0) &&
                    ((mods & Modifier.PRIVATE) != 0)) ? meth : null;
        } catch (NoSuchMethodException ex) {
            return null;
        }
    }

到這里我們就大概上明白了,ObjectInputStream會(huì)通過(guò)反射的形式棚赔,調(diào)用private的readObject方法帝簇。
實(shí)現(xiàn)反序列化。

其實(shí)在java集合框架中忆嗜,還有很多中集合都采用了這種方式己儒,修飾數(shù)據(jù)集合數(shù)組,比如
CopyOnWriteArrayList
private transient volatile Object[] elements;
HashMap
transient Node<K,V>[] table;
HashSet
private transient HashMap<E,Object> map;

究其原因捆毫,都是為了保證只序列化實(shí)際存儲(chǔ)的那些元素闪湾,而不是整個(gè)數(shù)組,從而節(jié)省空間和時(shí)間绩卤。

參考文章:

JAVA對(duì)象流序列化時(shí)的readObject途样,writeObject,readResolve是怎么被調(diào)用的

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末濒憋,一起剝皮案震驚了整個(gè)濱河市何暇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌凛驮,老刑警劉巖裆站,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異黔夭,居然都是意外死亡宏胯,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門本姥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)肩袍,“玉大人,你說(shuō)我怎么就攤上這事婚惫》沾停” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵先舷,是天一觀的道長(zhǎng)艰管。 經(jīng)常有香客問(wèn)我,道長(zhǎng)蒋川,這世上最難降的妖魔是什么蛙婴? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮尔破,結(jié)果婚禮上街图,老公的妹妹穿的比我還像新娘。我一直安慰自己懒构,他們只是感情好餐济,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著胆剧,像睡著了一般絮姆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上秩霍,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天篙悯,我揣著相機(jī)與錄音,去河邊找鬼铃绒。 笑死鸽照,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的颠悬。 我是一名探鬼主播矮燎,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼赔癌!你這毒婦竟也來(lái)了诞外?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤灾票,失蹤者是張志新(化名)和其女友劉穎峡谊,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刊苍,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡既们,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了班缰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贤壁。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖埠忘,靈堂內(nèi)的尸體忽然破棺而出脾拆,到底是詐尸還是另有隱情,我是刑警寧澤莹妒,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布名船,位于F島的核電站,受9級(jí)特大地震影響旨怠,放射性物質(zhì)發(fā)生泄漏渠驼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一鉴腻、第九天 我趴在偏房一處隱蔽的房頂上張望迷扇。 院中可真熱鬧百揭,春花似錦、人聲如沸蜓席。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)厨内。三九已至祈秕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間雏胃,已是汗流浹背请毛。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瞭亮,地道東北人方仿。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像街州,于是被迫代替她去往敵國(guó)和親兼丰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

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