序列化,反序列化和transient关键字

一、序列化和反序列化的概念

序列化:指把java对象转换为字节序列的过程。

反序列化:指把字节序列恢复为java对象的过程。

对象的序列化主要有两种用途:
  1) 把对象的字节序列保存到硬盘上,通常存放在一个文件中;
  2) 在网络上传送对象的字节序列。

1.当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

2.在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。

二、JDK类库中的序列化API

java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
  java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。

只有实现了Serializable接口或Externalizable接口的类的对象才能被序列化。
  对象序列化包括如下步骤:
  1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流,字节数组输出流;
  2) 通过对象输出流的writeObject()方法写对象。

  对象反序列化的步骤如下:
  1) 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流,字节数组输入流;
  2) 通过对象输入流的readObject()方法读取对象。

1、类未实现Serializable接口,进行序列化的范例:

定义一个未实现Serializable的类:User

 package com.paic.egis.smts.activity;

 public class User{
private String userId;
private String userName;
public String getUserId() {
return userId;
}
@Override
public String toString() {
return "User [userId=" + userId + ", userName=" + userName + "]";
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
} }

序列化:

 package com.paic.egis.smts.activity;

 import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; public class Test { public static void main(String[] args) throws Exception{
serialize();
User o = (User) deSerialize();
System.out.println(o.toString()); } public static void serialize() throws IOException{
User u = new User();
u.setUserId("11");
u.setUserName("df");
FileOutputStream fo = new FileOutputStream(new File("d://a.txt"));
ObjectOutputStream os = new ObjectOutputStream(fo);
os.writeObject(u);
} public static Object deSerialize() throws IOException, ClassNotFoundException {
FileInputStream fi = new FileInputStream(new File("d://a.txt"));
ObjectInputStream oi = new ObjectInputStream(fi);
return oi.readObject();
} }

运行结果如下:

序列化,反序列化和transient关键字

运行报错!

2、类实现Serializable接口,进行序列化和反序列化的范例:

定义一个实现Serializable的类:UserSerialize

 package com.paic.egis.smts.activity;

 import java.io.Serializable;

 public class UserSerialize implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private String userId;
private String userName;
public String getUserId() {
return userId;
}
@Override
public String toString() {
return "User [userId=" + userId + ", userName=" + userName + "]";
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
} }

序列化和反序列化:

 package com.paic.egis.smts.activity;

 import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; public class Test { public static void main(String[] args) throws Exception{
serialize();
UserSerialize o = (UserSerialize) deSerialize();
System.out.println(o.toString()); } public static void serialize() throws IOException{
UserSerialize u = new UserSerialize();
u.setUserId("11");
u.setUserName("df");
FileOutputStream fo = new FileOutputStream(new File("d://a.txt"));
ObjectOutputStream os = new ObjectOutputStream(fo);
os.writeObject(u);
} public static Object deSerialize() throws IOException, ClassNotFoundException {
FileInputStream fi = new FileInputStream(new File("d://a.txt"));
ObjectInputStream oi = new ObjectInputStream(fi);
return oi.readObject();
} }

运行结果如下:

序列化,反序列化和transient关键字

三、serialVersionUID的作用

serialVersionUID作用:序列化时为了保持版本的兼容性,即在版本升级时反序列化仍保持对象的唯一性。

有两种生成方式:

一个是默认的1L,比如:private static final long serialVersionUID = 1L;

一个是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:private static final long  serialVersionUID = xxxxL;

下面举例说明下:

还是上面说到的类:UserSerialize,把定义的serialVersionUID去掉。

 package com.paic.egis.smts.activity;

 import java.io.Serializable;

 public class UserSerialize implements Serializable{
// /**
// *
// */
// private static final long serialVersionUID = 1L;
private String userId;
private String userName;
public String getUserId() {
return userId;
}
@Override
public String toString() {
return "User [userId=" + userId + ", userName=" + userName + "]";
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
} }

序列化和反序列化:

 package com.paic.egis.smts.activity;

 import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; public class Test { public static void main(String[] args) throws Exception{
serialize();
UserSerialize o = (UserSerialize) deSerialize();
System.out.println(o.toString()); } public static void serialize() throws IOException{
UserSerialize u = new UserSerialize();
u.setUserId("11");
u.setUserName("df");
FileOutputStream fo = new FileOutputStream(new File("d://a.txt"));
ObjectOutputStream os = new ObjectOutputStream(fo);
os.writeObject(u);
} public static Object deSerialize() throws IOException, ClassNotFoundException {
FileInputStream fi = new FileInputStream(new File("d://a.txt"));
ObjectInputStream oi = new ObjectInputStream(fi);
return oi.readObject();
} }

运行结果:

序列化,反序列化和transient关键字

是成功的。

下面我们修改下UserSerialize类:添加一个熟悉sex

 package com.paic.egis.smts.activity;

 import java.io.Serializable;

 public class UserSerialize implements Serializable{
// /**
// *
// */
// private static final long serialVersionUID = 1L;
private String userId;
private String userName; private String sex; public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getUserId() {
return userId;
}
@Override
public String toString() {
return "User [userId=" + userId + ", userName=" + userName + "]";
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
} }

这时执行反序列化操作:

 package com.paic.egis.smts.activity;

 import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; public class Test { public static void main(String[] args) throws Exception{
// serialize();
UserSerialize o = (UserSerialize) deSerialize();
System.out.println(o.toString()); } public static void serialize() throws IOException{
UserSerialize u = new UserSerialize();
u.setUserId("11");
u.setUserName("df");
FileOutputStream fo = new FileOutputStream(new File("d://a.txt"));
ObjectOutputStream os = new ObjectOutputStream(fo);
os.writeObject(u);
} public static Object deSerialize() throws IOException, ClassNotFoundException {
FileInputStream fi = new FileInputStream(new File("d://a.txt"));
ObjectInputStream oi = new ObjectInputStream(fi);
return oi.readObject();
} }

运行结果:

Exception in thread "main" java.io.InvalidClassException: com.paic.egis.smts.activity.UserSerialize; local class incompatible: stream classdesc serialVersionUID = -3074015237131537750, local class serialVersionUID = -126714174808369076
at java.io.ObjectStreamClass.initNonProxy(Unknown Source)
at java.io.ObjectInputStream.readNonProxyDesc(Unknown Source)
at java.io.ObjectInputStream.readClassDesc(Unknown Source)
at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
at java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.readObject(Unknown Source)
at com.paic.egis.smts.activity.Test.deSerialize(Test.java:31)
at com.paic.egis.smts.activity.Test.main(Test.java:14)

意思就是说,文件流中的class和classpath中的class,也就是修改过后的class,不兼容了,处于安全机制考虑,程序抛出了错误,并且拒绝载入。那么如果我们真的有需求要在序列化后添加一个字段或者方法呢?应该怎么办?那就是自己去指定serialVersionUID

      因此强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。

四、transient关键字的作用

在实际开发过程中,我们常常会遇到这样的问题,这个类的有些属性需要序列化,而其他属性不需要被序列化,打个比方,如果一个用户有一些敏感信息(如密码,银行卡号等),为了安全起见,不希望在网络操作(主要涉及到序列化操作,本地序列化缓存也适用)中被传输,这些信息对应的变量就可以加上transient关键字。换句话说,这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。

java 的transient关键字为我们提供了便利,你只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。

范例如下:

UserSerialize类:

package com.paic.egis.smts.activity;

import java.io.Serializable;

public class UserSerialize implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private String userId;
private String userName; private transient String sex; public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
@Override
public String toString() {
return "UserSerialize [userId=" + userId + ", userName=" + userName
+ ", sex=" + sex + "]";
} }

执行序列化和反序列化

 package com.paic.egis.smts.activity;

 import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; public class Test { public static void main(String[] args) throws Exception{
serialize();
UserSerialize o = (UserSerialize) deSerialize();
System.out.println(o.toString()); } public static void serialize() throws IOException{
UserSerialize u = new UserSerialize();
u.setUserId("111");
u.setUserName("df");
u.setSex("y");
FileOutputStream fo = new FileOutputStream(new File("d://a.txt"));
ObjectOutputStream os = new ObjectOutputStream(fo);
os.writeObject(u);
} public static Object deSerialize() throws IOException, ClassNotFoundException {
FileInputStream fi = new FileInputStream(new File("d://a.txt"));
ObjectInputStream oi = new ObjectInputStream(fi);
return oi.readObject();
} }

结果如下:

序列化,反序列化和transient关键字

set字段为null,说明反序列化时根本没有从文件中获取到信息

另静态变量不管是否被transient修饰,均不能被序列化

范例:

UserSerialize类:

 package com.paic.egis.smts.activity;

 import java.io.Serializable;

 public class UserSerialize implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private String userId;
private String userName; private static String sex; public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
@Override
public String toString() {
return "UserSerialize [userId=" + userId + ", userName=" + userName
+ ", sex=" + sex + "]";
} }

反序列化:

 package com.paic.egis.smts.activity;

 import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import com.paic.pafa.exceptions.BusinessException; public class Test { public static void main(String[] args) throws Exception{
// serialize();
UserSerialize o = (UserSerialize) deSerialize();
System.out.println(o.toString());
} public static void serialize() throws IOException{
UserSerialize u = new UserSerialize();
u.setUserId("111");
u.setUserName("df");
u.setSex("yy");
FileOutputStream fo = new FileOutputStream(new File("d://a.txt"));
ObjectOutputStream os = new ObjectOutputStream(fo);
os.writeObject(u);
} public static Object deSerialize() throws IOException, ClassNotFoundException {
FileInputStream fi = new FileInputStream(new File("d://a.txt"));
ObjectInputStream oi = new ObjectInputStream(fi);
return oi.readObject();
} }

执行反序列化之前已执行过serialize()序列化操作。

执行结果如下:

序列化,反序列化和transient关键字

若同时执行序列化和反序列化,代码如下:

 package com.paic.egis.smts.activity;

 import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; public class Test { public static void main(String[] args) throws Exception{
serialize();
UserSerialize o = (UserSerialize) deSerialize();
System.out.println(o.toString());
} public static void serialize() throws IOException{
UserSerialize u = new UserSerialize();
u.setUserId("111");
u.setUserName("df");
u.setSex("yy");
FileOutputStream fo = new FileOutputStream(new File("d://a.txt"));
ObjectOutputStream os = new ObjectOutputStream(fo);
os.writeObject(u);
} public static Object deSerialize() throws IOException, ClassNotFoundException {
FileInputStream fi = new FileInputStream(new File("d://a.txt"));
ObjectInputStream oi = new ObjectInputStream(fi);
return oi.readObject();
} }

执行结果如下:

序列化,反序列化和transient关键字

可能会比较奇怪为什么sex有值,这时因为这个值并不是从文件中反序列化读出来的,是从当前JVM中对应static变量的值。

五、Externalizable接口的作用

它是Serializable接口的子类,有时我们不希望序列化那么多,可以使用这个接口,这个接口的writeExternal()和readExternal()方法可以指定序列化哪些属性;

范例如下:

UserSerialize类:

 package com.paic.egis.smts.activity;

 import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput; public class UserSerialize implements Externalizable{
/**
*
*/
private static final long serialVersionUID = 1L;
private String userId;
private String userName; private String sex; public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
@Override
public String toString() {
return "UserSerialize [userId=" + userId + ", userName=" + userName
+ ", sex=" + sex + "]";
}
@Override
public void writeExternal(ObjectOutput objectoutput) throws IOException {
objectoutput.writeObject(userId); }
@Override
public void readExternal(ObjectInput objectinput) throws IOException,
ClassNotFoundException {
userId = (String) objectinput.readObject(); } }

序列化和反序列化:

 package com.paic.egis.smts.activity;

 import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; public class Test { public static void main(String[] args) throws Exception{
serialize();
UserSerialize o = (UserSerialize) deSerialize();
System.out.println(o.toString());
} public static void serialize() throws IOException{
UserSerialize u = new UserSerialize();
u.setUserId("111");
u.setUserName("df");
u.setSex("yy");
FileOutputStream fo = new FileOutputStream(new File("d://a.txt"));
ObjectOutputStream os = new ObjectOutputStream(fo);
os.writeObject(u);
} public static Object deSerialize() throws IOException, ClassNotFoundException {
FileInputStream fi = new FileInputStream(new File("d://a.txt"));
ObjectInputStream oi = new ObjectInputStream(fi);
return oi.readObject();
} }

执行结果如下:

序列化,反序列化和transient关键字

上一篇:java 对象序列化与反序列化


下一篇:Android 实现简单音乐播放器(一)