概念
- 序列化:把Java对象转换为字节序列的过程。
- 反序列化:把字节序列恢复为Java对象的过程。
主要用途:
- 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;(持久化对象)
- 在网络上传送对象的字节序列。(网络传输对象)
实现方法
- 简单的序列化和反序列化(实现Serializable接口)
import java.io.*;
// case 1 normal Serializable
class Data implements Serializable {
private String name;
private int id;
Data(String name, int id) {
System.out.println("Data Constructor called, " + "name = " + name + ", id = " + id);
this.name = name;
this.id = id;
}
@Override
public String toString() {
return "Data toString called," + "name = " + name + ", id = " + Integer.toString(id);
}
}
class NormalSerializable {
interface StreamManager {
OutputStream GetOutPutStream();
InputStream GetInputStream();
}
static class FileStream implements StreamManager {
@Override
public OutputStream GetOutPutStream() {
FileOutputStream out = null;
try {
out = new FileOutputStream("data.file");
} catch (Exception e) {}
return out;
}
@Override
public InputStream GetInputStream() {
FileInputStream in = null;
try {
in = new FileInputStream("data.file");
} catch (Exception e) {}
return in;
}
}
static class BufferStream implements StreamManager {
private ByteArrayOutputStream buff = new ByteArrayOutputStream();
@Override
public OutputStream GetOutPutStream() {
return buff;
}
@Override
public InputStream GetInputStream() {
return new ByteArrayInputStream(buff.toByteArray());
}
}
void DataStorageTest(String desc, StreamManager streamManager) {
Data[] data = { new Data("demo1",1),
new Data("demo2",2)};
try (
ObjectOutputStream out = new ObjectOutputStream(streamManager.GetOutPutStream())
) {
out.writeObject(desc);
out.writeObject(data[0]);
out.writeObject(data[1]);
} catch (Exception e) {
e.printStackTrace();
}
try(
ObjectInputStream in = new ObjectInputStream(streamManager.GetInputStream())
) {
// -- 尝试注释掉下面两句,java会在运行时报错:class java.lang.String cannot be cast to class Data
String readDesc = (String)in.readObject();
System.out.println(readDesc);
Data data1 = (Data)in.readObject();
Data data2 = (Data)in.readObject();
System.out.println(data1);
System.out.println(data2);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String... args) {
// 文件存储
new NormalSerializable().DataStorageTest("file data storage",
new NormalSerializable.FileStream());
// 缓冲区存储
new NormalSerializable().DataStorageTest("buffer data storage",
new NormalSerializable.BufferStream());
}
}
输出:
Data Constructor called, name = demo1, id = 1
Data Constructor called, name = demo2, id = 2
file data storage
Data toString called,name = demo1, id = 1
Data toString called,name = demo2, id = 2
Data Constructor called, name = demo1, id = 1
Data Constructor called, name = demo2, id = 2
buffer data storage
Data toString called,name = demo1, id = 1
Data toString called,name = demo2, id = 2
说明:
- 可以使用缓冲区保存序列化对象,也可以使用文件保存序列化对象。
- 同一个文件/缓存中,序列化和反序列化的顺序必须相同,否则会报错/强转错。
- 序列化一个对象,会自动序列化其引用的对象,从而完成一个对象网的序列化。
- 从输出可以看出,反序列化完全是序列化保存的对象的字节码翻译过程,并不存在对象的重新构造过程。
- 控制对象的某一部分被序列化(实现Externalizable接口)
Externalizable 接口继承了 Serializable 接口,同时增添了两个方法:writeExternal() 和 readExternal()。这两个方法会在序列化和反序列化还原的过程中被自动调用,以便执行一些特殊操作。
使用场景:例如,也许要考虑特殊的安全问题,而且你不希望对象的某一部分被序列化;或者一个对象被还原以后,某子对象需要重新创建,从而不必将该子对象序列化。
import java.io.*;
class Data2 implements Externalizable {
private String name;
private int id;
public Data2() { // - 必须是public ,否则反序列化是会报错
System.out.println("No arg constructor called");
}
Data2(String name, int id) {
this.name = name;
this.id = id;
}
@Override
public String toString() {
return "Data toString called," + "name = " + name + ", id = " + Integer.toString(id);
}
@Override
public void readExternal(ObjectInput in) {
try {
name = (String) in.readObject();
id = (int)in.readObject() + 1;
} catch (Exception e) {}
}
@Override
public void writeExternal(ObjectOutput out) {
try {
out.writeObject(name);
out.writeObject(id);
} catch (Exception e) {}
}
}
public class ExternalizableClass {
public static void main(String... args) {
Data2 data = new Data2("hello", 1);
System.out.println(data);
try(
ObjectOutputStream o = new ObjectOutputStream(
new FileOutputStream("data.file"))
) {
System.out.println("Saving object:");
o.writeObject(data);
} catch(IOException e) {
throw new RuntimeException(e);
}
System.out.println("Recovering object:");
try(
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("data.file"))
) {
data = (Data2)in.readObject();
System.out.println(data);
} catch(IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
输出:
Data toString called,name = hello, id = 1
Saving object:
Recovering object:
No arg constructor called
Data toString called,name = hello, id = 2
上面的例子实现了序列化反序列化后,保持对象的name不变,id增加1。可以这样理解Externalizable的作用,通过Externalizable序列化的对象,java会在序列化时按照用户在writeExternal()中的实现,采用Serializable的方式序列化其内部成员(按照字节存储和翻译)。而在反序列化时,会调用无参构造函数
先创建一个默认的对象,然后根据用户readExternal()的实现再对成员进行赋值。因此这一切就全在用户的掌握中,相对更灵活一些。
- 控制对象的某一部分不被序列化(transient 关键字)
有时候,你需要对象的大部分按照Serializable方式序列化,但还想控制其中的个别信息不要被序列化(例如密码,你不想它被序列化后在网络传输的过程中被截获)。当然你可以使用Externallizable接口实现,但是你需要对大部分做特殊处理,这样做显然是不划算的。合理的做法是,使用Serializable接口满足大部分序列化要求,然后使用transient 关键字来控制个别字段。
import java.io.*;
// 父类必须也是Serializable的实现,才能保证父类的成员被自动序列化,否则不会被序列化,需要按照Externalizable的机制手动实现。
abstract class Data3 implements Serializable {
String name;
int id;
public Data3() {
System.out.println("No arg constructor called");
}
abstract String GetPassWord();
Data3(String name, int id) {
this.name = name;
this.id = id;
}
@Override
public String toString() {
return "this.toString called," + "name = " + name + ", id = " + Integer.toString(id) + ", password = " + GetPassWord();
}
}
class TransientSerializableData3 extends Data3 implements Serializable {
private transient String password;
public TransientSerializableData3() {}
public TransientSerializableData3(String name, int id) {
super(name, id);
}
public void SetPassWord(String password) {
this.password = password;
}
@Override
String GetPassWord() {
return password;
}
}
class ExernalizableData3 extends Data3 implements Externalizable {
private String password;
@Override
String GetPassWord() {
return password;
}
public ExernalizableData3() {
}
ExernalizableData3(String name, int id) {
super(name, id);
}
public void SetPassWord(String password) {
this.password = password;
}
@Override
public void readExternal(ObjectInput in) {
try {
name = (String) in.readObject();
id = (int)in.readObject() + 1;
} catch (Exception e) {}
}
@Override
public void writeExternal(ObjectOutput out) {
try {
out.writeObject(name);
out.writeObject(id);
} catch (Exception e) {}
}
}
public class TransientKeyWord {
static void ExernalizableData3Test() {
System.out.println("\n===============ExernalizableData3Test================");
ExernalizableData3 data = new ExernalizableData3("hello", 1);
data.SetPassWord("123");
System.out.println(data);
try(
ObjectOutputStream o = new ObjectOutputStream(
new FileOutputStream("data.file"))
) {
System.out.println("Saving object:");
o.writeObject(data);
} catch(IOException e) {
throw new RuntimeException(e);
}
System.out.println("Recovering object:");
try(
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("data.file"))
) {
data = (ExernalizableData3) in.readObject();
System.out.println(data);
data.SetPassWord("456");
System.out.println(data);
} catch(IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
static void TransientSerializableData3Test() {
System.out.println("\n===============TransientSerializableData3================");
TransientSerializableData3 data = new TransientSerializableData3("hello", 1);
data.SetPassWord("123");
System.out.println(data);
try(
ObjectOutputStream o = new ObjectOutputStream(
new FileOutputStream("data.file"))
) {
System.out.println("Saving object:");
o.writeObject(data);
} catch(IOException e) {
throw new RuntimeException(e);
}
System.out.println("Recovering object:");
try(
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("data.file"))
) {
data = (TransientSerializableData3) in.readObject();
System.out.println(data);
data.SetPassWord("456");
System.out.println(data);
} catch(IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
public static void main(String... args) {
ExernalizableData3Test();
TransientSerializableData3Test();
}
}
输出:
===============ExernalizableData3Test================
this.toString called,name = hello, id = 1, password = 123
Saving object:
Recovering object:
No arg constructor called
this.toString called,name = hello, id = 2, password = null
this.toString called,name = hello, id = 2, password = 456
===============TransientSerializableData3================
this.toString called,name = hello, id = 1, password = 123
Saving object:
Recovering object:
this.toString called,name = hello, id = 1, password = null
this.toString called,name = hello, id = 1, password = 456
从例子可以看出,只特殊处理password一个字段,显然使用Serializable中加transient关键字的方法比使用Exernalizable方式实现更简单。
有趣的是,你可以尝试将Data3声明处去掉对Serializable的实现,当反序列化TransientSerializableData3的对象时,竟然调用了Data3的构造函数,这说明此时对Data3的反序列化,不是采用Serializable方式的,但是仍然尝试去构造Data3的对象(对于这种继承关系,此时Data3中的两个成员是什么状态?到底在序列化文件中有没有存储?待研究)。
-
序列化对象保存在xml中
-
Preference
这种方式不常用,它不需要我们自己指定我们存储到某个文件/流中,而是自动帮我们存储。因此使用这种方式进行序列化,只能存储基本类型和字符串等小的受限的数据,且存储长度不能超过8k。典型的使用场景:存储和读取用户偏好以及程序配置项的设置。
注意点
- 恢复对象的地方(另一个程序或者另一台计算机),除了要拿到对象的序列化文件外,还需要拿到对象的class定义(即.class文件),否则会报ClassNotFoundException。
序列化跟hibernate的区别:
对象序列化可以实现轻量级持久性(lightweight persistence),“持久性”意味着一个对象的生存周期并不取决于程序是否正在执行它可以生存于程序的调用之间。通过将一个序列化对象写入磁盘,然后在重新调用程序时恢复该对象,就能够实现持久性的效果。之所以称其为“轻量级”,是因为不能用某种"persistent"(持久)关键字来简单地定义一个对象,并让系统自动维护其他细节问题(尽管将来有可能实现)。相反,对象必须在程序中显式地序列化(serialize)和反序列化还原(deserialize),如果需要个更严格的持久性机制(有完善的框架支持以屏蔽人工显示的进行对象序列化和反序列化),可以考虑像 Hibernate 之类的工具。
Serializable和反射的关系
附:序列化后保存的文件里面是什么内容?
java序列化的文件是二进制字节码,无法直接查看其内容。
网友评论