Java序列化与反序列化
用途
1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中。
2) 在网络上传送对象的字节序列。
操作类
java.io.ObjectOutputStream
代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
java.io.ObjectInputStream
代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
步骤
对象序列化包括如下步骤:
1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
2) 通过对象输出流的writeObject()方法写对象。
对象反序列化的步骤如下:
1) 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
2) 通过对象输入流的readObject()方法读取对象。
前提
只有实现了Serializable和Externalizable接口的类的对象才能被序列化。Externalizable接口继承自 Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以 采用默认的序列化方式 。
serialVersionUID
字面意思上是序列化的版本号,凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量。
主要是辅助序列化和反序列化,序列化和反序列化的时候,serialVersionUID必须一致,反序列化才会成功。当然,如果不显式的声明 serialVersionUID,系统在进行序列化的时候会根据当前类的特征进行哈希运算,最终得到一个版本号。
序列化操作的时候系统会把当前类的serialVersionUID写入到序列化文件中,当反序列化时系统会去检测文件中的serialVersionUID,判断它是否与当前类的serialVersionUID一致,如果一致就说明序列化类的版本与当前类版本是一样的,可以反序列化成功,否则失败。
为了提高serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。
自动生成
IDEA下设置自动生成 serialVersionUID
输入:serialVer
这时候再来看看user对象,已经有黄色背景的警告了。
双击User对象,按 Alt + Enter 键,会弹出一个下面那样的小框,然后直接Enter确认即可。
接下来就会自动生成一个版本id
当然了,java也提供了方法让我们来自己生成这个版本号。
public static void main(String[] args) {
ObjectStreamClass osc = ObjectStreamClass.lookup(User.class);
long id = osc.getSerialVersionUID();
System.out.println("用户对象版本号:" + id);
}
运行结果与我们自动生成的值一样(因为对象并没有发生改变)
用户对象版本号:-6389397398684165955
生成原理
首先提前对象特征:类名,属性名,属性类型,方法等等。然后使用SHA摘要算法进行哈希运算。
感兴趣的可以去这个getSerialVersionUID()方法里面看看。
截图为证:
部分代码
序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
ObjectOutputStream oos = new ObjectOutputStream(bos);
//准备写入用户对象
oos.writeObject(user);
oos.flush();
//序列化后得到的字符串
String value = Hex.toHexString(bos.toByteArray());
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
反序列化
byte[] serialized = Hex.decode(value);
ByteArrayInputStream bis = new ByteArrayInputStream(serialized);
ObjectInputStream ois = new ObjectInputStream(bis);
User user = (User) ois.readObject();
实例
不实现序列化接口
import org.bouncycastle.util.encoders.Hex;
import java.io.*;
/**
* 序列化与反序列测试
*
* @author wzm
* @version 1.0.0
* @date 2020/1/27 18:21
**/
public class JavaSerializationTest {
static class User {
String name;
String sex;
int age;
User(String name, String sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
}
private static String save(User user) {
String value = "";
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos)) {
//准备写入用户对象
oos.writeObject(user);
oos.flush();
//序列化后得到的字符串
value = Hex.toHexString(bos.toByteArray());
System.out.println("用户:" + value);
} catch (IOException e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
return value;
}
private static User get(String userStr) {
try {
byte[] serialized = Hex.decode(userStr);
ByteArrayInputStream bis = new ByteArrayInputStream(serialized);
ObjectInputStream ois = new ObjectInputStream(bis);
return (User) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.getMessage());
throw new RuntimeException(e);
}
}
public static void main(String[] args){
User user = new User("小明", "男", 20);
String userStr = save(user);
User user1 = get(userStr);
System.out.println(user1);
}
}
报错如下:
排查错误的原因往往需要从根源着手。
java.io.NotSerializableException: fabric.edu.common.JavaSerializationTest$User
fabric.edu.common.JavaSerializationTest$User
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at fabric.edu.common.JavaSerializationTest.save(JavaSerializationTest.java:42)
at fabric.edu.common.JavaSerializationTest.main(JavaSerializationTest.java:69)
java.io.EOFException
null
at java.io.ObjectInputStream$PeekInputStream.readFully(ObjectInputStream.java:2681)
at java.io.ObjectInputStream$BlockDataInputStream.readShort(ObjectInputStream.java:3156)
at java.io.ObjectInputStream.readStreamHeader(ObjectInputStream.java:862)
at java.io.ObjectInputStream.<init>(ObjectInputStream.java:358)
at fabric.edu.common.JavaSerializationTest.get(JavaSerializationTest.java:58)
at fabric.edu.common.JavaSerializationTest.main(JavaSerializationTest.java:70)
Exception in thread "main" java.lang.RuntimeException: java.io.EOFException
at fabric.edu.common.JavaSerializationTest.get(JavaSerializationTest.java:63)
at fabric.edu.common.JavaSerializationTest.main(JavaSerializationTest.java:70)
Caused by: java.io.EOFException
at java.io.ObjectInputStream$PeekInputStream.readFully(ObjectInputStream.java:2681)
at java.io.ObjectInputStream$BlockDataInputStream.readShort(ObjectInputStream.java:3156)
at java.io.ObjectInputStream.readStreamHeader(ObjectInputStream.java:862)
at java.io.ObjectInputStream.<init>(ObjectInputStream.java:358)
at fabric.edu.common.JavaSerializationTest.get(JavaSerializationTest.java:58)
... 1 more
然后去源码里面看看(ObjectOutputStream.java:1184)
private void writeObject0(Object obj, boolean unshared)
throws IOException
{
boolean oldMode = bout.setBlockDataMode(false);
depth++;
try {
// handle previously written and non-replaceable objects
int h;
if ((obj = subs.lookup(obj)) == null) {
writeNull();
return;
} else if (!unshared && (h = handles.lookup(obj)) != -1) {
writeHandle(h);
return;
} else if (obj instanceof Class) {
writeClass((Class) obj, unshared);
return;
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
return;
}
// check for replacement object
Object orig = obj;
Class<?> cl = obj.getClass();
ObjectStreamClass desc;
for (;;) {
// REMIND: skip this check for strings/arrays?
Class<?> repCl;
desc = ObjectStreamClass.lookup(cl, true);
if (!desc.hasWriteReplaceMethod() ||
(obj = desc.invokeWriteReplace(obj)) == null ||
(repCl = obj.getClass()) == cl)
{
break;
}
cl = repCl;
}
if (enableReplace) {
Object rep = replaceObject(obj);
if (rep != obj && rep != null) {
cl = rep.getClass();
desc = ObjectStreamClass.lookup(cl, true);
}
obj = rep;
}
// if object replaced, run through original checks a second time
if (obj != orig) {
subs.assign(orig, obj);
if (obj == null) {
writeNull();
return;
} else if (!unshared && (h = handles.lookup(obj)) != -1) {
writeHandle(h);
return;
} else if (obj instanceof Class) {
writeClass((Class) obj, unshared);
return;
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
return;
}
}
// remaining cases
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
} finally {
depth--;
bout.setBlockDataMode(oldMode);
}
}
实现序列化接口
从抛出的异常就可以确定是没有实现序列化:因此对这个用户对象稍加修改:
static class User implements Serializable {
String name;
String sex;
int age;
User(String name, String sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
}
执行结果:
用户:aced00057372002c6661627269632e6564752e636f6d6d6f6e2e4a61766153657269616c697a6174696f6e546573742455736572e16c1276e8beef3b0200034900036167654c00046e616d657400124c6a6176612f6c616e672f537472696e673b4c000373657871007e0001787000000014740006e5b08fe6988e740003e794b7
User{name='小明', sex='男', age=20}
这样的话用户就可以正常序列化了,可以用任何可能的方式存储起来(持久化),然后再需要使用的时候再反序列化(恢复)出来。