Java对象的序列化与反序列化

转载自 Java对象的序列化与反序列化

序列化与反序列化

序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。一般将一个对象存储至一个储存媒介,例如档案或是记亿体缓冲等。在网络传输过程中,可以是字节或是XML等格式。而字节的或XML编码格式可以还原完全相等的对象。这个相反的过程又称为反序列化。

Java对象的序列化与反序列化

在Java中,我们可以通过多种方式来创建对象,并且只要对象没有被回收我们都可以复用该对象。但是,我们创建出来的这些Java对象都是存在于JVM的堆内存中的。只有JVM处于运行状态的时候,这些对象才可能存在。一旦JVM停止运行,这些对象的状态也就随之而丢失了。

但是在真实的应用场景中,我们需要将这些对象持久化下来,并且能够在需要的时候把对象重新读取出来。Java的对象序列化可以帮助我们实现该功能。

对象序列化机制(object serialization)是Java语言内建的一种对象持久化方式,通过对象序列化,可以把对象的状态保存为字节数组,并且可以在有需要的时候将这个字节数组通过反序列化的方式再转换成对象。对象序列化可以很容易的在JVM中的活动对象和字节数组(流)之间进行转换。

在Java中,对象的序列化与反序列化被广泛应用到RMI(远程方法调用)及网络传输中。

相关接口及类

Java为了方便开发人员将Java对象进行序列化及反序列化提供了一套方便的API来支持。其中包括以下接口和类:

java.io.Serializable

java.io.Externalizable

ObjectOutput

ObjectInput

ObjectOutputStream

ObjectInputStream

Serializable 接口

类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。

当试图对一个对象进行序列化的时候,如果遇到不支持 Serializable 接口的对象。在此情况下,将抛出 NotSerializableException

虽然Serializable接口中并没有定义任何属性和方法,但是如果一个类想要具备序列化能力也比必须要实现它。其实,主要是因为序列化在真正的执行过程中会使用instanceof判断一个类是否实现类Serializable,如果未实现则直接抛出异常。关于这部分内容,我会单开一篇文章讲解。

如果要序列化的类有父类,要想同时将在父类中定义过的变量持久化下来,那么父类也应该集成java.io.Serializable接口。

下面是一个实现了java.io.Serializable接口的类

package com.hollischaung.serialization.SerializableDemos;
import java.io.Serializable;
/**
* Created by hollis on 16/2/17.
* 实现Serializable接口
*/

public class User1 implements Serializable {
   private String name;
   private int age;
   public String getName() {
       return name;
   }
   public void setName(String name) {
       this.name = name;
   }
   public int getAge() {
       return age;
   }
   public void setAge(int age) {
       this.age = age;
   }
   @Override
   public String toString() {
       return "User{" +
               "name='" + name + '\'' +
               ", age=" + age +
               '}';
   }
}

通过下面的代码进行序列化及反序列化

package com.hollischaung.serialization.SerializableDemos;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
* Created by hollis on 16/2/17.
* SerializableDemo1 结合SerializableDemo2说明 一个类要想被序列化必须实现Serializable接口
*/

public class SerializableDemo1 {

   public static void main(String[] args) {
       //Initializes The Object
       User1 user = new User1();
       user.setName("hollis");
       user.setAge(23);
       System.out.println(user);

       //Write Obj to File
       try (FileOutputStream fos = new FileOutputStream("tempFile"); ObjectOutputStream oos = new ObjectOutputStream(
           fos)) {
           oos.writeObject(user);
       } catch (IOException e) {
           e.printStackTrace();
       }

       //Read Obj from File
       File file = new File("tempFile");
       try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
           User1 newUser = (User1)ois.readObject();
           System.out.println(newUser);
       } catch (IOException | ClassNotFoundException e) {
           e.printStackTrace();
       }
   }
}

//OutPut:
//User{name='hollis', age=23}
//User{name='hollis', age=23}


如果你观察够细微的话,你可能会发现,我在上面的测试代码中使用了IO流,但是我并没有显示的关闭他。这其实是Java 7中的新特性try-with-resources。这其实是Java中的一个语法糖,背后原理其实是编译器帮我们做了关闭IO流的工作。后面我会单独出一篇文章介绍下如何使用语法糖提高代码质量。

上面的代码中,我们将代码中定义出来的User对象通过序列化的方式保存到文件中,然后再从文件中将他到序列化成Java对象。结果是我们的对象的属性均被持久化了下来。


Externalizable接口

除了Serializable 之外,java中还提供了另一个序列化接口Externalizable

为了了解Externalizable接口和Serializable接口的区别,先来看代码,我们把上面的代码改成使用Externalizable的形式。

package com.hollischaung.serialization.ExternalizableDemos;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

/**
* Created by hollis on 16/2/17.
* 实现Externalizable接口
*/

public class User1 implements Externalizable {

   private String name;
   private int age;

   public String getName() {
       return name;
   }
   public void setName(String name) {
       this.name = name;
   }
   public int getAge() {
       return age;
   }
   public void setAge(int age) {
       this.age = age;
   }
   public void writeExternal(ObjectOutput out) throws IOException {

   }
   public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

   }
   @Override
   public String toString() {
       return "User{" +
               "name='" + name + '\'' +
               ", age=" + age +
               '}';
   }
}


package com.hollischaung.serialization.ExternalizableDemos;

import java.io.*;

/**
* Created by hollis on 16/2/17.
* 对一个实现了Externalizable接口的类进行序列化及反序列化
*/

public class ExternalizableDemo1 {

  public static void main(String[] args) {
      //Write Obj to file
      User1 user = new User1();
      user.setName("hollis");
      user.setAge(23);
      try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"))){
          oos.writeObject(user);
      } catch (IOException e) {
          e.printStackTrace();
      }

      //Read Obj from file
      File file = new File("tempFile");
      try(ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file))){
          User1 newInstance = (User1) ois.readObject();
          //output
          System.out.println(newInstance);
      } catch (IOException | ClassNotFoundException e ) {
          e.printStackTrace();
      }
  }
}
//OutPut:
//User{name='null', age=0}

通过上面的实例的输出结果可以发现,对User1类进行序列化及反序列化之后得到的对象的所有属性的值都变成了默认值。也就是说,之前的那个对象的状态并没有被持久化下来。这就是Externalizable接口和Serializable接口的区别:

Externalizable继承了Serializable,该接口中定义了两个抽象方法:writeExternal()与readExternal()。当使用Externalizable接口来进行序列化与反序列化的时候需要开发人员重写writeExternal()与readExternal()方法。

由于上面的代码中,并没有在这两个方法中定义序列化实现细节,所以输出的内容为空。还有一点值得注意:在使用Externalizable进行序列化的时候,在读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。所以,实现Externalizable接口的类必须要提供一个public的无参的构造器。

如果实现了Externalizable接口的类中没有无参数的构造函数,在运行时会抛出异常:java.io.InvalidClassException。如果一个Java类没有定义任何构造函数,编译器会帮我们自动添加一个无参的构造方法,可是,如果我们在类中定义了一个有参数的构造方法了,编译器便不会再帮我们创建无参构造方法,这点需要注意。

按照要求修改之后代码如下:

package com.hollischaung.serialization.ExternalizableDemos;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

/**
* Created by hollis on 16/2/17.
* 实现Externalizable接口,并实现writeExternal和readExternal方法
*/

public class User2 implements Externalizable {

   private String name;
   private int age;

   public String getName() {
       return name;
   }
   public void setName(String name) {
       this.name = name;
   }
   public int getAge() {
       return age;
   }
   public void setAge(int age) {
       this.age = age;
   }
   public void writeExternal(ObjectOutput out) throws IOException {
       out.writeObject(name);
       out.writeInt(age);
   }
   public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
       name = (String) in.readObject();
       age = in.readInt();
   }

   @Override
   public String toString() {
       return "User{" +
               "name='" + name + '\'' +
               ", age=" + age +
               '}';
   }
}

再执行测试得到以下结果

//OutPut:
//User{name='hollis', age=23}

这次,就可以把之前的对象状态持久化下来了。

ObjectOutput和ObjectInput 接口

上面的writeExternal方法和readExternal方法分别接收ObjectOutput和ObjectInput类型参数。这两个类作用如下。

ObjectInput 扩展自 DataInput 接口以包含对象的读操作。

DataInput 接口用于从二进制流中读取字节,并根据所有 Java 基本类型数据进行重构。同时还提供根据 UTF-8 修改版格式的数据重构 String 的工具。

对于此接口中的所有数据读取例程来说,如果在读取所需字节数之前已经到达文件末尾 (end of file),则将抛出 EOFException(IOException 的一种)。如果因为到达文件末尾以外的其他原因无法读取字节,则将抛出 IOException 而不是 EOFException。尤其是,在输入流已关闭的情况下,将抛出 IOException。

ObjectOutput 扩展 DataOutput 接口以包含对象的写入操作。

DataOutput 接口用于将数据从任意 Java 基本类型转换为一系列字节,并将这些字节写入二进制流。同时还提供了一个将 String 转换成 UTF-8 修改版格式并写入所得到的系列字节的工具。

对于此接口中写入字节的所有方法,如果由于某种原因无法写入某个字节,则抛出 IOException。

ObjectOutputStream、ObjectInputStream类

通过前面的代码片段中我们也能知道,我们一般使用ObjectOutputStream的writeObject方法把一个对象进行持久化。再使用ObjectInputStream的readObject从持久化存储中把对象读取出来。

更多关于ObjectInputStream和ObjectOutputStream的相关知识,我会单独有一篇文章介绍,敬请期待。

transient 关键字

transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。关于transient 关键字的拓展同样下一篇文章介绍。

序列化ID

虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID)

序列化 ID 在 Eclipse 下提供了两种生成策略,一个是固定的 1L,一个是随机生成一个不重复的 long 类型数据(实际上是使用 JDK 工具生成),在这里有一个建议,如果没有特殊需求,就是用默认的 1L 就可以,这样可以确保代码一致时反序列化成功。那么随机生成的序列化 ID 有什么作用呢,有些时候,通过改变序列化 ID 可以用来限制某些用户的使用。



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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网站能拿到其他网站的cookie_网站能给公司带来哪些好处?

事实上,许多企业仍在努力建立一个网站,因为如果它建立起来,它需要一笔钱和其他费用。但此时客户业务不太好,企业客户需要考虑很多问题,但创建网站的企业已经尝到了甜头。其实有些朋友可能会有一些偏差。例如&#xff0…

笨办法学习@ConditionalOnProperty 烧脑配置记录

前言 今天继续学习springboot时,一不小心就被ConditionalOnProperty注解的配置真假搞得我真的变得真真假假了。。(此为真,彼为假,到底你是真还是你是假,晕了晕了。。。) 本片主要记录一下注解的真假情况 …

javaI/O流小结

【README】 1.本文总结java IO读取或写入数据的方式和相关类说明; 2.java IO建立在流之上的。输入流读取数据,输出流写入数据; 3.过滤器流-filter stream,可以串连(修饰)到输入流和输出流上;…

送给微软中文.NET社区的一份礼物,.NET FM

自报家门 大家好,我是.NET FM。做为一档专业而轻松的播客节目,在今后的日子里,我将为你奉上有关.NET和微软公司其他技术的新鲜资讯(偷偷讲下,还有各种八卦哦)。 Lex Li的回忆 认识吕鹏同学真的是非常偶然…

你真的以为你了解Java的序列化了吗

转载自 你真的以为你了解Java的序列化了吗 上一篇文章《Java对象的序列化与反序列化》中,简单介绍了Java中对象的序列化和反序列化的一些基础知识。看文那篇文章后,有小伙伴留言说:我终于了解了Java的序列化了。我只想说:小伙子&a…

SpringBoot配置mybatis-mysql数据源

前言 学习SpringBoot整合mybatis mysql配置,首先需要了解什么是ORM(对象映射关系)框架,ORM(Object Relational Mapping)对象关系映射,是 一种为了解决面向对象与关系型数据库不匹配而出现的技术,使开发者…

编程猜单词游戏python_Python实现简单的猜单词小游戏

本文实例为大家分享了Python实现猜单词小游戏的具体代码,供大家参考,具体内容如下 思路 1、一个words列表里存放若干的单词,例如:["extends", "private", "static", "public"]2、在words…

java URL和URI

【README】 本文阐述了 URL, URI,以及对应的java类的api; 1.URI,统一资源标识符,标识互联网上的某个网络资源,标识方式如 名称,位置等;就像人的标识一样,可以通过身份证…

HoloLens开发手记-全息Hologram

全息 Hologram HoloLens使我们可以通过周边世界的光线和声音来创建全息场景和物体,使得它们像真实物体那样。全息场景能够响应你的凝视、手势和语音指令,同时还会和你周边世界的表面交互。借助全息场景,你可以在周边世界创建数码物体。 class…

全网把Map中的hash()分析的最透彻的文章,别无二家。

转载自 全网把Map中的hash()分析的最透彻的文章,别无二家。你知道HashMap中hash方法的具体实现吗? 你知道HashTable、ConcurrentHashMap中hash方法的实现以及原因吗? 你知道为什么要这么实现吗? 你知道为什么JDK 7和JDK 8中hash方…

python下面的代码_解析一下下面的python代码?

class Model(dict, metaclassModelMetaclass): # 初始化, 没啥好说的 def __init__(self, **kw): super(Model, self).__init__(**kw) # 如果取不到值, 报错, 这是一个魔术方法, 使用时直接getattr(obj, key) def __getattr__(self, key): try: return self[key] except KeyErr…

SpringBoot多数据源(主从数据源)配置

🎶前言 学习springboot配置多数据源,先回顾一下springboot配置单数据源的方式 SpringBoot配置mybatis-mysql数据源 🔠主从数据源搭建 项目依赖 本次记录多数据源配置主要是通过druid mybatis plus aop的形式实现的,mybatis …

(转)HttpURLConnection与 HttpClient 区别

转自: HttpURLConnection与 HttpClient 区别/性能测试对比 - 尚码园HttpURLConnection与HttpClient随笔   目前在工做中遇到的须要各类对接接口的工做,须要用到HTTP的知识,工做完成后想要作一些笔记,原本知识打算把本身写的代码…

Raspkate - 基于.NET的可运行于树莓派的轻量型Web服务器

最近在业余时间玩玩树莓派,刚开始的时候在树莓派里写一些基于wiringPi库的C语言程序来控制树莓派的GPIO引脚,从而控制LED发光二极管的闪烁,后来觉得,是不是可以使用HTML5jQuery等流行的前端技术做一个简单的Web站点,让…

深入分析Java中的length和length()

转载自 深入分析Java中的length和length()在开始正文之前,请你快速回答如下问题:在不使用任何带有自动补全功能IDE的情况下,如何获取一个数组的长度?以及,如何获取一个字符串的长度?这个问题我问过不同水平…

归并排序示例

public class MergeSortMain {public static void main(String[] args) {int[] arr {5, 4, 6, 2, 3, 7, 9, 1, 8};sort(arr);print(arr);}static void sort(int[] arr) {sort(arr, 0, arr.length - 1);}/*** param arr 数组* param leftBound 左边界* param rightBoun…

windows监控txt写入_Windows的bug们

2020/9/26○Doriawinterwindows的锁屏界面我很喜欢,这个壁纸自动切换的模块叫windows聚焦,然而我某天开机时发现图片消失,变味了蓝色背景,如图:因为我的已经修好了,所以在网上找了一张一样的图,…

internet地址java表示

【README】 本文主要总结 internet地址相关知识,及相关的 java 类 ; 0)ip地址是什么 连接到internet的设备称为节点,计算机节点称为主机(host),每个节点至少由一个唯一的数(或数字…

微软也加入FB开放计算项目 发布交换机操作系统

微软3月10日宣布了一则震动技术业界的消息,它正式发布了基于Debian Linux的网络交换机操作系统。这款名为“SONiC”(Software for Open Networking in the Cloud)的软件以前仅供微软内部使用,主要运行在网络交换机上。 这条消息对…

Docker-Desktop储存路径更改

前言 Docker是一个非常好用的容器引擎, 使我们部署环境速度大幅度提升。但是windows版本的docker-desktop默认安装路径是C盘,这时候就有一个非常让人头疼的问题 -【C盘储存空间严重不足】。下面主要记录一下怎么解决这一个问题~ 原缓存路径 C:\Users${用户文件}\A…