序列化高级认识
1概述
两个进程进行远程通信时,彼此可以发送各种类型的数据,包括文本\图片\音频\视频等,都会以二进制序列的形式在网络上传送.
当两个java进程进行通信时,一个进程能否把一个java对象发送给另一个进程呢?
1.1
如何才能做到呢
1)发送方需要把这个java对象转换为字节序列,才能在网上传送
2)接收方需要把字节序列再恢复为java对象.
把java对象转换为字节序列的过程称为对象的序列化,
.
把字节序列恢复为java对象的过程称为对象的反序列化.
2jdk类库中的序列化API
2.1
java.io.ObjectOutputStream:代表对象输出流它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
Java.io.ObjectInputStream代表对象输入流,
它的readObject()方法从一个源输入流中读取字节,再把它们反序列化成一个对象,并将其返回。
2.2
哪些类可以被序列化呢?
只有实现了Serializable或Externalizable接口的类的对象才能被序列化,
否则ObjectOutputStream的writeObject(Object obj)方法会抛出IOException。
实现了Serializable或Externalizable接口的类也称为可序列化类。
Externalizable接口继承Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为。
而仅实现Serializable接口的类可以采用默认的序列化方式。
Jdk的部分类 如String\Date等都实现了Serializable接口
3.1
序列化步骤:
》创建一个对象输出流,它可以包装一个其他类型的目标输出流,
如文件输出流:
ObjectOutputStream out = new ObjectOutputStream(new fileOutputStream(“D:\\objectfile.obj”));
》通过对象输出流的writeObject()方法写对象,
如:
Out.writeObject(“hello”);
Out.writeObject(new Date());
例:
import java.io.*;
import java.util.*;
public class ObjectSaver{
public static void main(String agrs[]) throws Exception {
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("D:\\objectFile.obj"));
String obj1="hello";
Date obj2=new Date();
Customer obj3=new Customer("Tom",20);
//序列化对象
out.writeObject(obj1);
out.writeObject(obj2);
out.writeObject(obj3);
out.writeInt(123);
out.close();
反序列化步骤:
》创建一个对象输入流,它可以包装一个其他类型的源输入流,
如文件输入流:
ObjectInputStream in = new ObjectInputStream (new fileIutputStream(“D:\\objectfile.obj”));
》通过对象输入流的readObject()方法读取对象,
如:
String obj1=(String)in.readObject();
Date obj2=(Date)in.readObject();
注意:
为了能正确读取数据,必须保证向对象输出流写对象的顺序与从对象输入流读对象的顺序一致
例:
ObjectInputStream in=new ObjectInputStream(new FileInputStream("D:\\objectFile.obj"));
String obj11 = (String)in.readObject();
System.out.println("obj11:"+obj11);
System.out.println("obj11==obj1:"+(obj11==obj1));
Date obj22 = (Date)in.readObject();
System.out.println("obj22:"+obj22);
System.out.println("obj22==obj2:"+(obj22==obj2));
Customer obj33 = (Customer)in.readObject();
System.out.println("obj33:"+obj33);
System.out.println("obj33==obj3:"+(obj33==obj3));
int var= in.readInt();
System.out.println("var:"+var);
in.close();
4:
注意事项:
1.
序列化的ID要一致,
private static final long serialVersionUID = 1L;
2.
序列化不保存静态变量
因为序列化保存的是对象的状态,静态变量属于类的状态,因此序列化并不保存静态变量
3.父类的序列化与Transient关键字
要想让父类序列化就要也实现Serializable接口,或者需要默认的无参构造函数
以为反序列化时,为了构造父类对象,值能调用父类的无参构造函数作为默认的父对象,因此当我们取父类对象的变量值时,它的值是调用父类无参构造函数后的值,如果你考虑到这种情况,那么在无参构造函数里就要符默认值,否则就是0或null。
Transient关键字的作用是控制变量的序列化,在变量声明前加上关键字,可以阻止该变量被序列化到文件中,在被序列化后,transient变量的值被设为初始值0或null
4.对敏感字段加密
用户自定义的writeObject()和readObject()方法可以运行控制序列化的过程,比如可以再序列化的过程中动态改变序列化的数值:
列:
public class EncryptTest implements Serializable {
private static final long serialVersionUID=1L;
private String password = "pass";
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public void readExternal(ObjectInputStream in) throws IOException,
ClassNotFoundException {
GetField readfileds=in.readFields();
Object object=readfileds.get("password", "");
System.out.println("要解密的字符串:"+object.toString());
password="pass";//模拟解密,需要获取本地的密钥
}
public void writeExternal(ObjectOutputStream out) throws IOException {
PutField putfileds=out.putFields();
System.out.println("原密码:"+password);
password="encryption";//模拟加密
putfileds.put("password",password);
System.out.println("加密后的密码"+password);
out.writeFields();
}
public static void main(String[] args) {
try {
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("result.obj"));
out.writeObject(new EncryptTest());
out.close();
ObjectInputStream in=new ObjectInputStream(new FileInputStream("result.obj"));
EncryptTest t=(EncryptTest) in.readObject();
System.out.println("解密后的字符串:"+t.getPassword());
in.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
5.java序列化具有特定的存储规则:
当写入文件为同一个对象时,并不会在将对象的内容进行存储,而只是再次存储一份引用,从而增加5个字节,反序列化时,恢复引用关系,使得清单指向唯一对象,节省了空间
例:
public class Savexulihua implements Serializable {
public static void main(String[] args) {
try {
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("result.obj"));
Savexulihua save = new Savexulihua();
//试图将对象两次两次写人文件
out.writeObject(save);
out.flush();
System.out.println(new File("result.obj").length());
out.writeObject(save);
out.close();
System.out.println(new File("result.obj").length());
ObjectInputStream in = new ObjectInputStream(new FileInputStream(
"result.obj"));
try {
//从文件中依次读出两个文件
Savexulihua save1 = (Savexulihua) in.readObject();
Savexulihua save2 = (Savexulihua) in.readObject();
//判断是否是同一个地址
System.out.println("save1==save2??"+(save1==save2));
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}