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. Some Useful Property Settings Explained Of Oracle Forms

    In Oracle forms when we have two or more blocks and there is a requirement to join them or make a re ...

  2. find command in linux terminal

    Say you want to search for files with keyword as "keyword". find / -name keyword 2> /de ...

  3. 49个jquery代码经典片段

    49个jquery代码经典片段,这些代码能够给你的javascript项目提供帮助.其中的一些代码段是从jQuery1.4.2才开始支持的做法,另一些则是真正有用的函数或方法,他们能够帮助你又快又好地 ...

  4. Nginx基础知识之————多模块(非覆盖安装、RTMP在线人数实例安装测试)

    说明:已经安装好的nginx,需要添加一个未被编译安装的模块,需要怎么弄呢? 具体:这里以安装第三方nginx-rtmp-module和nginx-accesskey-2.0.3模块为例,nginx的 ...

  5. 下载安装APK

    protected void downloadApk() { //apk下载链接地址,放置apk的所在路径 //1,判断sd卡是否可用,是否挂在上 if(Environment.getExternal ...

  6. 机器学习如何选择模型 & 机器学习与数据挖掘区别 & 深度学习科普

    今天看到这篇文章里面提到如何选择模型,觉得非常好,单独写在这里. 更多的机器学习实战可以看这篇文章:http://www.cnblogs.com/charlesblc/p/6159187.html 另 ...

  7. 键盘控制div移动

    <!DOCTYPE html><html lang="zh-CN"><head> <meta charset="UTF-8&qu ...

  8. 转:C++中Static作用和使用方法

    转自:http://blog.csdn.net/artechtor/article/details/2312766 1.什么是static?       static 是C++中很常用的修饰符,它被用 ...

  9. Google Chrome input 设置 line-height 后光标变得和input一样高

    Google Chrome input的height和line-height设置为相同的比默认高度高的值时,当input控件获得焦点并且没有输入内容时,input中的光标会占满整个input控件(如果 ...

  10. iOS开发 判断用户是否开启了热点

    - (BOOL)achiveUserHotspotOpening { return [UIApplication sharedApplication].statusBarFrame.size.heig ...