java序列化,看这篇就够了
Posted 哪 吒
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java序列化,看这篇就够了相关的知识,希望对你有一定的参考价值。
面试官:兄弟,说说你对transient的理解和感悟
哪吒:what?还有感悟?
先说结论,在序列化、反序列化时,被transient关键字修饰的成员属性变量不会被序列化。
面试官:这就完了?
哪吒:面试官明显不是很满意,这怎么能够。
追根溯源,先说一下序列化与反序列化。
一、序列化是啥?
1、序列化和反序列化定义
Java序列化是指把Java对象转换为字节序列的过程。
Java反序列化是指把字节序列恢复为Java对象的过程。
2、面试官没听懂,有点迷啊!
简而言之,序列化是将Java对象转变为字节序列,便于持久化到本地磁盘,避免程序运行结束后对象从内存中消失,字节序列也方便在网络中传输。
序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。
3、序列化和反序列化的作用
(1)序列化作用
在传递和保存对象时,保存对象的完整性和可传递性。
对象转换为有序字节流,可以在网络上传输或者保存在本地文件(一般json/xml文件居多)中。
(2)反序列化作用
根据字节流中保存的对象状态及描述信息,通过反序列化重建对象。
二、序列化和反序列化的优缺点
1、优点
- 将对象转为字节流存储到硬盘上,当JVM停机的时候,字节流还会在硬盘上等待,等待下一次JVM启动,把序列化的对象,通过反序列化转为原来的对象,并且序列化的二进制序列能够减少存储空间(永久性保存对象)。
- 序列化为字节流形式的对象可以进行网络传输(二进制形式)。
- 通过序列化可以在进程间传递对象。
2、缺点
(1)无法跨语言
无法跨语言是Java序列化最致命的问题。
对于跨进程的服务调用,服务提供者可能是Java之外的其它语言,当我们需要和其它语言交互时,Java序列化就难以胜任。
事实上,目前几乎所有流行的Java RPC通信框架,都没有使用Java序列化作为编解码框架,原因就是它无法跨语言,而这些RPC框架往往需要支持跨语言调用。
Java RPC通信框架简介
RPC是远程过程调用的简称,广泛应用在大规模分布式应用中,作用是有助于系统的垂直拆分,使系统更易扩展。Java中RPC框架比较多,各有特色,广泛使用的有RMI、Hession、Dubbo等。
1、RMI(远程方法调用):
JAVA自带的远程方法调用工具,不过有一定的局限性,毕竟是JAVA语言最开始时的设计,后来很多框架的原理都基于RMI。
2、Hessian(基于HTTP的远程方法调用):
基于HTTP协议传输,在性能方面还不够完美,负载均衡和失效转移依赖于应用的负载均衡器,Hessian的使用则与RMI类似,区别在于淡化了Registry的角色,通过显示的地址调用,利用HessianProxyFactory根据配置的地址create一个代理对象,另外还要引入Hessian的Jar包。
3、Dubbo(淘宝开源的基于TCP的RPC框架)
Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
(2)序列化后流的长度比通过缓冲区处理要大的多。
(3)序列化性能太低
三、序列化使用场景
- 像银行卡、密码这些字段不能被序列化;
- 将对象存储到文件中时进行序列化,从文件中读取对象时需要反序列化;
- 分布式传递对象,或者网络传输,需要序列化;
- 存入缓存数据库(如 Redis)时需要用到序列化,将对象从缓存数据库中读取出来需要反序列化;
四、序列化和反序列化的注意事项
1、Java序列化的方式
实现 Serializable 接口:可以自定义 writeObject、readObject、writeReplace、readResolve 方法,会通过反射调用。
实现 Externalizable 接口,它是Serializable接口的子类,用户要实现的writeExternal()和readExternal() 方法,用来决定如何序列化和反序列化。因为序列化哪些字段,需要方法指定,所以transient在这里无效。
2、序列化ID问题
虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)。
3、静态字段不会序列化
序列化时不保存静态变量,这是因为序列化保存的是对象的状态,静态变量属于类的状态,因此序列化并不保存静态变量。
4、transient
transient代表对象的临时数据。
如果你不想让对象中的某个成员被序列化可以在定义它的时候加上 transient 关键字进行修饰
,这样,在对象被序列化时其就不会被序列化。
transient 修饰过的成员反序列化后将赋予默认值,即 0 或 null。
有些时候像银行卡号这些字段是不希望在网络上传输的,transient的作用就是把这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。
5、父类的序列化
当一个父类实现序列化,子类自动实现序列化;而子类实现了 Serializable 接口,父类也需要实现Serializable 接口。
6、当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化
7、并非所有的对象都可以序列化
(1)安全方面的原因,比如一个对象拥有private,public等field,对于一个要传输的对象,比如写到文件,或者进行RMI传输等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的;
(2)资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分配,而且,也是没有必要这样实现;
8、序列化解决深拷贝问题
如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存,这是能用序列化解决深拷贝的重要原因。
五、通过代码体验一下
1、实体类
package com.guor.bean;
import lombok.Data;
import java.io.Serializable;
@Data
public class User implements Serializable
private Integer id;
private String username;
private Integer age;
private transient String password;
2、ObjectOutputStream实现序列化和ObjectInputStream实现反序列化
package com.guor.test;
import com.guor.bean.User;
import java.io.*;
public class TransientTest
public static void main(String[] args)
try
SerializeUser();
DeSerializeUser();
catch (IOException e)
e.printStackTrace();
catch (ClassNotFoundException e)
e.printStackTrace();
//序列化
private static void SerializeUser() throws IOException
User user = new User();
user.setId(1);
user.setUsername("哪吒编程");
user.setAge(28);
user.setPassword("123456");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D://data.txt"));
oos.writeObject(user);
oos.close();
System.out.println("普通字段序列化:username= "+user.getUsername());
System.out.println("添加了transient关键字序列化:password= "+user.getPassword());
//反序列化
private static void DeSerializeUser() throws IOException, ClassNotFoundException
File file = new File("D://data.txt");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
User user = (User)ois.readObject();
System.out.println("普通字段反序列化:username= "+user.getUsername());
System.out.println("添加了transient关键字反序列化:password= "+user.getPassword());
3、控制台输出
当实体类未实现Serializable时运行会报错,并抛出NotSerializableException异常:
)
为什么会这样呢?跟踪源码一探究竟!
public final void writeObject(Object obj) throws IOException
if (enableOverride)
writeObjectOverride(obj);
return;
try
writeObject0(obj, false);
catch (IOException ex)
if (depth == 0)
writeFatalException(ex);
throw ex;
如果一个对象既不是字符串、数组、枚举,也没有实现Serializable接口,在序列化时就会抛出NotSerializableException异常
private void writeObject0(Object obj, boolean unshared)
throws IOException
...
if (obj instanceof String)
writeString((String) obj, unshared);
else if (cl.isArray())
writeArray(obj, desc, unshared);
else if (obj instanceof Enum)
writeEnum((Enum<?>) obj, desc, unshared);
else if (obj instanceof Serializable)
writeOrdinaryObject(obj, desc, unshared);
else
if (extendedDebugInfo)
throw new NotSerializableException(
cl.getName() + "\\n" + debugInfoStack.toString());
else
throw new NotSerializableException(cl.getName());
...
从上述代码看,Serializable也仅仅是起到了一个标识作用,表示当前的bean是支持序列化和反序列化的。
正常情况下:
序列化文件:
控制台显示:
六、serialVersionUID
我记得实体类在序列化的时候,要写一个private static final long serialVersionUID = 1L;
来着,没写也没问题啊。
在实体类添加一个属性address,对上面生成的D://data.txt
文件,再反序列化一次,看看效果。
从红线部分提示可以看出两点:
- 在进行序列化、反序列化时,程序自动生成了一个serialVersionUID;
- 前后序列号不相同;
我觉得只要前后序列号相同,就可以反序列化成功,序列化号 serialVersionUID 属于版本控制的作用。
如果上图能够给我一个再来一次的机会!
package com.guor.bean;
import lombok.Data;
import java.io.Serializable;
@Data
public class User implements Serializable
private static final long serialVersionUID = -3796120637328232911L;
private Integer id;
private String username;
private Integer age;
private transient String password;
// 反序列化时,后添加的
private String address;
哪吒精品系列文章
以上是关于java序列化,看这篇就够了的主要内容,如果未能解决你的问题,请参考以下文章