Java 之 I/O 系列 目录

Java 之 I/O 系列 01 ——基础

Java 之 I/O 系列 02 ——序列化(一)

Java 之 I/O 系列 02 ——序列化(二)

一 序列化概述

  序列化,简单来讲,就是以“流”的方式来保存对象,至于保存的目标地址,可以是文件,可以是数据库,也可以是网络,即通过网络将对象从一个节点传递到另一个节点。

  在Java的I/O结构中,有ObjectOutputStream和ObjectInputStream,它们可以实现将对象输出为二进制流,并从二进制流中获取对象,那为什么还需要序列化呢?这需要从Java变量的存储结构谈起,我们知道对Java来说,基础类型存储在栈上,复杂类型(引用类型)存储在堆中,对于基础类型来说,上述的操作时可行的,但对复杂类型来说,上述操作过程中,可能会产生重复的对象,造成错误。

  而序列化的工作流程如下:

  1)通过输出流保存的对象都有一个唯一的序列号。

  2)当一个对象需要保存时,先对其序列号进行检查。

  3)当保存的对象中已包含该序列号时,不需要再次保存,否则,进入正常保存的流程。

  正是通过序列号的机制,序列化才可以完整准确的保存对象的各个状态。

  序列化保存的是对象中的各个属性的值,而不是方法或者方法签名之类的信息。对于方法或者方法签名,只要JVM能够找到正确的ClassLoader,那么就可以invoke方法。

  序列化不会保存类的静态变量,因为静态变量是作用于类型,而序列化作用于对象。

用来实现序列化的类都在java.io包中,我们常用的类或接口有:

ObjectOutputStream:提供序列化对象并把其写入流的方法

ObjectInputStream:读取流并反序列化对象

Serializable:一个对象想要被序列化,那么它的类就要实现 此接口

二 序列化示例

先通过一个简单的例子演示一起序列化/反序列化的过程

Book.java

 public class Book implements Serializable {

     private int isbn;
public Book(int isbn){
this.isbn = isbn;
}
public int getIsbn() {
return isbn;
}
public void setIsbn(int isbn) {
this.isbn = isbn;
} public String toString(){
return "Book [isbn = "+isbn+"]";
} }

Student.java

 public class Student implements Serializable {

     private Book book;
private String name; public Student(Book book, String name) {
this.book = book;
this.name = name; } public Book getBook() {
return book;
} public void setBook(Book book) {
this.book = book;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public String toString() {
return "Student [bool=" + book + ", name=" + name + "]";
} }

Simulator.java

 /**
* 序列化
* @ClassName: Simulator
* @author Xingle
* @date 2014-6-25 下午5:45:00
*/
public class Simulator {
public static void main(String[] args) {
new Simulator().go();
} private void go() {
Student student = new Student(new Book(2014), "xingle");
try {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("test"));
out.writeObject(student);
System.out.println("object has been written ");
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} try {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("test"));
Student stuRead = (Student) in.readObject();
System.out.println("object read here");
System.out.println(stuRead);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} } }

运行结果:

object has been written
object read here
Student [bool=Book [isbn = 2014], name=xingle]

可以看到,读取到的对象与保存的对象状态一样。这里有几点需要说明一下:

1、基本类型 的数据可以直接序列化

2、对象要被序列化,它的类必须要实现Serializable接口;如果一个类中有引用类型的实例变量,这个引用类型也要实现Serializable接口。比如上面 的例子中,Student类中有一个Book类型 的实例就是,要想让Student的对象成功序列化,那么Book也必须要实现Serializable接口。

3、这个语句:

ObjectOutputStreamout  = newObjectOutputStream(new FileOutputStream("seria"));

我们知道 FileOutputStream类有一个带有两个参数的重载Constructor——FileOutputStream(String,boolean),其第二个参数如果为true且String代表的文件存在,那么将把新的内容写到原来文件的末尾而非重写这个文件,这里我们不能用这个版本的构造函数,也就是说我们必须重写这个文件,否则在读取这个文件反序列化的过程中就会抛出异常,导致只有我们第一次写到这个文件中的对象可以被反序列化,之后程序就会出错。

下面的问题是如果 我们上面 用到的Book类没有实现Serializable接口,但是我们还想序列化Student类的对象 ,怎么办。

Java为我们提供了transient这个关键字。如果一个变量被声明成transient,那么 在序列化的过程 中,这个变量是会被无视的。我们还是通过对上面的代码进行小的修改来说明 这个问题。

新的Book类不实现Serializable接口

 public class Book {

     private int isbn;
public Book(int isbn){
this.isbn = isbn;
}
public int getIsbn() {
return isbn;
}
public void setIsbn(int isbn) {
this.isbn = isbn;
} public String toString(){
return "Book [isbn = "+isbn+"]";
} }

因为我们要序列化Student类的对象,所以我们必须实现Serializable接口,然而Book是Student的一个实例变量,它的类没有实现Serializable接口,所以为了顺序完成序列化,我们把这个实例变量声明为transient以在序列化的过程中跳过它。

 public class Student implements Serializable {

     private transient Book book;
private String name; public Student(Book book, String name) {
this.book = book;
this.name = name; } public Book getBook() {
return book;
} public void setBook(Book book) {
this.book = book;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public String toString() {
return "Student [bool=" + book + ", name=" + name + "]";
} }

Simulator.java和上面的一样,看一下运行结果:

object has been written
object read here
Student [bool=null, name=xingle]

可以看到,student对象被成功的序列化了。因为序列化过程中跳过了Book实例,所以当恢复对象状态的时候 ,它被赋予了默认值null,这也就意味着我们不能使用它。那如果Book类没有实现Serializable接口,但我们还想对它的状态进行保存,这可以实现吗?

java针对这种情况有一种特殊的机制—— 一组私有(回调)方法(这个马上在代码中看到),可以在要被序列化的类中实现它们,在序列化和的序列化的过程中它们会被自动调用。所以在这组方法中我们可以调用ObjectOutputStream/ObjectInputStream的一些有用方法来实现对象状态的保存。下面还是通过例子来说明:

Book类和Simulator类都不变,我们来看一下新的Student类:

 public class Student implements Serializable {

     private transient Book book;
private String name; public Student(Book book, String name) {
this.book = book;
this.name = name; } public Book getBook() {
return book;
} public void setBook(Book book) {
this.book = book;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public String toString() {
return "Student [bool=" + book + ", name=" + name + "]";
} //这个方法会在序列化的过程中被调用
private void writeObject(ObjectOutputStream out)
{
try {
//这个方法会把这当前中非静态变量和非transient变量写到流中 ,在这里就把name写到了流中。
out.defaultWriteObject();
//ObjectOutputStream中提供了写基本类型数据的方法
out.writeInt(book.getIsbn());
} catch (IOException e) {
e.printStackTrace();
}
} //这个方法会在反序列化的过程中被调用
private void readObject(ObjectInputStream in)
{
try {
//和defaultWriteObject()方法相对应,默认的反序列化方法,会从流中读取 非静态变量和非transient变量
in.defaultReadObject();
//用这个方法来读取一个int型值,这里我们是读取书号
int isbn = in.readInt();
book = new Book(isbn);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
} }

看下执行结果:

object has been written
object read here
Student [bool=Book [isbn = 2014], name=xingle]

正如预料 的一样,成功了。要注意的点我在代码的注释中有说明,请好好看下代码。

还有一点在代码中没写出来 ,就是一定要注意写入和读取的顺序一定要对应。像上面如果你是先写基本类型数据的话,那在读取的时候也一定要先读取基本类型的数据,文件是有position的。

最后,还有两个问题:

1、如果一个类没有实现Serializable接口,但是它的基类实现 了,这个类可不可以序列化?

2、和上面相反,如果一个类实现了Serializable接口,但是它的父类没有实现 ,这个类可不可以序列化?

第1个问题:一个类实现 了某接口,那么它的所有子类都间接实现了此接口,所以它可以被 序列化。

第2个问题:Object是每个类的超类,但是它没有实现 Serializable接口,但是我们照样在序列化对象,所以说明一个类要序列化,它的父类不一定要实现Serializable接口。但是在父类中定义的状态能被正确 的保存以及读取吗?

还有一点,序列化保存对象的状态,而静态(static)变量不是对象的 状态,所以它们不会被序列化。

Java 之 I/O 系列 02 ——序列化(一)的更多相关文章

  1. Java 之 I/O 系列 02 ——序列化(二)

    Java 之 I/O 系列 目录 Java 之 I/O 系列 01 ——基础 Java 之 I/O 系列 02 ——序列化(一) Java 之 I/O 系列 02 ——序列化(二) 继续上篇的第二个问 ...

  2. Java 之 I/O 系列 01 ——基础

    Java 之 I/O 系列 目录 Java 之 I/O 系列 01 ——基础 Java 之 I/O 系列 02 ——序列化(一) Java 之 I/O 系列 02 ——序列化(二) 整理<疯狂j ...

  3. Java 集合系列 02 Collection架构

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  4. java io系列02之 ByteArrayInputStream的简介,源码分析和示例(包括InputStream)

    我们以ByteArrayInputStream,拉开对字节类型的“输入流”的学习序幕.本章,我们会先对ByteArrayInputStream进行介绍,然后深入了解一下它的源码,最后通过示例来掌握它的 ...

  5. 【JAVA零基础入门系列】Day14 Java对象的克隆

    今天要介绍一个概念,对象的克隆.本篇有一定难度,请先做好心理准备.看不懂的话可以多看两遍,还是不懂的话,可以在下方留言,我会看情况进行修改和补充. 克隆,自然就是将对象重新复制一份,那为什么要用克隆呢 ...

  6. Java下用Jackson进行JSON序列化和反序列化(转)

    Java下常见的Json类库有Gson.JSON-lib和Jackson等,Jackson相对来说比较高效,在项目中主要使用Jackson进行JSON和Java对象转换,下面给出一些Jackson的J ...

  7. Linux NIO 系列(02) 阻塞式 IO

    目录 一.环境准备 1.1 代码演示 二.Socket 是什么 2.1 socket 套接字 2.2 套接字描述符 2.3 文件描述符和文件指针的区别 三.基本的 SOCKET 接口函数 3.1 so ...

  8. Java日期时间API系列19-----Jdk8中java.time包中的新的日期时间API类,ZonedDateTime与ZoneId和LocalDateTime的关系,ZonedDateTime格式化和时区转换等。

    通过Java日期时间API系列6-----Jdk8中java.time包中的新的日期时间API类中时间范围示意图:可以很清晰的看出ZonedDateTime相当于LocalDateTime+ZoneI ...

  9. java 从零开始手写 RPC (04) -序列化

    序列化 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RPC (02)-netty4 实现客户端和服务端 java 从零开始手写 RPC (03) 如何实 ...

随机推荐

  1. PythonOCC 3D图形库学习—导入STEP模型

    PythonOCC comes with importers/exporters for the most commonly used standard data files format in en ...

  2. CSS定位小技巧

    CSS定位Static 默认定位Relative 相对定位:left 和topposition: relative;/*相对定位*/ left:40px;/*在原来的位置向右移动*/ top:100p ...

  3. [转]-Android Studio 快捷键整理分享-SadieYu

    文章编辑整理:Android Studio 中文组 - SadieYu Alt+回车 导入包,自动修正 Ctrl+N   查找类 Ctrl+Shift+N 查找文件 Ctrl+Alt+L  格式化代码 ...

  4. mysql概要(五)union

    1.union,union all;合并不像表联合那样记录之间交叉组合而是插入新记录的效果 union:有个特性是会去重复,如果两个结果出现相同一条记录会被忽略掉一条. uniion all:将不会出 ...

  5. JSON学习总结

    最近几天使用json的需求比较急迫,所以学习了一下json.此文仅当笔记,以防忘却. 此文主要分为js和java总结: 先介绍json格式: JSON 数据的书写格式是:名称/值对. ***:可以更容 ...

  6. 01 Developing Successful Oracle Applications

    varchar2 类型定义时, 个人认为应该选择byte 类型, 即 varchar2(20), oracle 支持的最大的字符串是 varchar2(4000), 同时, 个人认为, 当你定义一个v ...

  7. JavaScript基础知识点

    本书目录 第一章:  JavaScript语言基础 第二章:  JavaScript内置对象第三章:  窗口window对象第四章:  文档document对象第五章:  表单form对象第六章:   ...

  8. css 常用样式命名规则

    大家在写css的时候,对一些html标签起一个合适的名字是个很头疼的事情,现在给大家分享项目中常用的名字供参考. 外套:wrap  ——用于最外层 头部:header  ——用于头部 主要内容:mai ...

  9. Maven——使用Maven构建多模块项目

    原文:http://www.cnblogs.com/xdp-gacl/p/4242221.html 在平时的Javaweb项目开发中为了便于后期的维护,我们一般会进行分层开发,最常见的就是分为doma ...

  10. 如何让JS的变量名变量化

    unction message() { var k=0; // var olk+k="sdasdasd"; eval("var olk"+k+"='a ...