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来取回并加载该类。



相关文章
|
10天前
|
Java
java基础(12)抽象类以及抽象方法abstract以及ArrayList对象使用
本文介绍了Java中抽象类和抽象方法的使用,以及ArrayList的基本操作,包括添加、获取、删除元素和判断列表是否为空。
13 2
java基础(12)抽象类以及抽象方法abstract以及ArrayList对象使用
|
21天前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
11天前
|
存储 Java
Java编程中的对象和类
【8月更文挑战第55天】在Java的世界中,“对象”与“类”是构建一切的基础。就像乐高积木一样,类定义了形状和结构,而对象则是根据这些设计拼装出来的具体作品。本篇文章将通过一个简单的例子,展示如何从零开始创建一个类,并利用它来制作我们的第一个Java对象。准备好让你的编程之旅起飞了吗?让我们一起来探索这个神奇的过程!
25 10
|
15天前
|
Java 大数据 API
Java 流(Stream)、文件(File)和IO的区别
Java中的流(Stream)、文件(File)和输入/输出(I/O)是处理数据的关键概念。`File`类用于基本文件操作,如创建、删除和检查文件;流则提供了数据读写的抽象机制,适用于文件、内存和网络等多种数据源;I/O涵盖更广泛的输入输出操作,包括文件I/O、网络通信等,并支持异常处理和缓冲等功能。实际开发中,这三者常结合使用,以实现高效的数据处理。例如,`File`用于管理文件路径,`Stream`用于读写数据,I/O则处理复杂的输入输出需求。
|
14天前
|
存储 Java 开发者
Java编程中的对象序列化与反序列化
【9月更文挑战第20天】在本文中,我们将探索Java编程中的一个核心概念——对象序列化与反序列化。通过简单易懂的语言和直观的代码示例,你将学会如何将对象状态保存为字节流,以及如何从字节流恢复对象状态。这不仅有助于理解Java中的I/O机制,还能提升你的数据持久化能力。准备好让你的Java技能更上一层楼了吗?让我们开始吧!
|
16天前
|
Java
Java实现:将带时区的时间字符串转换为LocalDateTime对象
通过上述方法,你可以将带时区的时间字符串准确地转换为 `LocalDateTime`对象,这对于处理不需要时区信息的日期和时间场景非常有用。
206 4
|
16天前
|
SQL Java 关系型数据库
在Java中,创建数据源对象
在Java中,创建数据源对象
28 1
|
2月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
3月前
|
Java 大数据
解析Java中的NIO与传统IO的区别与应用
解析Java中的NIO与传统IO的区别与应用
|
2月前
|
Java 数据处理
Java IO 接口(Input)究竟隐藏着怎样的神秘用法?快来一探究竟,解锁高效编程新境界!
【8月更文挑战第22天】Java的输入输出(IO)操作至关重要,它支持从多种来源读取数据,如文件、网络等。常用输入流包括`FileInputStream`,适用于按字节读取文件;结合`BufferedInputStream`可提升读取效率。此外,通过`Socket`和相关输入流,还能实现网络数据读取。合理选用这些流能有效支持程序的数据处理需求。
28 2
下一篇
无影云桌面