将java对象序列化为二进制文件,在大部分情况下,开发人员只需要了解被序列化的类需要实现 Serializable 接口,使用 ObjectInputStream 和 ObjectOutputStream 进行对象的读写。序列化 ID 问题:情境:两个客户端 A 和 B 试图通过网络传递对象数据,A 端将对象 C 序列化为二进制数据再传给 B,B 反序列化得到 C。问题:C 对象的全类路径假设为 com.inout.Test,在 A 和 B 端都有这么一个类文件,功能代码完全一致。也都实现了 Serializable 接口,但是反序列化时总是提示不成功。解决:虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)。清单 1 中,虽然两个类的功能代码完全一致,但是序列化 ID 不同,他们无法相互序列化和反序列化。
public class Test implements Serializable {
private static final long serialVersionUID =1L;
private static int staticVar = 5;
private String passWord = "pass";
private int i;
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
/*private void writeObject(ObjectOutputStream out){
try {
String passWord = "pass";
ObjectOutputStream.PutField putFields= out.putFields();
System.out.println("原密码:"+passWord);
passWord = "encryption";//模拟加密
putFields.put("passWord",passWord);
System.out.println("加密后的密码:"+passWord);
out.writeFields();
} catch (Exception e) {
e.printStackTrace();
}
}*/
/*private void readObject(ObjectInputStream in){
try {
ObjectInputStream.GetField readFiles = in.readFields();
Object object =readFiles.get("passWord","");
System.out.println("要解密的字符串:" + object.toString());
String password = "pass";//模拟解密,需要获得本地的密钥
setPassWord(password);
} catch (Exception e) {
e.printStackTrace();
}
}*/
/**
* 静态变量序列化
* 序列化时,并不保存静态变量,序列化保存的是对象的状态,静态变量属于类的状态,
* 因此序列化不保存静态变量
*/
@org.junit.Test
public void test1() throws IOException, ClassNotFoundException {
//初始时,staticVar为5
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("result.obj"));
out.writeObject(new Test());
out.close();
//序列化后修改为10
Test.staticVar=10;
ObjectInputStream oin = new ObjectInputStream(
new FileInputStream("result.obj"));
Test test = (Test) oin.readObject();
oin.close();
System.out.println("再读取"+test.staticVar);
}
/**
* 情境:服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,
* 进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。
* 解决: 在序列化过程中,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化,
* 如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。
* 用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。
* 基于这个原理,可以在实际应用中得到使用,用于敏感字段的加密工作
* RMI 技术是完全基于 Java 序列化技术的,服务器端接口调用所需要的参数对象来至于客户端,它们通过网络相互传输。这就涉及 RMI 的安全传输的问题。
* 一些敏感的字段,如用户名密码(用户登录时需要对密码进行传输),我们希望对其进行加密,这时,就可以采用本节介绍的方法在客户端对密 码进行加密,服务器端进行解密,确保数据传输的安全性
*/
@org.junit.Test
public void test2(){
try {
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("result.obj"));
out.writeObject(new Test());
out.close();
ObjectInputStream oin = new ObjectInputStream(
new FileInputStream("result.obj"));
Test test = (Test) oin.readObject();
System.out.println("解密后的字符串:" + test.getPassWord());
oin.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 序列化存储规则
* 对同一对象两次写入文件,打印出写入一次对象后的存储大小和写入两次后的存储大小,然后从文件中反序列化出两个对象,
* 比较这两个对象是否为同一对象。一 般的思维是,两次写入对象,文件大小会变为两倍的大小,反序列化时,由于从文件读取,
* 生成了两个对象,判断相等时应该是输入 false 才对,但是:我们看到,第二次写入对象时文件只增加了 5 字节,并且两个对象是相等的
* 解答:Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,
* 而只是再次存储一份引用,上面增加的 5 字节的存储空间就是新增引用和一些控制信息的空间。
* 反序列化时,恢复引用关系,使得清单 3 中的 t1 和 t2 指向唯一的对象,二者相等,输出 true。该存储规则极大的节省了存储空间
*/
@org.junit.Test
public void test3(){
try {
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("result.obj"));
Test test = new Test();
//试图将对象两次写入文件
out.writeObject(test);
out.flush();
System.out.println(new File("result.obj").length());
out.writeObject(test);
out.close();
System.out.println(new File("result.obj").length());
ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
"result.obj"));
//从文件依次读出两个文件
Test t1 = (Test) oin.readObject();
Test t2 = (Test) oin.readObject();
oin.close();
//判断两个引用是否指向同一个对象
System.out.println(t1 == t2);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 目的是希望将 test 对象两次保存到 result.obj 文件中,写入一次以后修改对象属性值再次保存第二次,
* 然后从 result.obj 中再依次读出两个对象,输出这两个对象的 i 属性值。
* 案例代码的目的原本是希望一次性传输对象修改前后的状态
* 结果:两个输出的都是 1, 原因就是第一次写入对象以后,第二次再试图写的时候,虚拟机根据引用关系知道已经有一个相同对象已经写入文件,
* 因此只保存第二次写的引用,所以读取时,都是第一次保存的对象。读者在使用一个文件多次 writeObject 需要特别注意这个问题
*/
@org.junit.Test
public void test4(){
try {
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("result.obj"));
Test test = new Test();
test.setI(1);
//test.i=1;
out.writeObject(test);
out.flush();
//test.i = 2;
test.setI(2);
out.writeObject(test);
out.close();
ObjectInputStream oin = new ObjectInputStream(
new FileInputStream("result.obj"));
Test t1 = (Test) oin.readObject();
Test t2 = (Test) oin.readObject();
System.out.println(t1.i);
System.out.println(t2.i);
} catch (Exception e) {
e.printStackTrace();
}
}
}