序列化(Serialization)

说说你对object serialization的理解
  • object serialization 是Java的一个框架,serializing是把objects 编程成 byte streams,deserializing 是从byte streams中重构出objects
如何实现类的序列化?
  • 实现Serializable接口
Java serialization的缺陷
  • 安全性隐患,易遭受袭击:可以调用ObjectInputStream中的readObject方法来反序列化出Object graphs
  • 正确性隐患,性能隐患,可维护性隐患
// Deserialization bomb - deserializing this stream takes forever
// deserializing the set causes the hashCode method to be invoked over 2100 times
static byte[] bomb() {
    Set<Object> root = new HashSet<>();
    Set<Object> s1 = root;
    Set<Object> s2 = new HashSet<>();
    for (int i = 0; i < 100; i++) {
        Set<Object> t1 = new HashSet<>();
        Set<Object> t2 = new HashSet<>();
        t1.add("foo"); // Make t1 unequal to t2
        s1.add(t1);  s1.add(t2);
        s2.add(t1);  s2.add(t2);
        s1 = t1;
        s2 = t2;
    }
    return serialize(root); // Method omitted for brevity
}
针对Java serialization的缺陷,有什么使用建议?
  • 不要再使用Java native serialization
  • 推荐采用其他的 cross-platform structured data 代表:JSON和protobuf
JSON和protobuf的对比,各自的优缺点
  • JSON被设计用于browser-server的通信;protobuf被设计用于servers之间的structured data通信
  • JSON是由 JavaScript开发;protobuf是由C++开发
  • JSON是基于文本的,可读性好;protobuf是二进制的,性能更高
  • JSON仅仅是数据代表;protobuf提供更丰富的数据类型(Schemas)
  • protobuf也能提供文本代表,叫pbtxt
如果仅仅采用Java默认序列化方式(implements Serializable),有哪些灾难?
  • 一旦一个类released,再次更改该类的实现将很难,这还涉及到serial version UIDs的问题
  • 增加了bugs 和 security holes,因为它能够直接操作私有成员,破坏了封装性
  • 在发布新版本类时,增加了测试负担,因为要考虑到前后版本的序列化和反序列化的兼容性
当一个对象的物理描述和其逻辑内容不一致时,如果采用默认序列化方式,有哪些缺陷?
  • 永久的把导出的API和当前的内部描述拴在一起
  • 消耗过度的空间
  • 消耗过度的时间
  • 造成stack overflows
// Awful candidate for default serialized form
public final class StringList implements Serializable {
    private int size = 0;
    private Entry head = null;
    private static class Entry implements Serializable {
        String data;
        Entry  next;
        Entry  previous;
    }

    ... // Remainder omitted
}
如何自己设计一种序列化方式?
  • 当一个对象的物理描述和其逻辑内容一致时,才适合使用Java的默认序列化方式
// Good candidate for default serialized form
public class Name implements Serializable {
    /**
     * Last name. Must be non-null.
     * @serial
     */
    private final String lastName;

    /**
     * First name. Must be non-null.
     * @serial
     */
    private final String firstName;
    /**
     * Middle name, or null if there is none.
     * @serial
     */
    private final String middleName;

    ... // Remainder omitted
}
  • 尽量使对象的实例字段设为transient,除非其value代表该对象的逻辑状态,如果你使用自定义的序列化方式,尽量让每个实例字段都标为transient
  • 提供自定义的writeObject和readObject方法
// StringList with a reasonable custom serialized form
public final class StringList implements Serializable {
    // transient modifier indicates that an instance field 
    // is to be omitted from a class’s default serialized form
    private transient int size   = 0;
    private transient Entry head = null;

    // No longer Serializable!
    private static class Entry {
        String data;
        Entry  next;
        Entry  previous;
    }

    // Appends the specified string to the list
    public final void add(String s) { ... }

    /**
     * Serialize this {@code StringList} instance.
     *
     * @serialData The size of the list (the number of strings
     * it contains) is emitted ({@code int}), followed by all of
     * its elements (each a {@code String}), in the proper
     * sequence.
     */
    private void writeObject(ObjectOutputStream s)
            throws IOException {
        s.defaultWriteObject();
        s.writeInt(size);
        // Write out all elements in the proper order.
        for (Entry e = head; e != null; e = e.next)
            s.writeObject(e.data);
    }

    private void readObject(ObjectInputStream s)
            throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        int numElements = s.readInt();
        // Read in all elements and insert them in list
        for (int i = 0; i < numElements; i++)
            add((String) s.readObject());
    }

    ... // Remainder omitted
}
  • if you have a thread-safe object that achieves its thread safety by synchronizing every method and you elect to use the default serialized form, use the following write-Object method
// writeObject for synchronized class with default serialized form
private synchronized void writeObject(ObjectOutputStream s)
        throws IOException {
    s.defaultWriteObject();
}
  • declare an explicit serial version UID in every serializable class you write
private static final long serialVersionUID = randomLongValue;
写readObject方法时,应该注意什么?
  • 对于object reference fields,必须进行有防御的copy
  • Check任何不变性,如果check失败,要抛出InvalidObjectException
  • 在反序列化后,如果要验证整个 object graph,要使用ObjectInputValidation接口
  • 不要直接或间接的激活overridable methods
// readObject method with defensive copying and validity checking
private void readObject(ObjectInputStream s)
        throws IOException, ClassNotFoundException {
    s.defaultReadObject();

    // Defensively copy our mutable components
    start = new Date(start.getTime());
    end   = new Date(end.getTime());

    // Check that our invariants are satisfied
    if (start.compareTo(end) > 0)
        throw new InvalidObjectException(start +" after "+ end);
}
如果单例类implements Serializable,为了确保instance control(是真正的单例),有哪些建议?
public class Elvis {
    public static final Elvis INSTANCE = new Elvis();
    private Elvis() {  ... }

    public void leaveTheBuilding() { ... }
}
  • 提供一个readResolve method,同时确保该类的instance fields是原始类型或者带transient的引用类型
// singleton - transient object reference field
public class Elvis implements Serializable {
    public static final Elvis INSTANCE = new Elvis();
    private Elvis() { }
    private transient String[] favoriteSongs =
        { "Hound Dog", "Heartbreak Hotel" };
    public void printFavorites() {
        System.out.println(Arrays.toString(favoriteSongs));
    }

    private Object readResolve() {
        return INSTANCE;
    }
}
  • 相对于采用readResolve方法,优先使用enum types来确保单例
// Enum singleton - the preferred approach
public enum Elvis {
    INSTANCE;
    private String[] favoriteSongs =
        { "Hound Dog", "Heartbreak Hotel" };
    public void printFavorites() {
        System.out.println(Arrays.toString(favoriteSongs));
    }
}
  • 也可以采用序列化代理模式(serialization proxy pattern),考虑类Period
// Immutable class that uses defensive copying
public final class Period {
    private final Date start;
    private final Date end;
    /**
     * @param  start the beginning of the period
     * @param  end the end of the period; must not precede start
     * @throws IllegalArgumentException if start is after end
     * @throws NullPointerException if start or end is null
     */
    public Period(Date start, Date end) {
        this.start = new Date(start.getTime());
        this.end   = new Date(end.getTime());
        if (this.start.compareTo(this.end) > 0)
            throw new IllegalArgumentException(
                          start + " after " + end);
    }

    public Date start () { return new Date(start.getTime()); }

    public Date end () { return new Date(end.getTime()); }

    public String toString() { return start + " - " + end; }
 ... // Remainder omitted
}
public final class Period implements Serializable {
    private final Date start;
    private final Date end;
    /**
     * @param  start the beginning of the period
     * @param  end the end of the period; must not precede start
     * @throws IllegalArgumentException if start is after end
     * @throws NullPointerException if start or end is null
     */
    public Period(Date start, Date end) {
        this.start = new Date(start.getTime());
        this.end   = new Date(end.getTime());
        if (this.start.compareTo(this.end) > 0)
            throw new IllegalArgumentException(
                          start + " after " + end);
    }

// Serialization proxy for Period class
    private static class SerializationProxy implements Serializable {
         private final Date start;
         private final Date end;

         SerializationProxy(Period p) {
             this.start = p.start;
             this.end = p.end;
         }
         private static final long serialVersionUID = 234098243823485285L; // Any number will do (Item  87)
         // readResolve method for Period.SerializationProxy
         private Object readResolve() {
              return new Period(start, end);    // Uses public constructor
         }
    }

    // writeReplace method for the serialization proxy pattern
    private Object writeReplace() {
        return new SerializationProxy(this);
    }

    // readObject method for the serialization proxy pattern
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
        throw new InvalidObjectException("Proxy required");
    }

    public Date start () { return new Date(start.getTime()); }

    public Date end () { return new Date(end.getTime()); }

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

推荐阅读更多精彩内容

  • JAVA序列化机制的深入研究 对象序列化的最主要的用处就是在传递,和保存对象(object)的时候,保证对象的完整...
    时待吾阅读 10,834评论 0 24
  • 正如前文《Java序列化心得(一):序列化设计和默认序列化格式的问题》中所提到的,默认序列化方法存在各种各样的问题...
    登高且赋阅读 8,324评论 0 19
  • 本章关注对象序列化API,它提供了一个框架,用来将对象编码成字节流,并从字节流编码中重新构建对象。 相反的处理过程...
    Timorous阅读 246评论 0 1
  • 长城这部电影在我第一眼看的时候,就是一堆人站在长城上打小怪兽。虽然有点不符合当时宋朝的逻辑,有一点违反历史...
    冯政瑜阅读 327评论 0 0
  • 不知从那里看到过这样一句话,长相年轻,身材姣好的人都是严格自律的人,都是阳光明媚的人,都是心态平和快乐的人。我不知...
    简单而快乐hx阅读 595评论 0 0