java序列化和反序列化
一、什么是序列化
1.序列化(Serialization)
是将对象的状态信息转化为可以存储或者传输的形式的过程,一般将一个对象存储到一个储存媒介,例如档案或记忆体缓冲等,在网络传输过程中,可以是字节或者XML等格式;而字节或者XML格式的可以还原成完全相等的对象,这个相反的过程又称为反序列化;
2.Java中的序列化
我们可以通过多种方式来创建对象,并且只要对象没有被回收我们都可以复用此对象。但是,我们创建出来的这些对象都存在于JVM中的堆(stack)内存中,只有JVM处于运行状态的时候,这些对象才可能存在。一旦JVM停止,这些对象也就随之消失;
对象序列化,就是将对象中的信息
二、应用场景
1.需要将对象信息持久化保存在硬盘中,如保存成文件
2.需要将对象信息在网络间传输,
3.需要将对象信息在后台服务间调用,如dubbo调用
三、序列化的方式
方式一:java.io.Serialization
Java类通过实现java.io.Serialization接口来启用序列化功能,
未实现此接口的类将无法将其任何状态或者信息进行序列化或者反序列化。
可序列化类的所有子类型都是可以序列化的。
序列化接口没有方法或者字段,仅用于标识可序列化的语义。
当试图对一个对象进行序列化时,如果遇到一个没有实现java.io.Serialization接口的对象时,
将抛出NotSerializationException异常。
1.示例代码
UserModel.java
import java.io.Serializable;
public class UserModel implements Serializable {
// private static final long serialVersionUID = 8294180014912103005L;
private String username;
private String password;
// private String age;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
// public static long getSerialVersionUID() {
// return serialVersionUID;
// }
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
// public String getAge() {
// return age;
// }
//
// public void setAge(String age) {
// this.age = age;
// }
@Override
public String toString() {
return "UserModel{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
TransientTest.java
import org.yungu.config.model.UserModel;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
class TransientTest {
public static void main(String[] args) {
UserModel user = new UserModel();
user.setUsername("zhangsan");
user.setPassword("admin");
System.out.println("1。read before Serializable: ");
System.out.println("2。username: " + user.getUsername());
System.err.println("3。password: " + user.getPassword());
try {
ObjectOutputStream os = new ObjectOutputStream(
new FileOutputStream("/Users/yzh/Documents/user.txt"));
// 将User对象写进文件
os.writeObject(user);
os.flush();
os.close();
ObjectInputStream is = new ObjectInputStream(new FileInputStream(
"/Users/yzh/Documents/user.txt"));
// 从流中读取User的数据
user = (UserModel) is.readObject();
is.close();
System.out.println("4。read after Serializable: ");
System.out.println("5。username: " + user.getUsername());
System.err.println("6。password: " + user.getPassword());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
2.直接运行看结果:
方法一序列化成功!
拓展点:sys.out()和sys.err()打印的顺序
从控制台的打印结果来看,打印顺序并没有像预期的那样,按照1-6的顺序来执行。sys.err()打印的顺序是随机的。这点大家可以去百度下,看看原因。这里不做多的描述。
方式二:java.io.Externalizable(自定义序列化)
1.示例代码
UserModel.java
import java.io.*;
public class UserModel implements Externalizable {
@Override
public void writeExternal(ObjectOutput out) throws IOException {
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
}
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "UserModel{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
2.直接运行TransientTest
序列化不成功!
原因:
Externalizable继承了Serializable,该接口中定义了两个抽象方法:writeExternal()与readExternal()。当使用Externalizable接口来进行序列化与反序列化的时候需要开发人员重写writeExternal()与readExternal()方法。由于上面的代码中,并没有在这两个方法中定义序列化实现细节,所以输出的内容为空。还有一点值得注意:在使用Externalizable进行序列化的时候,在读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。所以,实现Externalizable接口的类必须要提供一个public的无参的构造器。
3.在对象中,重写writeExternal()和readExternal()方法
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(username);
out.writeObject(password);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
username = (String) in.readObject();
password = (String) in.readObject();
}
再次运行,结果:
方法二序列化成功!
四、不被序列化的情况
情况一:被static修饰
1、实现Externalizable接口
UserModel.java
import java.io.*;
public class UserModel implements Externalizable {
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(username);
out.writeObject(password);
out.writeObject(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
username = (String) in.readObject();
password = (String) in.readObject();
age = (String)in.readObject();
}
private String username;
private String password;
private static String age = "18";
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public static String getAge() {
return age;
}
public static void setAge(String age) {
UserModel.age = age;
}
@Override
public String toString() {
return "UserModel{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", age='" + age + '\'' +
'}';
}
}
TransientTest.java
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
class TransientTest {
public static void main(String[] args) {
UserModel user = new UserModel();
user.setUsername("zhangsan");
user.setPassword("admin");
System.out.println("1。read before Serializable: ");
System.out.println(user.toString());
try {
ObjectOutputStream os = new ObjectOutputStream(
new FileOutputStream("/Users/yzh/Documents/user.txt"));
// 将User对象写进文件
os.writeObject(user);
os.flush();
os.close();
ObjectInputStream is = new ObjectInputStream(new FileInputStream(
"/Users/yzh/Documents/user.txt"));
// 从流中读取User的数据
user = (UserModel) is.readObject();
is.close();
System.out.println("4。read after Serializable: ");
System.out.println(user.toString());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
1)运行看结果
age被初始化进来了?验证一下
2)验证:
步骤:先初始化到本地文件中,再修改age的值,最后反序列化
a.注销反序列化的代码,只执行序列化
b.将age改为“16”
c.执行反序列化
3)结论:
实现Externalizable接口,static修饰的也会被序列化!
2、实现Serializable接口
测试代码略,步骤同上:
结果:
总结:被static修饰的静态变量,在序列化时,实现Externalizable接口的会被序列化,实现Serializable的不会被序列化。
情况二:被transient修饰
分别测试了自动序列化和手动序列化两种情况:
测试结果:
1.实现Externalizable,仍然被序列化
2.实现Serializable,没有被序列化
五、serialVersionUID的作用
情况一:实现Serializable接口
UserModel.java
import java.io.*;
public class UserModel implements Serializable {
private String username;
private transient String password;
// private String age = "16";
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
// public String getAge() {
// return age;
// }
//
// public void setAge(String age) {
// this.age = age;
// }
@Override
public String toString() {
return "UserModel{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
// ", age='" + age + '\'' +
'}';
}
}
步骤:
1.先序列化到本地文件
2.修改UserModel.java ,增加一个字段
3. 反序列化到本地
4.运行结果:
原因:
对象中没有显示声明版本id,当对象内容发生变化(新增,修改,删除),java会默认生成一个id;反序列化时,如果id不一致,就会报异常。
解决办法:
序列化的对象,都显示的声明一个版本id。
情况二:实现Externalizable
import java.io.*;
public class UserModel implements Externalizable {
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(username);
out.writeObject(password);
// out.writeObject(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
username = (String) in.readObject();
password = (String) in.readObject();
// age = (String) in.readObject();
}
private String username;
private transient String password;
// private String age = "16";
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
// public String getAge() {
// return age;
// }
//
// public void setAge(String age) {
// this.age = age;
// }
@Override
public String toString() {
return "UserModel{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
// ", age='" + age + '\'' +
'}';
}
}
步骤同上:
总结:
需要序列化的对象,如果不显示声明serialVersionUID,当对象发生改变时,反序列会因为版本不一致报异常。
六、序列化需要注意的事项
1。序列化只保存对象的状态,而不管对象的方法。
序列化的是属性和值,get()和set()方法拿不到
2。当一个父类实现了序列化,它的子类也自动实现序列化,不用显示进行实现了。
3。当一个实例对象引用其他对象,当序列化该对象时也把引用的对象进行了实例化。