序列化知识要点:
1:what 是什么
java对象序列化 :把对象转换为字节序列的过程。
java对象反序列化:把字节序列恢复为对象的过程。
2:why , 为什么要用序列化,有什么优势
序列化主要有两种用途:// 就是用来将对象编码成字节流,用于方便网络传输和存储对象
1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
2) 在网络上传送对象的字节序列。
字节流:而字节流处理单元为 1 个字节,操作字节和字节数组。
字符流:字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符、字符数组或字符串。
比如目前比较常用的RPC框架RMI Hessian httpinvoker netty等涉及到网络传输的对象都需要实现对象序列化
// 拓展
远程传输实现序列化调用: 1:既能传输对象类型,也能传输基本类型
2:方便的持久化对象,比如mybatis和herbenate都需要bean对象继承序列化接口
常用的Http传输: 1:只能传输基本类型
// 实现原理与步骤
Hessian:客户端(basic.hello())——>序列化写到输出流——>远程方法(服务器端)——>序列化写到输出流 ——>客户端读取输入流——>输出结果
RMI:客户端——>stub——>序列化——>skeleton——>远程方法——>序列化——>stub—— >输出结果
3:how 怎么用
1:具体的使用场景
1:分布式框架的rpc调用对象 服务之间的网络传输对象
2:需要缓存的数据对象
2:java序列化具体的过程:
serialVersionUID适用于Java的序列化机制。简单来说,Java的序列化机制是通过判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,
JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,
否则就会出现序列化版本不一致的异常,即是InvalidCastException。
具体的序列化过程是这样的:
序列化操作的时候系统会把当前类的serialVersionUID写入到序列化文件中,
当反序列化时系统会去检测文件中的serialVersionUID,
判断它是否与当前类的serialVersionUID一致,如果一致就说明序列化类的版本与当前类版本是一样的,可以反序列化成功,否则失败。
serialVersionUID有两种显示的生成方式:
一是默认的1L,比如:private static final long serialVersionUID = 1L;
二是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:private static final long serialVersionUID = xxxxL;
当实现java.io.Serializable接口的类没有显式地定义一个serialVersionUID变量时候,
Java序列化机制会根据编译的Class自动生成一个serialVersionUID作序列化版本比较用,
这种情况下,如果Class文件(类名,方法明等)没有发生变化(增加空格,换行,增加注释等等),就算再编译多次,serialVersionUID也不会变化的。
如果我们不希望通过编译来强制划分软件版本,即实现序列化接口的实体能够兼容先前版本,
就需要显式地定义一个名为serialVersionUID,类型为long的变量,不修改这个变量值的序列化实体都可以相互进行串行化和反串行化。
问题:
假如不定义具体的serialVersionUID,就会产生版本兼容性问题,默认生成的serialVersionUID会根据你的字段修改状态来改变serialVersionUID,
反序列化时就会导致InvalidClassException报错
3:实现序列化的接口及用例:
Throwable类异常的抽象类实现,当RMI调用异常的时候,报错信息可以从服务端向客户端传递
HttpServlet请求的抽象类实现,因此可以缓存会话对象
4:设计序列化注意点
内部类不能实现序列化 -- 因为内部类可以访问外部类所有的成员变量,内部类有外部类的一个引用。但是这些属性与类的作用域在类中并没有明确的定义,所以内部类的序列化形式也就不清楚,
就好比匿名类和局部类编译时只会产生一个临时的记号而不是固定的一个记号一样的道理
尽量不要使用继承方式来设计序列化类 -- 除非你的功能固定 以后的设计并不会涉及到父类的业务修改
对于不想被序列化的属性 使用transient -- 比如用户类对象里属性为passwd
尽量显示调用serialVersionUID --避免版本由于版本冲突导致序列化异常
尽量使用自定义的序列化方式进行对象序列化
5:默认序列化设计的缺陷
1:实现默认序列化会出现bug和安全漏洞 -- 由于java序列化机制的问题,java反序列化都会由一个默认的构造方法进行对象恢复,由于这个默认的机制就有可能使你的数据被非法访问和获取
2:消耗过多的空间 -- 由于默认序列化并不知道,你需要序列化的元素和不需要序列化的元素,在复杂的对象类中,比如对象是一个大链表,序列化不仅实现了每一项,而且还维系了每个链表的指针关系
3:消耗过多的时间 -- 由于默认序列化是不知道你不同对象的元素是多少,所以它会用一个比较通用的对象遍历方法来实现序列化
4:引起栈溢出 -- 由于默认序列化是使用递归的形式进行类内元素恢复,假如元素过多的时候很容易产生栈溢出
5: 这个类到处的API永远的束缚在该类的内部表示法上 -- 假如你发布了这个类,这个类就上的方法就会变成共有方法,对于这个新版本迭代时你就必须得实现这些代码,否则会反编译失败
什么是堆栈:
Java 把内存划分成两种:一种是栈内存,另一种是堆内存。
栈:主要用于存储局部变量和对象的引用变量
堆:主要用于存储实例化的对象(new),数组
// 递归引起栈溢出
递归调用,只有走到最后的结束点后函数才能依次退出,而未到达最后的结束点之前,占用的栈空间一直没有释放,如果递归调用次数过多,就可能导致占用的栈资源超过线程的最大值,从而导致栈溢出,导致程序的异常退出。
6:自定义序列化结构如何实现
实现serializable 添加public void writeObject(ObjectOutPutSteam steam){}; -- 自定义输出字节码内容
public void readObject(ObjectInputSteam steam){}; -- 自定义读取字节码内容
4:IDEA中如何自动化生成serialVersionUID
如下图:Preferences -> Inspections -> Serialization issues -> Serialization class without 'serialVersionUID' 打上勾
类实现Serializable接口
对类按ALT+ENTER 添加add serialVersionUID