java基础——序列化
2021-04-12 18:26
标签:code 办理 支持 tin 替换 版本控制 没有 span otf 我们知道,java程序在运行时,对象是在对上创建的,如果程序停止了,那么这个对象也不复存在了。当我们需要将对象存储在硬盘上时,就需要序列化的技术了。 序列化就是一种将对象转换成字节序列的过程。反序列化就是将字节序列代表的对象恢复成原来的样子。通过序列化与反序列化,我们可以实现进程间的通信。 序列化使用场景: 1、持久化存储某个对象。 2、进程间的通信(包括网络中传输对象)。 如果要序列化某个对象,那么这个类应该实现 Serializable 接口或者 Externalizable 接口之一。 Serializable 接口是一个空接口,里面没有任何的方法,只要一个对象实现了该接口,那么这个对象就可以被序列化。 序列化步骤: 1、创建 ObjectOutputStream 输出流对象, 指定对象字节流的输出位置。 2、通过 writeObject() 将对象的字节流写入文件。 3、关闭输出流。 反序列化步骤: 1、创建一个 ObjectInputStream 输入流,指定需要读取的文件。 2、通过 readObject() 获取已经序列化的对象。 通过上面的例子我们看到,在反序列的过程中,并没有调用类的构造函数。反序列化的对象是由 JVM 自己生成的,不需要调用构造方法。 如果一个可序列化的类的成员不是基本类型,也不是String类型,而是一个引用类型,那这个引用类型也必须是可序列化的;否则,会导致此类不能序列化。 我们看到程序直接报错,因为Person类的对象是不可序列化的,这导致了Teacher的对象不可序列化。 类的静态变量不会被序列化。 同一对象序列化多次,会将这个对象序列化多次吗?答案是否定的。 从输出结果来看,同一个对象只会序列化一次,并不是每次调用 writeObject() 方法都会序列化一个对象。 序列化过程 1、如果一个对象已经被序列化过,那么这个对象会保存一个序列化编码号,并将序列化编号一起保存在输出的序列化文件中。 2、当程序试图序列化一个对象时,首先会检查该对象是否已经存在序列化编号,如果有,则直接输出编号到序列化文件中。否则进行序列化。 序列化存在的问题 根据序列的过程,如果一个对象已经被序列化了,那么再次调用 writeObject() 方法并不会再次序列化该对象。如果该对象的属性是可以 set() 等方式修改的,那么反序列化得到的对象并不会显示被修改的内容。 有些时候,我们有这样的需求,某些属性不需要序列化。使用 transient 关键字选择不需要序列化的字段。 通过上面的代码我们发现了一个问题,尽管 transient 可以决定哪些内容是不需要序列化的,但在反序列化的过程中,JVM会忽视那些没有被序列化的部分,这样我们并不能完整的获得这个对象。对于 transient 修饰的成员变量会赋默认值。 对象的某些信息可能是敏感信息,比如银行卡号等,如果直接序列化可能存在信息泄露的风险,如果使用 transient 不进行序列化,那么反序列化时又得不到这个对象的完整信息。为了解决这些问题,我们可以重写以下三个方法,从而对序列化的信息进行加密,防止信息泄露: 1、private void writeObject(ObjectOutputStream out) //序列化时会调用此方法 2、private void readObject(ObjectInputStream in) //反序列化时会调用此方法 3、private void readObjectNoData() //当序列化的类版本和反序列化的类版本不同时,或者 ObjectInputStream 流被修改时,会调用此方法。 Serializable 接口是一个空接口,上面的三个方法都不是必需的,但一般我们会重写前面两个。 除了上面的方法外,我们还可以使用 private/default/protected/public Object writeReplace(){......} 或者 private/default/protected/public Object writeReplace(){......}。这两个方法返回一个 Object 对象,重写该方法,可以做一些格式化处理。 writeReplace:在序列化时,会先调用此方法,再调用 writeObject 方法。此方法可将任意对象代替目标序列化对象。 readResolve:在反序列化读取对象后,会自动调用此方法,将读取的对象替换为指定的对象。反序列化出来的对象被立即丢弃。此方法在readeObject后调用。常用来反序列化单例类,保证单例的唯一性。 两者只是作用的时间点不同,可以联合使用。 除了实现 Serializable 接口外,还可以通过 Externalizable 接口实现序列化。该接口中有两个方法: Externalizable 接口不同于 Serializable 接口,实现此接口必须实现接口中的两个方法实现自定义序列化,这是强制性的;特别之处是必须提供 pulic 的无参构造器,因为在反序列化的时候需要反射创建对象。 虽然Externalizable接口带来了一定的性能提升,但变成复杂度也提高了,所以一般通过实现Serializable接口进行序列化。 在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(InvalidCastException)。 一个类实现了 Serializable 接口,如果我们没有显示的指定版本号,那么 JVM 会给这个类指定一个默认的 serialVersionUID,默认样式为: private static final long serialVersionUID = 1L; 当然,我们也可以显示的指定版本号: private static final long serialVersionUID = 123456789L; serialVersionUID 主要是为了解决版本问题,如果一个类的方法或字段有所改动,当我们设置了 serialVersionUID 后,在反序列化是就会报出异常。如果我们没有设置serialVersionUID,由于默认的 serialVersionUID 的值是一样的,所以在反序列化过程中,缺少的字段会默认空值,多余的字段会被丢弃。 使用 serialVersionUID 的情形: 1、如果只是修改了方法,反序列化不容影响,则无需修改版本号; 2、如果只是修改了静态变量,瞬态变量(transient修饰的变量),反序列化不受影响,无需修改版本号; 3、如果修改了非瞬态变量,则可能导致反序列化失败。如果新类中实例变量的类型与序列化时类的类型不一致,则会反序列化失败,这时候需要更改serialVersionUID。如果只是新增了实例变量,则反序列化回来新增的是默认值;如果减少了实例变量,反序列化时会忽略掉减少的实例变量。 java序列化,看这篇就够了 Java 自定义序列化、反序列化 serialVersionUID的作用 java基础——序列化 标签:code 办理 支持 tin 替换 版本控制 没有 span otf 原文地址:https://www.cnblogs.com/Zz-feng/p/13340530.html为什么需要序列化
序列化的实现方式
实现 Serializable 接口
package javaIO;
import java.io.Serializable;
//创建一个 Student 类实现 Serializable 接口
public class Student implements Serializable{
private String name;
private int num;
//只写了带参的构造函数, 没有提供无参的构造函数
public Student(String name, int num) {
super();
this.name = name;
this.num = num;
System.out.println("反序列化是否调用了构造函数?");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
@Override
public String toString() {
return "Student [name=" + name + ", num=" + num + "]";
}
}
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class TestSerializable {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
// 创建 ObjectOutputStream 输出流对象, 传入一个 FileOutputStream 对象, 指定对象字节流的输出位置
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D:\\javatest\\student.txt"));
Student student = new Student("Zz-feng", 1001);
//通过 writeObject() 将对象的字节流写入文件
out.writeObject(student);
//关闭输出流
out.close();
// 创建 ObjectOutputStream 输出流对象, 传入一个 FileInputStream 对象, 指定需要反序列化的文件
ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:\\javatest\\student.txt"));
//通过 readObject() 将以序列化的对象恢复
Object object = in.readObject();
System.out.println(object); //输出 Student [name=Zz-feng, num=1001]
//关闭输入流
in.close();
}
}
成员变量是引用的序列化
public class Person{
//省略相关属性与方法
}
public class Teacher implements Serializable {
private String name;
private Person person;
public Teacher(String name, Person person) {
this.name = name;
this.person = person;
}
public static void main(String[] args) throws Exception {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("teacher.txt"))) {
Person person = new Person("路飞", 20);
Teacher teacher = new Teacher("雷利", person);
oos.writeObject(teacher);
}
}
}
同一对象序列化多次
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class TestSerializable {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
// 创建 ObjectOutputStream 输出流对象, 传入一个 FileOutputStream 对象, 指定对象字节流的输出位置
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D:\\javatest\\student.txt"));
Student s1 = new Student("Zz-feng", "1001");
Student s2 = new Student("mytest", "1002");
Student s3 = new Student("mytest", "1002");
//通过 writeObject() 将对象的字节流写入文件
out.writeObject(s1);
out.writeObject(s2);
out.writeObject(s3);
out.writeObject(s1);
out.writeObject(s2);
out.writeObject(s3);
//关闭输出流
out.close();
// 创建 ObjectOutputStream 输出流对象, 传入一个 FileInputStream 对象, 指定需要反序列化的文件
ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:\\javatest\\student.txt"));
//通过 readObject() 将以序列化的对象恢复
Object read_s1 = in.readObject();
Object read_s2 = in.readObject();
Object read_s3 = in.readObject();
Object read_s11 = in.readObject();
Object read_s22 = in.readObject();
Object read_s33 = in.readObject();
System.out.println(read_s1 == read_s2); // false
System.out.println(read_s2 == read_s3); // false
System.out.println(read_s1 == read_s33); // false
System.out.println(read_s2 == read_s33); // false
System.out.println(read_s1 == read_s11); // true
System.out.println(read_s2 == read_s22); // true
//关闭输入流
in.close();
}
}
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class TestSerializable {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
// 创建 ObjectOutputStream 输出流对象, 传入一个 FileOutputStream 对象, 指定对象字节流的输出位置
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D:\\javatest\\student.txt"));
Student s1 = new Student("Zz-feng", "1001");
Student s2 = new Student("mytest", "1002");
//通过 writeObject() 将对象的字节流写入文件
out.writeObject(s1);
s1.setName("newName");
s1.setNum("newNum");
out.writeObject(s1);
out.writeObject(s1);
//关闭输出流
out.close();
// 创建 ObjectOutputStream 输出流对象, 传入一个 FileInputStream 对象, 指定需要反序列化的文件
ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:\\javatest\\student.txt"));
//通过 readObject() 将以序列化的对象恢复
Object read_s1 = in.readObject();
Object read_s11 = in.readObject();
System.out.println(read_s1); // Student [name=Zz-feng, num=1001]
System.out.println(read_s11); // Student [name=Zz-feng, num=1001]
//关闭输入流
in.close();
}
}
自定义序列化
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class TestSerializable {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
// 创建 ObjectOutputStream 输出流对象, 传入一个 FileOutputStream 对象, 指定对象字节流的输出位置
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D:\\javatest\\student.txt"));
Student s1 = new Student("Zz-feng", "1001", 18, "香波地岛");
//通过 writeObject() 将对象的字节流写入文件
out.writeObject(s1);
out.writeObject(s1);
//关闭输出流
out.close();
// 创建 ObjectOutputStream 输出流对象, 传入一个 FileInputStream 对象, 指定需要反序列化的文件
ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:\\javatest\\student.txt"));
//通过 readObject() 将以序列化的对象恢复
Student read_s1 = (Student)in.readObject();
System.out.println(read_s1); // Student [name=Zz-feng, num=1001]
System.out.println(read_s1.getAge()); // 0
System.out.println(read_s1.getAddress()); // null
//关闭输入流
in.close();
}
}
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
//创建一个 Student 类实现 Serializable 接口
public class Student implements Serializable{
private String name;
private String num;
private transient int age;
private transient String address;
//自定义序列化
private void writeObject(ObjectOutputStream out) throws IOException {
//只序列化以下3个成员变量
out.writeObject(name);
out.writeInt(age);
//写入反序后的信息,当然我们也可以使用其他加密方式。这样别人打开文件,看到的就不是真正的信息,更安全。
out.writeObject(new StringBuffer(address).reverse());
}
//自定义反序列化。注意:read()的顺序要和write()的顺序一致。比如说序列化时写的顺序是name、age、address,反序列化时读的顺序也要是name、age、address
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
//readObject()返回的是Object,要强制类型转换
this.name= (String)in.readObject();
this.name=(String)in.readObject();
//反序才得到真正的信息
StringBuffer pwd=(StringBuffer)in.readObject();
this.address=pwd.reverse().toString();
}
}
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Student s1=new Student ("张三", "1234", 18, "空岛");
//序列化
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("./obj.txt"));
//调用我们自定义的writeObject()方法
out.writeObject(s1);
out.close();
//反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream("./obj.txt"));
//调用自定义的readObject()方法
Student s = (Student )in.readObject();
in.close()
//测试
System.out.println(s.getAge()); //18
System.out.println(s.getName()); //张三
System.out.println(s.getAddress()); //空岛
}
}
//implements Serializable
class User implements Serializable{
private int id;
private String name;
private String password;
public User(int id,String name,String password){
this.id=id;
this.name=name;
this.password=password;
}
//......
private Object writeReplace(){
String info="请编号为"+id+"的客户"+name+"到1号柜台办理业务。";
return info;
}
}
//implements Serializable
class User implements Serializable{
private int id;
private String name;
private String password;
//......其他成员变量
public User(int id,String name,String password){
this.id=id;
this.name=name;
this.password=password;
}
//...........
//用指定的对象替换掉反序列化读取的对象
private Object readResolve(){
String info="请编号为"+id+"的客户"+name+"到1号柜台办理业务。";
return info;
}
}
Externalizable:强制自定义序列化
public interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException; //调用writeObject()时,会自动调用此方法来序列化对象
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException; //调用readObject()时,会自动调用此方法来反序列化
}
//implements Externalizable
class User implements Externalizable{
private int id;
private String name;
private String password;
//......其他成员变量
//必须要有无参的构造函数
public User(){
}
public User(int id,String name,String password){
this.id=id;
this.name=name;
this.password=password;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public String getPassword() {
return password;
}
//自定义序列化
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(id);
out.writeObject(name);
out.writeObject(password);
}
//自定义反序列化。注意读的顺序要和写的顺序一致。
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.id=in.readInt();
this.name=(String)in.readObject();
this.password=(String)in.readObject();
}
}
两种序列化对比
实现Serializable接口
实现Externalizable接口
系统自动存储必要的信息,有没有无参构造函数都行
程序员决定存储哪些信息,不需要有无参构造函数
Java内建支持,易于实现,只需要实现该接口即可,无需任何代码支持
必须实现接口内的两个方法
性能略差
性能略好
版本控制——serialVersionUID
参考资料