听到“串行化”一词时,想到的第一个问题是...
“什么是序列化?” 我们知道我们可以用Java创建可重用的对象。 但是,这些对象的生存期仅在Java虚拟机运行时才持续。 一旦关闭JVM,我们将丢失所有这些对象。
如果有人希望在JVM重新启动后可以使用对象,该怎么办? 这就是序列化的作用。
序列化是将对象的当前状态存储到流中的过程。 该流充当对象的容器。 稍后,我们可以从该流中还原等效对象。
序列化或外部化
流容器可以是以下两种类型之一:
- 瞬态(基于RAM)或
- 持久(基于磁盘)。
瞬态存储用于创建对象以从一台计算机传输到另一台计算机。
持久存储允许在当前会话结束后使用对象。
有两种方法可以序列化类对象:
(i)通过实现Serializable接口:
public class MyClass implements Serializable {
........
........//your code goes here
}
(ii)通过实现Externalizable接口:
public class MyClass implements Externalizable {
........
........//your code goes here
}
接口
现在,有关Serializable接口的一些重要事实:
- Serializable接口本身没有任何方法。 相反,当一个类实现Serializable时,它告诉JVM可以使用持久流容器存储该类。
- 此接口依靠Java的运行时默认机制来保存对象的状态。
- 通过类ObjectOutputStream的writeObject()方法(或在objectOutput接口中)将对象写入流中。
- 为了写入原始值,我们可以使用write <datatype>()方法。
- 读取对象是通过ObjectInputStream类的readObject()方法完成的。
- 序列化类的所有子类本身都已序列化。
有关Externalizable接口的一些重要事实:
- 与Serializable接口不同,Externalizable接口指定实现类将自行处理序列化,而不是依赖默认的运行时机制。
- 请注意,这意味着您有责任确保以正确的顺序保存所有数据。
Externalizable声明两个方法:
writeExternal()
用于将您的类写入流容器。
与实现接口的任何类一样,您的类必须定义此方法。
定义此方法时,请使用ObjectOutputStream类及其方法来简化流的编写。
readExternal()
用于从流容器读取序列化的类。
与实现接口的任何类一样,您的类必须定义此方法。
定义此方法时,请使用ObjectInputStream类及其方法来促进从流中读取。
现在,通过一些示例来了解所有这些内容。 在给定的类下面是Serialize类的示例...
import java.io.*;
public class SerializeClass implements Serializable {
private double i = 0;
private String str = null;
SerializeClass() {
i = 49.90;
str = "MySerializeClass Object";
}
public double getDoubleValue(){
return i;
}
public String getStringValue(){
return str;
}
}
Serializabe接口是Java IO包的一部分。
实现Serializabe接口的类必须声明为public(其原因很容易理解)。
该类的所有数据成员必须是原始的或可序列化的对象本身。我们可以轻松地将该类的对象保存到流中,例如:
String path = "e:/folder/file"//set path u want.
FileOutputStream fos = new FileOutputStream(path);//Creating a stream for writing.
ObjectOutputStream oos = new ObjectOutputStream(fos);//Creating a object to write to the file
SerializeClass serializeObject = new SerializeClass();//SerializeClass Object
oos.writeObject(serializeObject);
oos.flush();//forces the data to get written to the stream.
将对象写入文件后,以后您可以从文件中取回该对象,并使用附加到该对象的值。
可以这样完成:
FileInputStream fis = new FileInputStream(path);
ObjectInputStream ois = new ObjectInputStream(fis);
serialObject = (SerializeClass)ois.readObject();
您可以使用此对象访问附加值:-
System.out.println(serialObject.getStringValue());
System.out.println(serialObject.getDoubleValue());
Externalizable接口示例:
import java.io.*;
public class ExternalizeClass implements Externalizable {
private double i = 0;
private String str = null;
ExternalizeClass() {
i = 99.0;
str = "MyExternalize Object";
}
public double getDoubleValue(){
return i;
}
public String getStringValue(){
return str;
}
//we must define these methods
public void writeExternal(ObjectOutput out) throws IOException {
out.writeDouble(this.i);
out.writeObject(this.str);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.i = in.readDouble();
this.str = (String)in.readObject();
}
}
与我们对SerializeClass对象所做的操作相同,我们可以对ExternalizeClass对象进行操作以从文件进行读写。
在这里,我们必须使用writeExternal()和readExternal()方法。
我们已经学习了如何向流中写入和读取序列化对象。 如果要防止某些字段存储在序列化对象中,那该怎么办? 通过在变量声明中的数据类型之前放置关键字transient,可以很容易地做到这一点。 静态字段未序列化(写出),因此无法序列化(读回)。
完成上述任务的另一种方法是重写writeObject()和readObject()方法。 在这些方法的定义中,您可以控制不写入和读取哪个字段。
对于Externalize接口,由于必须同时将readExternal()和writeExternal()方法都声明为公共方法,因此这增加了对象可以使用它们确定序列化对象状态的风险。 因此,在使用此接口保存对象数据时应格外小心。
serialVersionUID当对象存储了很长时间(恢复时)会发生什么?
它发现它的格式已被该类的新的不同版本所取代? 读取流考虑了任何此类差异。 目的是Java类的较新版本应能够与同一类的较旧表示形式进行互操作,只要该类结构没有某些更改即可。
因此,无论是在运行时还是在反序列化时,我们都会检查向后兼容性。
可以使用版本号指定对类的更改。 特定班级
变量serialVersionUID(代表流唯一标识符或SUID)可用于指定可以反序列化的类的最早版本。 SUID声明如下:
static final long serialVersionUID = 2L;
该声明表明版本2可以追溯到此类为止。
它与该类的版本1编写的对象不兼容。
如果遇到版本1对象,它将抛出InvalidClassException。
如果未明确定义SUID,则将生成默认的SUID。 这个预设
SUID是散列或唯一数值,使用类计算
名称,接口,字段和方法。 如何在以下位置获取类的SUID
运行时确定兼容性? 您可以这样获得:
ObjectSteamClass myObject = ObjectStreamClass.lookUp(class.forName("Myclass");
long SUID = myObject.getSerialVersionUID();
这两行是做什么的?
他们向虚拟机查询信息
关于流中表示的类的信息,请使用该类的方法
ObjectStreamClass。
您如何确定可以在类版本之间进行哪些更改?
这些更改可能会影响对象的还原:
分别删除字段,或将其从非静态或非瞬态更改为静态或瞬态。
更改类在层次结构中的位置。
更改原始字段的数据类型。
将类的接口从“可序列化”更改为“可外部化”(反之亦然)。
这些对类的更改是可以接受的:
添加字段,这将导致还原时将默认值(基于数据类型)分配给新字段。
由于类结构信息包含在流中,因此添加类仍将允许创建所添加类的对象。 但是,其字段将设置为默认值。
添加或删除writeObject()或readObject()方法。
更改字段的访问修饰符(公共,私有等),因为仍然可以为该字段分配值。
将字段分别从静态或瞬态更改为非静态或非瞬态。
为了理解上面给出的概念,请尝试在上面给出的Serializable类中添加一个变量。
static final long serialVersionUID = 1L;
添加两个新变量,例如。
private double j = 77.7; //&
private String str1 = "New String";
尝试通过序列化类对象访问这些变量。
它将为您提供这些对象的默认值,即,对于双精度对象为0,对于字符串为null,并且不会引发任何异常。
但是如果您将varialbe serialVersionUID更改为
2L,它将不会读取这两个新变量,并且将引发InvalidClassException。
From: https://bytes.com/topic/java/insights/703298-author-madhoriya22-serialization