序列化

一、序列化的用途

序列化的主要用途有两个,一个是对象持久化,另一个是跨网络的数据交换、远程过程调用。

二、各种序列化机制比较

(跨语言、序列化大小、性能)
1、(000)Java标准的序列化机制有很多优点,使用简单,可自动处理对象引用和循环引用,也可以方便的进行定制,处理版本问题等,但它也有一些重要的局限性:
  Java序列化格式是一种私有格式,是一种Java语言特有的技术,不能被其他语言识别,不能实现跨语言的数据交换。
  Java在序列化字节中保存了很多描述信息,使得序列化格式比较大。
  Java的默认序列化使用反射分析遍历对象结构,性能比较低。
  Java的序列化格式是二进制的,不方便查看和修改。
2、(100)由于这些局限性,实践中往往会使用一些替代方案。在跨语言的数据交换格式中,XML/JSON是被广泛采用的文本格式,各种语言都有对它们的支持,文件格式清晰易读,有很多查看和编辑工具,它们的不足之处是性能和序列化大小。
3、(111)在性能和大小敏感的领域,往往会采用更为精简高效的二进制方式如ProtoBuf, Thrift, MessagePack等

三、Java序列化

Java中有两个接口用于序列化:java.io.Serializable(默认序列化)、java.io.Externalizable(自定义序列化)

(一)默认序列化

1、实现java.io.Serializable即可被序列化,Serializable没有定义任何方法,只是一个标记接口。
默认序列化能hold住多个对象引用同一对象、对象间循环引用等情况,重复序列化同一个对象时只在第一次把对象转换成字节序列,以后只输出前面的序列化编号
静态属性不会被序列化,因为本身就在类中;方法也不会被序列化

2、默认的序列化机制已经很强大了,它可以自动将对象中的所有字段自动保存和恢复,但这种默认行为有时候不是我们想要的。
对于有些字段,它的值可能与内存位置有关,比如默认的hashCode()方法的返回值,当恢复对象后,内存位置肯定变了,基于原内存位置的值也就没有了意义。还有一些字段,可能与当前时间有关,比如表示对象创建时的时间,保存和恢复这个字段就是不正确的。

如果类中的字段表示的是类的实现细节,而非逻辑信息,那默认序列化也是不适合的。因为序列化格式表示一种契约,应该描述类的逻辑结构,而非与实现细节相绑定,绑定实现细节将使得难以修改,破坏封装。如LinkedList,它的默认序列化就是不适合的,因为LinkedList表示一个List,它的逻辑信息是列表的长度,以及列表中的每个对象,但LinkedList类中的字段表示的是链表的实现细节, 如头尾节点指针,对每个节点,还有前驱和后继节点指针等。

(二)定制序列化

1、对于java.io.Serializable,可以通过transient、writeObject/writeObject、readResolve/writeReplace实现定制序列化

  1. 可以用 transient 修饰不想被Java自动序列化的属性,再用Java默认的序列化。(transient可且只可用于修饰不想被Java自动序列化的属性)
  2. 或者通过实现方法 void writeObject(java.io.ObjectOutputStream out)、void readObject(java.io.ObjectInputStream in) 来实现自定义的序列化逻辑
  3.  实现 Object writeReplace() 替换。如果定义了该方法,在序列化时,会先调用该方法,该方法的返回值才会被当做真正的对象进行序列化;实现 Object readResolve() 还原(自实现的枚举类、 单例中)。如果定义了该方法,在反序列化之后,会额外调用该方法,该方法的返回值才会被当做真正的反序列化的结果。这个方法通常用于反序列化单例对象的场景。(即序列化时的执行顺序是: writeReplace、writeObject、readObject、readResolve)

2、实现java.io.Externalizable接口,并实现两个方法:  void readExternal(ObjectInput in)、void writeExternal(ObjectOutput out) 。

  • Externalizable继承于Serializable,当使用该接口时,之前基于Serializable接口的序列化机制就将失效,序列化的细节需要由程序员去完成。基于此,可以减少序列化后文件的大小。
  • 实现Externalizable接口的类必须要提供一个无参的构造器,且它的访问权限为public。因为使用Externalizable进行序列化,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。
  • 默认的序列化机制由于需要分析对象结构,往往比较慢,通过实现Externalizable接口,可以提高性能

示例:如下代码中,实现Serializable比实现Externalizable时的序列化文件大,且前者中有out.defaultWriteObject();/in.defaultReadObject();比没有时序列化后文件大。但它们功能都一样。

 1 //public class Fuck implements Serializable {
 2 public class Fuck implements Serializable {
 3     /**
 4      * 
 5      */
 6     private static final long serialVersionUID = -3466537175580251255L;
 7     public byte age;
 8     public String name;
 9 
10     public Fuck() {
11 
12     }
13 
14     public Fuck(byte age, String name) {
15         this.age = age;
16         this.name = name;
17     }
18 
19     @Override
20     public String toString() {
21         return "[" + age + "," + name + "]";
22     }
23 
24     public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
25         String fileName = "tmp0.out";
26         ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(fileName));
27         objectOutputStream.writeObject(new Fuck((byte) 1, "小华"));
28         objectOutputStream.flush();
29         objectOutputStream.close();
30 
31         ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(fileName));
32         Fuck fuck = (Fuck) objectInputStream.readObject();
33         System.out.println(fuck);
34         objectInputStream.close();
35     }
36 
37     private void writeObject(java.io.ObjectOutputStream out) {
38         try {
39             // out.defaultWriteObject();
40             out.writeByte(age);
41             out.writeObject(name);
42         } catch (Exception e) {
43             e.printStackTrace();
44         }
45     }
46 
47     private void readObject(java.io.ObjectInputStream in) {
48         try {
49             // in.defaultReadObject();
50             this.age = in.readByte();
51             this.name = (String) in.readObject();
52         } catch (Exception e) {
53             e.printStackTrace();
54         }
55     }
56 
57     public void writeExternal(ObjectOutput out) throws IOException {
58         out.writeByte(age);
59         out.writeObject(name);
60     }
61 
62     public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
63         this.age = in.readByte();
64         this.name = (String) in.readObject();
65     }
66 }
View Code

(三)示例

  1 public class _10_IO_SerialTest {
  2 
  3     public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
  4         // TODO Auto-generated method stub
  5         // BasicTest
  6         // BasicTest();
  7 
  8         // customSerialTest
  9         // customSerialTestOne();
 10         // customSerialTestTwo();
 11         // customSerialTestThree();
 12         customSerialTestFour();
 13     }
 14 
 15     // 基本测试
 16     private static void BasicTest() throws FileNotFoundException, IOException, ClassNotFoundException {
 17         // 系统默认序列化,默认writeObject、readObject
 18         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
 19         ArrayList<Person> lst = new ArrayList<Person>();
 20         {
 21             lst.add(new Person("小张", 22));
 22             lst.add(new Person("小李", 23));
 23             // 以下两次输出同一个对象lst
 24             oos.writeObject(lst);
 25             lst.get(0).setAge(11);
 26             lst.remove(1);
 27             oos.writeObject(lst);
 28 
 29             oos.writeObject(new Person("小王", 24));
 30         }
 31 
 32         // 反序列化
 33         {
 34             ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
 35             lst = (ArrayList<Person>) ois.readObject();
 36             System.out.println(lst.size());// 2
 37             lst = (ArrayList<Person>) ois.readObject();
 38             System.out.println(lst.size());// 2
 39             System.out.println(lst.get(0).getAge());// 22
 40             // 从上面两次输出反应的都是lst改变前的状态,说明重复序列化同一个对象时只在第一次把对象转换成字节序列。
 41 
 42             Person p = (Person) ois.readObject();
 43             System.out.printf("%s %s %s %s\n", p.getAge(), p.getName(), p.getHome(), p.getNumber());// transient修饰的home字段为null说明没有被序列化
 44         }
 45     }
 46 
 47     // 自定义序列化测试1
 48     private static void customSerialTestOne() throws FileNotFoundException, IOException, ClassNotFoundException {
 49         // -----自定义序列化:写入、读出
 50         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
 51         ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
 52         //
 53         PersonTypeOne ptt = new PersonTypeOne("小虎", 11);
 54         oos.writeObject(ptt);
 55         //
 56         ptt = (PersonTypeOne) ois.readObject();
 57         System.out.println(ptt.getAge() + " " + ptt.getName());
 58     }
 59 
 60     // 自定义序列化测试2
 61     private static void customSerialTestTwo() throws FileNotFoundException, IOException, ClassNotFoundException {
 62         // -----自定义序列化:对象替换writeReplace
 63         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
 64         ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
 65 
 66         // 写,调用PersonTypeTwo的writeReplace方法,对象替换为String
 67         PersonTypeTwo ptt = new PersonTypeTwo("小虎", 11);
 68         oos.writeObject(ptt);
 69 
 70         // 读,已是ArrayList类型
 71         ArrayList<Object> pttList = (ArrayList<Object>) ois.readObject();
 72         System.out.println(pttList.get(0) + " " + pttList.get(1));
 73     }
 74 
 75     // 自定义序列化测试3
 76     @SuppressWarnings("unused")
 77     private static void customSerialTestThree() throws FileNotFoundException, IOException, ClassNotFoundException {
 78         // -----自定义序列化:自定义enum的问题的解决readResolve
 79         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
 80         ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
 81 
 82         // 没有readResolve
 83         oos.writeObject(Season_0.SPRING);
 84         System.out.println(((Season_0) ois.readObject()) == Season_0.SPRING);// false,可见反序列化后得到的并不是我们希望的枚举常量SPRING
 85         // 有readResolve
 86         oos.writeObject(Season_1.SPRING);
 87         System.out.println(((Season_1) ois.readObject()) == Season_1.SPRING);// true,通过readResolve解决上述问题
 88     }
 89 
 90     // 自定义序列化测试4
 91     private static void customSerialTestFour() throws FileNotFoundException, IOException, ClassNotFoundException {
 92         // 通过实现Externalizable接口实现的序列化,必须实现两个抽象方法,其他的用法与Serializable一样。
 93         // 使用很少,一般通过实现Serializable接口实现序列化
 94         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
 95         ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
 96         //
 97         PersonTypeThree ptt = new PersonTypeThree("xiaozhou", 30);
 98         oos.writeObject(ptt);
 99 
100         //
101         ptt = (PersonTypeThree) ois.readObject();
102         System.out.println(ptt.getAge() + " " + ptt.getName());
103     }
104 }
105 
106 class Person implements java.io.Serializable {
107 
108     /**
109      * 默认序列化,一个个字段输出,读入时再按同样顺序一个个读入
110      */
111     private static final long serialVersionUID = -2032185216332524429L;
112     private String name;
113     private int age;
114     transient private String home = "北京";
115     private static int number = 3;// 静态属性不会被序列化,因为本身就在类中
116 
117     public Person(String name, int age) {
118         this.age = age;
119         this.name = name;
120     }
121 
122     public String getHome() {
123         return home;
124     }
125 
126     public void setHome(String home) {
127         this.home = home;
128     }
129 
130     public String toString() {
131         return name + " " + age;
132     }
133 
134     public String getName() {
135         return name;
136     }
137 
138     public void setName(String name) {
139         this.name = name;
140     }
141 
142     public int getAge() {
143         return age;
144     }
145 
146     public void setAge(int age) {
147         this.age = age;
148     }
149 
150     public static int getNumber() {
151         return number;
152     }
153 
154     public static void setNumber(int number) {
155         Person.number = number;
156     }
157 }
158 
159 class PersonTypeOne implements java.io.Serializable {
160     /**
161      * 自定义序列化,可以自己确定什么顺序输出哪些东西,反序列化时再同序读入,否则出错<br/>
162      * private void writeObject(java.io.ObjectOutputStream out)<br>
163      * private void readObject(java.io.ObjectInputStream in)<br>
164      * private void readObjectNoData()<br>
165      */
166     private static final long serialVersionUID = 5990380364728171582L;
167     private String name;
168     private int age;
169 
170     private void writeObject(java.io.ObjectOutputStream out) throws IOException {
171         out.writeInt(age);
172         out.writeObject(new StringBuffer(name).reverse());// 反转name
173     }
174 
175     private void readObject(java.io.ObjectInputStream in) throws ClassNotFoundException, IOException {
176         // 所读取的内容的顺序须与写的一样,否则出错
177         this.age = in.readInt();
178         this.name = ((StringBuffer) in.readObject()).reverse().toString();
179     }
180 
181     public PersonTypeOne(String name, int age) {
182         this.name = name;
183         this.age = age;
184     }
185 
186     public String getName() {
187         return name;
188     }
189 
190     public void setName(String name) {
191         this.name = name;
192     }
193 
194     public int getAge() {
195         return age;
196     }
197 
198     public void setAge(int age) {
199         this.age = age;
200     }
201 
202 }
203 
204 class PersonTypeTwo implements java.io.Serializable {
205     /**
206      * 自定义序列化,写入时对象替换。系统在序列化某个对象前会自动先后调用writeReplace、writeObject方法<br>
207      * ANY-ACCESS-MODIFIER Object writeReplace()
208      */
209     private static final long serialVersionUID = -7958822199382954305L;
210     private String name;
211     private int age;
212 
213     // private void writeReplace() {
214     // // java序列化机制在序列化对象前总先调用该对象的writeReplace方法,若方法返回另一java对象则转为序列化该对象否则序列化原对象
215     // }
216 
217     private Object writeReplace() {
218         // java序列化机制在序列化对象前总先调用该对象的writeReplace方法,若方法返回另一java对象则转为序列化该对象否则序列化原对象
219         ArrayList<Object> al = new ArrayList<Object>();
220         al.add(age);
221         al.add(name);
222         return al;
223     }
224 
225     public PersonTypeTwo(String name, int age) {
226         this.name = name;
227         this.age = age;
228     }
229 
230     public String getName() {
231         return name;
232     }
233 
234     public void setName(String name) {
235         this.name = name;
236     }
237 
238     public int getAge() {
239         return age;
240     }
241 
242     public void setAge(int age) {
243         this.age = age;
244     }
245 }
246 
247 class Season_0 implements java.io.Serializable {
248     // readResolve() test
249     private int id;
250 
251     private Season_0(int id) {
252         this.id = id;
253     }
254 
255     public static final Season_0 SPRING = new Season_0(1);
256     public static final Season_0 SUMMER = new Season_0(2);
257     public static final Season_0 FALL = new Season_0(3);
258     public static final Season_0 WINTER = new Season_0(4);
259 }
260 
261 class Season_1 implements java.io.Serializable {
262     // readResolve() test
263     /**
264      * 与上面的Season_0相比,多了readResolve方法。<br>
265      * 反序列化机制会在调用readObject后调用readResolve方法, 与writeReplace相对应<br>
266      * ANY-ACCESS-MODIFIER Object readResolve()
267      */
268     private int id;
269 
270     private Season_1(int id) {
271         this.id = id;
272     }
273 
274     public static final Season_1 SPRING = new Season_1(1);
275     public static final Season_1 SUMMER = new Season_1(2);
276     public static final Season_1 FALL = new Season_1(3);
277     public static final Season_1 WINTER = new Season_1(4);
278 
279     private Object readResolve() {
280         // 系统会在调用readObject后调用此方法
281         switch (id) {
282         case 1:
283             return SPRING;
284         case 2:
285             return SUMMER;
286         case 3:
287             return FALL;
288         case 4:
289             return WINTER;
290         default:
291             return null;
292         }
293     }
294 }
295 
296 class PersonTypeThree implements java.io.Externalizable {
297     private String name;
298     private int age;
299 
300     public PersonTypeThree() {
301         // 继承Externalizable实现的序列化必须要有此构造方法
302     }
303 
304     public PersonTypeThree(String name, int age) {
305         this.name = name;
306         this.age = age;
307     }
308 
309     public String getName() {
310         return name;
311     }
312 
313     public void setName(String name) {
314         this.name = name;
315     }
316 
317     public int getAge() {
318         return age;
319     }
320 
321     public void setAge(int age) {
322         this.age = age;
323     }
324 
325     // 通过实现Externalizable接口实现的序列化必须实现如下两个方法,其他的用法与Serializable一样。
326     @Override
327     public void writeExternal(ObjectOutput out) throws IOException {
328         // TODO Auto-generated method stub
329         out.writeObject(new StringBuffer(name).reverse());
330         out.writeInt(age);
331         System.out.println("writeExternal run");
332     }
333 
334     @Override
335     public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
336         // TODO Auto-generated method stub
337         this.name = ((StringBuffer) in.readObject()).reverse().toString();
338         this.age = in.readInt();
339         System.out.println("readExternal run");
340     };
View Code

其他:

子类序列化时:

如果父类实现了Serializable接口,则父类和子类都可以序列化。

如果父类没有实现Serializable接口:若也没有提供默认构造函数,那么子类的序列化会出错;若提供了默认的构造函数,那么子类可以序列化,父类的成员变量不会被序列化。

    

 四、参考资料

1、http://mp.weixin.qq.com/s/y3b3prB54Mj1JN2CEURyrg-码农翻身

2、《疯狂Java讲义》

 

posted @ 2017-04-19 17:10  March On  阅读(441)  评论(0编辑  收藏  举报
top last
Welcome user from
(since 2020.6.1)