Java对象序列化与反序列化

Java对象序列化与反序列化

对象序列化的目标是将对象保存在磁盘中或者在网络中进行传输。实现的机制是允许将对象转为与平台无关的二进制流。

java中对象的序列化机制是将允许对象转为字节序列。这些字节序列可以使Java对象脱离程序存在,从而可以保存在磁盘上,也可以在网络间传输。

对象的序列化是将一个Java对象写入IO流;与此对应的,反序列化则是从IO流中恢复一个Java对象。

实现序列化

如果要将一个java对象序列化,那么对象的类需要是可序列化的。要让类可序列化,那么这个类需要实现如下两个接口:

  • Serializable
  • Externalizable

使用Serializable序列化

实现Serializable接口非常简单,只要让java实现Serializable接口即可,无需实现任何方法。

一个类一旦实现了Serializable接口,那么该类的对象就是可序列化的。实现类的对象的序列化可以使用ObjectOutputStream,实现步骤如下:

  • 创建ObjectOutputStream对象;
  • 调用ObjectOutputStream的writeObject方法输出对象。

以下是一个实例:

package com.zhyea.test;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * 序列化测试类
 *
 * @author robin
 * @date 2014年12月18日
 */
public class SerialTest {

    public static void main(String[] args) {
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("D:\\object.txt"));
            Person robin = new Person("robin", 29);
            oos.writeObject(robin);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != oos) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

/**
 * 序列化测试用对象
 *
 * @author robin
 * @date 2014年12月18日
 */
class Person implements Serializable{

    private static final long serialVersionUID = -6412852654889352693L;

    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

如上的代码实现了将一个Person对象保存在了磁盘的一个文本文件object.txt上。运行程序在D盘上生成了一个object.txt文件。以下是文件内容:

有乱码(字节流转字符流导致的),但仍不影响我们分辨出里面是不是我们保存的对象。

接下来需要反序列化将Person对象从磁盘上读出。相应的反序列化需要使用的类是ObjectInputStream,反序列化步骤如下:

  • 创建ObjectInputStream对象;
  • 使用ObjectInputStream的readObject方法取出对象。

接下来,重构下我们的代码,实现反序列化,如下:

package com.zhyea.test;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * 序列化测试类
 *
 * @author robin
 * @date 2014年12月18日
 */
public class SerialTest {

    public static void main(String[] args) {
        Person robin = new Person("robin", 29);
        String savePath = "D:\\object.txt";

        SerialTest test = new SerialTest();

        try {
            test.serialize(robin, savePath);
            Person person = (Person) test.deSerialize(savePath);
            System.out.println("Name:" + person.getName() + "   Age:"
                    + person.getAge());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 实现序列化
     *
     * @param obj
     *            要被序列化保存的对象
     * @param path
     *            保存地址
     * @throws IOException
     */
    public void serialize(Object obj, String path) throws IOException {
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream(path));
            oos.writeObject(obj);
        } finally {
            if (null != oos)
                oos.close();
        }
    }

    /**
     * 反序列化取出对象
     *
     * @param path
     *            被序列化对象保存的位置
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public Object deSerialize(String path) throws IOException,
            ClassNotFoundException {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(path));
            return ois.readObject();
        } finally {
            if (null != ois)
                ois.close();
        }
    }

}

/**
 * 序列化测试用对象
 *
 * @author robin
 * @date 2014年12月18日
 */
class Person implements Serializable {

    private static final long serialVersionUID = -6412852654889352693L;

    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

关于对象序列化与反序列化还有几点需要注意:

  • 反序列化无需通过构造器初始化对象;
  • 如果使用序列化机制向文件中写入了多个对象,那么取出和写入的顺序必须一致;
  • Java对类的对象进行序列化时,若类中存在对象引用(且值不为null),也会对类的引用对象进行序列化。

使用transient

在一些特殊场景下,比如银行账户对象,出于保密考虑,不希望对存款金额进行序列化。或者类的一些引用类型的成员是不可序列化的。此时可以使用transient关键字修饰不想被或者不能被序列化的成员变量。

继续调整我们的代码来做演示:

package com.zhyea.test;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * 序列化测试类
 *
 * @author robin
 * @date 2014年12月18日
 */
public class SerialTest {

    public static void main(String[] args) {
        Person robin = new Person("robin", 29);
        School school = new School("XX学校");

        Teacher tRobin = new Teacher(robin);
        tRobin.setSchool(school);
        tRobin.setSalary(12.0);

        String savePath = "D:\\object.txt";

        SerialTest test = new SerialTest();

        try {
            test.serialize(savePath, tRobin);

            Teacher t = (Teacher) test.deSerialize(savePath);
            System.out.println("Name:" + t.getPerson().getName()
                             +" Salary:" + t.getSalary());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 实现序列化
     *
     * @param obj
     *            要被序列化保存的对象
     * @param path
     *            保存地址
     * @throws IOException
     */
    public void serialize(String path, Object ... obj) throws IOException {
        ....
    }

    /**
     * 反序列化取出对象
     *
     * @param path
     *            被序列化对象保存的位置
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public Object deSerialize(String path) throws IOException,
            ClassNotFoundException {
        ...
    }

}

/**
 * Teacher类
 * @author robin
 * @date 2014年12月18日
 */
class Teacher implements Serializable{

    private static final long serialVersionUID = -8751853088437904443L;

    private Person person;
    private transient School school;
    private transient double salary;

    public Teacher(Person person){
        this.person = person;
    }

    /*略去get、set,请自行补充*/
}

/**
 * School类,不可序列化
 *
 * @author robin
 * @date 2014年12月18日
 */
class School{
    private String name;

    public School(String name){
        this.name = name;
    }

    /*略去get、set,请自行补充*/
}

/**
 * Person类,可序列化
 *
 * @author robin
 * @date 2014年12月18日
 */
class Person implements Serializable {

     ....
}

在不对Teacher类的school成员添加transient标识的情况下,若school值不为null,会报NotSerializableException。异常信息如下: 

在不对Teacher类的salary成员添加transient标识的时候,会如实输出salary的值,添加后则只会输出salary的默认初始值即0.0。

需要注意的是transient只能修饰属性(filed),不能修饰类或方法。

自定义序列化

transient提供了一种简洁的方式将被transient修饰的成员属性完全隔离在序列化机制之外。这样子固然不错,但是Java还提供了一种自定义序列化机制让开发者更自由地控制如何序列化各个成员属性,或者不序列化某些属性(与transient效果相同)。

在需要自定义序列化和反序列化的类中需要提供以下方法:

  • private void writeObject(ObjectOutputStream out)
  • private void readObject(ObjectInputStream in)
  • private void readObjectNoData()

先说下前两个方法writeObject和readObject,这两个方法和ObjectOutputStream及ObjectInputStream里对应的方法名称相同。实际上,尽管这两个方法是private型的,但是仍然是在被序列化(或反序列化)阶段被外部类ObjectOutputStream(或ObjectInputStream)调用。仅以序列化为例,ObjectOutputStream在执行自己的writeObject方法前会先通过反射在要被序列化的对象的类中(有点绕口是吧)查找有无自定义的writeObject方法,如有的话,则会优先调用自定义的writeObject方法。因为查找反射方法时使用的是getPrivateMethod,所以自定以的writeObject方法的作用域要被设置为private。通过自定义writeObject和readObject方法可以完全控制对象的序列化与反序列化。

如下是示例代码:

package com.zhyea.test;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

import com.sun.xml.internal.ws.encoding.soap.DeserializationException;

/**
 * 序列化测试类
 *
 * @author robin
 * @date 2014年12月18日
 */
public class SerialTest {

    public static void main(String[] args) {
        Person robin = new Person("robin", 29);

        String savePath = "D:\\object.txt";

        SerialTest test = new SerialTest();

        try {
            test.serialize(savePath, robin);
            Person person = (Person) test.deSerialize(savePath);

            System.out.println("Name:" + person.getName()
                             +" Age:" + person.getAge());

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

    /**
     * 实现序列化
     *
     * @param obj
     *            要被序列化保存的对象
     * @param path
     *            保存地址
     * @throws IOException
     */
    public void serialize(String path, Person ... obj) throws IOException {
        ObjectOutputStream oos = null;
        ...
    }

    /**
     * 反序列化取出对象
     *
     * @param path
     *            被序列化对象保存的位置
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public Object deSerialize(String path) throws IOException,
            ClassNotFoundException {
        ...
    }

}

/**
 * Person类,可序列化
 *
 * @author robin
 * @date 2014年12月18日
 */
class Person implements Serializable {

    private static final long serialVersionUID = -6412852654889352693L;

    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private int age;

    public Person() {}

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /*  略去get和set,请自行实现  */

    private void writeObject(ObjectOutputStream out) throws IOException{
        out.writeObject(name);
        out.writeInt(age + 1);

        System.out.println("my write");
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
        this.name = "zhangsan";
        this.age = 30;
        System.out.println("my read");
    }
}

以下是输出结果:

关于readObjectNoData,在网上找了如下一段说明:

readObjectNoData
  原始情况
    pojo
        public class Person implements Serializable {
            private int age;
            public Person() {  }
            //setter getter...
        }
    序列化
         Person p = new Person();
         p.setAge(10);
         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("c:/person.ser"));
         oos.writeObject(p);
         oos.flush();
         oos.close();
  类结构变化后, 序列化数据不变     pojo
      Animal
        implements Serializable
        显式编写readObjectNoData
        public class Animal implements Serializable {
                private String name;
                public Animal() {  }
                //setter getter...
                private void readObjectNoData() {
                    this.name = "zhangsan";
                }
        }
      Person
        extends Animal
        public class Person extends Animal implements Serializable {
                private int age;
                public Person() {  }
                // setter getter...
        }
    反序列化
      ObjectInputStream ois = new ObjectInputStream(new FileInputStream("c:/person.ser"));
      Person sp = (Person) ois.readObject();
      System.out.println(sp.getName());
      readObject时, 会调用readObjectNoData

readObjectNoData在我理解看来像是一种异常处理机制,用来在序列化的流不完整的情况下返回正确的值。

使用 writeReplace和readResolve

writeReplace和readResolve是一种更彻底的序列化的机制,它甚至可以将序列化的目标对象替换为其它的对象。

但是与writeObject和readObject不同的是,这二者不是必须要一起使用的,而且尽量应分开使用。若一起使用的话,只有writeReplace会生效。

代码可以说明一切,首先是writeReplace:

package com.zhyea.test;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;

/**
 * 序列化测试类
 *
 * @author robin
 * @date 2014年12月18日
 */
public class SerialTest {

    public static void main(String[] args) {
        Person robin = new Person("robin", 29);
        String savePath = "D:\\object.txt";
        SerialTest test = new SerialTest();
        try {
            //序列化
            test.serialize(savePath, robin);
            //反序列化
            String person = (String) test.deSerialize(savePath);

            System.out.println(person);

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

    /**
     * 实现序列化
     *
     * @param obj
     *            要被序列化保存的对象
     * @param path
     *            保存地址
     * @throws IOException
     */
    public void serialize(String path, Person ... obj) throws IOException {
        ObjectOutputStream oos = null;
        ....
    }

    /**
     * 反序列化取出对象
     *
     * @param path
     *            被序列化对象保存的位置
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public Object deSerialize(String path) throws IOException,
            ClassNotFoundException {
        ....
    }

}

/**
 * Person类,可序列化
 *
 * @author robin
 * @date 2014年12月18日
 */
class Person implements Serializable {

    private static final long serialVersionUID = -6412852654889352693L;

    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private int age;

    public Person() {}

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /*   set和get方法请自行添加    */

    private Object writeReplace() throws ObjectStreamException{
        System.out.println("my writeReplace");
        return "robin";
    }

    private Object readResolve() throws ObjectStreamException{
        System.out.println("my readResolve");
        return "zhangsan";
    }

    private void writeObject(ObjectOutputStream out) throws IOException{
        ....
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
        ....
    }
}

以下是运行结果:

在Person类中,保留了之前的writeObject和readObject方法,并且还添加了readResolve方法。但是从运行结果可以看出来,这3个方法都没有被调用,只有writeReplace方法被使用。可以理解为当使用writeReplace时,其他的自定义方法都不会被调用,即writeReplace的优先级最高。

现在注释掉writeReplace方法,再次执行,结果如下:

这次writeObject,readObject和readResolve方法都被调用。readResolve方法紧跟着readObject方法被调用且最终返回的值是readResolve返回的值,readObject里反序列化生成的对象被抛弃。

此外还有一点需要说明:writeReplace和readResolve可以使用任何作用域,这意味着子类也可以调用超类的这两个方法。但是如果子类还有不同的序列化及反序列化需求,这就需要子类重写这个方法,有些时候这样做是没有必要的。因此一般情况下将这两个方法的作用域设置为private。

使用Externalizable

一开始有提到过实现Externalizable接口也可以实现类的序列化。使用这种方法,可以由开发者完全决定如何序列化和反序列化目标对象。Externalizable接口提供了writeExternal和readExternal两个方法。

实际上这种方法和前面的自定义序列化方法很相似,只是Externalizable强制自定义序列化。在使用了Externalizable的类中仍可以使用writeReplace和readResolve方法。使用Externalizable进行序列化较之使用Serializable性能略好,但是复杂度较高。

版本问题

执行序列化和反序列化时有可能会遇到JRE版本问题。尤其是在网络的两端进行通信时,这种情况更为多见。

为了解决这种问题,Java允许为序列化的类提供一个serialVersionUID的常量标识该类的版本。只要serialVersionUID的值不变,Java就会把它们当作相同的序列化版本。

如果不显式定义serialVersionUID,那么JVM就会计算出一个serialVersionUID的值。不同的编译器下会产生不同的serialVersionUID值。serialVersionUID值不同则会导致编译失败。可以使用jdk的bin目录下的serial.exe查看可序列化类的serialVersionUID,指令如下:

serial Person

如果对类的修改确实会导致反序列化失败,则应主动调整serialVersionUID的值。导致类的反序列化失败的修改有以下几种情形:

  • 只是修改了类的方法,不会影响反序列化。
  • 只是修改了类的static Field或transient Field,不会影响反序列化。
  • 修改了类的非static和非transient Field,会影响序列化。

序列化注意事项

关于对象的序列化,总结下注意事项:

  • 对象的类名、Field(包括基本类型、数组及对其他对象的引用)都会被序列化,对象的static Field,transient Field及方法不会被序列化;
  • 实现Serializable接口的类,如不想某个Field被序列化,可以使用transient关键字进行修饰;
  • 保证序列化对象的引用类型Filed的类也是可序列化的,如不可序列化,可以使用transient关键字进行修饰,否则会序列化失败;
  • 反序列化时必须要有序列化对象的类的class文件;
  • 当通过文件网络读取序列化对象的时候,必需按写入的顺序来读取。
时间: 2024-10-01 21:30:14

Java对象序列化与反序列化的相关文章

Java对象序列化和反序列化

Java对象序列化和反序列化 在Java中,我们如果要保存一个对象的瞬时状态值,以便在下次使用时能够得到这些值,或者持久化对象,或者使用RMI(远程方法调用),或在网络中传递对象时,此时我们就需要将对象序列化,实现序列化,我们只要实现Serializable接口,该接口是一个标记接口(Tag interface),即里面没有方法,其主要作用就是告诉JVM该类的对象可以进行序列化. 一般来说,很多类的对象都实现了Serializable接口,但是,有些对象是不能进行序列化的,比如与数据库相关的连接

java 对象序列化与反序列化

Java序列化与反序列化是什么? 为什么需要序列化与反序列化? 如何实现Java序列化与反序列化? 本文围绕这些问题进行了探讨. 1.Java序列化与反序列化  Java序列化是指把Java对象转换为字节序列的过程: Java反序列化是指把字节序列恢复为Java对象的过程. 2.为什么需要序列化与反序列化 我们知道,当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本.图片.音频.视频等, 而这些数据都会以二进制序列的形式在网络上传送.那么当两个Java进程进行通信时,能否实现进程间的

【转】Java对象序列化和反序列化

[转自]孤傲苍狼的Java基础学习总结——Java对象的序列化和反序列化 一.序列化和反序列化的概念 把对象转换为字节序列的过程称为对象的序列化. 把字节序列恢复为对象的过程称为对象的反序列化. 对象的序列化主要有两种用途: 1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中: 2) 在网络上传送对象的字节序列. 在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存.比如最常见的是Web服务器中的Session对 象,当有 10万用户并发访问,就有可

java对象序列化、反序列化

平时我们在Java内存中的对象,是无法进行IO操作或者网络通信的,因为在进行IO操作或者网络通信的时候,人家根本不知道内存中的对象是个什么东西,因此必须将对象以某种方式表示出来,即存储对象中的状态.一个Java对象的表示有各种各样的方式,Java本身也提供给了用户一种表示对象的方式,那就是序列化.换句话说,序列化只是表示对象的一种方式而已.OK,有了序列化,那么必然有反序列化,我们先看一下序列化.反序列化是什么意思. 序列化:将一个对象转换成一串二进制表示的字节数组,通过保存或转移这些字节数据来

Java对象序列化与反序列化(1)

序列化机制允许将实现序列化的Java对象转换为字节序列,这些字节序列可以被保存在磁盘上,或通过网络传输,以备以后重新恢复成原来的对象.序列化机制使得对象可以脱离程序的运行而独立存在. 对象的序列化(Serialize)指将一个Java对象写入IO流中,与此对应的是,对象的反序列化(Deserialize)则指从IO流中恢复该Java对象.如果需要让某个对象可以支持序列化机制,必须让它的类是可序列化的(serializable),为了让某个类是可序列化的,该类必须实现如下两个接口之一: (1)Se

Java的序列化与反序列化

Java序列化与反序列化是什么?为什么需要序列化与反序列化?如何实现Java序列化与反序列化?本文围绕这些问题进行了探讨. 1.Java序列化与反序列化 Java序列化是指把Java对象转换为字节序列的过程:而Java反序列化是指把字节序列恢复为Java对象的过程. 2.为什么需要序列化与反序列化 我们知道,当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本.图片.音频.视频等, 而这些数据都会以二进制序列的形式在网络上传送.那么当两个Java进程进行通信时,能否实现进程间的对象传送

Java Object 对象序列化和反序列化

Java Object 对象序列化和反序列化 @author ixenos 对象序列化是什么 1.对象序列化就是把一个对象的状态转化成一个字节流. 我们可以把这样的字节流存储为一个文件,作为对这个对象的复制(深拷贝):在一些分布式应用中,我们还可以把对象的字节流发送到网络上的其他计算机. 反序列化是把流结构的对象恢复为其原有形式 2.Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长.但

Java对象序列化/反序列化的注意事项(转)

Java对象序列化 对于一个存在Java虚拟机中的对象来说,其内部的状态只是保存在内存中.JVM退出之后,内存资源也就被释放,Java对象的内部状态也就丢失了.而在很多情况下,对象内部状态是需要被持久化的,将运行中的对象状态保存下来(最直接的方式就是保存到文件系统中),在需要的时候可以还原,即使是在Java虚拟机退出的情况下. 对象序列化机制是Java内建的一种对象持久化方式,可以很容易实现在JVM中的活动对象与字节数组(流)之间进行转换,使用得Java对象可以被存储,可以被网络传输,在网络的一

java对象序列化小结

百度百科上介绍序列化是这样的: 序列化 (Serialization): 将对象的状态信息转换为可以存储或传输的形式的过程.在序列化期间,对象将其当前状态写入到临时或持久性存储区.以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象. 序列化使其他代码可以查看或修改那些不序列化便无法访问的对象实例数据.确切地说,代码执行序列化需要特殊的权限:即指定了 SerializationFormatter 标志的 SecurityPermission.在默认策略下,通过 Internet 下载