当前位置: 首页 > article >正文

《Effective Java》学习笔记——第8部分 序列化

文章目录

      • 一、前言:
      • 二、序列化机制最佳实践
        • 1. 了解序列化的工作原理
        • 2. 显式声明 serialVersionUID
        • 3. 避免序列化不必要的字段
        • 4. 控制序列化过程(自定义序列化)
        • 5. 避免序列化中的 Singleton问题
        • 6. 避免不必要的反序列化
        • 7. 考虑序列化的性能
        • 8. 选择合适的序列化机制
      • 三、小结


image-20250122103431384

一、前言:

《Effective Java》第8部分“序列化”深入讨论了 Java 中的序列化机制及其最佳实践。序列化是将对象的状态转换为字节流,以便于存储或通过网络传输。Java 提供了内置的序列化机制,但在使用时需要谨慎,尤其是在处理复杂对象和版本控制时。合理使用序列化能够确保系统的稳定性、兼容性和性能。

二、序列化机制最佳实践

1. 了解序列化的工作原理
  • 原因:序列化是将 Java 对象的状态转换为字节流的过程,而反序列化则是将字节流转换回对象。了解序列化机制的工作原理对于使用和优化序列化至关重要。

  • 最佳实践:

    • 序列化依赖于 Serializable 接口,这个接口没有方法,它仅仅是一个标记接口。实现该接口的类表示该类的对象可以被序列化。
    • 使用 ObjectOutputStreamObjectInputStream 类来完成对象的序列化和反序列化操作。
  • 示例:

    // 实现序列化接口
    public class Person implements Serializable {
        private static final long serialVersionUID = 1L;
        private String name;
        private int age;
    
        // 构造器、getter 和 setter
    }
    
2. 显式声明 serialVersionUID
  • 原因serialVersionUID 是用于版本控制的标识符。它用于确保反序列化时,序列化版本的兼容性。如果反序列化时类的版本不同且没有显式声明 serialVersionUID,可能会导致 InvalidClassException

  • 最佳实践:

    • 显式声明 serialVersionUID,并确保它在类版本发生更改时更新。这样可以防止因为类的变更导致反序列化失败。
    • 通过手动定义 serialVersionUID,避免 JVM 根据类的结构自动生成它。
  • 示例:

    public class Person implements Serializable {
        private static final long serialVersionUID = 1L;  // 显式声明 serialVersionUID
        private String name;
        private int age;
    
        // 构造器、getter 和 setter
    }
    
3. 避免序列化不必要的字段
  • 原因:序列化会将对象的所有字段都写入字节流。如果对象包含大量不必要的字段(如临时计算的字段或非序列化字段),则会浪费空间,并增加序列化和反序列化的开销。

  • 最佳实践:

    • 使用 transient 关键字标记不需要序列化的字段。这些字段在序列化时将被忽略。
    • 确保只序列化那些真正需要持久化的字段。
  • 示例:

    public class Person implements Serializable {
        private static final long serialVersionUID = 1L;
        private String name;
        private transient int age;  // 标记为 transient,避免序列化
    }
    
4. 控制序列化过程(自定义序列化)
  • 原因:默认的序列化行为可能无法满足某些特定需求。通过自定义序列化方法,可以控制序列化和反序列化的行为,如对字段的加密、压缩等操作。

  • 最佳实践:

    • 重写 writeObject()readObject() 方法来自定义序列化和反序列化的行为。
    • 在自定义序列化过程中,要注意处理 ObjectInputStreamObjectOutputStream,确保序列化的一致性和安全性。
  • 示例:

    public class Person implements Serializable {
        private static final long serialVersionUID = 1L;
        private String name;
        private transient int age;  // 标记为 transient,避免序列化
    
        private void writeObject(ObjectOutputStream oos) throws IOException {
            oos.defaultWriteObject();
            // 自定义序列化操作
            oos.writeInt(age);  // 手动序列化 age 字段
        }
    
        private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
            ois.defaultReadObject();
            // 自定义反序列化操作
            age = ois.readInt();  // 手动反序列化 age 字段
        }
    }
    
5. 避免序列化中的 Singleton问题
  • 原因:序列化和反序列化可能会破坏单例模式,因为反序列化过程中会创建一个新的实例,从而导致多个实例。为了避免这种情况,需要采取特殊措施。

  • 最佳实践:

    • Singleton 类中使用 readResolve() 方法来确保反序列化时返回现有的单例实例。
  • 示例:

    public class Singleton implements Serializable {
        private static final long serialVersionUID = 1L;
        private static final Singleton INSTANCE = new Singleton();
    
        private Singleton() {}
    
        public static Singleton getInstance() {
            return INSTANCE;
        }
    
        // 反序列化时返回现有的单例实例
        private Object readResolve() {
            return INSTANCE;
        }
    }
    
6. 避免不必要的反序列化
  • 原因:反序列化是一个昂贵的操作,尤其是在涉及复杂对象和大量数据时。频繁的反序列化可能会导致性能问题。

  • 最佳实践:

    • 如果可能,避免频繁进行反序列化操作,尤其是当数据只需要处理一次时,可以考虑通过其他机制(如数据库、缓存等)代替序列化。
    • 使用 Serializable 接口时,避免在每次需要数据时都进行反序列化,而是可以使用缓存策略来存储已序列化的对象。
  • 示例:

    // 通过缓存避免频繁反序列化
    public class Cache {
        private Map<String, Object> cache = new HashMap<>();
    
        public Object getObject(String key) {
            if (!cache.containsKey(key)) {
                // 反序列化操作
                cache.put(key, deserializeFromFile(key));
            }
            return cache.get(key);
        }
    }
    
7. 考虑序列化的性能
  • 原因:序列化和反序列化可能会带来性能问题,尤其是在需要序列化大型对象图时。通过优化序列化过程,可以提高性能。

  • 最佳实践:

    • 只序列化必要的字段,避免不必要的字段增加序列化的负担。
    • 在需要时,考虑使用更高效的序列化机制,如 Google 的 Protobuf 或 Apache Avro,这些工具提供了比 Java 默认序列化更高效的序列化格式。
  • 示例:

    // 使用 Google Protobuf 序列化数据(比 Java 默认序列化更高效)
    // Protobuf 定义和生成代码示例略
    
8. 选择合适的序列化机制
  • 原因:Java 默认的序列化机制虽然简单易用,但它的性能和兼容性在某些情况下可能不足。可以考虑使用其他序列化框架来提高性能。

  • 最佳实践:

    • 使用如 Google Protobuf、Kryo、Apache Avro 等高效的序列化库,这些库在性能和兼容性方面通常优于 Java 内建的序列化机制。
  • 示例:

    // 使用 Protobuf 序列化
    // Protobuf 序列化过程(示例代码略)
    

三、小结

《Effective Java》第8部分“序列化”强调了如何有效地使用 Java 的序列化机制,并介绍了多种优化和最佳实践。序列化虽然在许多应用场景中非常有用,但如果不加以控制和优化,可能会带来性能和兼容性问题。通过明确声明 serialVersionUID、控制序列化字段、避免不必要的反序列化、以及使用更高效的序列化框架,开发者可以编写出更加高效、可靠和可维护的序列化代


http://www.kler.cn/a/517737.html

相关文章:

  • Node.js下载安装及环境配置
  • 数据结构(四) B树/跳表
  • 学习std::is_base_of笔记
  • Flutter中PlatformView在鸿蒙中的使用
  • SQL基础、函数、约束(MySQL第二期)
  • MySQL入门(数据库、数据表、数据、字段的操作以及查询相关sql语法)
  • PyQt5菜单加多页签实现
  • Python爬虫之——Cookie存储器
  • Spring--SpringMVC的调用流程
  • 网关与云平台携手打造全轮转胶印机远程物联网监控系统
  • Spring Boot 后端跨域解决方案:解锁前后端通信的障碍
  • 使用python调用JIRA6 进行OAuth1认证获取AccessToken
  • 【u8g2模拟仿真】windows环境下使用sdl模拟仿真u8g2图形库
  • Ubuntu 顶部状态栏 配置,gnu扩展程序
  • FS飞速创新内推~
  • Windows中本地组策略编辑器gpedit.msc打不开/微软远程桌面无法复制粘贴
  • 【MySQL】我在广州学Mysql 系列——MySQL用户管理详解
  • SpringBoot Test详解
  • MySQL离线安装文档(Linux版)
  • 探索 SolidJS:一款高速的前端框架
  • 二叉树(了解)c++
  • Microsoft Edge 企业策略禁用更新
  • 【设计模式】访问者模式(Visitor Pattern): visitor.visit(), accept()
  • RocketMQ 系列文章
  • 【读书笔记·VLSI电路设计方法解密】问题43:什么是TestBench
  • python http调用视觉模型moondream