1、Java 序列化
Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。
将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,也就是说,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象。
整个过程都是 Java 虚拟机(JVM)独立的,也就是说,在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象。
类 ObjectInputStream 和 ObjectOutputStream 是高层次的数据流,它们包含反序列化和序列化对象的方法。
ObjectOutputStream 类包含很多写方法来写各种数据类型,但是一个特别的方法例外:
public final void writeObject(Object x) throws IOException
上面的方法序列化一个对象,并将它发送到输出流。相似的 ObjectInputStream 类包含如下反序列化一个对象的方法:
public final Object readObject() throws IOException,
ClassNotFoundException
该方法从流中取出下一个对象,并将对象反序列化。它的返回值为Object,因此,你需要将它转换成合适的数据类型。
为了演示序列化在Java中是怎样工作的,我将使用之前教程中提到的User类,假设我们定义了如下的User类,该类实现了Serializable 接口。
public class User implements java.io.Serializable
{
public String name;
public String address;
public transient String passWord;
public int number;
.....
public static void main(String[] args) {
User u = new User();
u.setName("IT农民工");
u.setPassWord("123456");
u.setNumber(1264064998);
u.setAddress("XXXXX");
System.out.println("序列化之前-->姓名:"+u.getName()+" 密码:"+u.getPassWord());
try {
FileOutputStream fs = new FileOutputStream("/home/xjb/text.txt");
ObjectOutputStream os = new ObjectOutputStream(fs);
os.writeObject(u);
os.flush();
os.close();
FileInputStream fi = new FileInputStream("/home/xjb/text.txt");
ObjectInputStream is = new ObjectInputStream(fi);
u = (User) is.readObject();
is.close();
System.out.println("反序列化之后-->姓名:"+u.getName()+" 密码:"+u.getPassWord());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
运行结果:
序列化之前-->姓名:IT农民工 密码:123456
反序列化之后-->姓名:IT农民工 密码:null
请注意:
(1)、一个类的对象要想序列化成功,必须满足两个条件:
该类必须实现 java.io.Serializable 对象。
该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须使用关键字transient注明是短暂的。
(2)、readObject() 方法中的 try/catch代码块尝试捕获 ClassNotFoundException 异常。对于 JVM 可以反序列化对象,它必须是能够找到字节码的类。如果JVM在反序列化对象的过程中找不到该类,则抛出一个 ClassNotFoundException 异常。
这里,readObject() 方法的返回值被转化成 User 引用,当对象被序列化时,属性 passWord 的值为 123456,但是因为该属性是短暂的,该值没有被发送到输出流。所以反序列化后 User 对象的 passWord 属性为 null。
2关键字transient
在实际开发过程中,我们常常会遇到这样的问题,这个类的有些属性需要序列化,而其他属性不需要被序列化,打个比方,如果一个用户有一些敏感信息(如密码,银行卡号等),为了安全起见,不希望在网络操作(主要涉及到序列化操作,本地序列化缓存也适用)中被传输,这些信息对应的变量就可以加上transient关键字。换句话说,这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化,也就是上文提到的短暂的。
(1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
(2)transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
(3)被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化
第三点可能有点很迷惑,因为发现在User类中的name字段前加上static关键字后,程序运行结果依然不变,即static类型的name也读出来为[IT农民工]了,这不与第三点说的矛盾吗?实际上是这样的:第三点确实没错(一个静态变量不管是否被transient修饰,均不能被序列化),反序列化后类中static型变量name的值为当前JVM中对应static变量的值,这个值是JVM中的不是反序列化得出的:
public class User implements java.io.Serializable
{
private static String name;
private String address;
private transient String passWord;
private int number;
...
public static void main(String[] args) {
User u = new User();
u.setName("admin");
u.setPassWord("IT农民工");
u.setNumber(1264064998);
u.setAddress("XXXXX");
System.out.println("序列化之前-->姓名:"+u.getName()+" 密码:"+u.getPassWord());
try {
FileOutputStream fs = new FileOutputStream("/home/xjb/text.txt");
ObjectOutputStream os = new ObjectOutputStream(fs);
os.writeObject(u);
os.flush();
os.close();
//反序列化之前
User.name="IT农民工Test";
FileInputStream fi = new FileInputStream("/home/xjb/text.txt");
ObjectInputStream is = new ObjectInputStream(fi);
u = (User) is.readObject();
is.close();
System.out.println("反序列化之后-->姓名:"+u.getName()+" 密码:"+u.getPassWord());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
运行结果:
序列化之前-->姓名:IT农民工 密码:123456
反序列化之后-->姓名:IT农民工Test 密码:null
这说明反序列化后类中static型变量name的值为当前JVM中对应static变量的值,为修改后[IT农民工Test],而不是序列化时的值[IT农民工]。
注意:被transient关键字修饰的变量真的不能被序列化吗?
public class ExternalizableTest implements Externalizable{
private transient String text = "真的不会被序列化吗?";
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(text);
}
@Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
text = (String) in.readObject();
}
public static void main(String[] args) {
try {
ExternalizableTest test = new ExternalizableTest();
ObjectOutput out = new ObjectOutputStream(new FileOutputStream(new File("/home/xjb/text.txt")));
out.writeObject(test);
ObjectInput in = new ObjectInputStream(new FileInputStream(new File("/home/xjb/text.txt")));
test = (ExternalizableTest) in.readObject();
System.out.println(test.text);
out.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
运行结果:
真的不会被序列化吗?
因为在java中,对象的序列化可以通过实现两种接口来实现,若实现的是Serializable接口,则所有的序列化将会自动进行,若实现的是Externalizable接口,则没有任何东西可以自动序列化,需要在writeExternal方法中进行手工指定所要序列化的变量,这与是否被transient修饰无关。因此第二个例子输出的是变量text初始化的内容,而不是null。
参考来源:
菜鸟教程
Java transient关键字使用小记