Java学习笔记 09、IO流—对象序列化

简介: Java学习笔记 09、IO流—对象序列化

一、认识序列化


保存程序中的数据方式有哪些呢?


保存当前状态及信息方式:


展开方式:若是程序需要存储状态,可以将每个对象的单个变量写入到特定格式的文件中,之后再读取文件读取其中变量值并还原。

序列化:使用面对对象方式来做,将对象本身"冻干、碾平、保存、脱水",之后再"重组、展开、恢复、泡开"。

使用方式根据情境来选择:


若是只有自己写的java程序会用到这些数据:那么选择序列化方式,注意序列化文件正常打开内容是无意义的。

好处:程序更容易恢复,一般人不知道如何更改其中内容,比较安全;但是很难让人阅读。

若是数据还需要其他程序引用:将数据按格式保存到纯文本文件,例如tab字符分割写到文件中去,方便其他程序或电子表格或数据库应用程序能够应用。

好处:方便阅读;但这种存储肯定是按一定规则顺序,很容易被修改,不太安全。


介绍序列化


介绍序列化:


Java的输入、输出API中带有连接类型的串流,它表示将来源与目的地之间的连接,连接串流将其他串流连接起来。初始连接的串流是很低层的,用来读取写入字节,例如FileOutputStream为例,若我们想要将对对象以串流的形式保存那么就需要高级一点的流例如ObjectOutputStreatm,


Java中提供了各种形式的流,方便开发人员进行选择传输。


二、实现序列化


1、实现序列化要求及说明


实现Serializable接口,并且定义一个private的serialVersionUID。


Serializable接口目的是让声明实现它的类是可以被序列化的,实现序列化的类其子类也可以自动进行序列化。

①若是不实现该接口是不能被序列化的,执行期一定会出问题;②若是实现该接口而不定义serialVersionUID,那么很有可能在序列化回来时出现问题。你不定义serialVersionUID系统会自动给你一个默认生成的UID,但是可能会因编译器不同而不同的类会出现问题,也有可能你修改了类的部分结构之后解序列化也会有问题。

默认情况下,基本数据类型可序列化。

序列化对象中的实例对象(也就是属性)也应该是实现序列化的,否则会有问题!!!

static静态变量不会被序列化,当对象还原时,静态变量会维持类中原来的样子,而不是存储的样子。


2、实例程序


自定义类准备

首先准备好一些自定义类方便等会进行对象序列化存储与恢复


class Dog{
}
class Cat implements Serializable{
    private static final long serialVersionUID = -6848894470770667710L;
    private String name = "喵喵";
    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                '}';
    }
}
class Person implements Serializable {
    //Dog类无序列化 可设置transient修饰符在序列化时跳过
    private transient Dog dog = new Dog();
    private Cat cat = new Cat();
    //
    private static String str = "123456";
    @Override
    public String toString() {
        return "Person{" +
                "dog=" + dog +
                ", cat=" + cat +
                ", str='" + str + '\'' +
                '}';
    }
}


9行的serialVersionUID:可去其他实现Serializable的类中去拿到修改其中一点值即可。

transient:在进行序列化的时候会跳过此变量,解序列化时基本数据类型为默认值,对象实例为null。

30行static:静态变量不会被序列化,在类中独此一份,解序列化时还是原值。


①序列化对象

其中new的类是上面声明的


import org.junit.Test;
import java.io.*;
public class Main {
    //序列化对象到文件
    @Test
    public void test01(){
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("changlu.data"));
            //将对象序列化写入
            oos.writeObject(new Person());
            oos.writeObject(new Cat());
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(oos != null){
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


执行之后,工程下会自动生成changlu.data文件!



②解序列化
import org.junit.Test;
import java.io.*;
public class Main {
    //读取序列化对象
    @Test
    public void test02(){
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("changlu.data"));
            //读取序列化对象时应当与存储顺序相同
            Person person = (Person) ois.readObject();
            Cat cat = (Cat) ois.readObject();
            System.out.println(person);
            System.out.println(cat);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }finally {
            if(ois != null){
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


这里唯一需要注意的是在解序列化过程中拿对象顺序应该与当时存储的顺序相同!!!




三、深入了解序列化


序列化过程

序列化对象的状态以及要保存什么?


若是对象被序列化时,不仅仅是该对象引用的实例变量、被对象的实例变量引用的对象都会被实例化,这些操作都是自动执行的。




具体过程如下:



对象被序列化发生事情:①在堆上的对象②被序列化的对象


堆上对象:有状态并包含实例变量的值,这些值让同一类的不同实例有不同的意义。

序列化对象:保存了实例变量的值,因此之后可以在堆上带回一模一样的实例。


解序列化过程

过程如下:



首先对象从stream中读出来

Java虚拟机通过存储的信息判断对象的class类型。

Java虚拟机尝试寻找和加载对象的类。如果Java虚拟机找不到或无法加载该类,Java虚拟机会抛出异常

新的对象会配置到堆上,其构造函数不会执行!设想一下如果执行对象状态会抹去,我们需要的是对象回到存储时的状态!!!

若对象的继承树上有无可序列化的祖先类,则该不可序列化类及以上类都会执行构造函数(以上若是实现序列化也没用),也就是说从上的第一个不可序列化父类开始全都会回到初始状态。

transient修饰的对象引用会变为null,基本数据类型会变为默认值如0,false等;不被transient修饰的回复到默认值。


四、序列化相关问题


共有四个相关问题


1.若是我要序列化自定义类,其是序列化的,但其类中的实例类并没有被序列化,那还可以序列化成功吗?


import java.io.*;
class Person implements Serializable {
    //Dog类无序列化
    private Dog dog = new Dog();
}
class Dog{
}
public class Main {
    public static void main(String[] args) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("changlu.ser"));
        oos.writeObject(new Person());
    }
}



很明显若是将序列化对象其实例类若不是被序列化,执行期间会出现异常!



针对于上面例子,若是我存储的序列化类中,我想让某些变量不进行序列化,如何做到?

使用transient(瞬时)修饰符来表示在序列化程序中跳过该变量。无论该实例是否是序列化的都跳过,不进行序列化存储。在恢复对象时,transient修饰的对象会以null返回。


import java.io.*;
class Dog{
}
class Person implements Serializable {
    //Dog类无序列化 跳过无序列化的Dog实例
    private transient Dog dog = new Dog();
    //跳过支持序列化的String
    private transient String str = "123456";
}


那么问题来了,为什么有些变量不能被序列化呢?


①可能是设计者忘记实现Serializable接口。

②或者是对于一些动态数据只有在执行期创建才有意义。

针对于transient修饰的变量在回复时的状态,如上第10行str本身是赋予值的,但由于使用transient的引用实例变量会以null返回,不会管存储当时它的值是什么!那么我们针对这种情况如何解决这个恢复为null呢?


方案1:序列化返回时,可以重新赋予其变量值,因为既然打上了transient修饰,说明这个值并无是很重要,重新赋值也无关紧要。

方案2:若是其transient修饰的值真的很重要,那么在进行序列化保存前先保存下来,之后回复时重新赋值。


若是两个对象都有引用实例变量指向相同的对象会怎么样,例如两个Cat指向同一个Owner,Owner会被序列化存储两次吗?

序列化聪明得足以分辨两个对象是否相同,别担心这种情况下也只有一个对象会被存储,其他引用会复原成指向该对象。



序列化过程中为什么类不会存储成对象的一部分,这样就不会出现找不到类的问题了?

这样设计会非常浪费空间并有很多额外的工作,针对于对象序列化本机硬盘上并不是困难的事情,但序列化也有将对象送到网络联机上的用途,这样会造成带宽的消耗大。


网络传输序列化:有一种机制可以让类使用URL来指定位置,该机制使用在Java的Remote Method Invocation(RMI,远程程序调用机制),可以把序列化对象当做参数来传递,即使接收此调用的java虚拟机没有这个类的话,也可以自动使用URL来取回并加载该类。



参考资料


[1]. 书籍《head first java 2.0》


[2]. 尚硅谷Java基础教程-宋红康主讲-IO流之序列化


[3]. JAVA中流的flush()方法


相关文章
|
算法
KMP字符串算法演进(一)
学习kmp算法,字符串匹配,。核心思想: 不是从目标字符串出发,而是比对的字符串。 根据现有的信息,减少比对工作
277 0
|
8天前
|
人工智能 数据可视化 安全
王炸组合!阿里云 OpenClaw X 飞书 CLI,开启 Agent 基建狂潮!(附带免费使用6个月服务器)
本文详解如何用阿里云Lighthouse一键部署OpenClaw,结合飞书CLI等工具,让AI真正“动手”——自动群发、生成科研日报、整理知识库。核心理念:未来软件应为AI而生,CLI即AI的“手脚”,实现高效、安全、可控的智能自动化。
34510 22
王炸组合!阿里云 OpenClaw X 飞书 CLI,开启 Agent 基建狂潮!(附带免费使用6个月服务器)
|
20天前
|
人工智能 JSON 机器人
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
本文带你零成本玩转OpenClaw:学生认证白嫖6个月阿里云服务器,手把手配置飞书机器人、接入免费/高性价比AI模型(NVIDIA/通义),并打造微信公众号“全自动分身”——实时抓热榜、AI选题拆解、一键发布草稿,5分钟完成热点→文章全流程!
45372 143
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
|
2天前
|
人工智能 自然语言处理 安全
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
本文介绍了Claude Code终端AI助手的使用指南,主要内容包括:1)常用命令如版本查看、项目启动和更新;2)三种工作模式切换及界面说明;3)核心功能指令速查表,包含初始化、压缩对话、清除历史等操作;4)详细解析了/init、/help、/clear、/compact、/memory等关键命令的使用场景和语法。文章通过丰富的界面截图和场景示例,帮助开发者快速掌握如何通过命令行和交互界面高效使用Claude Code进行项目开发,特别强调了CLAUDE.md文件作为项目知识库的核心作用。
3150 11
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
|
9天前
|
人工智能 JSON 监控
Claude Code 源码泄露:一份价值亿元的 AI 工程公开课
我以为顶级 AI 产品的护城河是模型。读完这 51.2 万行泄露的源码,我发现自己错了。
5017 21
|
2天前
|
人工智能 监控 安全
阿里云SASE 2.0升级,全方位监控Agent办公安全
AI Agent办公场景的“安全底座”
1137 1
|
8天前
|
人工智能 API 开发者
阿里云百炼 Coding Plan 售罄、Lite 停售、Pro 抢不到?最新解决方案
阿里云百炼Coding Plan Lite已停售,Pro版每日9:30限量抢购难度大。本文解析原因,并提供两大方案:①掌握技巧抢购Pro版;②直接使用百炼平台按量付费——新用户赠100万Tokens,支持Qwen3.5-Max等满血模型,灵活低成本。
2013 6
阿里云百炼 Coding Plan 售罄、Lite 停售、Pro 抢不到?最新解决方案

热门文章

最新文章