Java对象序列化(pass)

转载自:http://www.cnblogs.com/xrq730/p/4821958.html

IO操作或网络通信无法直接处理Java对象,必须将对象以某种方式表示出来,才能被IO或网络通信识别。

序列化:将一个对象转成二进制表示的字节数组,通过保存或转移这些字节数组来持久化。

反序列化:将二进制数组转化为对象。

Java序列化之需要实现java.io.Serializable接口就可以了。

默认序列化

序列化的时候又一个serialVersionUID参数,Java序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化的时候,Java虚拟机毁把传过来的字节流中的serialVersionUID和本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的实体类,可以进行反序列化,否则Java虚拟机毁拒绝对这个实例类进行反序列化病抛出异常。

生成serialVersionUID有两种方式:

1,默认的1L

2,根据类名/接口名/成员方法以及属性等来生成一个64位的Hash字段

如果实现了Serializable接口的实体类没有显式定义一个名为serialVersonUID,类型为long的变量时,Java序列化机制会根据编译的.class文件自动生成一个serialVersionUID, 如果.class没有变化,那么就算编译两次,serialVersionUID也不会变化。Java为用户定义了默认的序列化/反序列化方法,其实就是ObjectInputStream的defaultWriteObject() 和 defaultReadObject方法。

示例1 :

package com.basic;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;


public class SerializableTest implements Serializable{

private static final long serialVersionUID = -4573981250507373015L;
private String str0; private transient String str1; private static String str2 = "aaa"; public SerializableTest(String str0, String str1) { this.str0 = str0; this.str1 = str1; } public String getStr0() { return this.str0; } public String getStr1() { return this.str1; } public static void main(String[] args) throws Exception { // TODO Auto-generated method stub File file = new File("C:" + File.separator + "tool" + File.separator + "SerialTest.txt"); OutputStream os = new FileOutputStream(file); ObjectOutputStream oos = new ObjectOutputStream(os); oos.writeObject(new SerializableTest("str0", "str1")); oos.close(); InputStream is = new FileInputStream(file); ObjectInputStream ois = new ObjectInputStream(is); SerializableTest so = (SerializableTest)ois.readObject(); System.out.println("str0 = " + so.getStr0()); System.out.println("str1 = " + so.getStr1()); } }

结果: 

str0 = str0
str1 = null

序列化文件:

aced 0005 7372 001a 636f 6d2e 6261 7369
632e 5365 7269 616c 697a 6162 6c65 5465
7374 c085 f449 6236 0629 0200 014c 0004
7374 7230 7400 124c 6a61 7661 2f6c 616e
672f 5374 7269 6e67 3b78 7074 0004 7374
7230 

序列化文件分五部分:

第一部分:序列化文件头

第二部分:要序列化的类的描述

第三部分:对象中各种属性的描述

第四部分:该对象父类的信息,如果没有父类就没有这部分

第五部分:输出对象的属性项的实际值,如果属性项是一个对象,还会序列化这个对象

总结:

1,序列化之后保存的是类的信息,因为一个serialVersionUID对应一个类

2,被声明为transient的属性不会被序列化

3,被声明为static的属性不会被序列化,因为序列化保存的是对象的状态,而静态属性属于类不属于对象,所以不会序列化它。

自定义序列化过程

Java并不强求用户非要使用默认的序列化方式,用户也可以按照自己的喜好自己指定自己想要的序列化方式----只要你自己能保证序列化前后能得到想要的数据就好了。手动指定序列化方式的规则是:

进行序列化、反序列化时,虚拟机会首先试图调用对象里的writeObject和readObject方法,进行用户自定义的序列化和反序列化。如果没有这样的方法,那么默认调用的是ObjectOutputStream的defaultWriteObject以及ObjectInputStream的defaultReadObject方法。换言之,利用自定义的writeObject方法和readObject方法,用户可以自己控制序列化和反序列化的过程。

这是非常有用的。比如:

1、有些场景下,某些字段我们并不想要使用Java提供给我们的序列化方式,而是想要以自定义的方式去序列化它,比如ArrayList的elementData、HashMap的table(至于为什么在之后写这两个类的时候会解释原因),就可以通过将这些字段声明为transient,然后在writeObject和readObject中去使用自己想要的方式去序列化它们

2、因为序列化并不安全,因此有些场景下我们需要对一些敏感字段进行加密再序列化,然后再反序列化的时候按照同样的方式进行解密,就在一定程度上保证了安全性了。要这么做,就必须自己写writeObject和readObject,writeObject方法在序列化前对字段加密,readObject方法在序列化之后对字段解密

示例2:

package com.basic;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;


public class SerializableTest implements Serializable{

    private static final long serialVersionUID = -4573981250507373015L;
    private String str0;
    private transient String str1;
    private static String str2 = "aaa";
    
    public SerializableTest(String str0, String str1) {
        this.str0 = str0;
        this.str1 = str1;
    }
    
    public String getStr0() {
        return this.str0;
    }
    
    public String getStr1() {
        return this.str1;
    }
    
    private void writeObject(ObjectOutputStream s) throws IOException {
        System.out.println("serializing by myself");
        s.defaultWriteObject();
        s.writeInt(str1.length());
        for (int i=0; i<str1.length(); i++) {
            s.writeChar(str1.charAt(i));
        }
    }
    
    private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
        System.out.println("deserializing by myself");
        s.defaultReadObject();
        int length = s.readInt();
        char[] cs = new char[length];
        for (int i =0; i<length; i++) {
            cs[i] = s.readChar();
        }
        str1 = new String(cs, 0, length);
    }
    
    public static void main(String[] args) throws Exception {
        // TODO Auto-generated method stub

        File file = new File("C:" + File.separator + "tool" + File.separator + "SerialTest.txt");
        OutputStream os = new FileOutputStream(file);
        ObjectOutputStream oos = new ObjectOutputStream(os);
        oos.writeObject(new SerializableTest("str0", "str1"));
        oos.close();
        
        InputStream is = new FileInputStream(file);
        ObjectInputStream ois = new ObjectInputStream(is);
        SerializableTest so = (SerializableTest)ois.readObject();
        System.out.println("str0 = " + so.getStr0());
        System.out.println("str1 = " + so.getStr1());
    }

}

先通过defaultWriteObject和defaultReadObject方法序列化、反序列化对象,然后在文件结尾追加需要额外序列化的内容/从文件的结尾读取额外需要读取的内容。 

复杂序列化情况总结

虽然Java的序列化能够保证对象状态的持久保存,但是遇到一些对象结构复杂的情况还是比较难处理的,最后对一些复杂的对象情况作一个总结:

1、当父类继承Serializable接口时,所有子类都可以被序列化

2、子类实现了Serializable接口,父类没有,父类中的属性不能序列化(不报错,数据丢失),但是在子类中属性仍能正确序列化

3、如果序列化的属性是对象,则这个对象也必须实现Serializable接口,否则会报错

4、反序列化时,如果对象的属性有修改或删减,则修改的部分属性会丢失,但不会报错

5、反序列化时,如果serialVersionUID被修改,则反序列化时会失败

原文地址:https://www.cnblogs.com/IvySue/p/7489174.html