java的序列化_浅谈java序列化

在谈及java序列化之前,先给大家举一个比较实用的例子;

小明准备出国留学,但是随身携带的都是人民币,到了国外后,发现人民币好像不太好使了,那边只用当前国家规定的法币或者美元,没有办法,小明只好找到一家银行,将人民币兑换成当地货币或者美元,才能保证有钱花。

没有规矩,不成方圆。这个理儿,在java中也是一样的存在。首先,我们先来了解一下什么是序列化。

把对象转换为字节序列的过程称为对象的序列化。

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

有序列化,自然就有反序列化,就好比将人民币兑换成美元,同样可以将美元兑换成人民币。在java中,将对象序列化主要有两种用途。

一种是将对象的字节序列存储到磁盘中存储,另外一种则是在网络中进行数据传输。

JDK为我们提供了一种序列化方式,我们可以通过JDK的序列化将一个对象转换成字节序列存储到磁盘中,也可以将对象序列化成字节序列用户网络数据传输。下面,我们先来看一下将一个对象存储到本地磁盘没有进行系列化会是怎样的。

import lombok.Data;

/**

* 对象信息:测试为序列化情况下

*/

@Data

public class User {

private String name;

private String address;

private Integer age;

}

测试代码

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

public class Test02 {

/**

* 测试java序列化

*

* @param args

*/

public static void main(String[] args) {

User user = new User();

user.setName("加耀");

user.setAddress("湖北武汉");

user.setAge(24);

String localAddress = "C:\\Users\\Administrator\\Desktop\\user.txt";

try {

// 测试将对象写入本地磁盘

FileOutputStream fileOut = new FileOutputStream(localAddress);

ObjectOutputStream out = new ObjectOutputStream(fileOut);

out.writeObject(user);

out.close();

fileOut.close();

System.out.println("对象文件保存成功,储存位置:" + localAddress);

// 测试将本地磁盘中的对象字节序列读取储存到对象中

FileInputStream fileInput = new FileInputStream(localAddress);

ObjectInputStream input = new ObjectInputStream(fileInput);

user = (User) input.readObject();

System.out.println(user);

System.out.println("成功将对象字节反序列化");

} catch (Exception e) {

e.printStackTrace();

}

}

}

通过运行测试代码,在运行中代码出错。错误信息如下:

java.io.NotSerializableException: User

at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)

at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)

at Test02.main(Test02.java:22)

通过错误日志可以看出,在代码的第22行,将对象写入到磁盘文件时出错。从报错信息的第一行可以看得出,没有序列化异常,对象User。所以,当对象没有序列化时,是无法写入到本地磁盘的。那下面,我们尝试将对象序列化一下,然后重新运行一下上述代码。

import lombok.Data;

import java.io.Serializable;

/**

* 对象信息:测试为序列化情况下

*/

@Data

public class User implements Serializable {

private String name;

private String address;

private Integer age;

}

将上面的对象进行改造后,即 实现Serializable接口。

然后重新运行一下上面的代码:

对象文件保存成功,储存位置:C:\Users\Administrator\Desktop\user.txt

User(name=加耀, address=湖北武汉, age=24)

成功将对象字节反序列化

可以见得,当对象实现了Serializable接口后,对象信息就可以成功的写入到本地磁盘并且可以从本地磁盘中顺利读取到。我们再看一下本地电脑桌面上新建的这个user.txt文本,打开看看。

对象实现序列化接口可以完美的解决这种问题,但是,上述代码并不完美。此时User对象中只有3个字段,name、address、age;如果再添加一个字段后,然后重新运行一下代码,然后再删除一个字段,此时代码还能顺利运行吗?

在User对象中添加一个sex字段。

private String sex;

然后重新运行一下代码,然后删除User中的age字段,并且注释掉代码中写入本地磁盘的代码,只保留读取的方法,此时代码还能运行吗?

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

public class Test02 {

/**

* 测试java序列化

*

* @param args

*/

public static void main(String[] args) {

User user = new User();

user.setName("加耀");

user.setAddress("湖北武汉");

// user.setAge(24);

String localAddress = "C:\\Users\\Administrator\\Desktop\\user.txt";

try {

// 测试将对象写入本地磁盘

// FileOutputStream fileOut = new FileOutputStream(localAddress);

// ObjectOutputStream out = new ObjectOutputStream(fileOut);

// out.writeObject(user);

// out.close();

// fileOut.close();

// System.out.println("对象文件保存成功,储存位置:" + localAddress);

// 测试将本地磁盘中的对象字节序列读取储存到对象中

FileInputStream fileInput = new FileInputStream(localAddress);

ObjectInputStream input = new ObjectInputStream(fileInput);

user = (User) input.readObject();

System.out.println(user);

System.out.println("成功将对象字节反序列化");

} catch (Exception e) {

e.printStackTrace();

}

}

}

此时,再运行代码,出现了下列异常:

java.io.InvalidClassException: User; local class incompatible: stream classdesc serialVersionUID = -5370401598153958555, local class serialVersionUID = -5049882203261325612

at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)

at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1843)

at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713)

at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2000)

at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535)

at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)

at Test02.main(Test02.java:29)

通过报错信息可以看的出来,log日志中指出,读取本地磁盘文件时所采用的序列化标识为serialVersionUID = -5370401598153958555,但是本地文件的序列化标识是serialVersionUID = -5049882203261325612。

读取和写入采用的是不同的系列化标志,导致出错。那么,怎样避免这种情况呢?

让我们回到最开始。

前面,我们将对象实现了序列化接口Serializable,但是我们并没有指定对象的序列化方式(或序列化标识)。我们将上面的对象再进行改造一次。

import lombok.Data;

import java.io.Serializable;

/**

* 对象信息:测试为序列化情况下

*/

@Data

public class User implements Serializable {

// 指定其序列化的UID

private static final long serialVersionUID = 1L;

private String name;

private Integer age;

private String address;

}

然后我们先将当前对象写入到本地并且读取本地文件打印出来。

对象文件保存成功,储存位置:C:\Users\Administrator\Desktop\user.txt

User(name=加耀, age=24, address=湖北武汉)

成功将对象字节反序列化

然后,我们再进行上面的操作,添加一个sex字段,删除一个age字段,注释代码中的写入本地磁盘的代码,只保留读取的代码。运行代码:

User(name=加耀, address=湖北武汉, sex=null)

成功将对象字节反序列化

可以见得,在写入本地的时候是有字段age的,但是读取的时候却没有 了这个字段,但是还是顺利读取到了。可见序列化标志的重要性。

当我们将一个对象实现了序列化接口但是没有指定序列化标志时,jdk会根据当前类型的属性特征等生成一个UID,但是当对象的字段结构发生改变后,其UID也会随之改变,所以才会导致上面的读写UID不一致的问题。当我们指定了UID,读取和写入都是使用这一个UID,这样序列化和反序列化就不会有问题了。就好比我们拿1000元人民币换成143.8美元,但是换回来的时候却用143.8日元来兑换1000人民币,当然会出问题的了;

由此可以见得,序列化的重要性,规则的重要性。

在网络中传输也是一样的道理,由于网络数据交换,不同平台不同语言,所使用的编码及操作系统等都不一样,因此经常会发生数据丢失数据解析异常等现象,当有一个统一的规则后,大家才会和谐共处的嘛。

这样形容,是不是感觉序列化没想象中的那么复杂了呢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值