Java 序列化 和 反序列化

序列化和反序列化

序列化是将对象的状态信息转化为可以存储或者传输的形式的过程(即将对象写入IO流中)

  • 一般将一个对象存储到一个存储媒介,例如档案或记忆体缓冲
  • 在网络传输过程中,可以是字节或、XML、JSON等格式

反序列化是将字节、XML、JSON等格式还可以还原成原来的对象(即从IO流中恢复对象)


序列化的好处

  1. 实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里)

  2. 利用序列化实现远程通信,即在网络上传送对象的字节序列


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

对象序列化机制是Java语言内建的一种对象持久化方式,通过对象序列化,可以将对象的状态信息保存为字节数组,并且可以在有需要的时候将这个字节数组通过反序列化的方式转换成对象,对象的序列化可以很容易的在JVM中的活动对象和字节数组(流)之间进行转换

在JAVA中,对象的序列化和反序列化被广泛的应用到 远程方法调用 及 网络传输


Java 序列化常用接口 - Serializable

Java类通过实现 java.io.Serializable 接口来启用序列化功能,未实现此接口的类将无法将其任何状态或者信息进行序列化或者反序列化。可序列化类的所有子类型都是可以序列化的。序列化接口没有方法或者字段,仅用于标识可序列化的语义

public interface Serializable {
}

使用注意点

  • 当试图对一个对象进行序列化时,如果遇到一个没有实现 java.io.Serializable 接口的对象时,将抛出NotSerializationException异常
  • 如果要序列化的类有父类,要想将在父类中定义过的变量序列化下来,那么父类也应该实现 java.io.Serializable 接口
  • 若一个类的成员变量不是基本数据类型,也不是String类型的时候,而是其他引用类型时,则该成员必须是可序列化的,否则会导致该类无法完成序列化

使用实例

public class User implements Serializable {
    private String name;
    private String age;

    public User() {
        System.out.println("无参构造方法");
    }

    public User(String name, String age) {
        this.name = name;
        this.age = age;
        System.out.println("有参构造方法");
    }

    //set、get省略
    
    @Override
    public String toString() {
        return "User{'name' :" + name + ",'age' :" + age + "}";
    }
}
public class TestSerializable {
    public static void main(String[] args) {
        try {
            //序列化对象
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));

            User user = new User("wen", "12");
            oos.writeObject(user);//文件存储对象的序列化编码

            //反序列化
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
            User user1 = (User) ois.readObject();
            System.out.println(user1.toString());//User{'name' :wen,'age' :12}
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

通过反序列化输出结果,可以知道反序列化没有调用类的构造方法,而是由JVM自己生成对象

注意:反序列化的顺序和序列化时的顺序一致

 由于Java序列化算法不会重复序列化同一个对象,只会记录已序列化对象的序列化编号。而当一个可变的对象中的内容发生改变时,此时进行序列化,却不会重新将此对象转换为字节序列,而是保存序列化编号

public class TestSerializable {
	public static void main(String[] args) {
        try {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
            
            User user1 = new User("wen", "20");
            System.out.println("修改前:" + user1.toString());//修改前:User{'name' :wen,'age' :20}
            oos.writeObject(user1);

            User1.setName("cheng");
            System.out.println("修改后:" + User1.toString()); //修改后:User{'name' :cheng,'age' :20}
            oos.writeObject(User1);

            User u1 = (User) ois.readObject();
            User u2 = (User) ois.readObject();

            System.out.println(u1 == u2);//true
            System.out.println(u1.getName().equals(u2.getName()));//true
            System.out.println(u1.getName());//wen
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 使用 transient 修饰的属性,Java序列化时会忽略该属性。而当反序列化时,被 transient 修饰的属性则赋予默认值对于引用类型则为null,boolean类型为false,基本类型为0

 序列化和反序列化注意点

  1.  对象的类名、实例变量(包括基本类型,数组,对其他对象的引用)都会被序列化;方法、类变量、transient实例变量都不会被序列化
  2. 所有需要网络传输的对象都需要实现序列化接口,通过建议所有的 javaBean 都实现 Serializable 接口 ,如果想让某个变量不被序列化,使用 transient 修饰
  3. 序列化对象的引用类型成员变量,也必须是可序列化的,否则,会报错
  4. 反序列化时必须有序列化对象的class文件
  5. 当通过文件、网络来读取序列化后的对象时,必须按照实际写入的顺序读取
  6. 单例类序列化,需要重写 readResolve() 方法;否则会破坏单例原则
  7. 同一对象序列化多次,只有第一次序列化为二进制流,以后都只是保存序列化编号,不会重复序列化
  8. 建议所有可序列化的类加上 serialVersionUID 版本号,方便项目升级
    1. 数组不能显式地声明 serialVersionUID,因为它们始终都有默认的计算值,但是对于数组类,无需匹配 serialVersionUID
  9. 可以通过序列化和反序列化的方式实现对象的深拷贝

 

posted @ 2022-05-16 15:11  伊文小哥  阅读(233)  评论(0编辑  收藏  举报