Java 输入与输出(I\O)之对象流与对象序列化

什么是Java的对象流?

Java对象流是用于存储和读取基本数据类型数据或对象数据的输入输出流。
Java的对象流可分为两种:

  • 1,对象输入流类ObjectInputStream 用于从数据源读取对象数据,它是可以读取基本数据类型数据或对象数据的输入流。
  • 2,对象输出流类ObjectOutputStream 用于把对象写入到数据源,它是可以输出基本数据类型数据或对象数据的输出流。

Java中提供了ObjectInputStream、ObjectOutputStream这两个类用于对象序列化操作,这两个类是用于存储和读取对象的输入输出流类,只要把对象序列化转换成平台无关的二进制数据存储起来,就等于保存了这个对象;之后根据需要再把保存的对象的二进制数据读取进来就可以创建并使用此对象。
ObjectInputStream、ObjectOutputStream是可以帮助开发者完成保存和读取对象的二进制数据的输入输出流。但前提是对象必须实现Serializable接口。

什么是对象的序列化和反序列化?

序列化与反序列化

  • 序列化:序列化是将内存中的对象转换成与平台无关的二进制流(二进制字节数组)的过程。序列化的二进制流可通过网络传输到别的网络节点;或者保存为二进制的磁盘文件,从而达到持久化的目的。
  • 反序列化:反序列化则是把从网络接收或者从磁盘文件读取的二进制流,重新构建为对象的过程。

序列化的作用 序列化是将可序列化的Java对象转换为字节序列,这此序列可存储在磁盘中,或者通过网络传输,以便在需要的时候可重新构建为Java对象。序列化机制使得Java对象可以脱离程序而独立存在,从而实现持久化。

序列化版本UID

序列化版本UID,是指可序列化的类的serialVersionUID参数。
Java序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,Java虚拟机会把传过来的字节流中的serialVersionUID和本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的实体类,可以进行反序列化;否则Java虚拟机会拒绝对这个实体类进行反序列化并抛出异常。

serialVersionUID的三种生成方式:

  • 1、默认的serialVersionUID是1L(长整型数 1)。
  • 2、用户指定一个长整型数作为serialVersionUID。
  • 3、根据类名、接口名、成员方法以及属性等通过散列(Hash)生成一个64位的长整型数作为serialVersionUID。

如果实现java.io.Serializable接口的实体类没有显式定义一个名为serialVersionUID、类型为long的属性时,Java序列化机制会根据编译的.class文件自动散列(Hash)生成一个serialVersionUID。如果.class文件没有改变,那么多次编译,其serialVersionUID也不变。

对象中有三种属性不可序列化:
1、static 修饰的属性。因为,序列化存储的是对象的属性状态,而static属性是属于类的,不属于对象,所以序列化时不会被序列化。
2、transient 修饰的属性,在序列化时不会被序列化。例如,有些敏感信息,如密码等可用transient修饰来避开序列化。
3、未实现序列化(serializable)接口的属性,无法被序列化。

一、默认序列化

一个类实现了java.io.Serializable接口,才能支持对象序列化。

默认序列化:Java语言为用户定义了默认的序列化、反序列化方法,其实就是ObjectOutputStream的defaultWriteObject方法和ObjectInputStream的defaultReadObject方法。

我们先来看一个默认序列化的演示程序:
类PersonSerial
默认序列化测试演示使用的类PersonSerial,实现了Serializable接口,但没有重写writeObject()和readObject()方法。

package IOStream.ObjectSerialization;
import java.io.Serializable;
public class PersonSerial implements Serializable {
    private String     name;
    private int     age;
    private String address;
    
    public PersonSerial(String name, int age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
	}
    
	@Override
	public String toString() {
        return "PersonSerial {" +
                "姓名='" + name + '\'' +
                ", 年龄=" + age +
                ", 地址='" + address + '\'' +
                '}';
	}
}

默认序列化的测试演示程序:

package IOStream.ObjectSerialization;
import java.io.*;
public class ObjectSerialization {
	// 序列化对象方法
	public static void serializable(File file) throws Exception
	{
	    OutputStream outputFile = new FileOutputStream(file);
	    ObjectOutputStream oos = new ObjectOutputStream(outputFile);
	    oos.writeObject(new PersonSerial("张三丰", 108,"五台山"));
	    oos.writeObject("海内存知己,天涯若比邻。");
	    oos.writeDouble(3.1416D);
	    oos.close();
	}
	    
	// 反序列化对象方法
	public static void deserializable(File file) throws Exception
	{
	    InputStream inputFile = new FileInputStream(file);
	    ObjectInputStream ois = new ObjectInputStream(inputFile);
	    PersonSerial p = (PersonSerial)ois.readObject();
	    System.out.println(p);
	    String txt = (String)ois.readObject();
	    double pi = (double)ois.readDouble();
	    System.out.println(txt);
	    System.out.println("PI = "+pi);
	    ois.close();
	}

	public static void main(String[] args)  throws Exception
		{
		    File file = new File("D:/temp/对象序列化.txt");
		    serializable(file);
		    deserializable(file);
		}


}

默认序列化的测试演示结果图:
在这里插入图片描述
说明:

1,序列化的效果:文本文件“对象序列化.txt”中的内容是程序序列化后写入的与平台无关的二进制流(二进制字节数据)。这 个序列化产生的文件,其文件头,包括序列化协议、序列化协议版本等信息,后面才是真正对象序列化后的二进制字节数据。
2,反序列化的效果:文本文件上面控制台上显示的三行信息,是程序从文本文件“对象序列化.txt”中读入的信息,再创建的对象、字符串和双精度的浮点数。

测试结果说明:序列化和反序列化成功了,类PersonSerial只实现了Serializable接口,并没有重写writeObject()和readObject()方法,但类PersonSerial具备了序列化的能力。因此,序列化和反序列化过程中,默认调用的是ObjectOutputStream的defaultWriteObject()以及ObjectInputStream的defaultReadObject()方法。

注意事项: 无论是默认序列化,还是定制序列化,writeObject()方法存储属性变量的顺序都应该和readObject()方法恢复属性变量的顺序一致,否则将不能正确恢复该Java对象。

二、定制序列化

定制序列化的实现:当一个类实现java.io.Serializable接口时,就可以支持对象序列化。如果用户有特殊需求,可以通过在类的自定义writeObject()和readObject()方法来达到定制的目的。两个方法的方法签名如下所示:

    private void writeObject(java.io.ObjectOutputStream s) throws Exception {
		//在这里编写定制的代码
	}
    
    private void readObject(java.io.ObjectInputStream s) throws Exception {
    	//在这里编写定制的代码
    }

进行序列化、反序列化时,虚拟机会首先试图调用对象里的writeObject()和readObject()方法,进行用户自定义的序列化和反序列化。如果没有这样的方法,那么默认调用的是ObjectOutputStream的defaultWriteObject()以及ObjectInputStream的defaultReadObject()方法。换言之,利用自定义的writeObject()方法和readObject()方法,用户可以自己控制序列化和反序列化的过程。
这是非常有用的。比如:
1、有些场景下,某些字段我们并不想要使用Java提供给我们的序列化方式,而是想要以自定义的方式去序列化它,比如ArrayList的elementData、HashMap的table(至于为什么在之后写这两个类的时候会解释原因),就可以通过将这些字段声明为transient,然后在writeObject()和readObject()中去使用自己想要的方式去序列化它们
2、因为序列化并不安全,因此有些场景下我们需要对一些敏感属性进行加密再序列化,然后再反序列化的时候按照同样的方式进行解密,这样会有更高的安全性。要这么做,就必须自定义编写writeObject()和readObject(),writeObject()方法在序列化前对字段加密,readObject()方法在序列化之后对字段解密。

三、另一种定制序列化机制,使用Externalizable接口

对象的序列化和反序列化非常复杂,Java语言还有另一种自定义序列化机制,这种方式完全由程序员决定如何存储和恢复Java对象数据。这种机制要求Java类必须实现Externalizable接口
Externalizable接口提供了更高级的自定义序列化能力,我们可以控制序列化的过程,选择序列化哪些属性,以及如何序列化。

Externalizable接口
Externalizable接口是Java提供的自定义序列化接口,它继承自Serializable接口,并添加了两个方法:writeExternal(ObjectOutput out)和readExternal(ObjectInput in)。这两个方法分别用于序列化和反序列化对象。

public interface Externalizable extends Serializable {
    void writeExternal(ObjectOutput out) throws IOException;
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

在某些场景,例如,在处理大型对象或复杂对象图形时,使用Externalizable接口进行序列化定制可能会获得更好的性能。

我们来看一个简单的Externalizable序列化与反序列化例子:
在这里插入图片描述

首先,我们来定义一个PersonExter类,该类是实现了Externalizable接口的Person,并重写实现writeExternal()方法和readExternal()方法。

package IOStream.ObjectSerialization;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class PersonExter implements Externalizable {
    private String name;
    private int age;
    private String address;
    
    public PersonExter() { //无参数的构造器是必须的。
	}
    
    public PersonExter(String name, int age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
	}
    
	@Override
	public void writeExternal(ObjectOutput out) throws IOException {
        // 序列化name、age和address属性
        out.writeObject(name);
        out.writeInt(age);
        out.writeObject(address);
	}

	@Override
	public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
		// 反序列化name、age和address属性
        name = (String) in.readObject();
        age = in.readInt();
        address = (String) in.readObject();
	}

	@Override
	public String toString() {
        return "PersonExter {" +
                "姓名='" + name + '\'' +
                ", 年龄=" + age +
                ", 地址='" + address + '\'' +
                '}';
	}
}

Externalizable接口实现序列化的注意事项:

  • 必须有默认构造器:这个类定义中,无参数构造器是必须的,不定义,会出现“无有效构造器”例外。
    public PersonExter() { //无参数的构造器是必须的。
	}
  • 必须重写实现writeExternal()方法和readExternal()方法。
@Override
public void readExternal(ObjectInput arg0) throws IOException, ClassNotFoundException {

}

@Override
public void writeExternal(ObjectOutput arg0) throws IOException {

}

一个简单的使用Externalizable接口序列化与反序列化演示例程
下面是Externalizable接口序列化与反序列化的简单演示例程:

package IOStream.ObjectSerialization;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class ExternalizableDemo {

	public static void main(String[] args) {
		String path = "D:/temp/person.txt";
        PersonExter person = new PersonExter("左中堂", 80, "湖南");

        // 序列化
        try (FileOutputStream foS = new FileOutputStream(path);
        	ObjectOutputStream out = new ObjectOutputStream(foS)) {
            out.writeObject(person);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 反序列化
        try (FileInputStream fis = new FileInputStream(path);
        	ObjectInputStream in = new ObjectInputStream(fis)) {
        	PersonExter restoredPerson = (PersonExter) in.readObject();
            System.out.println(restoredPerson);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
	}
}

例程测试结果图:
在这里插入图片描述
对象序列化与反序列化有一些经典的应用场景:
1,在联机游戏中各参与方中的场景处理,;
2,网络电子画板中各个画板内容的同步显示;
3,线上教育中电子黑板上内容的同步显示。

参考文献:

  • JAVA中的对象流ObjectInputStream
  • JAVA中的ObjectOutputStream类
  • java 序列化 之 externalizable
  • Java IO流详解(七)----对象流(序列化与反序列化)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/900564.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

pikachu靶场CSRF-token测试报告

目录 一、测试环境 1、系统环境 2、使用工具/软件 二、测试目的 三、操作过程 1、抓包使用burp生成csrf脚本 四、源代码分析 五、结论 一、测试环境 1、系统环境 渗透机:本机(127.0.0.1) 靶 机:本机(127.0.0.1) 2、使用工具/软件 Burp sui…

python+大数据+基于热门视频的数据分析研究【内含源码+文档+部署教程】

博主介绍:✌全网粉丝10W,前互联网大厂软件研发、集结硕博英豪成立工作室。专注于计算机相关专业毕业设计项目实战6年之久,选择我们就是选择放心、选择安心毕业✌ 🍅由于篇幅限制,想要获取完整文章或者源码,或者代做&am…

QT 调用QRencode库生成二维码和使用Code128生成简单条形码

目录导读 前言使用Code128生成简单条形码使用QRencode库生成二维码添加QRencode.Pri 模块化 前言 对在QT开发中使用QRencode库生成二维码 和使用Code128生成简单条形码 进行一个学习使用总结。 使用Code128生成简单条形码 ‌Code128条形码是一种高密度条码,广泛应用…

4K双模显示器7款评测报告

4K双模显示器7款评测报告 HKC G27H7Pro 4K双模显示器 ROG华硕 XG27UCG 4K双模显示器 雷神 ZU27F160L 4K双模显示器 泰坦军团 P275MV PLUS 4K双模显示器 外星人(Alienware)AW2725QF 4K双模显示器 SANC盛色 D73uPro 4K双模显示器 ANTGAMER蚂蚁电竞 …

lvgl

lvgl 目录 lvgl Lvgl移植到STM32 -- 1、下载LVGL源码 -- 2、将必要文件复制到工程目录 -- 3、修改配置文件 将lvgl与底层屏幕结合到一块 -- lvgl也需要有定时器,专门给自己做了一个函数,告诉lvgl经过了多长时间(ms(毫秒&a…

第三十篇:TCP连接断开过程,从底层说明白,TCP系列五

上一篇《第二十九篇:图解TCP三次握手,看过不会忘,从底层说清楚,TCP系列四》说了TCP的三次握手,接下来我将讲解TCP四次挥手。 既然有连接就有断开,谈到这里,有的同学可能会想,不就是…

log4j 和 logback 冲突解决

很多springboot starter自带logback 如果我们要用log4j就要把logback排除掉 点idea的maven侧栏工具的分析依赖关系 然后我们要选中我们有冲突的模块,搜索logback 这时候我们发现有logback相关的依赖,在点一下,我们就在右边发现,原…

STM32--I2C通信

对于I2C通信会分为两大块来讲解,第一块,就是介绍协议规则,然后用软件模拟的形式来实现协议,第二块,就是介绍STM32的12C外设,然后用硬件来实现协议,因为12C是同步时序,软件模拟协议也非常方便。 在学12C之前,我们已经学习了串口通信&#xff…

openlayers 封装加载本地geojson数据 - vue3

Geojson数据是矢量数据,主要是点、线、面数据集合 Geojson数据获取:DataV.GeoAtlas地理小工具系列 实现代码如下: import {ref,toRaw} from vue; import { Vector as VectorLayer } from ol/layer.js; import { Vector as VectorSource } fr…

蓄电池在线监测系统 各大UPS铅酸蓄电池监测 保障安全

蓄电池的不断普及,确实推动了蓄电池监控和管理技术的持续升级。蓄电池检测系统的研发为我们带来了诸多好处,这些好处主要体现在以下几个方面: 一、提高蓄电池管理的智能化水平 蓄电池检测系统通过实时监测蓄电池的电压、电流、温度等关键参数…

ZEISS ATOS Q蓝光三维扫描仪高效把控零件质量检测【上海沪敖3D】

位于Bengaluru的施耐德电气工厂拥有一流的计量设备,可以检测所有供应商的零件。当时,他们在使用一款激光扫描设备进行质量检测,但是,该设备不便于携带,且检测时需要喷涂大量的显影液。此外,它需要被安装在夹…

docker基础使用创建固定硬盘大小为40G的虚拟机

在docker中创建的服务器,匹配出容器id,服务器ip,服务器核数,服务器内存,服务器硬盘空间 for i in $(docker ps | grep -aiE web | awk {print $1});do echo $i; docker inspect $i|grep -aiE ipaddr|tail -1|grep -ai…

医院信息化与智能化系统(7)

医院信息化与智能化系统(7) 这里只描述对应过程,和可能遇到的问题及解决办法以及对应的参考链接,并不会直接每一步详细配置 如果你想通过文字描述或代码画流程图,可以试试PlantUML,告诉GPT你的文件结构,让他给你对应的…

最新PHP网盘搜索引擎系统源码 附教程

最新PHP网盘搜索引擎系统源码 附教程,这是一个基于thinkphp5.1MySQL开发的网盘搜索引擎,可以批量导入各大网盘链接,例如百度网盘、阿里云盘、夸克网盘等。 功能特点:网盘失效检测,后台管理功能,网盘链接管…

使用freemarker实现在线展示文档功能开发,包括数据填充

首先,在这个独属于程序员节日的这一天,祝大家节日快乐【求职的能找到心仪的工作,已经工作的工资翻倍】。 ---------------------------------------------------------------回到正文-----------------------------------------------------…

状态栏黑底白字后如何实现圆角以及固定状态栏

如何实现如下效果: 上述是将状态栏实现黑底白字+圆角+状态栏固定的逻辑 具体代码patch如下: From 6a3b8ed5d3f49a38d8f9d3e488314a66ef5576b8 Mon Sep 17 00:00:00 2001 From: andrew.hu <andrew.hu@quectel.com> Date: Fri, 18 Oct 2024 16:43:49 +0800 Subject: [P…

Next.js14快速上手

文章目录 ***客户端***什么是Next项目在线地址官方文档项目创建查看项目目录结构app属于根目录 ***服务端***vercel数据库prisma 客户端 什么是Next Next.js 是一个用于构建全栈 Web 应用程序的 React 框架。您可以使用 React Components 来构建用户界面&#xff0c;并使用 Ne…

Unity引擎:游戏开发的核心力量

目录 引言 Unity引擎的发展历程 早期发展 跨平台支持 Unity引擎的核心特性 易用性 社区支持 跨平台能力 Unity在游戏开发中的应用 移动游戏 独立游戏 3A游戏 Unity的未来展望 高级图形和渲染技术 扩展现实&#xff08;XR&#xff09;支持 云服务和多人游戏 结论…

excel中,将时间戳(ms或s)转换成yyyy-MM-dd hh:mm.ss或毫秒格式

问题 在一些输出为时间戳的文本中&#xff0c;按照某种格式显示更便于查看。 如下&#xff0c;第一列为时间戳(s)&#xff0c;第二列是转换后的格式。 解决方案&#xff1a; 在公式输入框中输入&#xff1a;yyyy/mm/dd hh:mm:ss TEXT((A18*3600)/8640070*36519, "yyy…

Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks

Abstract 图像到图像转换是一类视觉和图形问题&#xff0c;其目标是使用对齐图像对的训练集来学习输入图像和输出图像之间的映射。 然而&#xff0c;对于许多任务&#xff0c;配对训练数据将不可用。 我们提出了一种在没有配对示例的情况下学习将图像从源域 X 转换到目标域 Y …