Java篇 - 聊聊Serializable (常见问题集锦)

2023-05-16

接着来聊聊Serializable,Serializable的意思是序列化。

 

1. 序列化的概念

序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。

 

 

2. 序列化的使用场景

  • 当你想把内存中的对象状态保存到一个文件中或者数据库中时候。
  • 当你想用套接字在网络上传送对象的时候。
  • 当你想通过RMI传输对象的时候。

RMI是Java中的概念:远程方法调用(Remote Method Invocation),能够让在客户端Java虚拟机上的对象像调用本地对象一样调用服务端java虚拟机中的对象上的方法。

 

 

3. 序列化的过程

在没有序列化前,每个保存在堆(Heap)中的对象都有相应的状态(state),即实例变量比如:
 

    public static void main(String[] args) {

        Coder coder = new Coder();
        coder.name = "kuang";
        coder.age = 27;
        coder.lang = "java";
    }

    private static class Coder implements Serializable {

        private static final long serialVersionUID = -7245589157910452589L;

        String name;
        int age;
        String lang;
    }

当通过下面的代码序列化之后,coder对象中的实例变量的值都被保存到coder.ser文件中,这样以后又可以把它从文件中读出来,重新在堆中创建原来的对象。当然保存时候不仅仅是保存对象的实例变量的值,JVM还要保存一些小量信息,比如类的类型等以便恢复原来的对象。

    public static void main(String[] args) {

        Coder coder = new Coder();
        coder.name = "kuang";
        coder.age = 27;
        coder.lang = "java";
        try {
            FileOutputStream fs = new FileOutputStream("./coder.ser");
            ObjectOutputStream os = new ObjectOutputStream(fs);
            os.writeObject(coder);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

看看生成的coder.ser文件:

aced 0005 7372 002f 696f 2e6b 7a77 2e61
6476 616e 6365 2e63 7364 6e5f 626c 6f67
2e54 6573 7453 6572 6961 6c69 7a61 626c
6524 436f 6465 729b 727f 0cb6 5366 9302
0003 4900 0361 6765 4c00 046c 616e 6774
0012 4c6a 6176 612f 6c61 6e67 2f53 7472
696e 673b 4c00 046e 616d 6571 007e 0001
7870 0000 001b 7400 046a 6176 6174 0005
6b75 616e 67

以十六进制的方式存储,具体不深入剖析,里面包含了序列化号,类的信息(本身和父类),变量类型,变量值等,和Java的字节码一个道理。

 

 

4. 反序列化过程


        try {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./coder.ser")));
            Coder coder1 = (Coder) ois.readObject();
            System.out.println("deserialize coder = " + coder1.name + " " + coder1.age + " " + coder1.lang);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

执行输出:

deserialize coder = kuang 27 java

 

 

5. 关于serialVersionUID

序列化操作的时候系统会把当前类的serialVersionUID写入到序列化文件中,当反序列化时系统会去检测文件中的serialVersionUID,判断它是否与当前类的serialVersionUID一致,如果一致就说明序列化类的版本与当前类版本是一样的,可以反序列化成功,否则失败。

serialVersionUID有两种显示的生成方式:        

  • 一是默认的1L,比如:private static final long serialVersionUID = 1L;        
  • 二是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,一般IDE可以帮助你生成。

如果我序列化成功了一个对象,然后更改它的serialVersionUID,反序列化时会报错,比如上面的例子,serialVersionUID改成7245589157910452599:

java.io.InvalidClassException: io.kzw.advance.csdn_blog.TestSerializable$Coder; local class incompatible: stream classdesc serialVersionUID = -7245589157910452599, local class serialVersionUID = -7245589157910452689
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1630)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
	at io.kzw.advance.csdn_blog.TestSerializable.main(TestSerializable.java:24)

 

 

6. 序列化后能修改类吗?

修改之后需要重新进行序列化,但是其实是可以的,如果你能接收反序列化的对象拿不到修改的属性值。

private static class Coder implements Serializable {

        private static final long serialVersionUID = -7245589157910452689L;

        String name;
        int age;
        String lang;
        int sex;
    }

上面给Coder类新增了一个sex属性,反序列化后拿不到sex的值,因为之前序列化的对象没有这个属性。

 

 

7. 序列化前和序列化后的对象的关系

是==还是equal? 是浅复制还是深复制?

答案是深复制,反序列化还原后的对象地址与原来的的地址不同。

 

 

8. 静态变量能否序列化?

不能。

    private static class Coder implements Serializable {

        private static final long serialVersionUID = -7245589157910452689L;

        static int years;

        String name;
        int age;
        String lang;
        int sex;
    }

 给上面的Coder类加一个静态属性years。

 

然后序列化:

        Coder coder = new Coder();
        coder.name = "kuang";
        coder.age = 27;
        coder.lang = "java";
        coder.sex = 1;
        Coder.years = 6;
        try {
            FileOutputStream fs = new FileOutputStream("./coder.ser");
            ObjectOutputStream os = new ObjectOutputStream(fs);
            os.writeObject(coder);
        } catch (IOException e) {
            e.printStackTrace();
        }

 

再看看反序列化:

        try {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./coder.ser")));
            Coder coder1 = (Coder) ois.readObject();
            System.out.println("deserialize coder = " + coder1.name + " " + coder1.age + " " + coder1.lang
            + " " + coder1.sex + " " + coder1.years);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

执行输出:

deserialize coder = kuang 27 java 1 0
 

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

 

 

9transient

Java中transient关键字的作用就是让某些被修饰的成员属性变量不被序列化。

private static class Coder implements Serializable {

        private static final long serialVersionUID = -7245589157910452689L;

        static int years;

        transient String name;
        int age;
        String lang;
        int sex;
    }

执行反序列化:

deserialize coder = null 27 java 1 0

比如在android中,不同页面间传递的数据时,支持的类型包括基本类型和String,还有Serializable对象和Parcelable。如果Serializable的某个对象中的某个属性被transient修饰了,那么接收数据的地方时拿不到这个属性的值的。

 

 

10. 对象包含其他类型对象时

    public static void main(String[] args) {

        Coder coder = new Coder();
        coder.name = "kuang";
        coder.age = 27;
        coder.lang = "java";
        coder.sex = 1;
        Coder.years = 6;
        GF gf = new GF();
        gf.name = "unknow";
        coder.gf = gf;
        try {
            FileOutputStream fs = new FileOutputStream("./coder.ser");
            ObjectOutputStream os = new ObjectOutputStream(fs);
            os.writeObject(coder);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static class Coder implements Serializable {

        private static final long serialVersionUID = -7245589157910452689L;

        static int years;

        transient String name;
        int age;
        String lang;
        int sex;

        GF gf;
    }

    private static class GF {

        String name;
    }

上面的GF类没有序列化,所以对Coder对象进行序列化时会报错:

java.io.NotSerializableException: io.kzw.advance.csdn_blog.TestSerializable$GF

 

必须让GF类也实现Serializable接口:

    private static class GF implements Serializable {

        private static final long serialVersionUID = -9136411921026672520L;
        
        String name;
    }

所以一个对象要想被成功序列化和反序列化,它包含的自定义类必须也是可序列化的。String类自身实现了Serializable接口。

 

 

11. 单例模式中的readResolve()

单例模式是为了运行时,只创建一个该类的对象。

熟悉单例模式的同学都知道有这么一种写法:

   public static class CoderInstance implements Serializable {

        private static final long serialVersionUID = 19856593275581391L;

        private static class InstanceHolder {
            private static final CoderInstance instatnce = new CoderInstance("kuang", 27);
        }

        public static CoderInstance getInstance() {
            return InstanceHolder.instatnce;
        }

        private String name;

        private int age;

        private CoderInstance() {
            System.out.println("none-arg constructor");
        }

        private CoderInstance(String name, int age) {
            System.out.println("arg constructor");
            this.name = name;
            this.age = age;
        }

        private Object readResolve() throws ObjectStreamException {
            return InstanceHolder.instatnce;
        }
    }

那么为什么要加入readResolve()这个方法呢?

无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象,而被创建的对象则会被垃圾回收掉。

这样才能保证单例对象的唯一性。

 

 

最后,Serializable是需要走IO的,所以性能可想而知,不过它非常简单方便,正确使用还是能满足需求的。android中的Parcelable也是一个序列化实现,是在内存中做数据传递用的,性能比Serializable高出很多,不过编写维护起来很费劲。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java篇 - 聊聊Serializable (常见问题集锦) 的相关文章

  • kmalloc和vmalloc

  • linux内核中内存相关的操作函数

    1 kmalloc kfree static always inline void kmalloc size t size gfp t flags 内核空间申请指定大小的内存区域 xff0c 返回内核空间虚拟地址 在函数实现中 xff0c
  • Oracle安全:SCN可能最大值与耗尽问题Oracle安全:SCN可能最大值与耗尽问题

    SCN的问题一旦出现 xff0c 使得数据库的一切事务停止 xff0c 由于SCN不能后退 xff0c 所以数据库必须重建 xff0c 才能够重用 在2012年第一季度的CPU补丁中 xff0c 包含了一个关于SCN修正的重要变更 xff0
  • Linux内核 申请和释放内存流程

    1 内核初始化 xff1a 内核建立好内核页目录页表数据库 xff0c 假设物理内存大小为len xff0c 则建立了 3G 3G 43 len 0 len 这样的虚地址vaddr和物理地址paddr的线性对应关系 xff1b 内核建立一个
  • 编译器"自举与移植"原理

    本文基于对 编译原理与实践 中有关编译器自举与移植部分的读书 笔记 形式 xff0c 因为原书是老外写的 xff0c 感觉翻译的地方好多语句不通或难以理解 xff0c 所以花了好多功夫研究这一块 注 xff1a 本文中与原书一致的地方都是P
  • Linux 内核 由block_read和block_write函数引发的设备块号转换问题的思考

    在1 2内核版本中 xff0c 在Linux fs目录下 xff0c 有一个block dev c文件 xff0c 里面主要包含了block read block write block fsync函数 先说说我遇到的问题 xff0c 在块
  • Linux 进程调度时机

    Linux调度时机主要 有 xff1a 1 进程状态转换的时刻 xff1a 进程终止 进程睡眠 2 当前进程的时间 片用完时 xff08 current gt counter 61 0 xff09 3 设备驱动程序 4 进程从中断 异常及系
  • linux 下批量转换pdf的命令方法

    由于在windows下的图形界面 xff0c 难以批量进行其他格式的文件到PDF格式文件的转换 xff0c 而一些其他的软件也不是很满意 xff0c 所以转到linux下 xff0c 想利用linux强大的命令行来完成这件事 linux下有
  • Linux内核 内存映射文件机制mmap

    今天研究Linux1 2内核运行加载a out格式的可执行文件的代码时 xff0c 无意中研究明白了内核提供的内存映射机制 mmap xff08 memory map xff09 当内核要加载可执行文件到相应的用户地址空间时 xff0c 有
  • bash提示符的配置:

    bash提示符的配置 xff1a 如果您很容易使 shell 提示行变得色彩绚烂斓且带有更多信息 xff0c 为什么还要坚持用单调的标准 shell 提示行呢 xff1f 在这篇技巧中 xff0c Daniel Robbins 将说明如何获
  • Linux线性地址空间的划分及内核寻址方式

    今天研究Linux1 2内核时 xff0c 注意到该版本中的PAGE OFFSET宏被定义为0 xff0c 考虑到进程的地址空间被划分为3G的用户态地址空间和1G的内核态地址空间 xff0c 于是深入的研究了一下这个问题 一开始我只是疑惑
  • linux 最简单的模块的编写和运行

    第一次动手编写一个内核模块 xff0c 但是查找了许多资料没有一个可以完美通过编译的 xff0c 郁闷 xff0c 最后还是解决了 xff0c 分享出来 首先是hello c include lt linux kernel h gt Nee
  • 截获或替换linux系统调用

    直接上代码吧 xff1a hello c include lt linux kernel h gt Needed by all modules include lt linux module h gt Needed for KERN inc
  • oracle临时表实际应用

    xff08 这段是后面添加的 xff1a 临时表 xff0c 在实际应用中 xff0c 其实和nologging的固定表 xff0c 是差不多的 xff0c 都是中间表 xff0c 所以这里为什么添加这段话 xff0c 是让自己记得 xff
  • linux sys_call_table 初始化

    前几天看内核中系统调用代码 xff0c 在系统调用向量表初始化中 xff0c 有下面这段代码写的让我有点摸不着头脑 xff1a const sys call ptr t sys call table NR syscall max 43 1
  • Linux gcc 利用反汇编来研究C语言函数堆栈的分配方式

    越来越感觉学习C和汇编才是最能接近计算机本质的途径 所以 xff0c 今天开始研究汇编了 xff0c 先从gcc反汇编开始 首先是下面的C代码 xff1a include lt stdio h gt int sum int a int b
  • ubuntu linux GAIM QQ

    sudo add apt repository ppa lainme pidgin lwqq sudo apt get update sudo apt get install pidgin lwqq sudo apt get install
  • Ubuntu 14.10 播放avi视频闪屏的简单解决方案

    昨天将ubuntu升级到了14 10版本 xff0c 没有得到想象中的视觉效果 但是 xff0c 后来却发现不能播放avi视频了 xff0c 尝试了各种播放器 xff0c 都无解 xff0c 总是闪屏 xff0c 而播放flv格式的视频就没
  • Ubuntu删除Compiz之后 恢复方法

    因为ubuntu14 10不能播放avi的问题纠结了很久 xff0c 最终使用转码软件解决的 但是还是不甘心啊 xff0c 总想解决这个问题或者知道问题的原因 偶然看到了系统中的compiz软件 xff0c 想到在网上看到的有人说是因为系统
  • Ubuntu14.10 unity-tweak-tool 不能正常打开的解决方法

    新的系统 xff0c 风格都变了 xff0c 折腾了一晚上 xff0c 想安装个苹果主题 xff0c 可是unity tweak tool 这个东西就是死活打不开 这么晚了 xff0c 不想多说了 xff0c 直接给解决方法吧 xff1a

随机推荐

  • 安装ubuntu14.04后做的一些事情

    ubuntu又折腾坏了 xff0c 于是有重新装系统了 装完之后 xff0c 美化美化 xff1a 1 本来嫌每次都sudo麻烦 xff0c 于是想将我的帐号设置为sudo不用输入密码 xff0c 但是编辑sudoer文件的时候 xff0c
  • LFS编译GCC GNU_USER_TARGET_OS_CPP_BUILTINS not declared in this scope

    gcc v 发现使用的gcc版本是4 7 安装gcc 4 4 or 4 3 xff0c 重新链接 xff0c 问题解决 apt get install gcc 4 4 g 43 43 4 4 进入 usr bin cd usr bin 建个
  • 将LFS系统通过grub装到优盘上启动

    最近在搞LFS xff0c 就是通过自己动手 xff0c 亲自编译源代码的方式 xff0c 建立自己的linux发行版 通过这个过程可以详细的深入了解linux内部的工作方式 xff0c 对理解操作系统的机制有很大的帮助 做这个项目 xff
  • kali linux 解决风扇猛转

    之前玩ubuntu的时候 xff0c 就感觉风扇猛转个不停 xff0c 之前是通过安装nvidia的闭源驱动 xff0c 然后再安装一个管理双显卡的工具解决的 xff0c 确实风扇及立马安静了下来 http www linuxidc com
  • PHP下十六位数值转IP地址

    PHP函数提供的long2ip和ip2long是IP和整形数值之间的转换 xff0c 没有和十六位数值的转换 xff0c 所以写下这个函数 function ntoip iphex len 61 strlen iphex 得到16进制字符串
  • linux系统备份命令

    tar cvpzf backup tgz exclude 61 proc exclude 61 lost 43 found exclude 61 backup tgz exclude 61 mnt exclude 61 sys exclud
  • 硬盘寻址能力的变换

    1 8G限制 硬盘寻址的限制 硬盘最初使用的寻址方法是柱面 磁头 扇区CHS xff08 Cylinder Head Sector xff09 xff0c 也称为3D模式 xff0c 是硬盘最早采用的寻址模式 通过分别指定柱面 磁头 扇区来
  • 关于动态new二维数组的问题

  • Linux 调节并自动保存屏幕亮度,重启有效

    用过几个Linux的发行版 xff0c 发现在笔记本上每次调节亮度后 xff0c 不能固定下来 xff0c 重新开机后就又恢复最大亮度了 之前一直用的是网上流行的方法 xff0c 即往etc rc local文件写入命令 xff0c 使其每
  • kali linux 安装Pidgin QQ

    首先 xff0c 需要kali开启PPA源 PPA xff08 Personal Package Archives xff1a 个人软件包档案 xff09 是Ubuntu Launchpad网络提供的一项服务 xff0c 允许个人用户上传软
  • 教你如何用Kali Linux制作windows 10安装优盘

    最近win10发布了 xff0c 看样子挺不错 虽然我一直用的是Linux xff0c 但是还是想体验一把win10 主要是尼玛我想看百度云里存放的教学视频 xff0c 但是里面的视频还需要特殊的播放器 xff0c 也是不得己 xff0c
  • kali linux安装搜狗输入法

    昨天安装了kali linux2 0 xff0c 感觉输入法没有以前好了 于是查资料安装搜狗输入法 xff0c 还是搜狗输入法好使 有几篇文章倒是谈到如何在kali上安装搜狗了 xff0c 但是一方面是文章太老了 xff0c 二是还得自己下
  • 关于android系统对AndroidManifest文件的解析机制

    最近在学习android的过程中一直在思考一个问题 xff0c 我们都知道 xff0c 在android的AndroidManifest xml 是每个android程序中必须的文件 它位于整个项目的根目录 xff0c 描述了package
  • Android对xml文件的解析

    今天阅读android sdk官方文档时 xff0c 看到这么一段话 xff1a 感觉深受启发 xff1a 1 android的图形界面内部逻辑结构是树形结构 xff0c 这个从xml文件的结构可以直观的看出来 如果我们要使用java代码来
  • kail linux 安装QQ

    腾讯QQ 下载地址 xff1a http www ubuntukylin com appli p lang 61 cn amp id 61 23 下载后解压得到wine qqintl文件夹 xff0c 里面有三个deb包 xff1a fon
  • ubuntu kylin下宽带拨号

    用pppoeconf这个命令 xff0c 一般来说 xff0c 里面已经安装好了的 按照提示过程处理即可 xff0c 要提供adsl用户名和密码 一般很多用户会配置成开机自动启动adsl xff0c 也可以手动 xff1a pon dsl
  • linux主机远程桌面

    利用xrdp 43 rdesktop可以实现
  • 把vim打造成C++ IDE

    目录 准备工作第一个插件pathogenauto pairsNERDTreeMiniBufExplorerctagstaglistomnicppcomplete ctagstaglistOmniCppCompleteSuperTab 最终的
  • MatConvNet compiled with '-R2018a' and linked with '-R2017b'

    modify line 620 to args 61 horzcat 39 outdir 39 mex dir flags base flags mexlink 39 R2018a 39 39 LDFLAGS 61 LDFLAGS 39 s
  • Java篇 - 聊聊Serializable (常见问题集锦)

    接着来聊聊Serializable xff0c Serializable的意思是序列化 1 序列化的概念 序列化 Serialization 将对象的状态信息转换为可以存储或传输的形式的过程 在序列化期间 xff0c 对象将其当前状态写入到