你好!今天我们继续介绍Java对象的序列化和反序列化。在上一讲中,我们介绍了Serialized标记接口,查看了其使用示例,还学习了如何使用瞬态关键字控制序列化过程。嗯,“管理流程”当然是一个很强烈的词。我们只有一个关键字、一个版本 ID,基本上就是这样。该过程的其余部分是“硬连线”在 Java 内部的,无法访问它。从方便的角度来看,这当然是好的。但一个程序员在工作中不应该只关注自己的舒适度,对吧?:) 还有其他因素需要考虑。因此,Serialized并不是Java中唯一的序列化-反序列化工具。今天我们将熟悉Externalized接口。但即使在我们继续研究它之前,您可能会有一个合理的问题:为什么我们需要另一个工具?
Serializable
我应付了我的工作,整个流程的自动执行不能不让我庆幸。我们看到的例子也不复杂。那么到底是怎么回事呢?为什么要使用另一个界面来完成基本相同的任务?事实是Serializable
它有很多缺点。让我们列出其中一些:
-
表现。该接口有
Serializable
很多优点,但高性能显然不是其中之一。
首先,内部机制Serializable
在运行过程中会产生大量的服务信息和各类临时数据。
其次(如果您有兴趣,您现在不必深入讨论,可以在闲暇时阅读),这项工作Serializable
基于使用 Reflection API。这个装置可以让你做一些在 Java 中看似不可能的事情:例如,更改私有字段的值。JavaRush 有一篇关于 Reflection API 的优秀文章,您可以在这里阅读。
-
灵活性。使用
Serializable
.一方面,这非常方便,因为如果我们并不真正关心性能,那么不编写代码的能力似乎很方便。但是,如果我们确实需要向序列化逻辑添加一些我们自己的功能(下面是其中之一的示例)怎么办?
本质上,我们所需要控制的过程就是一个关键字
transient
来排除一些数据,仅此而已。有点像“工具包”:/ -
安全。这一点部分源于上一点。
我们之前没有考虑过这个问题,但是如果你课堂上的某些信息不是为了“其他人的耳朵”(更准确地说,眼睛)而准备的怎么办?一个简单的例子是用户的密码或其他个人数据,在现代世界中受到一系列法律的监管。
使用
Serializable
,我们实际上无能为力。我们按原样序列化一切。但是,以一种好的方式,我们必须在将此类数据写入文件或通过网络传输之前对其进行加密。但
Serializable
它不给这个机会。
Externalizable
.
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class UserInfo implements Externalizable {
private String firstName;
private String lastName;
private String superSecretInformation;
private static final long SERIAL_VERSION_UID = 1L;
//...конструктор, геттеры, сеттеры, toString()...
@Override
public void writeExternal(ObjectOutput out) throws IOException {
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
}
}
正如您所看到的,我们做出了重大改变!主要的一个很明显:实现接口时,Externalizable
必须实现两个强制方法 -writeExternal()
和readExternal()
。正如我们之前所说,序列化和反序列化的所有责任都由程序员承担。不过,现在你可以解决这个过程缺乏控制的问题了!整个过程由您直接编程,这当然创建了更加灵活的机制。此外,安全问题也得到解决。正如您所看到的,我们的类中有一个字段:无法以未加密的方式存储的个人数据。现在我们可以轻松编写满足此约束的代码。例如,向我们的类添加两个简单的私有方法,用于加密和解密秘密数据。我们将它们写入文件并以加密形式从文件中读取它们。我们将按原样写入和读取其余数据:) 因此,我们的类将如下所示:
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Base64;
public class UserInfo implements Externalizable {
private String firstName;
private String lastName;
private String superSecretInformation;
private static final long serialVersionUID = 1L;
public UserInfo() {
}
public UserInfo(String firstName, String lastName, String superSecretInformation) {
this.firstName = firstName;
this.lastName = lastName;
this.superSecretInformation = superSecretInformation;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(this.getFirstName());
out.writeObject(this.getLastName());
out.writeObject(this.encryptString(this.getSuperSecretInformation()));
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
firstName = (String) in.readObject();
lastName = (String) in.readObject();
superSecretInformation = this.decryptString((String) in.readObject());
}
private String encryptString(String data) {
String encryptedData = Base64.getEncoder().encodeToString(data.getBytes());
System.out.println(encryptedData);
return encryptedData;
}
private String decryptString(String data) {
String decrypted = new String(Base64.getDecoder().decode(data));
System.out.println(decrypted);
return decrypted;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getSuperSecretInformation() {
return superSecretInformation;
}
}
我们已经实现了两种方法,它们使用与我们在讲座中已经遇到的 相同的ObjectOutput out
和作为参数。在正确的时间,我们加密或解密必要的数据,并以这种形式使用它来序列化我们的对象。让我们看看这在实践中会是什么样子: ObjectInput
Serializable
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
UserInfo userInfo = new UserInfo("Ivan", "Ivanov", "Ivan Ivanov's passport data");
objectOutputStream.writeObject(userInfo);
objectOutputStream.close();
}
}
在encryptString()
和方法中decryptString()
,我们专门向控制台添加了输出,以检查秘密数据将以何种形式写入和读取。 上面的代码将以下行输出到控制台: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh 加密成功! 该文件的完整内容如下所示: Øн sr UserInfoГ!}АџC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx 现在让我们尝试使用我们编写的反序列化逻辑。
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
UserInfo userInfo = (UserInfo) objectInputStream.readObject();
System.out.println(userInfo);
objectInputStream.close();
}
}
嗯,这里看起来没什么复杂的,应该可以用! 让我们运行... 线程“main”中的异常 java.io.InvalidClassException: UserInfo; 没有有效的构造函数 哎呀 :( 事实证明并不是那么简单!反序列化机制抛出了一个异常,并要求我们创建一个默认构造函数。我想知道为什么?Serializable
我们没有它就成功了... :/ 这里我们遇到了另一个重要的细微差别Serializable
和的区别Externalizable
不仅在于程序员的“扩展”访问和更灵活管理进程的能力,还在于进程本身,首先在反序列化机制上……使用时,Serializable
内存简单来说就是为一个对象分配,之后从流中读取值,填充它的所有字段。如果我们使用Serializable
,则不会调用对象构造函数!所有工作都是通过反射(Reflection API,我们在上一篇中简要提到过)完成的讲座)。在这种情况下,Externalizable
反序列化机制会有所不同。一开始,会调用默认的构造函数。然后才会UserInfo
在创建的对象上调用方法readExternal()
,该方法负责填充对象的字段。即为什么任何实现该接口的类都Externalizable
必须有一个默认构造函数。让我们将其添加到我们的类中UserInfo
并重新运行代码:
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
UserInfo userInfo = (UserInfo) objectInputStream.readObject();
System.out.println(userInfo);
objectInputStream.close();
}
}
控制台输出: Ivan Ivanov 的护照数据 UserInfo{firstName='Ivan', lastName='Ivanov', superSecretInformation='Ivan Ivanov 的护照数据'} 完全不同的事情!首先,解密后的带有秘密数据的字符串被输出到控制台,然后我们的对象以字符串格式从文件中恢复出来!这就是我们成功解决所有问题的方法:) 序列化和反序列化的主题看起来很简单,但正如你所看到的,我们的讲座结果很长。这还不是全部!使用这些界面时还有更多微妙之处,但为了让您的大脑不会因大量新信息而爆炸,我将简要列出一些更重要的要点并提供其他阅读材料的链接。那么您还需要了解什么? 首先,序列化时(无论使用Serializable
or还是Externalizable
),要注意变量static
。使用时,Serializable
这些字段根本不会序列化(因此,它们的值不会改变,因为static
这些字段属于类,而不是对象)。但是在使用的时候,Externalizable
你自己控制这个过程,所以从技术上来说这是可以做到的。但不建议这样做,因为这充满了微妙的错误。 其次,还应注意带有修饰符的变量final
。使用时,Serializable
它们像平常一样序列化和反序列化,但是使用时,不可能Externalizable
反序列化final
变量!原因很简单:所有final
字段都会在调用默认构造函数时初始化,之后它们的值就无法更改。因此,要序列化包含final
-fields 的对象,请通过Serializable
. 第三,当使用继承时,从某个类派生的所有继承类Externalizable
也必须具有默认构造函数。以下是一些有关序列化机制的好文章的链接:
再见!:)
GO TO FULL VERSION