序列化与反序列化
Posted zenghi-home
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了序列化与反序列化相关的知识,希望对你有一定的参考价值。
一、序列化和反序列化的概念
把对象转换为字节序列的过程称为对象的序列化。
把字节序列恢复为对象的过程称为对象的反序列化。
对象的序列化主要有两种用途:
- 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
- 在网络上传送对象的字节序列。
在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,保存在物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。
当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。
二、JDK类库中的序列化API
Java中序列化与反序列化用到两个对象流:
java.io.ObjectOutputStream
:对象输出流,它的writeObject(Object obj)
方法可对参数指定的Object
对象进行序列化,把得到的字节序列写到一个目标输出流中。java.io.ObjectInputStream
:对象输入流,它的readObject()
方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
对于上面两个对象流,还需要两个流:
- 保存到到本地硬盘
java.io.FileOutputStream
:对象序列化为字节输出到本地硬盘java.io.FileInputStream
:从本地硬盘读取字节反序列化
- 网络传输
java.io.ByteArrayOutputStream
:将对象序列化为字节java.io.ByteArrayInputStream
:从字节反序列化为对象
Java实现序列化与反序列化提供有两个接口:
Serializable
接口:实现Serializable
接口的类可以 采用默认的序列化方式Externalizable
接口:实现Externalizable
接口的类完全由自身来控制序列化的行为
只有实现了Serializable和Externalizable接口的类的对象才能被序列化。
对象序列化步骤:
- 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
- 通过对象输出流的
writeObject()
方法写对象。
对象反序列化的步骤如下:
- 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
- 通过对象输入流的
readObject()
方法读取对象。
实现Serializable
的序列化与反序列化
对象序列化保存在本地硬盘
public class UserInfoTest {
/**
* 从内存序列化到本地
*/
@Test
public void serialize() throws IOException {
UserInfo userInfo = new UserInfo();
userInfo.setName("张三");
userInfo.setAge(23);
//序列化到本地
FileOutputStream fileOutputStream = new FileOutputStream("./aa.txt");
ObjectOutputStream os = new ObjectOutputStream(fileOutputStream);
os.writeObject(userInfo);
System.out.println("序列化成功");
}
/**
* 从本地反序列化到内存
*/
@Test
public void deserialize() throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream("./aa.txt");
ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
UserInfo userInfo = (UserInfo) inputStream.readObject();
System.out.println("反序列化成功:" + userInfo);
}
}
@Data
class UserInfo implements Serializable {
private String name;
private Integer age;
}
网络传输(使用RabbitMQ测试)
/**
* @author Hayson
* @date 2018/11/13 09:08
* @description RabbitMQ测试序列化对象
*/
public class Send {
private final static String QUEUE_NAME = "test_serializable";
private final static String HOST = "192.168.239.134";
private final static Integer PORT = 5672;
private final static String USERNAME = "admin";
private final static String PASSWORD = "admin";
public static void main(String[] args) throws IOException, TimeoutException {
Send(); //发送消息
Recv(); //接收消息
}
/**
* 对象序列化为字节数组,发送字节数组消息
* @throws IOException
* @throws TimeoutException
*/
private static void Send() throws IOException, TimeoutException {
Connection connection = getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(QUEUE_NAME, BuiltinExchangeType.DIRECT);
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.queueBind(QUEUE_NAME, QUEUE_NAME, QUEUE_NAME);
UserInfo userInfo = new UserInfo();
userInfo.setUserName("张三");
userInfo.setPassWord("23");
//对象序列化为字节数组
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(512);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(userInfo);
byte[] userInfoBytes = outputStream.toByteArray();
channel.basicPublish(QUEUE_NAME, QUEUE_NAME, null, userInfoBytes);
System.out.println("发送对象字节大小 = " + userInfoBytes.length);
objectOutputStream.close();
connection.close();
}
/**
* 接收字节数组消息,反序列化为对象
* @throws IOException
* @throws TimeoutException
*/
private static void Recv() throws IOException, TimeoutException {
Connection connection = getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
// 把接收到的字节数组反序列化为对象
ByteArrayInputStream inputStream = new ByteArrayInputStream(body);
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
UserInfo userInfo = null;
try {
userInfo = (UserInfo) objectInputStream.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally{
objectInputStream.close();
}
System.out.println(userInfo);
}
};
channel.basicConsume(QUEUE_NAME, true, consumer);
}
/**
* 获取Connection连接
* @return
* @throws IOException
* @throws TimeoutException
*/
private static Connection getConnection() throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
return factory.newConnection();
}
}
@Data
class UserInfo implements Serializable{
private String name;
private Integer age;
}
上面运行的序列化和反序列化都没有问题,但是如果我们序列化了对象到硬盘,而项目在后期再原来的UserInfo
类的基础上添加成员变量private String address;
@Data
class UserInfo implements Serializable{
private String name;
private Integer age;
private String address;//新添加成员变量
}
添加新成员变量后,再从本地反序列化,则会有以下异常(截取部分):
java.io.InvalidClassException: com.hayson.config.UserInfo; local class incompatible: stream classdesc serialVersionUID = 6288102273809508157, local class serialVersionUID = -4025030802652115000
出现异常原因:
serialVersionUID
是用于记录class文件的版本信息的,serialVersionUID
这个数字是JVM(JAVA虚拟机)
通过一个类的类名、成员、包名、工程名算出的一个数字。而这时候序列化文件中记录的serialVersionUID
与项目中的不一致,即找不到对应的类来反序列化。
serialVersionUID
如果序列化与反序列化的时候可能会修改类的成员,那么最好一开始就给这个类指定一个serialVersionUID
,如果一类已经指定的serialVersionUID
,然后
在序列化与反序列化的时候,jvm
都不会再自己算这个 class
的serialVersionUID
了。
去掉刚才添加的成员变量;,并且在User类中指定一个serialVersionUID
@Data
private static class UserInfo implements Serializable {
private static final long serialVersionUID = 7078078434151067638L;
private String name;
private Integer age;
//private String address;
}
重新序列化到aa.txt
文件中,然后再在javaBean
中添加private String address;
新成员变量(将上面的注释去掉),再执行反序列化操作,此时运行不再出现上面的异常情况,并且执行反序列化结果与javaBean
中的成员变量增加不影响结果。
如果在UserInfo类中再添加成员变量,而这个变量为一个class ,如Address,那么Address类也必须要实现Serializable接口。
@Data
private static class UserInfo implements Serializable {
private static final long serialVersionUID = 7078078434151067638L;
private String name;
private Integer age;
private Address address; //新增成员变量是一个类,这个类必须实现Serializable
}
class Address implements Serializable{
private String country;
private String city;
}
transient关键字
transient关键字,当你不想要某些字段序列化,可以用transient关键字修饰
@Data
private static class UserInfo implements Serializable {
private static final long serialVersionUID = 7078078434151067638L;
private String name;
private Integer age;
transient String address;//新添加成员变量,有transient关键字,不序列化该字段
}
以上是关于序列化与反序列化的主要内容,如果未能解决你的问题,请参考以下文章