百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

Java之Serializable可以这样自定义序列化,你不来看看

toyiye 2024-04-07 14:12 28 浏览 0 评论


我们先回顾一下上期讲到的三个用来自定义的方法:

private void writeObject(java.io.ObjectOutputStream out) throws IOException
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
private void readObjectNoData() throws ObjectStreamException;

简单来说:

writeObject方法是用来序列化对象向 ObjectOutputStream 作持久化.

readObject方法是从ObjectInputStream读取对象持久化的信息作反序列化.

readObjectNoData方法(个人愚见)主要是为了处理类在序列化方未继承某父类,而反序列方却继承了此父类,此时父类中的属性正常来说是没法反序列化的,此时通过readObjectNoData方法来填充默认值().

一. 自定义反序列化:

有时候我们的对象里面会有一些通过其它属性或额外因素计算得来的属性值,这些值我们在序列化时一方面考虑空间占用,另一方面考虑数据准确性,我们会为此类属性添加修饰符transient,以使序列化时忽略.

那么这些属性在反序列化时就需要合适的时机重新计算赋值,因为正常反序列化是通过对象流直接在内存中生成对象的,是不会执行我们定义的计算过程的,那么这个时候就需要我们自定义反序列化方法,让这一过程在反序列化时执行.

private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;

在可序列化类中定义上述方法,理论上这一方法的访问级别没有限制,但是一般我们为了避免这一方法被误调用还有安全因素考虑,都会将其定义为private级别.

readObject方法,顾名思义,其就是从对象输入流中读取生成对象.

ObjectInputStream#defaultReadObject 这一方法就是JVM默认的反序列化行为,所以简单的自定义,我们一般会先调用此方法完成默认行为,再接着定义自有行为,示例如下:

static class DerVo implements Serializable {
private static final long serialVersionUID = 101L;
private transient int size;
private final boolean[] values;
private final String desc;
public DerVo(boolean[] values,String desc) {
this.values = values;
this.size=values.length;
this.desc=desc;
}
/*private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
this.size=this.values.length;
}*/
@Override
public String toString() {
return new StringJoiner(", ", DerVo.class.getSimpleName() + "[", "]").add("size=" + size).add("values=" + Arrays.toString(values)).add("desc='" + desc + "'").toString();
}
}

定义如下测试代码:

DerVo vo1 = new DerVo(new boolean[]{false,true,false,false},"Test");
System.out.println("vo1 = "+vo1);
try(ObjectOutputStream serializable=new ObjectOutputStream(new FileOutputStream("G://vo1.txt"))) {
serializable.writeObject(vo1);
}
System.out.print("vo1-read = ");
try(ObjectInputStream read= new ObjectInputStream(new FileInputStream("G://vo1.txt"))) {
vo1 = (DerVo)read.readObject();
System.out.print(vo1);
}

1) 我们先注释掉自定义的readObject方法,输出如下:

// 对象输出
// vo1 = DerVo[size=4, values=[false, true, false, false], desc='Test']
// 序列化后反序列化输出
// vo1-read = DerVo[size=0, values=[false, true, false, false], desc='Test']

我们能发现,除size属性外其它都正常回填了,因为sizetransient修饰的,所以序列化时是忽略的,导致反序列化时未正常回填.

2) 现在我们去掉readObject方法注释,输出如下:

// 序列化后反序列化输出
// vo1-read = DerVo[size=4, values=[false, true, false, false], desc='Test']

size属性的值符合预期了.

以上呢,就是一个简单的自定义反序列化的示例了,其实ObjectInputStream类里面还有很多read开头的方法,这些方法都是用来读取序列化的内容的,都是按位读取,我们再做一些更复杂的反序列化时就需要仔细研读每个方法了。

二. 自定义序列化和反序列化

一般来说如果我们需要自定义序列化,那几乎可以肯定我们必须要同时自定义反序列化! 也就是说如果我们自定义了:

private void writeObject(java.io.ObjectOutputStream out) throws IOException

方法逻辑,我们就必然要去自定义readObject的逻辑,以正确读取我们序列化的结果.

我们对上面定义的类结构做如下改造,定义上述两个方法:

static class DerVo implements Serializable {
private static final long serialVersionUID = 101L;
private transient int size;
private transient boolean[] values;
private transient String desc;
public DerVo(boolean[] values,String desc) {
this.values = values;
this.size=values.length;
this.desc=desc;
}
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
out.writeUTF(this.desc);
out.writeUTF(Arrays.toString(this.values).replaceAll("\\s",""));
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
this.desc=in.readUTF();
String[] valueStr=in.readUTF().replaceFirst("[\\[]]", "").split(",");
this.values= new boolean[valueStr.length];
for (int i = 0; i < valueStr.length; i++) {
this.values[i]=Boolean.parseBoolean(valueStr[i]);
}
this.size=this.values.length;
}
@Override
public String toString() {
return new StringJoiner(", ", DerVo.class.getSimpleName() + "[", "]").add("size=" + size).add("values=" + Arrays.toString(values)).add("desc='" + desc + "'").toString();
}
}

大家能看到,我们现在将原来的所有实例属性都添加了修饰符transient,同时完全自定义序列化和反序列化行为:

我们可以先对比下再次序列化的结果:

改造前(vo1.txt 130字节):


改造后(vo1.txt 89字节):


我们再次来运行上面写的测试代码:

//vo1 = DerVo{size=4, values=[false, true, false, false], desc='Test'}
//vo1-read = DerVo{size=4, values=[false, true, false, false], desc='Test'}

大家自己也可以尝试下,完全可以正常反序列化对象。

而且明显我们自定义的序列化减少了空间占用,这在一些缓存或数据传输时会非常有用,尤其是在对象大和多的情况下,其差别会更明显。

同时我们还可以序列化和反序列化方法中添加自己的一些安全校验逻辑等等.

三. readObjectNoData方法的定义使用:

我们先来定义一个父类:

static class ParDerVo implements Serializable{
protected String kind;
/*private void readObjectNoData() throws ObjectStreamException{
this.kind="默认分类!!!";
}*/
}

然后让DerVo类继承此父类:

static class DerVo extends ParDerVo implements Serializable {
......
@Override
public String toString() {
return new StringJoiner(", ", DerVo.class.getSimpleName() + "[", "]").add("kind='" + kind + "'").add("size=" + size).add("values=" + Arrays.toString(values)).add("desc='" + desc + "'").toString();
}
}

1) 现在我们对DerVo前面的vo1实例的序列化结果进行反序列化,可以明确得到以下结果:

// vo1-read = DerVo[kind='null', size=4, values=[false, true, false, false], desc='Test']

因为我们现在的kind在序列化的时候还没有定义,也自然没有值了,但是如果这个kind是一个必要属性,或者说不能为NULL的属性,我们要怎么处理呢?

这个时候就该readObjectNoData方法起作用了,我们把注释去掉再来测试看结果:

// vo1-read = DerVo[kind='默认分类!!!', size=4, values=[false, true, false, false], desc='Test']

2) 那我们现在重新对vo1进行序列化再反序列化看看:

// vo1 = DerVo[kind='null', size=4, values=[false, true, false, false], desc='Test']
// vo1-read = DerVo[kind='null', size=4, values=[false, true, false, false], desc='Test']

我们发现因为现在的序列化时的DerVo结构和反序列化的时候一致了,所以readObjectNoData方法并没有被调用,这就是此方法生效的比较关键的第一个点了.

我们给kind属性赋值再进行上述过程看看:


......
vo1.kind="TestKind";
......
// 输出结果如下
// vo1 = DerVo[kind='TestKind', size=4, values=[false, true, false, false], desc='Test']
// vo1-read = DerVo[kind='TestKind', size=4, values=[false, true, false, false], desc='Test']

是不是发现跟正常的序列化,反序列化没两样了。

3) 不知道大家有没有注意,ParDerVo是实现了接口Serializable的,和前期说过的序列化的支持一样,readObjectNoData方法也必须在此前期下才能生效。

但是确实在实践中这个readObjectNoData方法我也从来没用过,不是很明确它的实践应用场景,大家有用过的可以分享一下经验!

四. 其实Serializable家族还有两个隐含方法:

private Object writeReplace() throws ObjectStreamException;
private Object readResolve() throws ObjectStreamException;

这两个方法同样对应的是序列化和反序列化,即:write对read;

这两个方法的应用场景一般是用在一些特殊需求上,在对象序列化和反序列化时对象本身将进行替换代替时使用.

在这里就不展开讲了,大家可以自行探索,欢迎大家分享学习!


相关推荐

「linux专栏」top命令用法详解,再也不怕看不懂top了

在linux系统中,我们经常使用到的一个命令就是top,它主要是用来显示系统运行中所有的进程和进程对应资源的使用等信息,所有的用户都可以使用top命令。top命令内容量丰富,可令使用者头疼的是无法全部...

Linux 中借助 perf 对 php 程序模拟CPU高的案例分析

导语本文是一篇Linux借助工具分析CPU高的优化案例,没有任何干货内容,很详细的展示了优化CPU高的具体步骤,非常适合初中级读者阅读!...

centos漏洞处理方法(centos podman)

centos服务器最近有诸多漏洞,修复命令及对应的漏洞整理后,分享给大家RHSA-2020:1176-低危:avahi安全更新yumupdateavahi-libsRHSA-2017:326...

Linux上的free命令详解(Buffer和Cache)

解释一下Linux上free命令的输出。下面是free的运行结果,一共有4行。为了方便说明,我加上了列号。这样可以把free的输出看成一个二维数组FO(FreeOutput)。例如:FO[2][1]...

linux 命令行之你真的会用吗?--free 基本用法篇

free命令行统计内存使用率及swap交换分区的使用率数据。是由sourceforge负责维护的,在ubuntu上其包名为procps,这个源码包中,除了free还有ps,top,vmstat,ki...

kong api gateway 初体验(konga github)

kongapigateway初体验(firstsight?)。Kong是一个可扩展的开源API层(也称为API网关或API中间件)。Kong运行在任何RESTfulAPI的前面,并通过插件...

在Ubuntu下开启IP转发的方法(ubuntu20 ip)

IP地址分为公有ip地址和私有ip地址,PublicAddress是由INIC(internetnetworkinformationcenter)负责的,这些IP地址分配给了注册并向INIC提...

基于 Kubernetes 的 Serverless PaaS 稳定性建设万字总结

作者:许成铭(竞霄)数字经济的今天,云计算俨然已经作为基础设施融入到人们的日常生活中,稳定性作为云产品的基本要求,研发人员的技术底线,其不仅仅是文档里承诺的几个九的SLA数字,更是与客户切身利益乃...

跟老韩学Ubuntu Linux系列-sysctl 帮助文档

sysctl一般用于基于内核级别的系统调优,man帮助手册如下。...

如何在 Linux/Unix/Windows 中发现隐藏的进程和端口

unhide是一个小巧的网络取证工具,能够发现那些借助rootkit、LKM及其它技术隐藏的进程和TCP/UDP端口。这个工具在Linux、UNIX类、MS-Windows等操作系统下都...

跟老韩学Ubuntu Server 2204-Linux性能管理-uptime指令帮助手册

uptime指令是每个从事Linux系统工作的相关同学必知必会的指令之一,如下是uptime指令的帮助手册。UPTIME(1)...

Openwrt+Rclone+emby+KODI搭建完美家庭影音服务器

特别声明:本篇内容参考了波仔分享,在此表示感谢!上一篇《Openwrt+emby+KODI搭建家庭影音服务器》只适用影音下载到本地的情形,不能播放云盘中的影音,内容较少,缺少了趣味性,也不直观。...

Linux Shell脚本经典案例(linux shell脚本例子)

编写Shell过程中注意事项:开头加解释器:#!/bin/bash语法缩进,使用四个空格;多加注释说明。命名建议规则:变量名大写、局部变量小写,函数名小写,名字体现出实际作用。默认变量是全局的,在函数...

解决 Linux 性能瓶颈的黄金 60 秒

如果你的Linux服务器突然负载暴增,告警短信快发爆你的手机,如何在最短时间内找出Linux性能问题所在?来看Netflix性能工程团队的这篇博文,看它们通过十条命令在一分钟内对机器性能问题进行诊断。...

跟老韩学Ubuntu Server 2204-Linux性能管理-vmstat指令帮助手册

vmstat可查看ubuntlinux的综合性能,是每个从事Linux人员必知必会、需掌握的核心指令之一。vmstat指令帮助手册如下。VMSTAT(8)...

取消回复欢迎 发表评论:

请填写验证码