Java 之 I/O 系列 02 ——序列化(一)
2014-06-25 11:41
429 查看
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
Student.java
Simulator.java
运行结果:
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接口
因为我们要序列化Student类的对象,所以我们必须实现Serializable接口,然而Book是Student的一个实例变量,它的类没有实现Serializable接口,所以为了顺序完成序列化,我们把这个实例变量声明为transient以在序列化的过程中跳过它。
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类:
看下执行结果:
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 系列 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 ——序列化(二)
- Java 集合系列02之 Collection架构
- java io系列02之 ByteArrayInputStream的简介,源码分析和示例(包括InputStream)
- Java 集合系列02之 Collection架构
- Java 集合系列02之 Collection架构
- Java功底篇系列-02-如何理解实际开发中与“排序”相关的问题
- JAVA系列之对象的序列化与反序列化
- Java 集合系列02之 Collection架构
- Java 集合系列 02 Collection架构
- Java学习系列(十二)Java面向对象之序列化机制及版本
- Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock
- Java多线程系列--“JUC原子类”02之 AtomicLong原子类
- java io系列02之 ByteArrayInputStream的简介,源码分析和示例(包括InputStream)
- Java多线程系列--“JUC线程池”02之 线程池原理(一)
- 序列化接口的id有什么用?—— Java经典面试题系列
- JAVA系列之对象的序列化与反序列化
- [Java] 设计模式之工厂系列 02-面向抽象编程 抽象工厂 - 缺点 : 产生新的产品品种的时候,改动太多
- Java多线程系列--“JUC原子类”02之 AtomicLong原子类
- Java编程思想笔记02:组合与继承、final、策略设计模式与适配器模式、内部类、序列化控制(注意事项)