java序列化机制_谈谈Java序列化机制

一、序列化、反序列化概念及其使用场景

1、序列化、反序列化的概念

简单的讲,序列化就是将java对象转化成二进制保存到磁盘中去,反序列化就是从磁盘中读取文件流然后转成java对象。

2、使用场景

1、网络通讯传输java对象数据

2、永久保存java对象

二、实现序列化的方式有哪些?

JDK提供了下面两种方式实现序列化:

1、实现Serializable接口

2、实现Externalizable

下面分别实例演示两种实现方式:

假设本文所有的序列化对象为User,其拥有下面属性:

/**

* 序列化對象

*

* @Author jiawei huang

* @Since 2020年1月2日

* @Version 1.0

*/

public class User {

private String userName;

private String address;

// ....setter/getter

}

复制代码

1、基于Serializable接口

我们在上面User对象的基础上实现Serializable接口,代码如下:

public class User implements Serializable {

// 序列化ID

private static final long serialVersionUID = 1L;

复制代码

序列化和反序列化代码为:

// 序列化方法

public static void serialize(User user) {

ObjectOutputStream outputStream = null;

try {

outputStream = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Administrator\\Desktop\\data.txt"));

outputStream.writeObject(user);

} catch (Exception e) {

e.printStackTrace();

} finally {

// close stream

if (outputStream != null) {

try {

outputStream.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

// 反序列化方法

public static void deserialize() {

ObjectInputStream objectInputStream = null;

try {

objectInputStream = new ObjectInputStream(

new FileInputStream("C:\\Users\\Administrator\\Desktop\\data.txt"));

User user = (User) objectInputStream.readObject();

System.out.println(user.getAddress());

System.out.println(user.getUserName());

} catch (Exception e) {

// error

e.printStackTrace();

} finally {

// close stream

if (objectInputStream != null) {

try {

objectInputStream.close();

} catch (IOException e) {

// error

e.printStackTrace();

}

}

}

}

复制代码

测试代码如下:

public static void main(String[] args) {

User user = new User();

user.setAddress("广东深圳");

user.setUserName("hjw");

serialize(user);

deserialize();

}

复制代码

输出如下:

广东深圳

hjw

复制代码Q1、User实现Serializable接口是必须的吗?

是的,是必须的,否则报下面异常:

java.io.NotSerializableException: ex.serializable.User

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

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

at ex.serializable.Main.serialize(Main.java:41)

at ex.serializable.Main.main(Main.java:33)

复制代码

因为在ObjectOutputStream中执行了如下代码限制:7af526b133b47e27e08c6fddc435692b.png

这也说明,jdk并没有默认支持对象的序列化,为什么默认不支持呢?因为java的安全机制限制,我们设想一下,假设对象都默认支持序列化,那么就像上面那个User对象,其私有private修饰属性也被序列化了,那么不符合private的设计语义

2、基于Externalizable接口

1、Externalizable接口继承自Serializable接口

2、Externalizable接口允许我们自定义对象属性的序列化

3、实现Externalizable接口必须重写writeExternal和readExternal方法

我们重新新建一个User1对象如下:

public class User1 implements Externalizable {

private String userName;

private String address;

// setter、getter

@Override

public void writeExternal(ObjectOutput out) throws IOException {

out.writeObject(userName);

}

@Override

public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

System.out.println(in.readObject());

}

}

复制代码

测试代码如下:

User1 user1 = new User1();

user1.setAddress("广东深圳");

user1.setUserName("hjw");

user1.writeExternal(

new ObjectOutputStream(new FileOutputStream("C:\\Users\\Administrator\\Desktop\\data.txt")));

user1.readExternal(new ObjectInputStream(new FileInputStream("C:\\Users\\Administrator\\Desktop\\data.txt")));

复制代码

输出如下:

hjw

复制代码

两种实现方式的区别在于Externalizable允许我们自定义序列化规则,Externalizable接口会初始化一次无参构造器,而Serializable不会

三、transient关键字和static成员变量

通过实现Externalizable接口我们可以自定义序列化的属性,同样的道理,关键字transient也可以达到相同的效果,但是两者之间还是有一些区别的。

1、transient修饰的变量,即使使用private修饰也会被序列化

2、如果我们想private属性不被序列化,则可以使用Externalizable

static修饰的成员变量属于类全局属性,其值在JDK1.8存储于元数据区(1.8以前叫方法区),不属于对象范围,将不会被序列化。

下面验证static不会被序列化:

修改User对象userName属性为static修饰

User user = new User();

user.setAddress("广东深圳");

user.setUserName("hjw");

serialize(user);

user.setUserName("mike");

deserialize();

复制代码

输出如下:

广东深圳

mike

复制代码

我们将hjw序列化到了磁盘文件,结果反序列化之后得到的值却是mike

四、关于serialVersionUID

java能否反序列化成功,取决于serialVersionUID是否一致,我们可以把它理解成为版本号,一旦版本号不一致,将会报序列化出错。我们可以为对象声明一个自定义serialVersionUID,也可以使用默认的1L。

总结就是:

当我们新增一个类实现Serializable接口时,建议我们为其新增一个serialVersionUID,因为假设我们没有声明serialVersionUID,那么后面假设我们修改了该类的接口(新增字段)时,当我们再次反序列化时,就会报错。因为java会拿编译器根据类信息自动生成一个id1和反序列化得到的id2进行比较,如果类有改动,id2和id1肯定不一致啦。

Q1:既然static修饰的不会被序列化,而java又是如何通过serialVersionUID进行比较的呢?

serialVersionUID应该是一个特殊字段可以被序列化,应该可以从源码中找到答案,小编没找到,欢迎评论区留言。

五、序列化、反序列化实现深度克隆

深度克隆即把引用类型的成员也给克隆了,基于上面例子,我们新增一个类Car,并且User拥有一个Car类型对象。

public class Car implements Serializable {

private static final long serialVersionUID = 1L;

private String carName;

private int price;

// ......

}

复制代码public class User implements Serializable {

private static final long serialVersionUID = 1L;

private String userName;

//

private Car car;

private String address;

@Override

public String toString() {

return "User [userName=" + userName + ", carName=[" + car.getCarName() + "],price=[" + car.getPrice() + "]"

+ ", address=" + address + "]";

}

// ......

}

复制代码

克隆方法

public static T cloneObject(T obj) throws IOException {

ObjectOutputStream oos = null;

ByteArrayOutputStream baos = null;

byte[] bytes = null;

try {

// 序列化

baos = new ByteArrayOutputStream();

oos = new ObjectOutputStream(baos);

oos.writeObject(obj);

bytes = baos.toByteArray();

} catch (Exception e) {

e.printStackTrace();

} finally {

if (baos != null) {

baos.close();

}

if (oos != null) {

oos.close();

}

}

ByteArrayInputStream bais = null;

ObjectInputStream ois = null;

try {

// 反序列化

bais = new ByteArrayInputStream(bytes);

ois = new ObjectInputStream(bais);

return (T) ois.readObject();

} catch (Exception e) {

e.printStackTrace();

} finally {

if (bais != null) {

baos.close();

}

if (oos != null) {

ois.close();

}

}

return null;

}

复制代码

测试代码及输出如下:

User user = new User();

user.setAddress("广东深圳");

user.setUserName("hjw");

Car car = new Car();

car.setCarName("单车");

car.setPrice(300);

user.setCar(car);

User clonedUser = cloneObject(user);

System.out.println(clonedUser);

复制代码User [userName=hjw, carName=[单车],price=[300], address=广东深圳]

复制代码

六、总结

1、新增序列化类时,建议新增一个自定义id,防止后续版本之间不兼容

2、static、transient修饰的字段不会被序列化,序列化同样会序列化private属性

3、序列化可以实现深度克隆

4、实现序列化有两种方式,一种是直接实现Serializable接口,另一种是实现Externalizable接口,后者允许我们自定义序列化规则

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值