Java 笔记——序列化

基础知识

1.序列化

引用该博客一部分内容

把对象转换为字节序列的过程称为对象的序列化。
把字节序列恢复为对象的过程称为对象的反序列化。
  对象的序列化主要有两种用途:
  1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
  2) 在网络上传送对象的字节序列。

  在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。

  当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

  
  Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。
  被序列化的类需要实现 Serializable 接口,将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,也就是说,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象。
  那么,怎么序列化一个对象?首先,创建一个OutputStream对象(eg. FileOutputStream,ByteArrayOutputStream等),并将其封装到ObjectOutputStream中,利用writeObject()方法序列化一个对象。(对象的序列化是基于字节的,不能使用Reader和Writer等基于字符的层次结构)。反序列化则是将一个InputOutput对象封装到ObjectInputStream中,调用readObject()方法读取对象并将其反序列化。
  整个过程都是Java虚拟机(JVM)独立的,也就是说,在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象。
  类ObjectInputStream 和ObjectOutputStream是高层次的数据流,它们包含序列化和反序列化对象的方法如下:

public final void writeObject(Object obj) throws IOException

writeObject()方法序列化一个对象,并将它发送到输出流

public final Object readObject() throws IOException, ClassNotFoundException

readObject()方法从流中取出下一个对象,并将对象反序列化,由于返回类型是Object,所以在使用时可能需要将其转化为对应的类类型。

2.序列化ID

  一个类implements Serializable接口后,根据warming可以生成一个private static final long serialVersionUID,一般default设置为private static final long serialVersionUID = 1L,当然,也可以选择随机生成一个。
   序列化ID的作用是可以用来限制一些用户的使用,在反序列时会先判断原来序列化的对象的serialVersionUID与当前该对象的serialVersionUID是否一致,不一致则不能进行反序列化。
   举个例子:
序列化时:将serialVersionUID 设置为1L,将对象保存成一个字节序列存入Serialization.obj文件

public class JavaTest {

    private static final long serialVersionUID = 1L;//这里是1L
    private String str = new String("ABC");
    public static void main(String[] args){
        JavaTest test = new JavaTest();
        File file = new File("Serialization.obj");
        try {
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
            out.writeObject(test);
            out.close();            
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

当反序列化时,将当前类的serialVersionUID 改为随机生成的ID,

public class JavaTest implements Serializable{

    private static final long serialVersionUID = 1115713621710341241L;//随机生成一个
    private String str = new String("ABC");
    public static void main(String[] args){
        JavaTest test = new JavaTest();
        File file = new File("Serialization.obj");
        try {

            ObjectInputStream in = new ObjectInputStream(new FileInputStream(new File("Serialization.obj")));
            JavaTest res = (JavaTest) in.readObject();
            System.out.println(res.str);
            in.close();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

两者的serialVersionUID 不一致,就会报错,如下所示:

java.io.InvalidClassException: com.test.JavaTest; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 1115713621710341241
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:617)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1622)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1517)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1771)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
    at com.test.JavaTest.main(JavaTest.java:25)

将两者的serialVersionUID 改为一致,即可成功运行。

常见的案例

1.静态变量序列化

  序列化不保存静态变量的值,从下面的例子中可以看出:

public class JavaTest implements Serializable{

    private static final long serialVersionUID = 1L;//1115713621710341241L;
    private  String str = new String("ABC");
    private  static String staticStr = new String("staticABC");
    public static void main(String[] args){
        JavaTest test = new JavaTest();
        File file = new File("Serialization.obj");
        try {
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
            out.writeObject(test);
            out.close();
            JavaTest.staticStr = "ChangedStaticStr";//改变静态变量的值,如果输出的结果是staticStr,表明序列化保存了静态变量的值,否则表示没有保存
            ObjectInputStream in = new ObjectInputStream(new FileInputStream(new File("Serialization.obj")));
            JavaTest res = (JavaTest) in.readObject();

            System.out.println(res.str);
            System.out.println(res.staticStr);
            in.close();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

结果输出:

ABC
ChangedStaticStr

通过Debug也可知,res中并没有保存staticStr变量
这里写图片描述

2.transient关键字:

(1)当某个字段被声明为transient后,默认序列化机制就会忽略该字段。
例如将之前的例子中

private String str = new String("ABC");

替换成

private  transient String str = new String("ABC");

则在输出时res.str = null,不再是原来的”ABC”,且debug时你也看不见str变量。

(2)transient关键字只能修饰变量,而不能修饰方法和类。

(3)静态变量不管是否被transient修饰,均不能被序列化。
例如将之前的例子中

private  static String staticStr = new String("staticABC");

替换成

private transient static String staticStr = new String("staticABC");

两者的效果是一样的。

3.Externalizable接口

  是否使用了transient关键字,变量就一定不能被序列化了呢?
  不是的,当我们把Serializable接口替换成Externalizable接口时,序列化极值就会失效。无论是使用transient关键字,还是使用writeObject()和readObject()方法,其实都是基于Serializable接口的序列化。
  例如:

public class JavaTest implements Externalizable{

    private static final long serialVersionUID = 1L;
    private transient String str = new String("ABC");
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        // TODO Auto-generated method stub

    }
    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        // TODO Auto-generated method stub

    }
    public static void main(String[] args){
        JavaTest test = new JavaTest();
        File file = new File("Serialization.obj");
        try {
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
            out.writeObject(test);
            out.close();
            ObjectInputStream in = new ObjectInputStream(new FileInputStream(new File("Serialization.obj")));
            JavaTest res = (JavaTest) in.readObject();
            System.out.println(res.str);
            in.close();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

}

虽然加了transient关键字,但是结果仍然输出

ABC

  Externalizable接口继承于Serializable接口,并且增加了writeExternal()与readExternal()方法,将需要序列化的变量自己在writeExternal()方法中实现,反序列化是调用readExternal()方法读取数据。(即需要自己手动去完成序列化)

public interface Externalizable extends java.io.Serializable {
    /**
     * The object implements the writeExternal method to save its contents
     * by calling the methods of DataOutput for its primitive values or
     * calling the writeObject method of ObjectOutput for objects, strings,
     * and arrays.
     *
     * @serialData Overriding methods should use this tag to describe
     *             the data layout of this Externalizable object.
     *             List the sequence of element types and, if possible,
     *             relate the element to a public/protected field and/or
     *             method of this Externalizable class.
     *
     * @param out the stream to write the object to
     * @exception IOException Includes any I/O exceptions that may occur
     */
    void writeExternal(ObjectOutput out) throws IOException;

    /**
     * The object implements the readExternal method to restore its
     * contents by calling the methods of DataInput for primitive
     * types and readObject for objects, strings and arrays.  The
     * readExternal method must read the values in the same sequence
     * and with the same types as were written by writeExternal.
     *
     * @param in the stream to read data from in order to restore the object
     * @exception IOException if I/O errors occur
     * @exception ClassNotFoundException If the class for an object being
     *              restored cannot be found.
     */
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

举个例子:

public class JavaTest implements Externalizable{

    private static final long serialVersionUID = 1L;
    private String str = new String("ABC");
    private int integer = 5;
    public JavaTest() {
        System.out.println("This is JavaTest constructor");
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("This is writeExternal function");
        out.writeObject(str);//需要序列化str
        out.writeInt(integer);//需要序列化integer
    }
    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        System.out.println("This is readExternal function");
        String res = (String) in.readObject();
        System.out.println(res);
        int resint = (int) in.readInt();
        System.out.println(resint);
    }
    public static void main(String[] args){
        File file = new File("Serialization.obj");
        try {
            JavaTest test = new JavaTest();
            ObjectOutput objout = new ObjectOutputStream(new FileOutputStream(file));
            test.writeExternal(objout);
            objout.close();

            ObjectInput objin = new ObjectInputStream(new FileInputStream(file));
            test.readExternal(objin);
            objin.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

  在writeExternal()方法中说明了我要序列化str和integer两个变量,在readExternal()方法中反序列化这两个变量。
输出结果:

This is JavaTest constructor
This is writeExternal function
This is readExternal function
ABC
5 

  如果我在writeExternal()方法中只序列化了str变量,那么readExternal()方法将得不到integer变量。

@Override
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("This is writeExternal function");
        out.writeObject(str);
    }
    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        System.out.println("This is readExternal function");
        String res = (String) in.readObject();
        System.out.println(res);
        int resint = (int) in.readInt();//会出错
        System.out.println(resint);
    }

readExternal()调用in.readInt()方法会出错

This is JavaTest constructor
This is writeExternal function
This is readExternal function
ABC
java.io.EOFException
    at java.io.DataInputStream.readInt(DataInputStream.java:392)
    at java.io.ObjectInputStream$BlockDataInputStream.readInt(ObjectInputStream.java:2820)
    at java.io.ObjectInputStream.readInt(ObjectInputStream.java:971)
    at com.test.JavaTest.readExternal(JavaTest.java:38)
    at com.test.JavaTest.main(JavaTest.java:50)

  另外,若使用Externalizable进行序列化,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。由于这个原因,实现Externalizable接口的类必须要提供一个无参的构造器,且它的访问权限为public。
  如下所示:

public class JavaTest implements Externalizable{

    private static final long serialVersionUID = 1L;
    private String str = new String("ABC");
    private int integer = 5;
    public JavaTest() {
        System.out.println("This is non-paramater JavaTest constructor");
    }
    public JavaTest(String s,int i) {
        System.out.println("This is paramater JavaTest constructor");
        this.str = s;
        this.integer = i;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("This is writeExternal function");
        out.writeObject(str);
        out.writeInt(integer);
    }
    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        System.out.println("This is readExternal function");
        String res = (String) in.readObject();
        System.out.println(res);
        int resint = (int) in.readInt();
        System.out.println(resint);
    }
    public static void main(String[] args){
        File file = new File("Serialization.obj");
        try {
            JavaTest test = new JavaTest("zfq",323);
            ObjectOutput objout = new ObjectOutputStream(new FileOutputStream(file));
            objout.writeObject(test);
            objout.close();

            ObjectInput objin = new ObjectInputStream(new FileInputStream(file));
            objin.readObject();
            objin.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

结果输出:

This is paramater JavaTest constructor
This is writeExternal function
This is non-paramater JavaTest constructor
This is readExternal function
zfq
323

如果将默认构造函数改为private类型,将会引起错误。

This is paramater JavaTest constructor
This is writeExternal function
java.io.InvalidClassException: com.test.JavaTest; no valid constructor
    at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:150)
    at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:768)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1772)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
    at com.test.JavaTest.main(JavaTest.java:56)

一些参考资料:
你所不知道的Java序列化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值