Java IO之对象的序列化、ObjectInputStream和ObjectOutputStream类
2016-02-24 15:29
686 查看
什么是IO流?
byte序列的读写,Java中的IO流是实现输入/输出的基础.
Java将数据从源(文件、内存、键盘、网络)读入到内存 中,形成了流,然后将这些流还可以写到另外的目的地(文件、内存、控制台、网络),之所以称为流,是因为这个数据序列在不同时刻所操作的是源的不同部分。按照不同的分类标准,IO流分为不同类型。主要有以下几种方式:按照数据流方向、数据处理的单位和功能。
不管流的分类是多么的丰富和复杂,其根源来自于四个基本的类。这个四个类的关系如下:
数据从内存到硬盘,通常认为是输出流,即写操作;相反,从硬盘到内存,通常认为是输入流,即读操作;这里的输入、输出是从内存的角度划分的。
字节流和字符流区别非常简单,它们的用法几乎一样。区别在于字节流和字符流所处理的最小数据单元不同。
文件中以字节的形式存储的。
节点流是可以从或向一个特定的地方(节点)读写数据,也叫 低级流。如FileReader。
处理流是在对节点流封装的基础上的一种流,通过封装后来实现数据的读写功能,也叫高级流。
常用节点流
父 类 InputStream OutputStream Reader Writer
文 件 FileInputStream FileOutputStrean FileReader FileWriter 文件进行处理的节点流
数 组 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter 对数组进行处理的节点流(对应的不再是文件,而是内存中的一个数组)
字符串 StringReader StringWriter 对字符串进行处理的节点流
管 道 PipedInputStream PipedOutputStream PipedReader PipedWriter 对管道进行处理的节点流
常用处理流(关闭处理流使用关闭里面的节点流)
父 类 InputStream OutputStream Reader Writer
缓冲流 BufferedImputStrean BufferedOutputStream BufferedReader BufferedWriter
----需要父类作为参数构造,增加缓冲功能,避免频繁读写硬盘,可以初始化缓冲数据的大小,由于带了缓冲功能,所以就写数据的时候需要使用flush 方法咯
转换流 *InputStreamReader OutputStreamWriter- 要inputStream 或OutputStream 作为
参数,实现从字节流到字符流的转换数据流 *DataInputStream DataOutputStream -提供将基础数据类型写入到文件中,或者
读取出来,为什么要有这个流呢?看这样的分析,如果没有这种流的话,有一个long,本身只占8 个字节,如果我要写入到文件,需要转成字符串,然后在转成字符数组,那空间会占用很多,但是有了这种流之后就很方便了,直接将这8 个字节写到文件就完了。。是不是既节约了内存空间有让程序写起来更加方便简单了呐。写倒是很简单,但是读取的时候就注意了,根据读取的数据类型,指针会往下移,所以你写的顺序必须要和读的顺序一致才能完成正确的需求.
Java是一种完全面向对象的高级语言,所以在编写程序的时候数据大都存放在对象当中。我们有时会需要将内存中的整个对象都写入到文件中去,然后在适当的时候再从文件中将对象还原至内存。我们可以使用java.io.ObjectInputStream和java.io.ObjectOutputStream类来完成这个任务。
1、什么是对象的序列化(Serialize)?为什么要实现对象的序列化?
序列化是指将对象的状态信息转换为可以存储或传输的形式(2进制数据)的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
序列化的目的:
1)永久的保存对象,保存对象的字节序列到本地文件中;
2)通过序列化对象在网络中传递对象;
3)通过序列化对象在进程间传递对象。
2、ObjectInputStream类 和ObjectOutputStream类
如果我们想要序列化一个对象,如我们自定义的User类的对象,那么这个对象必须实现Serializable接口。Serializable接口没有任何的抽象方法,实现这个接口仅仅是为了通知编译器已这个对象将要被序列化,所以此接口仅仅是一个表示接口。类似的用法还有Cloneable接口,实现这个接口也只是起到通知编译器的作用。
3.对象的序列化和反序列化
想要完成对象的输入输出,还必须依靠ObjectInputStream和ObjectOutputStream;
使用对象输出流输出序列化对象的步骤,也称为序列化,而使用对象输入流读入对象的过程,也称为反序列化。
![](http://img.blog.csdn.net/20160224111033680?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
为了演示如何进行对象的序列化,我们先设计一个User类:
在对象进行序列化或者反序列化操作的时候,要考虑JDK版本的问题,如果序列化的JDK版本和反序列化的JDK版本不统一则就可能造成异常,所以在序列化操作中引入了一个serialVersionUID的常量,可以通过此常量来验证版本的一致性,在进行反序列化时,JVM会将传过来的字节流中的serialVersionUID与本地相应实体的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就抛出不一致的异常。
5.到底序列化了哪些东西呢?
所有的对象拥有各自的属性值,但是所有的方法都是公共的,所以序列化对象的时候实际上序列化的就是属性。
下面我们使用ObjectInputStream类 和ObjectOutputStream类 向文件中写入3个User对象,追加1个User对象,最后再从文件中读回对象。
Header),在读取数据的时候会先检查这个流头。所以我们在向文件中追加对象的时候ObjectOutputStream就会再次向文件中写入流头,这样在读取对象的时候会发生StreamCorrupedException异常。
6.Externalzable接口
如果一个类实现了Serializable 接口,则肯定此类可以被序列化下来,那么也就意味着此类多了一项功能,可以被序列化,那么让所有的 类都实现此接口是不是更好啊?
因为JDK是会不断升级的,现在Serializable 接口中没有任何定义,那么以后呢?
使用Serilizable 接口可以方便的序列化一个对象,但是在序列化操作中也提供了另外一种序列化机制——Externalizable 接口。
被Serialization接口声明的类其对象可以被序列化,如果现在用户希望可以自己制定序列化的内容,则可以让一个类实现Externalizable接口,此接口定义如下:
public interface Externalization extends Serializable{
public void writeExternal(ObjectOutput out) throws IOException;
public void writeExternal(ObjectIntput in) throws IOException,ClassNotFoundException;
}
方法:
写入:void writeExternal(ObjectOutput out) throws IOException
读取:void readExternal(ObjectInput in)throws IOException,ClassNotFoundException
程序:
[java] view
plain copy
import java.io.Externalizable ;
public class Person implements Externalizable{
private static final long serialVersionUID = 1l;
private String name ; // 声明name属性
private int age ; // 声明age属性
public Person(String name,int age){ // 通过构造设置内容
this.name = name ;
this.age = age ;
}
public String toString(){ // 覆写toString()方法
return "姓名:" + this.name + ";年龄:" + this.age ;
}
public void writeExternal(ObjectOutput out) throws IOException{
out.writeObject(this.name); //保存姓名属性
out.writeInt(this.age); //保age属性
}
public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException{
this.name = in.readObject(); //读取姓名
this.age = in.readInt(); //读取年龄
}
};
为了方便测试,现在将,现在序列化及反序列化操作形成方法调用的形式。
[java] view
plain copy
import java.io.File ;
import java.io.IOException ;
import java.io.FileOutputStream ;
import java.io.OutputStream ;
import java.io.ObjectOutputStream ;
import java.io.FileInputStream ;
import java.io.InputStream ;
import java.io.ObjectInputStream ;
public class SerDemo03{
public static void main(String args[]) throws Exception{
//ser() ;
dser() ;
}
public static void ser() throws Exception {
File f = new File("D:" + File.separator + "test.txt") ; // 定义保存路径,参考附录1。
ObjectOutputStream oos = null ; // 1.声明对象输出流
OutputStream out = new FileOutputStream(f) ; // 2.文件输出流
oos = new ObjectOutputStream(out) ; //3.用文件输出流实例化对象输出流
oos.writeObject(new Person("张三",30)) ; //4.保存对象
oos.close() ; //5. 关闭
}
public static void dser() throws Exception {
File f = new File("D:" + File.separator + "test.txt") ; // 定义保存路径
ObjectInputStream ois = null ; // 声明对象输入流
InputStream input = new FileInputStream(f) ; // 文件输入流
ois = new ObjectInputStream(input) ; // 实例化对象输入流
Object obj = ois.readObject() ; // 读取对象
ois.close() ; // 关闭
System.out.println(obj) ;
}
};
以上程序执行的时候出现了一个错误:
![](http://img.blog.csdn.net/20160224150051331?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
在使用Externalizable接口的时候需要在被序列化的类中定义一个无参构造,因为此接口在进行反序列化的时候,会先使用
类中的无参构造方法为其进行实例化,之后再将内容分别设置到属性之中,因此,修改Person类:
[java] view
plain copy
import java.io.Externalizable;
import java.io.*;
public class Person implements Externalizable{
private static final long serialVersionUID = 1L;
private String name; //声明name属性
private int age; //声明age属性
public Person(){} //无参构造
public Person(String name, int age){
this.name = name;
this.age = age;
}
public String toString(){ //覆写toString()方法
return "姓名:" + this.name + ":年龄:" + this.age;
}
public void writeExternal(ObjectOutput out)
throws IOException{
out.writeObject(this.name); //保存姓名属性
out.writeInt(this.age); //保存age属性
}
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException{
this.name = (String)in.readObject(); //读取姓名
this.age = in.readInt(); //读取年龄
}
}
可以看出,序列化的一般步骤可以分为5步:
1.声明对象输出流
2.声明文件输出流,并实例化
3.用文件输出流对象实例化对象输出流
4.调用对象输出流的writeObject函数保存对象
5.关闭对象输出流
反序列化步骤:
1.声明对象输入流
2.声明文件输入流
3.用文件输入流对象实例化对象输入流
4.调用对象输入流的readObject函数读取对象,打印读取到的对象内容
5.关闭对象输入流
Externalizable和Serializable接口实现序列化的区别:
![](http://img.blog.csdn.net/20160224150902006?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
transient关键字:
在序列化操作的时候,如果某个属性不希望被序列化下来,则可以直接使用transient 关键字声明。
[java] view
plain copy
import java.io.Serializable ;
public class Person implements Serializable{
private transient
String name ; // 声明name属性,但是此属性不被序列化
private int age ; // 声明age属性
public Person(String name,int age){ // 通过构造设置内容
this.name = name ;
this.age = age ;
}
public String toString(){ // 覆写toString()方法
return "姓名:" + this.name + ";年龄:" + this.age ;
}
};
操作代码:
[java] view
plain copy
import java.io.File ;
import java.io.IOException ;
import java.io.FileOutputStream ;
import java.io.OutputStream ;
import java.io.ObjectOutputStream ;
import java.io.FileInputStream ;
import java.io.InputStream ;
import java.io.ObjectInputStream ;
public class SerDemo04{
public static void main(String args[]) throws Exception{
ser() ;
dser() ;
}
public static void ser() throws Exception {
File f = new File("D:" + File.separator + "test.txt") ; // 定义保存路径
ObjectOutputStream oos = null ; // 声明对象输出流
OutputStream out = new FileOutputStream(f) ; // 文件输出流
oos = new ObjectOutputStream(out) ;
oos.writeObject(new Person("张三",30)) ; // 保存对象
oos.close() ; // 关闭
}
public static void dser() throws Exception {
File f = new File("D:" + File.separator + "test.txt") ; // 定义保存路径
ObjectInputStream ois = null ; // 声明对象输入流
InputStream input = new FileInputStream(f) ; // 文件输入流
ois = new ObjectInputStream(input) ; // 实例化对象输入流
Object obj = ois.readObject() ; // 读取对象
ois.close() ; // 关闭
System.out.println(obj) ;
}
};
transient + Serilizable 接口完全可以取代Externalizable 接口的功能。
序列化一组对象:
![](http://img.blog.csdn.net/20160224152220677?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
如果要保存多个对象,则最好使用对象数组的形式完成。
[java] view
plain copy
import java.io.File ;
import java.io.IOException ;
import java.io.FileOutputStream ;
import java.io.OutputStream ;
import java.io.ObjectOutputStream ;
import java.io.FileInputStream ;
import java.io.InputStream ;
import java.io.ObjectInputStream ;
public class SerDemo05{
public static void main(String args[]) throws Exception{
Person per[] = {new Person("张三",30),new Person("李四",31),
new Person("王五",32)} ;
ser(per) ; //Object obj[]可以接收对象数组
Object o[] = (Object[])dser() ;
for(int i=0;i<o.length;i++){
Person p = (Person)o[i] ;
System.out.println(p) ;
}
}
public static void ser(Object obj[]) throws Exception {
File f = new File("D:" + File.separator + "test.txt") ; // 定义保存路径
ObjectOutputStream oos = null ; // 声明对象输出流
OutputStream out = new FileOutputStream(f) ; // 文件输出流
oos = new ObjectOutputStream(out) ;
oos.writeObject(obj) ; // 保存对象
oos.close() ; // 关闭
}
public static Object[] dser() throws Exception {
File f = new File("D:" + File.separator + "test.txt") ; // 定义保存路径
ObjectInputStream ois = null ; // 声明对象输入流
InputStream input = new FileInputStream(f) ; // 文件输入流
ois = new ObjectInputStream(input) ; // 实例化对象输入流
Object obj[] = (Object[])ois.readObject() ; // 读取对象
ois.close() ; // 关闭
return obj ;
}
};
总结:
1、对象序列化的作用,对象序列化并不一定都向文件中保存,也有可能面向于其他的输入或输出
2、被序列化的对象的类必须实现Serializable 接口,如果某个属性不希望被保存下来,则可以使用transient 关键字声明。
3、ObjectOutputStream 序列化对象,ObjectInputStream 反序列化对象
4、Externalizable 接口作用: 开发人员手式实现序列化的操作
5、使用序列化保存一组对象的时候要使用对象数组的形式操作
拓展:
保存的数据有限,所以为了解决这样的问题,Java
中引入了类集 框架解决数组的存储限制问题。不过已经超出本文的讨论范围,以后有机会将会为大家总结。
附录1:
在Windows下的路径分隔符和Linux下的路径分隔符是不一样的,当直接使用绝对路径时,跨平台会暴出“No such file or diretory”的异常。
比如说要在temp目录下建立一个test.txt文件,在Windows下应该这么写:
File file1 = new File ("C:\tmp\test.txt");
在Linux下则是这样的:
File file2 = new File ("/tmp/test.txt");
如果要考虑跨平台,则最好是这么写:
File myFile = new File("C:" + File.separator + "tmp" + File.separator, "test.txt");
File类有几个类似separator的静态字段,都是与系统相关的,在编程时应尽量使用。
separatorChar
public static final char separatorChar
与系统有关的默认名称分隔符。此字段被初始化为包含系统属性 file.separator 值的第一个字符。在 UNIX 系统上,此字段的值为 '/';在 Microsoft Windows 系统上,它为 '\'。
separator
public static final String separator
与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。此字符串只包含一个字符,即 separatorChar。
pathSeparatorChar
public static final char pathSeparatorChar
与系统有关的路径分隔符。此字段被初始为包含系统属性 path.separator 值的第一个字符。此字符用于分隔以路径列表 形式给定的文件序列中的文件名。在 UNIX 系统上,此字段为 ':';在 Microsoft Windows 系统上,它为 ';'。
pathSeparator
public static final String pathSeparator
与系统有关的路径分隔符,为了方便,它被表示为一个字符串。此字符串只包含一个字符,即 pathSeparatorChar。
附录2:
Android中实现序列化有两种选择:一是实现Serializable接口,二是实现Parcelable接口(android特有的功能,效率比实现Serializable接口高效,可用于Intent数据传递,也可以用于IPC)。
前面介绍了Serializable接口,实现Serializable接口来实现对象的序列化很简单,但是性能没有Parcelable接口高。所以建议使用Parcelable 。
1.什么是Parcelable接口:
Parcelable接口定义了将数据写入Parcel和从Parcel读出的接口。一个对象(实例),如果需要封装到消息中去,就必须实现这一接口,实现了这一接口,该实体就称为可打包的了。
2.应用场景:
需要在多个部件(Activity或Service)之间通过Intent传递一些数据时,简单类型的可以直接放入Intent,复杂类型的必须实现Parcelable接口。
3.Parcelable接口的定义:
通过实现Parcelable接口序列化对象的步骤:
1、实现Parcelable接口。
2、并且实现Parcelable接口的public void
writeToParcel(Parcel dest, int flags)方法 。将你的对象序列化为一个Parcel对象,即:将类的数据写入外部提供的Parcel中,打包需要传递的数据到Parcel容器保存,以便从
Parcel容器获取数据
3、重写describeContents方法,内容接口描述,默认返回0就可以
4、自定义类型中必须含有一个名称为CREATOR的静态成员,该成员对象要求实现Parcelable.Creator接口及其方法。
注:其中public
static final一个都不能少,内部对象CREATOR的名称也不能改变,必须全部大写。需重写本接口中的两个方法:createFromParcel(Parcel in) 实现从Parcel容器中读取传递数据值,封装成Parcelable对象返回逻辑层,newArray(int size) 创建一个类型为T,长度为size的数组,仅一句话即可(return new T[size]),供外部类反序列化本类数组使用。
简而言之:通过writeToParcel将你的对象映射成Parcel对象,再通过createFromParcel将Parcel对象映射成你的对象。也可以将Parcel看成是一个流,通过writeToParcel把对象写到流里面,在通过createFromParcel从流里读取对象,只不过这个过程需要你来实现,因此写的顺序和读的顺序必须一致。
4、Serializable实现与Parcelabel实现的区别:
1)Serializable的实现,只需要implements Serializable 即可。这只是给对象打了一个标记,系统会自动将其序列化。
2)Parcelabel的实现,不仅需要implements Parcelabel,还需要在类中添加一个静态成员变量CREATOR,这个变量需要实现 Parcelable.Creator 接口。
5、两者代码比较:
1)创建Person类,实现Serializable
2)创建Book类,实现Parcelable
byte序列的读写,Java中的IO流是实现输入/输出的基础.
Java将数据从源(文件、内存、键盘、网络)读入到内存 中,形成了流,然后将这些流还可以写到另外的目的地(文件、内存、控制台、网络),之所以称为流,是因为这个数据序列在不同时刻所操作的是源的不同部分。按照不同的分类标准,IO流分为不同类型。主要有以下几种方式:按照数据流方向、数据处理的单位和功能。
不管流的分类是多么的丰富和复杂,其根源来自于四个基本的类。这个四个类的关系如下:
字节流 | 字符流 | |
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
输入流与输出流:
数据从内存到硬盘,通常认为是输出流,即写操作;相反,从硬盘到内存,通常认为是输入流,即读操作;这里的输入、输出是从内存的角度划分的。
字节(byte 1字节)流和字符(char 2字节)流:
字节流和字符流区别非常简单,它们的用法几乎一样。区别在于字节流和字符流所处理的最小数据单元不同。文件中以字节的形式存储的。
处理最小数据单元 | 基类 | |
字节流 byte | 8 | In/OutStream |
字符流 char | 16 | Reader/writer |
节点流和处理流:
节点流是可以从或向一个特定的地方(节点)读写数据,也叫 低级流。如FileReader。处理流是在对节点流封装的基础上的一种流,通过封装后来实现数据的读写功能,也叫高级流。
节点流 | 未经封装,low level stream |
处理流 | 封装过, high level stream |
父 类 InputStream OutputStream Reader Writer
文 件 FileInputStream FileOutputStrean FileReader FileWriter 文件进行处理的节点流
数 组 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter 对数组进行处理的节点流(对应的不再是文件,而是内存中的一个数组)
字符串 StringReader StringWriter 对字符串进行处理的节点流
管 道 PipedInputStream PipedOutputStream PipedReader PipedWriter 对管道进行处理的节点流
常用处理流(关闭处理流使用关闭里面的节点流)
父 类 InputStream OutputStream Reader Writer
缓冲流 BufferedImputStrean BufferedOutputStream BufferedReader BufferedWriter
----需要父类作为参数构造,增加缓冲功能,避免频繁读写硬盘,可以初始化缓冲数据的大小,由于带了缓冲功能,所以就写数据的时候需要使用flush 方法咯
转换流 *InputStreamReader OutputStreamWriter- 要inputStream 或OutputStream 作为
参数,实现从字节流到字符流的转换数据流 *DataInputStream DataOutputStream -提供将基础数据类型写入到文件中,或者
读取出来,为什么要有这个流呢?看这样的分析,如果没有这种流的话,有一个long,本身只占8 个字节,如果我要写入到文件,需要转成字符串,然后在转成字符数组,那空间会占用很多,但是有了这种流之后就很方便了,直接将这8 个字节写到文件就完了。。是不是既节约了内存空间有让程序写起来更加方便简单了呐。写倒是很简单,但是读取的时候就注意了,根据读取的数据类型,指针会往下移,所以你写的顺序必须要和读的顺序一致才能完成正确的需求.
Java输入输出流总结:
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
抽象基类 | InputStream | OutputStream | Reader | Writer |
访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
访问字符串 | StringReader | StringWriter | ||
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
转换流 | InputStreamReader | OutputStreamWriter | ||
对象流 | ObjectInputStream | ObjectOutputStream | ||
抽象基类 | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter |
打印流 | PrintStream | PrintWriter | ||
推回输入流 | PushbackInputStream | PushbackReader | ||
特殊流 | DataInputStream | DataOutputStream |
1、什么是对象的序列化(Serialize)?为什么要实现对象的序列化?
序列化是指将对象的状态信息转换为可以存储或传输的形式(2进制数据)的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
序列化的目的:
1)永久的保存对象,保存对象的字节序列到本地文件中;
2)通过序列化对象在网络中传递对象;
3)通过序列化对象在进程间传递对象。
2、ObjectInputStream类 和ObjectOutputStream类
如果我们想要序列化一个对象,如我们自定义的User类的对象,那么这个对象必须实现Serializable接口。Serializable接口没有任何的抽象方法,实现这个接口仅仅是为了通知编译器已这个对象将要被序列化,所以此接口仅仅是一个表示接口。类似的用法还有Cloneable接口,实现这个接口也只是起到通知编译器的作用。
3.对象的序列化和反序列化
想要完成对象的输入输出,还必须依靠ObjectInputStream和ObjectOutputStream;
使用对象输出流输出序列化对象的步骤,也称为序列化,而使用对象输入流读入对象的过程,也称为反序列化。
为了演示如何进行对象的序列化,我们先设计一个User类:
<span style="color:#333333;">package cls; import java.io.*; public class User implements Serializable // 实现Serializable接口,</span><span style="color:#ff0000;">仅仅起到标识这个类可被序列化的作用</span><span style="color:#333333;"> { // 可序列化对象的版本 private static final long serialVersionUID = 1L; private String name; private int num; public User(String name,int num) { this.name = name; this.num = num; } public String getName() { return name; } public int getNum() { return num; } } </span>4.serialVersionUID
在对象进行序列化或者反序列化操作的时候,要考虑JDK版本的问题,如果序列化的JDK版本和反序列化的JDK版本不统一则就可能造成异常,所以在序列化操作中引入了一个serialVersionUID的常量,可以通过此常量来验证版本的一致性,在进行反序列化时,JVM会将传过来的字节流中的serialVersionUID与本地相应实体的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就抛出不一致的异常。
5.到底序列化了哪些东西呢?
所有的对象拥有各自的属性值,但是所有的方法都是公共的,所以序列化对象的时候实际上序列化的就是属性。
下面我们使用ObjectInputStream类 和ObjectOutputStream类 向文件中写入3个User对象,追加1个User对象,最后再从文件中读回对象。
package cls; import java.io.*; import java.util.*; import cls.User; public class ObjectStreamDemo { public static void main(String[] args) { User[] user = new User[]{new User("dogg",1),new User("catt",2),new User("pigg",3)}; // 向文件中写入对象 try { ObjectStreamDemo.writeObj(user,args[0]);//args[0]传入文件名 } catch(Exception e) { System.out.println(e.toString()); } // 向文件中追加对象 try { // 要追加的对象 User[] u = new User[]{new User("append1",4),new User("append2",5)}; ObjectStreamDemo.appendObj(u,args[0]); } catch(Exception e) { System.out.println(e.toString()); } // 读取对象 try { List<User> list = ObjectStreamDemo.readObj(args[0]); // 输出对象信息 Iterator<User> it = list.iterator(); while(it.hasNext()) { User temp = it.next(); System.out.println(temp.getName() + "," + temp.getNum()); } } catch(Exception e) { System.out.println(e.toString()); } } static private void appendObj(Object[] objs,String fileName) throws Exception { File file = new File(fileName); // 以追加模式创建文件流对象 FileOutputStream fis = new FileOutputStream(file,true); ObjectOutputStream oos = new ObjectOutputStream(fis) { // 重写 writeStreamHeader()方法,空实现 protected void writeStreamHeader(){}; }; // 写入数据 for(Object o : objs) { oos.writeObject(o); } // 关闭流 oos.close(); } static private List<User> readObj(String fileName) throws Exception { File file = new File(fileName); // 使用List保存读取出来的对象 ArrayList<User> list = new ArrayList<User>(); // 创建流对象 FileInputStream fis = new FileInputStream(file); ObjectInputStream ois = new ObjectInputStream(fis); // 读取对象并放入List容器中 while(fis.available() > 0) { list.add((User)ois.readObject()); } ois.close(); return list; // 返回List } static private void writeObj(Object[] objs,String fileName) throws Exception { // 使用命令行参数中指定的文件名 File file = new File(fileName); // 创建流对象 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file)); // 写入对象 for(Object o : objs) { oos.writeObject(o); } // 关闭流 oos.close(); } }注意,当我们想要向一个已经存在的文件中追加对象时,应该重写ObjectOutputStream的writeStreamHeader()方法,并空实现。因为,ObjectOutputStream在写入数据的时候会加上一个特别的流头(Stream
Header),在读取数据的时候会先检查这个流头。所以我们在向文件中追加对象的时候ObjectOutputStream就会再次向文件中写入流头,这样在读取对象的时候会发生StreamCorrupedException异常。
6.Externalzable接口
如果一个类实现了Serializable 接口,则肯定此类可以被序列化下来,那么也就意味着此类多了一项功能,可以被序列化,那么让所有的 类都实现此接口是不是更好啊?
因为JDK是会不断升级的,现在Serializable 接口中没有任何定义,那么以后呢?
使用Serilizable 接口可以方便的序列化一个对象,但是在序列化操作中也提供了另外一种序列化机制——Externalizable 接口。
被Serialization接口声明的类其对象可以被序列化,如果现在用户希望可以自己制定序列化的内容,则可以让一个类实现Externalizable接口,此接口定义如下:
public interface Externalization extends Serializable{
public void writeExternal(ObjectOutput out) throws IOException;
public void writeExternal(ObjectIntput in) throws IOException,ClassNotFoundException;
}
方法:
写入:void writeExternal(ObjectOutput out) throws IOException
读取:void readExternal(ObjectInput in)throws IOException,ClassNotFoundException
程序:
[java] view
plain copy
import java.io.Externalizable ;
public class Person implements Externalizable{
private static final long serialVersionUID = 1l;
private String name ; // 声明name属性
private int age ; // 声明age属性
public Person(String name,int age){ // 通过构造设置内容
this.name = name ;
this.age = age ;
}
public String toString(){ // 覆写toString()方法
return "姓名:" + this.name + ";年龄:" + this.age ;
}
public void writeExternal(ObjectOutput out) throws IOException{
out.writeObject(this.name); //保存姓名属性
out.writeInt(this.age); //保age属性
}
public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException{
this.name = in.readObject(); //读取姓名
this.age = in.readInt(); //读取年龄
}
};
为了方便测试,现在将,现在序列化及反序列化操作形成方法调用的形式。
[java] view
plain copy
import java.io.File ;
import java.io.IOException ;
import java.io.FileOutputStream ;
import java.io.OutputStream ;
import java.io.ObjectOutputStream ;
import java.io.FileInputStream ;
import java.io.InputStream ;
import java.io.ObjectInputStream ;
public class SerDemo03{
public static void main(String args[]) throws Exception{
//ser() ;
dser() ;
}
public static void ser() throws Exception {
File f = new File("D:" + File.separator + "test.txt") ; // 定义保存路径,参考附录1。
ObjectOutputStream oos = null ; // 1.声明对象输出流
OutputStream out = new FileOutputStream(f) ; // 2.文件输出流
oos = new ObjectOutputStream(out) ; //3.用文件输出流实例化对象输出流
oos.writeObject(new Person("张三",30)) ; //4.保存对象
oos.close() ; //5. 关闭
}
public static void dser() throws Exception {
File f = new File("D:" + File.separator + "test.txt") ; // 定义保存路径
ObjectInputStream ois = null ; // 声明对象输入流
InputStream input = new FileInputStream(f) ; // 文件输入流
ois = new ObjectInputStream(input) ; // 实例化对象输入流
Object obj = ois.readObject() ; // 读取对象
ois.close() ; // 关闭
System.out.println(obj) ;
}
};
以上程序执行的时候出现了一个错误:
在使用Externalizable接口的时候需要在被序列化的类中定义一个无参构造,因为此接口在进行反序列化的时候,会先使用
类中的无参构造方法为其进行实例化,之后再将内容分别设置到属性之中,因此,修改Person类:
[java] view
plain copy
import java.io.Externalizable;
import java.io.*;
public class Person implements Externalizable{
private static final long serialVersionUID = 1L;
private String name; //声明name属性
private int age; //声明age属性
public Person(){} //无参构造
public Person(String name, int age){
this.name = name;
this.age = age;
}
public String toString(){ //覆写toString()方法
return "姓名:" + this.name + ":年龄:" + this.age;
}
public void writeExternal(ObjectOutput out)
throws IOException{
out.writeObject(this.name); //保存姓名属性
out.writeInt(this.age); //保存age属性
}
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException{
this.name = (String)in.readObject(); //读取姓名
this.age = in.readInt(); //读取年龄
}
}
可以看出,序列化的一般步骤可以分为5步:
1.声明对象输出流
2.声明文件输出流,并实例化
3.用文件输出流对象实例化对象输出流
4.调用对象输出流的writeObject函数保存对象
5.关闭对象输出流
反序列化步骤:
1.声明对象输入流
2.声明文件输入流
3.用文件输入流对象实例化对象输入流
4.调用对象输入流的readObject函数读取对象,打印读取到的对象内容
5.关闭对象输入流
Externalizable和Serializable接口实现序列化的区别:
transient关键字:
在序列化操作的时候,如果某个属性不希望被序列化下来,则可以直接使用transient 关键字声明。
[java] view
plain copy
import java.io.Serializable ;
public class Person implements Serializable{
private transient
String name ; // 声明name属性,但是此属性不被序列化
private int age ; // 声明age属性
public Person(String name,int age){ // 通过构造设置内容
this.name = name ;
this.age = age ;
}
public String toString(){ // 覆写toString()方法
return "姓名:" + this.name + ";年龄:" + this.age ;
}
};
操作代码:
[java] view
plain copy
import java.io.File ;
import java.io.IOException ;
import java.io.FileOutputStream ;
import java.io.OutputStream ;
import java.io.ObjectOutputStream ;
import java.io.FileInputStream ;
import java.io.InputStream ;
import java.io.ObjectInputStream ;
public class SerDemo04{
public static void main(String args[]) throws Exception{
ser() ;
dser() ;
}
public static void ser() throws Exception {
File f = new File("D:" + File.separator + "test.txt") ; // 定义保存路径
ObjectOutputStream oos = null ; // 声明对象输出流
OutputStream out = new FileOutputStream(f) ; // 文件输出流
oos = new ObjectOutputStream(out) ;
oos.writeObject(new Person("张三",30)) ; // 保存对象
oos.close() ; // 关闭
}
public static void dser() throws Exception {
File f = new File("D:" + File.separator + "test.txt") ; // 定义保存路径
ObjectInputStream ois = null ; // 声明对象输入流
InputStream input = new FileInputStream(f) ; // 文件输入流
ois = new ObjectInputStream(input) ; // 实例化对象输入流
Object obj = ois.readObject() ; // 读取对象
ois.close() ; // 关闭
System.out.println(obj) ;
}
};
transient + Serilizable 接口完全可以取代Externalizable 接口的功能。
序列化一组对象:
如果要保存多个对象,则最好使用对象数组的形式完成。
[java] view
plain copy
import java.io.File ;
import java.io.IOException ;
import java.io.FileOutputStream ;
import java.io.OutputStream ;
import java.io.ObjectOutputStream ;
import java.io.FileInputStream ;
import java.io.InputStream ;
import java.io.ObjectInputStream ;
public class SerDemo05{
public static void main(String args[]) throws Exception{
Person per[] = {new Person("张三",30),new Person("李四",31),
new Person("王五",32)} ;
ser(per) ; //Object obj[]可以接收对象数组
Object o[] = (Object[])dser() ;
for(int i=0;i<o.length;i++){
Person p = (Person)o[i] ;
System.out.println(p) ;
}
}
public static void ser(Object obj[]) throws Exception {
File f = new File("D:" + File.separator + "test.txt") ; // 定义保存路径
ObjectOutputStream oos = null ; // 声明对象输出流
OutputStream out = new FileOutputStream(f) ; // 文件输出流
oos = new ObjectOutputStream(out) ;
oos.writeObject(obj) ; // 保存对象
oos.close() ; // 关闭
}
public static Object[] dser() throws Exception {
File f = new File("D:" + File.separator + "test.txt") ; // 定义保存路径
ObjectInputStream ois = null ; // 声明对象输入流
InputStream input = new FileInputStream(f) ; // 文件输入流
ois = new ObjectInputStream(input) ; // 实例化对象输入流
Object obj[] = (Object[])ois.readObject() ; // 读取对象
ois.close() ; // 关闭
return obj ;
}
};
总结:
1、对象序列化的作用,对象序列化并不一定都向文件中保存,也有可能面向于其他的输入或输出
2、被序列化的对象的类必须实现Serializable 接口,如果某个属性不希望被保存下来,则可以使用transient 关键字声明。
3、ObjectOutputStream 序列化对象,ObjectInputStream 反序列化对象
4、Externalizable 接口作用: 开发人员手式实现序列化的操作
5、使用序列化保存一组对象的时候要使用对象数组的形式操作
拓展:
保存的数据有限,所以为了解决这样的问题,Java
中引入了类集 框架解决数组的存储限制问题。不过已经超出本文的讨论范围,以后有机会将会为大家总结。
附录1:
在Windows下的路径分隔符和Linux下的路径分隔符是不一样的,当直接使用绝对路径时,跨平台会暴出“No such file or diretory”的异常。
比如说要在temp目录下建立一个test.txt文件,在Windows下应该这么写:
File file1 = new File ("C:\tmp\test.txt");
在Linux下则是这样的:
File file2 = new File ("/tmp/test.txt");
如果要考虑跨平台,则最好是这么写:
File myFile = new File("C:" + File.separator + "tmp" + File.separator, "test.txt");
File类有几个类似separator的静态字段,都是与系统相关的,在编程时应尽量使用。
separatorChar
public static final char separatorChar
与系统有关的默认名称分隔符。此字段被初始化为包含系统属性 file.separator 值的第一个字符。在 UNIX 系统上,此字段的值为 '/';在 Microsoft Windows 系统上,它为 '\'。
separator
public static final String separator
与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。此字符串只包含一个字符,即 separatorChar。
pathSeparatorChar
public static final char pathSeparatorChar
与系统有关的路径分隔符。此字段被初始为包含系统属性 path.separator 值的第一个字符。此字符用于分隔以路径列表 形式给定的文件序列中的文件名。在 UNIX 系统上,此字段为 ':';在 Microsoft Windows 系统上,它为 ';'。
pathSeparator
public static final String pathSeparator
与系统有关的路径分隔符,为了方便,它被表示为一个字符串。此字符串只包含一个字符,即 pathSeparatorChar。
附录2:
Android中实现序列化有两种选择:一是实现Serializable接口,二是实现Parcelable接口(android特有的功能,效率比实现Serializable接口高效,可用于Intent数据传递,也可以用于IPC)。
前面介绍了Serializable接口,实现Serializable接口来实现对象的序列化很简单,但是性能没有Parcelable接口高。所以建议使用Parcelable 。
1.什么是Parcelable接口:
Parcelable接口定义了将数据写入Parcel和从Parcel读出的接口。一个对象(实例),如果需要封装到消息中去,就必须实现这一接口,实现了这一接口,该实体就称为可打包的了。
2.应用场景:
需要在多个部件(Activity或Service)之间通过Intent传递一些数据时,简单类型的可以直接放入Intent,复杂类型的必须实现Parcelable接口。
3.Parcelable接口的定义:
public interface Parcelable { //内容描述接口,基本不用管 public int describeContents(); //写入接口函数,打包 public void writeToParcel(Parcel dest, int flags); //读取接口,目的是要从Parcel中构造一个实现了Parcelable的类的实例处理。因为实现类在这里还是不可知的,所以需要用到模板的方式,继承类名通过模板参数传入 //为了能够实现模板参数的传入,这里定义Creator嵌入接口,内含两个接口函数分别返回单个和多个继承类实例 public interface Creator<T> { public T createFromParcel(Parcel source); public T[] newArray(int size); } }
通过实现Parcelable接口序列化对象的步骤:
1、实现Parcelable接口。
2、并且实现Parcelable接口的public void
writeToParcel(Parcel dest, int flags)方法 。将你的对象序列化为一个Parcel对象,即:将类的数据写入外部提供的Parcel中,打包需要传递的数据到Parcel容器保存,以便从
Parcel容器获取数据
3、重写describeContents方法,内容接口描述,默认返回0就可以
4、自定义类型中必须含有一个名称为CREATOR的静态成员,该成员对象要求实现Parcelable.Creator接口及其方法。
注:其中public
static final一个都不能少,内部对象CREATOR的名称也不能改变,必须全部大写。需重写本接口中的两个方法:createFromParcel(Parcel in) 实现从Parcel容器中读取传递数据值,封装成Parcelable对象返回逻辑层,newArray(int size) 创建一个类型为T,长度为size的数组,仅一句话即可(return new T[size]),供外部类反序列化本类数组使用。
简而言之:通过writeToParcel将你的对象映射成Parcel对象,再通过createFromParcel将Parcel对象映射成你的对象。也可以将Parcel看成是一个流,通过writeToParcel把对象写到流里面,在通过createFromParcel从流里读取对象,只不过这个过程需要你来实现,因此写的顺序和读的顺序必须一致。
4、Serializable实现与Parcelabel实现的区别:
1)Serializable的实现,只需要implements Serializable 即可。这只是给对象打了一个标记,系统会自动将其序列化。
2)Parcelabel的实现,不仅需要implements Parcelabel,还需要在类中添加一个静态成员变量CREATOR,这个变量需要实现 Parcelable.Creator 接口。
5、两者代码比较:
1)创建Person类,实现Serializable
public class Person implements Serializable { private static final long serialVersionUID = -7060210544600464481L; 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; } }
2)创建Book类,实现Parcelable
public class Book implements Parcelable { private String bookName; private String author; private int publishDate; public Book() { } public String getBookName() { return bookName; } public void setBookName(String bookName) { this.bookName = bookName; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public int getPublishDate() { return publishDate; } public void setPublishDate(int publishDate) { this.publishDate = publishDate; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel out, int flags) {
//必须按成员变量声明的顺序封装数据,不然会出现获取数据出错out.writeString(bookName);
out.writeString(author);
out.writeInt(publishDate);
}
//必须实现Parcelable.Creator接口,否则会在获取Book数据的时候报错。
public static final Parcelable.Creator<Book> CREATOR = new Creator<Book>() //实现Parcelable.Creator接口对象名必须为CREATOR,不然同样会报错上面所提到的错;
//这个接口实现了从Percel容器读取Book数据,并返回Book对象给逻辑层使用{
@Override
public Book[] newArray(int size)
{
return new Book[size];
}
@Override
public Book createFromParcel(Parcel in)//从Percel容器读取Book数据,并返回Book对象给逻辑层使用
{
return new Book(in);
}
};
//这是一个Book的构造函数,通过Parcel对象构造Book对象。
public Book(Parcel in) {
//在读取Parcel容器里的数据时,必须按成员变量声明的顺序读取数据,不然会出现获取数据出错bookName = in.readString();
author = in.readString();
publishDate = in.readInt(); }
}
相关文章推荐
- iOS NSUserDefaults setObject forKey,本地化存储,删除字典中的null,空值,本地化的时候,如果value为空值所引起的崩溃
- Swift 与 Objective-C混合编程
- TypeError: ObjectId('') is not JSON serializable
- JSONObject_v3
- Java基础(二):Object、Class、克隆、异常编程
- objective_C 优缺点
- 目标检测“Object Detection Using Generalization and Efficiency Balanced Co-occurrence Features”
- Spring中JDBCTemplate使用queryForObject方法时 ”Incorrect column count: expected 1, actual 5“错误
- Spring中JDBCTemplate中queryForObject()方法的使用
- Objective-C Runtime 运行时之六:拾遗
- 《从零开始学Swift》学习笔记(Day 69)——Swift与Objective-C混合编程之语言
- Objective-C Runtime 运行时之三:方法与消息
- Objective-C中一些 值得程序员注意的地方(转载)
- Objective-C Runtime 运行时之二:成员变量与属性
- Objective-C Runtime 运行时之一:类与对象
- JAVA_对象一对一关系 ObjectOneToOneRelationship.java
- JAVA_对象一对一关系 ObjectOneToOneRelationship.java
- 《从零开始学Swift》学习笔记(Day 69)——Swift与Objective-C混合编程之语言
- reduce and each_with_object
- 组件Newtonsoft.Json实现object2json转换