序列化概念
对象序列化:将Java实体对象转化成二进制字节流的过程。
对象反序列化:将二进制字节流恢复为Java实体对象的过程。
主要用途
1. 持久化对象到磁盘
2. 网络传输
3. 对象的拷贝
序列化的实现
- 通过实现Serializable接口,java有默认的实现算法,我们也可以自定义序列化算法(用于实现一些特殊的需求),在反序列化时,不会调用该序列化的任何构造方法。
- 通过实现Externalizable接口,需要我们自己写序列化规则。
实现Serializable接口
package com.zd.test.java.ds;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class SerializableTest {
//首先我们用这个类来检验,探究一下序列化的算法。
static class Person implements Serializable{
/**
* 版本号
*/
private static final long serialVersionUID = -1285437413613384110L;
String name;
Integer age;
transient String address;
Dog dog;
public Person(String name, Integer age,String address,Dog dog) {
super();
this.name = name;
this.age = age;
this.address = address;
this.dog = dog;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", address="
+ address + ", dog=" + dog + "]";
}
}
static class Dog implements Serializable{
private static final long serialVersionUID = 5581802748286331206L;
String name;
public Dog(String name) {
super();
this.name = name;
}
}
//测试自定义的序列化方案
static class Man implements Serializable{
/**
* 版本号
*/
private static final long serialVersionUID = -1285437413613384110L;
String name;
Integer age;
transient String address;
Dog dog;
transient String password;
public Man(String name, Integer age,String address,String password,Dog dog) {
super();
this.name = name;
this.age = age;
this.address = address;
this.dog = dog;
this.password = password;
}
@Override
public String toString() {
return "Man [name=" + name + ", age=" + age + ", address="
+ address + ", dog=" + dog+", password=" + password + "]";
}
//重写持久化规则
private void writeObject(ObjectOutputStream out) throws IOException{
out.defaultWriteObject(); //执行默认的写对象方法,会把这当前中非静态成员变量和非transient成员变量写到流中
//在这里,我们把address这个属性先转换成大写,再持久化
if (address != null) {
out.writeUTF(address.toUpperCase());
}
if (password != null) {
out.writeUTF(new StringBuffer(password).reverse().toString());
}
//注意这里无需关闭流,否则会失败,这只是一个普通的回调方法,java底层调用完此方法之后还会处理其他的逻辑。
//out.close();
}
private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException{
in.defaultReadObject();
Object obj = in.readUTF();
if (obj != null) {
address = obj.toString();
}
obj = in.readUTF();
if (obj != null) {
password = new StringBuffer(obj.toString()).reverse().toString();
}
}
}
public static void main(String[] args) throws ClassNotFoundException, IOException {
//1.测试一般用法,顺便测试一下transient关键字
Person person = new Person("张三", 23, "北京市",new Dog("旺旺"));
//如果person里面的Dog类没有实现Serializable接口,会报异常:java.io.NotSerializableException: com.zd.test.java.ds.SerializableTest$Dog
Person p = deepCopy(person);
System.out.println(p);
//结果:Person [name=张三, age=23, address=null]
//结论:被transient关键字修饰的对象默认是不能够持久化的,但是我们可以重写持久化规则。
//测试重写测试化规则,持久化被transient修饰的成员变量,加密重要隐私信息
//需要按自定义方式序列化的成员变量,是否定义为transient类型无关紧要。
Man man = new Man("张三", 23, "北京市", "admin",new Dog("旺旺"));
Man m = deepCopy(man);
System.out.println(m);
}
public static <T> T deepCopy(T t) throws IOException, ClassNotFoundException{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(t);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
@SuppressWarnings("unchecked")
T clone = (T) objectInputStream.readObject();
return clone;
}
}
实现Externalizable接口
package com.zd.test.java.ds;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
public class ExternalizableTest {
static class Person implements Externalizable{
String name;
transient String password;
Integer age;
@Override
public void writeExternal(ObjectOutput out) throws IOException {
if (name != null) {
out.writeUTF(name);
}
if (password != null) {
out.writeUTF(new StringBuffer(password).reverse().toString());
}
if (age != null) {
out.writeInt(age);
}
}
@Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
Object obj = null;
if ((obj = in.readUTF()) != null) {
name = obj.toString();
}
if ((obj = in.readUTF()) != null) {
password = new StringBuffer(obj.toString()).reverse().toString();
}
if ((obj = in.readInt()) != null) {
age = (Integer) obj;
}
}
public Person(String name, String password, Integer age) {
super();
this.name = name;
this.password = password;
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", password=" + password + ", age="
+ age + "]";
}
//java.io.InvalidClassException: com.zd.test.java.ds.ExternalizableTest$Person; no valid constructor
public Person() { //给一个无参的构造器
}
}
public static void main(String[] args) throws Exception{
Person person = new Person("张三", "admin", 23);
Person person2 = deepCopy(person);
System.out.println(person2);
}
public static <T> T deepCopy(T t) throws IOException, ClassNotFoundException{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(t);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
@SuppressWarnings("unchecked")
T clone = (T) objectInputStream.readObject();
return clone;
}
}
补充:
默认序列化方式的不足
- 不宜对对象中对安全要求比较高的敏感数据进行序列化,安全性极低。
- 不会检查对象的成员变量是否合乎正确的约束条件。
- 默认的序列化方式需要对对象图(继承等关系)进行递归遍历,如果对象图很复杂,会消耗很多空间和时间,甚至引起Java虚拟机堆栈溢出。
总结
一般我们需要掌握的自定义序列化方式:
1. 实现Serializable接口,并且提供private的writeObject()和readObject()。
2. 实现Externalizable接口,实现writeExternal()和readExternal()方法,必须提供public无参的构造方法,否则会报异常,上面案例有涉及到。
3. 关于默认的序列化方案和自定义序列化方案,根据需求而定。
另外关于serialVersionUID,主要是为了兼容序列化类的变化,便于项目后期对序列化类的扩充和修改。
当然,还有很多的序列化方案,比如我们在java业务中前台和后台使用的json形式就是序列化的一种,它有着可以跨语言的优势,做过JavaEE的都知道怎么使用。
更多的序列化方案可以可以下https://github.com/eishay/jvm-serializers/wiki里面的,里面有详细比较。