Java序列化技术

简介: 什么是序列化和反序列化     Serialization(序列化)是一种将对象以一连串的字节描述的过程;反序列化deserialization是一种将这些字节重建成一个对象的过程。 什么情况下需要序列化  当你想把的内存中的对象保存到一个文件中或者数据库中时候(数据持久化); 利用序列化...

什么是序列化和反序列化

    Serialization(序列化)是一种将对象以一连串的字节描述的过程;反序列化deserialization是一种将这些字节重建成一个对象的过程。

什么情况下需要序列化 

  • 当你想把的内存中的对象保存到一个文件中或者数据库中时候(数据持久化);
  • 利用序列化实现远程通信,即在网络上传送对象的字节序列;

如何实现序列化

    将需要序列化的类实现Serializable接口就可以了,Serializable接口中没有任何方法,可以理解为一个标记,即表明这个类可以序列化.

序列化和反序列化例子

    如果我们想要序列化一个对象,首先要创建某些OutputStream(如FileOutputStream、ByteArrayOutputStream等),然后将这些OutputStream封装在一个ObjectOutputStream中。这时候,只需要调用writeObject()方法就可以将对象序列化,并将其发送给OutputStream(记住:对象的序列化是基于字节的,不能使用Reader和Writer等基于字符的层次结构)。而反序列的过程(即将一个序列还原成为一个对象),需要将一个InputStream(如FileInputstream、ByteArrayInputStream等)封装在ObjectInputStream内,然后调用readObject()即可。

 

public class Serialize implements Serializable{  
  
    private static final long serialVersionUID = -5211389707739541364L;  
    public int num = 1390;  
  
    public void serialized()  
    {  
        try {  
            FileOutputStream fos = new FileOutputStream("serialize.obj"); ObjectOutputStream oos = new ObjectOutputStream(fos); Serialize serialize = new Serialize(); oos.writeObject(serialize); oos.flush(); oos.close();  fos.close(); System.out.println("序列化结束"); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public void deserialized() { Serialize serialize = null; try { FileInputStream fis = new FileInputStream("serialize.obj"); ObjectInputStream ois = new ObjectInputStream(fis); serialize = (Serialize) ois.readObject(); ois.close(); fis.close(); System.out.println("反序列化结束"); } catch (ClassNotFoundException | IOException e) { e.printStackTrace(); } System.out.println(serialize.num); } public static void main(String[] args) { Serialize serialize = new Serialize(); serialize.serialized(); serialize.deserialized(); } } 

序列化的数据含有那些信息

    这里举个例子将上面例子中的serialize.obj的信息读取出来:

public class ReadSerialize {  
  
    public static void main(String[] args) {  
        try {  
            File file = new File("serialize.obj");  
            InputStream in = new FileInputStream(file);  
            byte buff[] = new byte[1024]; int len = 0; while((len = in.read(buff)) !=-1) { for(int i=0;i<len;i++) { System.out.printf("%02X ",buff[i]); } System.out.println(); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } 

运行结果:

AC ED 00 05 73   
72 00 17 63 6F 6D 2E 73 65 72 69 61 6C 69 7A 65 2E 53 65 72 69 61 6C 69 7A 65 B7 AD 6C AC 04 0E D0 8C 02 00 01   
49 00 03 6E 75 6D   
78 70  
00 00 05 6E  

解析:
第一部分是序列化文件头
AC ED:STREAM_MAGIC声明使用了序列化协议
00 05:STREAM_VERSION序列化协议版本
73:TC_OBJECT声明这是一个新的对象
第二部分是序列化类的描述
72:TC_CLASSDESC声明这里开始一个新class
00 17:class名字的长度是23字节
63 6F 6D 2E 73 65 72 69 61 6C 69 7A 65 2E 53 65 72 69 61 6C 69 7A 65:类名(ASCII码:com.serialize.Serialize)
B7 AD 6C AC 04 0E D0 8C: SerialVersionUID
02:标记号,改值声明改对象支持序列化
00 01:该类所包含的域的个数为1
第三部分是对象中各个属性项的描述
49:域类型,代表I,表示Int类型(又如:44,查ASCII码表为D,代表Double类型)
00 03:域名字的长度,为3
6E 75 6D: num属性的名称
第四部分输出该对象父类信息描述,这里没有父类,如果有,则数据格式与第二部分一样
78:TC_ENDBLOCKDATA,对象块接收标志
70:TC_NULL,说明没有其他超类的标志
第五部分输出对象的属性的实际值,如果属性项是一个对象,那么这里还将序列化这个对象,规则和第2部分一样。
00 00 05 6E:1390的值

序列化前和序列化后的对象的关系
    序列化时深复制,反序列化还原后的对象地址与原来的不同。

破坏单例模式

    序列化和反序列化可能会破坏单例。序列化前和序列化后的对象的关系已说明了这点。

  如何防止单例被破坏?在单例的类中加readResolve方法。当ObjectIputStream调用的时候可以不产出新的对象。

查看ObjectIputStream.class的源码,当中有这样一段话:   

Deserializing an object via readUnshared invalidates the stream handle associated with the returned object. Note that this in itself does not always guarantee that the reference returned by readUnshared is unique; the deserialized object may define a readResolve method which returns an object visible to other parties, or readUnshared may return a Class object or enum constant obtainable elsewhere in the stream or through external means. If the deserialized object defines a readResolve method and the invocation of that method returns an array, then readUnshared returns a shallow clone of that array; this guarantees that the returned

array object is unique and cannot be obtained a second time from an invocation of readObject or readUnshared on the ObjectInputStream, even if the underlying data stream has been manipulated.

    private Object readResolve()  
    {  
        return instance;  
    }  

序列化ID

    序列化 ID 在 Eclipse 下提供了两种生成策略,一个是固定的 1L,一个是随机生成一个不重复的 long 类型数据(实际上是使用 JDK 工具生成),在这里有一个建议,如果没有特殊需求,就是用默认的 1L 就可以,这样可以确保代码一致时反序列化成功。这也可能是造成序列化和反序列化失败的原因,因为不同的序列化id之间不能进行序列化和反序列化。

静态变量能否序列化

    序列化会忽略静态变量,即序列化不保存静态变量的状态。静态成员属于类级别的,所以不能序列化。即序列化的是对象的状态不是类的状态。这里的不能序列化的意思,是序列化信息中不包含这个静态成员域。transient后的变量也不能序列化。

transient使用小结

  • 一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
  • transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
  • 被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。

总结

  • 当父类继承Serializable接口时,所有子类都可以被序列化。
  • 子类实现了Serializable接口,父类没有,父类中的属性不能被序列化(不报错,数据不会丢失),但是在子类中的属性仍能正确序列化
  • 如果序列化的属性是对象,则这个对象也必须实现Serializable接口,否则会报错。
  • 在反序列化时,如果对象的属性有修改或删减,则修改的部分属性会丢失,但不会报错。
  • 在反序列化时,如果serialVersionUID被修改,则反序列化时会失败
  • 当一个对象的实例变量引用其他对象,序列化该对象时,也把引用对象进行序列化
  • static,transient后的变量不能被序列化

建议

在java环境下,java序列化能够很好的工作,但在多语言环境下,用java序列化存储后,很难用其他语言来还原出结果,在这种情况下,还是要尽量存储通用数据结构,如json或者xml的结构数据,当前也有比较好的序列化工具,如google的protobuf。

 

目录
相关文章
|
2月前
|
监控 Cloud Native Java
Quarkus 云原生Java框架技术详解与实践指南
本文档全面介绍 Quarkus 框架的核心概念、架构特性和实践应用。作为新一代的云原生 Java 框架,Quarkus 旨在为 OpenJDK HotSpot 和 GraalVM 量身定制,显著提升 Java 在容器化环境中的运行效率。本文将深入探讨其响应式编程模型、原生编译能力、扩展机制以及与微服务架构的深度集成,帮助开发者构建高效、轻量的云原生应用。
264 44
|
2月前
|
安全 Java API
Java Web 在线商城项目最新技术实操指南帮助开发者高效完成商城项目开发
本项目基于Spring Boot 3.2与Vue 3构建现代化在线商城,涵盖技术选型、核心功能实现、安全控制与容器化部署,助开发者掌握最新Java Web全栈开发实践。
306 1
|
3月前
|
安全 Java 编译器
new出来的对象,不一定在堆上?聊聊Java虚拟机的优化技术:逃逸分析
逃逸分析是一种静态程序分析技术,用于判断对象的可见性与生命周期。它帮助即时编译器优化内存使用、降低同步开销。根据对象是否逃逸出方法或线程,分析结果分为未逃逸、方法逃逸和线程逃逸三种。基于分析结果,编译器可进行同步锁消除、标量替换和栈上分配等优化,从而提升程序性能。尽管逃逸分析计算复杂度较高,但其在热点代码中的应用为Java虚拟机带来了显著的优化效果。
93 4
|
3月前
|
Java API Maven
2025 Java 零基础到实战最新技术实操全攻略与学习指南
本教程涵盖Java从零基础到实战的全流程,基于2025年最新技术栈,包括JDK 21、IntelliJ IDEA 2025.1、Spring Boot 3.x、Maven 4及Docker容器化部署,帮助开发者快速掌握现代Java开发技能。
703 1
|
4月前
|
人工智能 Java
Java多任务编排技术
JDK 5引入Future接口实现异步任务处理,但获取结果不够灵活。Java 8新增CompletableFuture,实现异步任务编排,支持流式处理、多任务组合及异常处理,提升执行效率与代码可读性,简化并发编程复杂度。
101 0
|
1月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
103 1
|
1月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
111 1
|
3月前
|
Java 测试技术 API
2025 年 Java 开发者必知的最新技术实操指南全览
本指南涵盖Java 21+核心实操,详解虚拟线程、Spring Boot 3.3+GraalVM、Jakarta EE 10+MicroProfile 6微服务开发,并提供现代Java开发最佳实践,助力开发者高效构建高性能应用。
519 4
|
2月前
|
安全 Cloud Native Java
Java 模块化系统(JPMS)技术详解与实践指南
本文档全面介绍 Java 平台模块系统(JPMS)的核心概念、架构设计和实践应用。作为 Java 9 引入的最重要特性之一,JPMS 为 Java 应用程序提供了强大的模块化支持,解决了长期存在的 JAR 地狱问题,并改善了应用的安全性和可维护性。本文将深入探讨模块声明、模块路径、访问控制、服务绑定等核心机制,帮助开发者构建更加健壮和可维护的 Java 应用。
219 0
|
3月前
|
JavaScript 安全 前端开发
Java开发:最新技术驱动的病人挂号系统实操指南与全流程操作技巧汇总
本文介绍基于Spring Boot 3.x、Vue 3等最新技术构建现代化病人挂号系统,涵盖技术选型、核心功能实现与部署方案,助力开发者快速搭建高效、安全的医疗挂号平台。
192 3