diff --git a/docs/java/basis/java-basic-questions-03.md b/docs/java/basis/java-basic-questions-03.md index d22e05cb..c13155e6 100644 --- a/docs/java/basis/java-basic-questions-03.md +++ b/docs/java/basis/java-basic-questions-03.md @@ -440,7 +440,9 @@ SPI 将服务接口和具体的服务实现分离开来,将服务调用方和 - 需要遍历加载所有的实现类,不能做到按需加载,这样效率还是相对较低的。 - 当多个 `ServiceLoader` 同时 `load` 时,会有并发问题。 -## I/O +## 序列化和反序列化 + +关于序列化和反序列化的详细解读,请看这篇文章 [Java 序列化详解](./serialization.md) ,里面涉及到的知识点和面试题更全面。 ### 什么是序列化?什么是反序列化? @@ -453,6 +455,13 @@ SPI 将服务接口和具体的服务实现分离开来,将服务调用方和 对于 Java 这种面向对象编程语言来说,我们序列化的都是对象(Object)也就是实例化后的类(Class),但是在 C++这种半面向对象的语言中,struct(结构体)定义的是数据结构类型,而 class 对应的是对象类型。 +下面是序列化和反序列化常见应用场景: + +- 对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化; +- 将对象存储到文件之前需要进行序列化,将对象从文件中读取出来需要进行反序列化; +- 将对象存储到数据库(如 Redis)之前需要用到序列化,将对象从缓存数据库中读取出来需要反序列化; +- 将对象存储到内存之前需要进行序列化,从内存中读取出来之后需要进行反序列化。 + 维基百科是如是介绍序列化的: > **序列化**(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。依照序列化格式重新获取字节的结果时,可以利用它来产生与原始对象相同语义的副本。对于许多对象,像是使用大量引用的复杂对象,这种序列化重建的过程并不容易。面向对象中的对象序列化,并不概括之前原始对象所关系的函数。这种过程也称为对象编组(marshalling)。从一系列字节提取数据结构的反向操作,是反序列化(也称为解编组、deserialization、unmarshalling)。 @@ -463,6 +472,21 @@ SPI 将服务接口和具体的服务实现分离开来,将服务调用方和
https://www.corejavaguru.com/java/serialization/interview-questions-1
+**序列化协议对应于 TCP/IP 4 层模型的哪一层?** + +我们知道网络通信的双方必须要采用和遵守相同的协议。TCP/IP 四层模型是下面这样的,序列化协议属于哪一层呢? + +1. 应用层 +2. 传输层 +3. 网络层 +4. 网络接口层 + + + +如上图所示,OSI 七层协议模型中,表示层做的事情主要就是对应用层的用户数据进行处理转换为二进制流。反过来的话,就是将二进制流转换成应用层的用户数据。这不就对应的是序列化和反序列化么? + +因为,OSI 七层协议模型中的应用层、表示层和会话层对应的都是 TCP/IP 四层模型中的应用层,所以序列化协议属于 TCP/IP 协议应用层的一部分。 + ### 如果有些字段不想进行序列化怎么办? 对于不想进行序列化的变量,使用 `transient` 关键字修饰。 @@ -475,6 +499,28 @@ SPI 将服务接口和具体的服务实现分离开来,将服务调用方和 - `transient` 修饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,如果是修饰 `int` 类型,那么反序列后结果就是 `0`。 - `static` 变量因为不属于任何对象(Object),所以无论有没有 `transient` 关键字修饰,均不会被序列化。 +### 常见序列化协议有哪些? + +JDK 自带的序列化方式一般不会用 ,因为序列化效率低并且存在安全问题。比较常用的序列化协议有 Hessian、Kryo、Protobuf、ProtoStuff,这些都是基于二进制的序列化协议。 + +像 JSON 和 XML 这种属于文本类序列化方式。虽然可读性比较好,但是性能较差,一般不会选择。 + +### 为什么不推荐使用 JDK 自带的序列化? + +我们很少或者说几乎不会直接使用 JDK 自带的序列化方式,主要原因有下面这些原因: + +- **不支持跨语言调用** : 如果调用的是其他语言开发的服务的时候就不支持了。 +- **性能差** :相比于其他序列化框架性能更低,主要原因是序列化之后的字节数组体积较大,导致传输成本加大。 +- **存在安全问题** :序列化和反序列化本身并不存在问题。但当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码。相关阅读:[应用安全:JAVA反序列化漏洞之殇](https://cryin.github.io/blog/secure-development-java-deserialization-vulnerability/) 。 + +## I/O + +关于I/O的详细解读,请看下面这几篇文章,里面涉及到的知识点和面试题更全面。 + +- [Java IO 基础知识总结](../io/io-basis.md) +- [Java IO 设计模式总结](../io/io-design-patterns.md) +- [Java IO 模型详解](../io/io-model.md) + ### Java IO 流了解吗? IO 即 `Input/Output`,输入和输出。数据输入到计算机内存的过程即输入,反之输出到外部存储(比如数据库,文件,远程主机)的过程即输出。数据传输过程类似于水流,因此称为 IO 流。IO 流在 Java 中分为输入流和输出流,而根据数据的处理方式又分为字节流和字符流。 @@ -484,8 +530,6 @@ Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生出来 - `InputStream`/`Reader`: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。 - `OutputStream`/`Writer`: 所有输出流的基类,前者是字节输出流,后者是字符输出流。 -相关阅读:[Java IO 基础知识总结](../io/io-basis.md)。 - ### I/O 流为什么要分为字节流和字符流呢? 问题本质想问:**不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?** @@ -497,11 +541,11 @@ Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生出来 ### Java IO 中的设计模式有哪些? -[Java IO 设计模式总结](../io/io-design-patterns.md) +参考答案:[Java IO 设计模式总结](../io/io-design-patterns.md) ### BIO、NIO 和 AIO 的区别? -[Java IO 模型详解](../io/io-model.md) +参考答案:[Java IO 模型详解](../io/io-model.md) ## 语法糖 diff --git a/docs/java/basis/serialization.md b/docs/java/basis/serialization.md index a0f0abd4..e6645178 100644 --- a/docs/java/basis/serialization.md +++ b/docs/java/basis/serialization.md @@ -5,9 +5,7 @@ tag: - Java基础 --- -## 序列化和反序列化相关概念 - -### 什么是序列化?什么是反序列化? +## 什么是序列化和反序列化? 如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化。 @@ -18,6 +16,13 @@ tag: 对于 Java 这种面向对象编程语言来说,我们序列化的都是对象(Object)也就是实例化后的类(Class),但是在 C++这种半面向对象的语言中,struct(结构体)定义的是数据结构类型,而 class 对应的是对象类型。 +下面是序列化和反序列化常见应用场景: + +- 对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化; +- 将对象存储到文件之前需要进行序列化,将对象从文件中读取出来需要进行反序列化; +- 将对象存储到数据库(如 Redis)之前需要用到序列化,将对象从缓存数据库中读取出来需要反序列化; +- 将对象存储到内存之前需要进行序列化,从内存中读取出来之后需要进行反序列化。 + 维基百科是如是介绍序列化的: > **序列化**(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。依照序列化格式重新获取字节的结果时,可以利用它来产生与原始对象相同语义的副本。对于许多对象,像是使用大量引用的复杂对象,这种序列化重建的过程并不容易。面向对象中的对象序列化,并不概括之前原始对象所关系的函数。这种过程也称为对象编组(marshalling)。从一系列字节提取数据结构的反向操作,是反序列化(也称为解编组、deserialization、unmarshalling)。 @@ -28,32 +33,26 @@ tag:https://www.corejavaguru.com/java/serialization/interview-questions-1
-### 实际开发中有哪些用到序列化和反序列化的场景? - -1. 对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化; -2. 将对象存储到文件中的时候需要进行序列化,将对象从文件中读取出来需要进行反序列化。 -3. 将对象存储到缓存数据库(如 Redis)时需要用到序列化,将对象从缓存数据库中读取出来需要反序列化。 - -### 序列化协议对应于 TCP/IP 4 层模型的哪一层? +**序列化协议对应于 TCP/IP 4 层模型的哪一层?** 我们知道网络通信的双方必须要采用和遵守相同的协议。TCP/IP 四层模型是下面这样的,序列化协议属于哪一层呢? -1. 网络接口层 -2. 网络层 -3. 传输层 -4. 应用层 +1. 应用层 +2. 传输层 +3. 网络层 +4. 网络接口层 - + 如上图所示,OSI 七层协议模型中,表示层做的事情主要就是对应用层的用户数据进行处理转换为二进制流。反过来的话,就是将二进制流转换成应用层的用户数据。这不就对应的是序列化和反序列化么? 因为,OSI 七层协议模型中的应用层、表示层和会话层对应的都是 TCP/IP 四层模型中的应用层,所以序列化协议属于 TCP/IP 协议应用层的一部分。 -## 常见序列化协议对比 +## 常见序列化协议有哪些? -JDK 自带的序列化方式一般不会用 ,因为序列化效率低并且部分版本有安全漏洞。比较常用的序列化协议有 hessian、kryo、protostuff。 +JDK 自带的序列化方式一般不会用 ,因为序列化效率低并且存在安全问题。比较常用的序列化协议有 Hessian、Kryo、Protobuf、ProtoStuff,这些都是基于二进制的序列化协议。 -下面提到的都是基于二进制的序列化协议,像 JSON 和 XML 这种属于文本类序列化方式。虽然 JSON 和 XML 可读性比较好,但是性能较差,一般不会选择。 +像 JSON 和 XML 这种属于文本类序列化方式。虽然可读性比较好,但是性能较差,一般不会选择。 ### JDK 自带的序列化方式 @@ -76,12 +75,33 @@ public class RpcRequest implements Serializable { } ``` -> 序列化号 serialVersionUID 属于版本控制的作用。序列化的时候 serialVersionUID 也会被写入二进制序列,当反序列化时会检查 serialVersionUID 是否和当前类的 serialVersionUID 一致。如果 serialVersionUID 不一致则会抛出 `InvalidClassException` 异常。强烈推荐每个序列化类都手动指定其 `serialVersionUID`,如果不手动指定,那么编译器会动态生成默认的序列化号 +**serialVersionUID 有什么作用?** -我们很少或者说几乎不会直接使用这个序列化方式,主要原因有两个: +序列化号 `serialVersionUID` 属于版本控制的作用。反序列化时,会检查 `serialVersionUID` 是否和当前类的 `serialVersionUID` 一致。如果 `serialVersionUID` 不一致则会抛出 `InvalidClassException` 异常。强烈推荐每个序列化类都手动指定其 `serialVersionUID`,如果不手动指定,那么编译器会动态生成默认的 `serialVersionUID`。 -1. **不支持跨语言调用** : 如果调用的是其他语言开发的服务的时候就不支持了。 -2. **性能差** :相比于其他序列化框架性能更低,主要原因是序列化之后的字节数组体积较大,导致传输成本加大。 +**serialVersionUID 不是被 static 变量修饰了吗?为什么还会被“序列化”?** + +`static` 修饰的变量是静态变量,位于方法区,本身是不会被序列化的。 `static` 变量是属于类的而不是对象。你反序列之后,`static` 变量的值就像是默认赋予给了对象一样,看着就像是 `static` 变量被序列化,实际只是假象罢了。 + +**如果有些字段不想进行序列化怎么办?** + +对于不想进行序列化的变量,可以使用 `transient` 关键字修饰。 + +`transient` 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 `transient` 修饰的变量值不会被持久化和恢复。 + +关于 `transient` 还有几点注意: + +- `transient` 只能修饰变量,不能修饰类和方法。 +- `transient` 修饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,如果是修饰 `int` 类型,那么反序列后结果就是 `0`。 +- `static` 变量因为不属于任何对象(Object),所以无论有没有 `transient` 关键字修饰,均不会被序列化。 + +**为什么不推荐使用 JDK 自带的序列化?** + +我们很少或者说几乎不会直接使用 JDK 自带的序列化方式,主要原因有下面这些原因: + +- **不支持跨语言调用** : 如果调用的是其他语言开发的服务的时候就不支持了。 +- **性能差** :相比于其他序列化框架性能更低,主要原因是序列化之后的字节数组体积较大,导致传输成本加大。 +- **存在安全问题** :序列化和反序列化本身并不存在问题。但当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码。相关阅读:[应用安全:JAVA反序列化漏洞之殇 - Cryin](https://cryin.github.io/blog/secure-development-java-deserialization-vulnerability/) 、[Java反序列化安全漏洞怎么回事? - Monica](https://www.zhihu.com/question/37562657/answer/1916596031)。 ### Kryo @@ -174,13 +194,13 @@ protostuff 基于 Google protobuf,但是提供了更多的功能和更简易 Github 地址:[https://github.com/protostuff/protostuff](https://github.com/protostuff/protostuff)。 -### hessian +### Hessian -hessian 是一个轻量级的,自定义描述的二进制 RPC 协议。hessian 是一个比较老的序列化实现了,并且同样也是跨语言的。 +Hessian 是一个轻量级的,自定义描述的二进制 RPC 协议。Hessian 是一个比较老的序列化实现了,并且同样也是跨语言的。  -dubbo RPC 默认启用的序列化方式是 hessian2 ,但是,Dubbo 对 hessian2 进行了修改,不过大体结构还是差不多。 +Dubbo2.x 默认启用的序列化方式是 Hessian2 ,但是,Dubbo 对 Hessian2 进行了修改,不过大体结构还是差不多。 ### 总结 @@ -191,8 +211,3 @@ Kryo 是专门针对 Java 语言序列化方式并且性能非常好,如果你 像 Protobuf、 ProtoStuff、hessian 这类都是跨语言的序列化方式,如果有跨语言需求的话可以考虑使用。 除了我上面介绍到的序列化方式的话,还有像 Thrift,Avro 这些。 - -## 其他推荐阅读 - -- 美团技术团队-序列化和反序列化:[https://tech.meituan.com/2015/02/26/serialization-vs-deserialization.html](https://tech.meituan.com/2015/02/26/serialization-vs-deserialization.html) -- 在 Dubbo 中使用高效的 Java 序列化(Kryo 和 FST): [https://dubbo.apache.org/zh/docs/v3.0/references/serializations/serialization/](https://dubbo.apache.org/zh/docs/v3.0/references/serializations/serialization/)