1.什么是序列化和反序列化?
- 序列化: 将java对象转换成字节序列的过程
- 反序列化:将在序列化过程中所生成的字节序列转换成java对象的过程
在实际生活中,需要将对象持久化,需要的时候再重新读取出来,通过对象序列化,可以将对象的状态保存为字节数组,需要的时候再将字节数组反序列化为对象。此外,两个进程行行远程通信时,需要互相发送各种类型的数据,比如图片、视频、音频、文字等等,这些数据都是以二进制序列的形式在网络上传送。那java进程之间的通信也是如此。通过java序列化和反序列化来实现。即把这个java对象转换为字节序列,然后在网络上传送,接收方再把字节序列恢复为java对象的过程。
2.如何实现Java序列化与反序列化?
前提:实现了Serializable或Externalizable接口的类的对象才能被序列化。否则抛出NotSerializableException异常。
有以下三种方式:
- 实现了Serializable接口。使用ObjectOutputStream和ObjcetInputStream对对象的非transient实例变量进行序列化和反序列化。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* @author 旺仔牛奶
* 2017年8月30日
*/
public class ApplyTransient {
public static void main(String[] args) {
User user = new User();
user.setUserPhone("1234567891");
user.setUserName("旺仔牛奶");
user.setPasswd("caka");
System.out.println("Before Serializable: ");
System.out.println("username: " + user.getUserName());
System.out.println("userPhone: " + user.getUserPhone());
System.err.println("password: " + user.getPasswd());
//序列化操作
try {
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("E:/wangzai.txt"));
os.writeObject(user); // 将User对象写进文件
os.flush();//刷新流到文件
os.close();//关闭流
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
//反序列化操作
ObjectInputStream is = new ObjectInputStream(new FileInputStream("E:/wangzai.txt"));
user = (User) is.readObject(); // 从流中读取User的数据
is.close();//关闭流
System.out.println("\nAfter Serializable: ");
System.out.println("username: " + user.getUserName());
System.out.println("userPhone: " + user.getPhone());
System.err.println("password: " + user.getPasswd());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
//User实体类
class User implements Serializable {
private static final long serialVersionUID = 8294180014912103005L;
public String userPhone;
public static String userName;
private transient String passwd;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserPhone() {
return userPhone;
}
public void setUserPhone(String userPhone) {
this.userPhone = userPhone;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
}
执行结果:
Before Serializable:
username: 旺仔牛奶
userPhone: 1234567891
password: caka
After Serializable:
username: 旺仔牛奶的牛奶
userPhone: 1234567891
password: null
问题:password反序列化的值为什么为空?username反序列化后的值为什么被临时改变的值替换?
上面两个问题涉及到java序列化需要注意的两个点:
- 静态成员变量属于类不属于对象,所以不会参与序列化(对象序列化保存的是对象的“状态”,也就是它的成员变量,因此序列化不会关注静态变量)
- 用transient关键字标记的成员变量不参与序列化(在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null)
答案是因为username取出的值不是反序列化得到的而是JVM中得到的,正如上例,如果是反序列化得到的,那么临时修改username的值是不会影响反序列化取得的值的。但username的值却被临时改变的值替换掉了,所以并不与第一个注意点冲突,username的值能被取出是因为在JVM中存放。
2.实现了Serializable接口,并且还定义了private类型的writeObject()和readObject()。
- 如果需要默认序列化方法或者默认反序列化方法,则可以在这两个方法中调用ObjectOutputStream的defaultWriteObject()方法或者ObjectOutputStream的defaultReadObject()方法。也就是上列中默认的序列化方法。
- 如果需要用户自定义序列化方式,从而控制序列化的行为。则使用ObjectOutputStream调用对象的writeObject(ObjectOutputStream out)方法进行序列化。用ObjectInputStream调用对象的readObject(ObjectInputStream in)进行反序列化。
例子如下:
import java.io.IOException;
import java.io.ObjectStreamException;
import java.io.Serializable;
/**
* @description
* @author 旺仔牛奶
* 2017年8月30日
*/
public class ApplyTransient implements Serializable {
/**
* 生成序列号标识
*/
private static final long serialVersionUID = -4083503801443301445L;
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 序列化时,
* 首先系统会先调用writeReplace方法,在这个阶段,
* 可以进行自己操作,将需要进行序列化的对象换成我们指定的对象.
* 一般很少重写该方法
* @return
* @throws ObjectStreamException
*/
private Object writeReplace() throws ObjectStreamException {
System.out.println("writeReplace invoked");
return this;
}
/**
*接着系统将调用writeObject方法,
* 来将对象中的属性一个个进行序列化,
* 我们可以在这个方法中控制住哪些属性需要序列化.
* 这里只序列化name属性
* @param out
* @throws IOException
*/
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
System.out.println("writeObject invoked");
out.writeObject(this.name == null ? "旺仔牛奶" : this.name);
}
/**
* 反序列化时,系统会调用readObject方法,将我们刚刚在writeObject方法序列化好的属性,
* 反序列化回来.然后通过readResolve方法,我们也可以指定系统返回给我们特定的对象
* 可以不是writeReplace序列化时的对象,可以指定其他对象.
* @param in
* @throws IOException
* @throws ClassNotFoundException
*/
private void readObject(java.io.ObjectInputStream in) throws IOException,ClassNotFoundException {
System.out.println("readObject invoked");
this.name = (String) in.readObject();
System.out.println("got name:" + name);
}
/**
* 通过readResolve方法,我们也可以指定系统返回给我们特定的对象
* 可以不是writeReplace序列化时的对象,可以指定其他对象.
* 一般很少重写该方法
* @return
* @throws ObjectStreamException
*/
private Object readResolve() throws ObjectStreamException {
System.out.println("readResolve invoked");
return this;
}
}
3.实现了Externalnalizable接口,且必须实现public类型的readExternal()和writeExternal()方法。
ObjectOutputStream调用对象的writeExternal(ObjectOutput out))的方法进行序列化。ObjectInputStream会调用对象的readExternal(ObjectInput in)的方法进行反序列化。
举例如下:
import java.io.Externalizable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
/**
* @author 旺仔牛奶
* 2017年8月30日
*/
public class ApplyTransient implements Externalizable {
private static final long serialVersionUID = -4083503801443301445L;
private transient String content = "旺仔牛奶";
@Override public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(content);
}
@Override public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {
content = (String) in.readObject();
}
public static void main(String[] args) throws Exception {
ApplyTransient applyTransientt = new ApplyTransient();
//序列化
ObjectOutput out = new ObjectOutputStream(new FileOutputStream(new File("wangzai")));
out.writeObject(applyTransientt);
//反序列化
ObjectInput in = new ObjectInputStream(new FileInputStream(new File("wangzai")));
applyTransientt = (ApplyTransient) in.readObject();
System.out.println(applyTransientt.content);
out.close(); in.close();
}
}
注意:
上面的三个例子中都显式的申明了serialVersionUID(唯一的序列版本号),每个可序列化的类都有一个唯一标识号与其相关联。
如果没有显式的指定私有静态final的long类型的serialVersionUID,系统会自动生成一个标识号(会受类名、接口名、成员的影响而发生变化)。如果一旦改变了这些信息,自动生成的序列版本会发生变化。因此如果没有显示声明,兼容性会遭到破坏,导致运行时出现InvalidClassException异常。所以,强烈建议所有可序列化类都显式声明serialVersionUID
显式地定义serialVersionUID有两种用途:
1)在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;
2)在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID
以上三种例子可见,对象序列化和反序列化的步骤如下:
对象序列化步骤:
1.创建一个对象输出流
2.通过对象输出流的writeObject(Objectobj)方法写对象
如:
ObjectOutputStream out = newObjectOutputStream(new FileOutputStream(filePath));//filePath:文件路径
out.writeObject(obj);//obj:要写入文件的对象
对象反序列化步骤:
1.创建一个对象输入流
2.通过对象输入流的readObject(j)方法读取对象.(读取对象时是按照对列方式进行的,既先进先出)
如:
ObjectInputStream in = new ObjectInputStream (newFileInputStream(filePath));
in.readObject();//读取对象
4.Java实现几种序列化方式
Java实现几种序列化方式包括Java原生以流的方法进行的序列化、Json序列化、FastJson序列化、Protobuff序列化。
- Java原生序列化方法即通过Java原生流(InputStream和OutputStream之间的转化)的方式进行转化。需要注意的是JavaBean实体类必须实现Serializable接口,否则无法序列化。比如上面的第一个例子。
- Json序列化一般会使用jackson包,通过ObjectMapper类来进行一些操作,比如将对象转化为byte数组或者将json串转化为对象。比如调用一个服务器接口,通常的请求为xxx.json?a=xxx&b=xxx的形式。
- fastjson序列化是由阿里开发的一个性能很好的Java
语言实现的 Json解析器和生成器。完全支持java bean、集合、Map、日期、Enum,支持范型和自省。无依赖,使用时候需引入FastJson第三方jar包。
- ProtocolBuffer序列化是一种轻便高效的结构化数据存储格式,可以用于结构化数据序列化。适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。