Java对象是在Java虚拟机上生成的,如果需要远程传输或者保存在硬盘上,就要将java对象转换成可传输的文件流
作用:
把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中(持久化对象)
在Java中创建的java对象只有当Java虚拟机运行时才能存在,也就是这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存指定的对象(持久化对象),并在将来重新读取被保存的对象。
在网络上传送对象的字节序列。(网络传输对象)
网络通信时,无论是何种类型的数据,都会转成字节序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象
序列化: 将Java对象转换为字节序列的过程
反序列化: 将字节序列转回java对象的过程
对象序列化流: ObjectOutputStream
将Java对象的原始数据类型和图形写入OutputStream。可以使用ObjectInputStream读取(重构)对象。可以通过使用流的文件来实现对象的持久存储。
如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象
构造方法:
ObjectOutputStream(OutputStream out) //创建一个写入指定的OutputStream的ObjectOutputStream
序列化对象的方法:
void writeObject(Object obj) //将指定的对象写入ObjectOutputStream
注意:
一个对象要想被序列化,该对象所属的类必须必须实现Serializable接口,Serializable只是一个标记接口,实现该接口,不需要重写任何方法
import java.io.Serializable;
public class Student implements Serializable {
}
举个栗子:
将学生对象存储到文件中
首先我们定义一个学生类,类中有姓名和年龄两个成员变量,实现了Serializable接口
import java.io.Serializable;
public class Student implements Serializable {
private String name;
private int age;
//无参构造,带参构造自行脑补吧
}
然后在测试类中,创建学生对象写入文件
public static void main(String[] args) throws IOException {
//创建一个写入指定的OutputStream的ObjectOutputStream
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("E:\\IO_Practice\\bytes.txt"));
Student student1=new Student("小红",20); //创建学生对象
oos.writeObject(student1); //将学生对象写入ObjectOutputStream
oos.close();//释放资源
}
到这里我们就成功的把学生对象转换为字节序列保存到硬盘上了,也就是所说的持久化对象.
然后运行后你会发现所保存路径文件里的内容是酱:
嘿呀,这怎么不是20岁的小红同学呢,为啥是一堆乱码.???
因为我们刚才做的是把整个学生对象按照一个字节序列写入的,所以碳基生物是读不懂的.
那怎么读懂这玩意呢,解铃还须系铃人,我们要利用下面要说的 反序列化对象
对象反序列化流:ObjectInputStream
ObjectInputStream反序列化先前使用ObjectOutputStream编写的原始数据和对象
构造方法:
ObjectInputStream(InputStream in):创建从指定的InputStream读取的ObjectInputStream
反序列化对象的方法:
Object readObject): 从ObjectInputStream读取一个对象
知道这些我们就可以读取文件中已经被序列化的对象了
public static void main(String[] args) throws IOException, ClassNotFoundException {
//创建从指定的InputStream读取的ObjectInputStream
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("E:\\IO_Practice\\bytes.txt"));
Object obj=ois.readObject(); //从文件中读取一个对象
Student s= (Student) obj; //向下转型 创建学生对象s
System.out.println(s.getName()+" "+s.getAge());
ois.close();
}
前面简单学习了序列化流和反序列化流后,我们会遇到一些问题
.
用对象序列化流序列化了一个对象后,假如我们修改对象所属的类文件
,读取数据会不会出问题呢?
举个栗子
先创建并序列化一个学生对象
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("E:\\IO_Practice\\st_practice.txt"));
Student student=new Student("钢铁侠",40);
oos.writeObject(student);
oos.close();
然后在之前的学生类中新增了一个方法
public void show(){
System.out.println("锤灭霸");
}
反序列化对象并在控制台输出
// ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("E:\\IO_Practice\\st_practice.txt"));
// Student student=new Student("钢铁侠",40);
// oos.writeObject(student);
// oos.close();
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("E:\\IO_Practice\\st_practice.txt"));
Object obj=ois.readObject();
Student s=(Student) obj;
System.out.println(s.getName()+" "+s.getAge());
ois.close();
然后就报错了。。。。这个异常抛出的意思就是 类的串行版本与从流中读取的类的不匹配
为啥会报错嘞? 在控制台我们可以看到两个值
local class incompatible: stream classdesc serialVersionUID = 6193054719990965948
local class serialVersionUID = 2598365193762865032
解释一下serialVersionUID
(序列化ID):
序列化运行时与每个可序列化的类关联一个版本号,称为serialVersionUID,它在反序列化过程中使用,以验证序列化对象的发送者和接收者是否加戟了与序列化兼容的对象的类。 如果接收者已经为具有与对应发件人类别不同的serialVersionUID的对象加载了一个类,则反序列化将导致一个InvalidClassException(即类的串行版本与从流中读取的类的不匹配 )。
用人类的语言描述就是:
我们之前创建的序列化学生对象生成了一个序列化ID,是可以通过反序列化流读取的. 但是在调用反序列化流读取之前修改了类的信息,这个序列化ID就会随之发生改变,所以我们再去读取就会抛出异常.
那怎么解决这个问题呢??
给对象所属的类加一个serialVersionUID
一个可序列化的类可以通过声明一个名为"serialVersionUID"的字段来显式地声明它自己的serialVersionUID,该字段必须是static,final和long类型
:
所以我们可以给对象所属的类加一个serialVersionUID
private static final long serialVersionUID = 42L;
在这里要说一下
如果可序列化类没有显式声明serialVersionUID,则序列化运行时将根据Java ™对象序列化规范中所述的类的各个方面计算该类的默认serialVersionUID值。然而,强烈建以所有可序列化的类显式声明serialVersionUID值,因为默认的serialVersionUI计算对类细节非常敏感,这些细节可能因编译器实蕴而异, 因此可能会在反序列化期间导致意外的InvalidClassException 。因此,为了保证不同Java编译器实现之间的一致的seralVersionUD值, 一个可序列化的类必须声明一个显式的serialVersionUID值。还强烈建议,显式的serialVersionUID声明在可能的情况下使用private修饰符, 因为这种声明仅适用于立即声明的类-serialVersionUID字段作为继承成员无效。数组类不能声明一个显式的serialVersionUD,所以它们总是具有默认的计算值,但是对于数组类,放弃了匹配serialVersionUID值的要求。
如果一个对象中某个成员变量的值不想被序列化,怎么去实现?
只需在private后面加上一个transient
private transient int age;
这样就看不到钢铁侠的年龄了,只能看到初始赋值
所以给该成员变量加transient关键字修饰,关键字标记的成员变量就不会参与序列化过程.