序列化
一、序列化的用途
序列化的主要用途有两个,一个是对象持久化,另一个是跨网络的数据交换、远程过程调用。
二、各种序列化机制比较
(跨语言、序列化大小、性能)
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实现定制序列化
- 可以用 transient 修饰不想被Java自动序列化的属性,再用Java默认的序列化。(transient可且只可用于修饰不想被Java自动序列化的属性)
- 或者通过实现方法 void writeObject(java.io.ObjectOutputStream out)、void readObject(java.io.ObjectInputStream in) 来实现自定义的序列化逻辑
- 实现 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 }
(三)示例
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 };
其他:
子类序列化时:
如果父类实现了Serializable接口,则父类和子类都可以序列化。
如果父类没有实现Serializable接口:若也没有提供默认构造函数,那么子类的序列化会出错;若提供了默认的构造函数,那么子类可以序列化,父类的成员变量不会被序列化。
四、参考资料
1、http://mp.weixin.qq.com/s/y3b3prB54Mj1JN2CEURyrg-码农翻身
2、《疯狂Java讲义》