Java:对象的序列化
一、对象序列化机制
序列化机制允许将实现序列化的Java对象转换为字节序列,这些字节序列可以被保存在磁盘上或通过网络传输,以备以后重新恢复原来的对象;
序列化机制使得对象可以脱离程序的运行而独立存在;
二、对象序列化与反序列化
对象的序列化(Serialize):指将一个Java对象写入IO流中;
对象的反反序列化(Deserialize):则指从IO流中恢复该Java对象;
三、序列化实现
如果需要让某个对象可以支持序列化机制,必须让它的类是可序列化(serializable),为了让某个类可序列化的,必须实现如下两个接口之一:
Serializable:标记接口,无须实现任何方法,只是表明该类是可序列化的;
Externalizable:
所有在网络上传输的对象都应该是可序列化的,否则将会出现异常;所有需要保存到磁盘里的对象的类都必须可序列化;
通常建议:程序创建的每个JavaBean类都实现Serializable;
四、使用对象流实现序列化
将某个对象保存到磁盘上或者通过网络传输:
1.创建一个ObjectOutputStream;
2.调用ObjectOutputStream对象的writeObject方法输出可序列化对象;
Person.java文件
[java] view plain copy 在CODE上查看代码片派生到我的代码片
public class Person implements Serializable {
private String name;
private int age;
//注意:此处没有提供无参数的构造方法,当程序调用的时候,没有看见调用构造器,这表明反序列化无须通过构造器来初始化Java对象;
public Person(String name, int age) {
super();
this.name = name;
this.age = 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;
}
}
WriteObject.java文件
[java] view plain copy 在CODE上查看代码片派生到我的代码片
public class WriteObject {
public static void main(String[] args) {
ObjectOutputStream oos = null;
try {
//1.创建一个ObjectOutputStream
oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
Person per = new Person("孙悟空", 500);
//2.将per对象写入输入流
oos.writeObject(per);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(oos != null){
oos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
程序希望从二进制流中恢复Java对象:
1.创建一个ObjectInputSteam;
2.调用ObjectInputStream的readObject方法读取流中的对象,该方法返回一个Object类型的Java对象,如果程序知道该Java对象的类型,则可以将该对象转换成其真实类型;
[java] view plain copy 在CODE上查看代码片派生到我的代码片
public class ReadObject {
public static void main(String[] args) {
ObjectInputStream ois = null;
try {
//1.创建一个ObjectInputStream输入流
ois = new ObjectInputStream(new FileInputStream("object.txt"));
//2.从输入流中读取一个Java对象,并将其强制类型转换为Person对象
Person p = (Person) ois.readObject();
System.ouwww.yinb666.cn t.println("名字为:" + p.getName() + "\n年龄为:" + p.getAge());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally{
try {
if (ois == null) {
ois.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
输出:
名字为:孙悟空
年龄为:500
注意:反序列化读取的仅仅是Java对象的数据,而不是Java类,因此采用反序列化恢复Java对象时,必须提供Java对象所属的class文件,否则会引发ClassNotFoundException异常;
如果我们向文件中使用序列化机制写入了多个Java对象,使用反序列化机制恢复对象必须按照实际写入的顺序读取。
当一个可序列化类有多个父类时(包括直接父类和间接父类),这些父类要么有无参的构造器,要么也是可序列化的—否则反序列化将抛出InvalidClassException异常。如果父类是不可序列化的,只是带有无参数的构造器,则该父类定义的Field值不会被序列化到二进制流中;
五、对象引用的序列化
如果某个类的Field类型不是基本类型或者String类型,而是另一个引用类型,那么这个引用类型必须是可序列化的,否则有用该类型的Field的类也是不可序列化的;
Teacher.java文件
[java] view plain copy 在CODE上查看代码片派生到我的代码片
public class Teacher implements Serializable {
private String name;
//类的属性是引用类型,也必须序列化。
//如果Person是不可序列化的,无论Teacher实现Serializable,Externalizable接口,则Teacher
//都是不可序列化的。
privatewww.yxin7.com Person student;
public Teacher(String name, Person student) {
super();
this.name = name;
this.student = student;
}
public String www.wuxing1688.cn getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person getStudent() {
return student;
}
public void setStudent(Person student) {
this.student = student;
}
}
Java学序列化才采用了一种特殊的序列化算法,其算法内容如下:
所有保存到磁盘中的对象都有一个序列化编号;
当程序视图序列化一个对象时,程序将先检查该对象是否已经被序列化过,只有该对象从未(在本次虚拟机)中序列化过,系统才会将该对象转换成字节序列并输出;
如果某个对象已经序列化过,程序将只是直接输出一个序列化编号,而不是再次重新序列化该对象;
WriterTeacher.java文件
[java] view plain copy 在CODE上查看代码片派生到我的代码片
public class WriterTeacher {
public static void main(String[] args) {
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("teacher.txt"));
Person per = new Person("孙悟空",500);
Teacher t1 = new Teacher("唐僧", per);
Teacher t2 = new Teacher("菩提祖师", per);
//依次将四个对象写入输出流
oos.writeObject(t1);
oos.writeObject(t2);
oos.writeObject(per);
oos.writeObject(t2);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
If (oos != www.zgktv.cn null) {
oos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
ReadTeacher.java文件
[java] view plain copy 在CODE上查看代码片派生到我的代码片
public class ReadTeacher {
public static void main(String[] args) {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("teacher.txt"));
//依次读取ObjectInputStream输入流中的四个对象
Teacher t1 = (Teacher) ois.readObject();
Teacher t2= (Teacher) ois.readObject();
Person p = (Person) ois.readObject();
Teacher t3 = (Teacher) ois.readObject();
System.out.println("t1的student引用和p是否相同:" + (t1.getStudent() == p));
System.out.println("t2的student引用和p是否相同:" + (t2.getStudent() == p));
System.out.println("t1和t3是是否是同一个对象:" + (t2 == t3));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally{
try {
if (ois != null) {
ois.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
输出:
t1的student引用和p是否相同:true
t2的student引用和p是否相同:true
t1和t3是是否是同一个对象:true
由于Java序列化机制使然,如果多次序列化同一个可变Java对象时,只有第一次序列化时才会把该Java对象转换成字节流并输出,即使后面对象的Field值已经改变;
SerializaMutable.java文件
[java] view plain copy 在CODE上查看代码片派生到我的代码片
public class SerializaMutable {
public static void main(String[] args) {
try {
// 创建一个ObjectOutputStream对象
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("mutable.txt"));
// 创建一个ObjectInputStream对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
"mutable.txt"));
Person per = new Person("孙悟空", 500);
// 系统将per对象转换成字节序列并输出
oos.writeObject(per);
per.setName("猪八戒");
// 系统只是输出序列化编号,所以改变后的name不会被序列化
oos.writeObject(per);
Person p1 = (Person) ois.readObject();
Person p2 = (Person) ois.readObject();
// 下面输出true,即反序列化后p1等于p2
System.out.println(p1 == p2);
// 下面依然看到输出“孙悟空”,即改变后的Field没有被序列化
System.out.println(p2.getName());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
输出:
true
孙悟空
六、自定义序列化
递归序列化:当某个对象进行序列化的时候,系统会自动把该对象的所有Field依次进行序列化;如果某个Field引用到另一个对象,则被引用的对象也会被序列化;如果被引用的对象的Field也引用了其它对象,则被引用的对象也会被序列化;
transient关键字:可以指定Java序列化时无须理会该Field;该关键字只能用于修饰Field,不可修饰Java程序的其它成分;
Person2.java文件
[java] view plain copy 在CODE上查看代码片派生到我的代码片
public class Person2 implements Serializable {
private String name;
// 指定transient,可以指定Java序列化时无需理会该Field
private transient int age;
// 注意此处没有提供无参数的构造器
public Person2(String name, int age) {
super();
System.out.println("没有参数的构造器");
this.name = name;
this.age = 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;
}
}
TransientTest.java文件
[java] view plain copy 在CODE上查看代码片派生到我的代码片
public class TransientTest {
public static void main(String[] args) {
try {
// 创建一个ObjectOutputStream对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("transient.txt"));
// 创建一个ObjectInputStream对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("transient.txt"));
Person2 per = new Person2("孙悟空", 500);
// 系统将per对象转换成字节序列并输出
oos.writeObject(per);
// 由于设置的transient关键字,故无法读取age属性
Person2 p = (Person2) ois.readObject();
System.out.println(p.getAge());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
Java还提提供了一种自定义序列化机制,通过这种自定义序列化机制可以让程序控制如何序列化各Field,甚至完全不序列化某些Field(与使用transient关键字的效果相同);序列化和反序列化过程需要特殊处理的类应该提供如下特殊签名的方法,这些特殊的方法用以实现自定义序列化:
private void writeObject(java.io.ObjectOutputStream out)throws IOException:负责写入类的特定实例状态,默认情况下,调用out.defaultWriteObject来保存Java对象的各Field;
private void readObject(java.io.ObjectInputStream in)throws IOException,ClassNotFoundException:负责从流中读取并恢复对象Field;默认情况下,调用in.defaultReadObject来恢复Java对象的非静态和非瞬态Field;
private void readObjectNoData()throws ObjectStreamException:用来正确地初始化反序列化对象,例如,接收方使用的反序列化类的版本不同于发送方,或者接收方版本扩展的类不是发送方版本扩展的类,或者序列化流被篡改时,系统都会调用readObjectNoData()方法来初始化反序列化对象;
Person3.java文件
[java] view plain copy 在CODE上查看代码片派生到我的代码片
public class Person3 implements Serializable {
private String name;
private int age;
// 注意此处没有提供无参数的构造器
public Person3(String name, int age) {
super();
System.out.println("无参数的构造器");
this.name = name;
this.age = 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;
}
private void writeObject(ObjectOutputStream out) throws IOException {
// 将name Field值反转后写入二进制流
out.writeObject(new StringBuffer(name).reverse());
out.writeInt(age);
}
private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException {
// 将去读的字符串反转后赋值给name Field
this.name = ((StringBuffer) in.readObject()).reverse().toString();
this.age = in.readInt();
}
}
CustomTest .java文件
[java] view plain copy 在CODE上查看代码片派生到我的代码片
public class CustomTest {
public static void main(String[] args) {
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("custom.txt"));
ObjectInputStream ooi = new ObjectInputStream(new FileInputStream("custom.txt"));
Person3 per = new Person3("孙悟空", 500);
oos.writeObject(per);
Person3 p = (Person3) ooi.readObject();
System.out.println(p.getName() + ":" + p.getAge());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
wirteReplace():只要该方法存在,由序列化机制调用,在序列化对象时将该对象替换成其它对象;
Person4.java文件
[java] view plain copy 在CODE上查看代码片派生到我的代码片
public class Person4 implements Serializable {
private String name;
private int age;
public Person4(String name, int age) {
super();
this.name = name;
this.age = 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;
}
// 重写wirteReplace方法,程序在自定义序列化之前,先调用该方法
private Object writeReplace() throws ObjectStreamException {
ArrayList<Object> list = new ArrayList<Object>();
list.add(name);
list.add(age);
return list;
}
}
ReplaceTest.java文件
[java] view plain copy 在CODE上查看代码片派生到我的代码片
public class ReplaceTest {
public static void main(String[] args) {
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("replace.txt"));
ObjectInputStream ooi = new ObjectInputStream(new FileInputStream("replace.txt"));
Person4 per = new Person4("孙悟空", 500);
// 系统将per对象转换成字节序列并输出
oos.writeObject(per);
// 反序列化读取得到的是ArrayList
ArrayList list = (ArrayList) ooi.readObject();
System.out.println(list.toString());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
Java还提供了一种完全由程序员决定存储和恢复对象数据的序列化机制,必须实现Externalizable接口,该接口里面定义如下两个方法:
void readExternal(ObjectInput in):实现反序列化,调用DataInput(ObjectInput的父接口)的方法来恢复基本类型的Field值,调用ObjectInput的readObject()方法来恢复引用类型的Field值;
void writeExternal(ObjectOutput out):实现序列化,调用DataOutput(ObjectOutput的父接口)的方法来保存基本类型的Field值,调用ObjectOutput的writeObject()方法来保存引用类型的Field值;
Person5.java文件
[java] view plain copy 在CODE上查看代码片派生到我的代码片
public class Person5 implements Externalizable {
private String name;
private int age;
// 必须提供无参数的构造函数,否则报java.io.InvalidClassException
public Person5() {
super();
}
// 注意此处没有提供无参数的构造器
public Person5(String name, int age) {
super();
System.out.println("有参数的构造器");
this.name = name;
this.age = 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 void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {
// 将去读的字符串反转后赋值给name Field
this.name = ((StringBuffer) in.readObject()).reverse().toString();
this.age = in.readInt();
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
// 将name Field值反转后写入二进制流
out.writeObject(new StringBuffer(name).reverse());
out.writeInt(age);
}
}
ExternalTest.java文件
[java] view plain copy 在CODE上查看代码片派生到我的代码片
public class ExternalTest {
public static void main(String[] args) {
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("external.txt"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("external.txt"));
Person5 per = new Person5("孙悟空", 500);
oos.writeObject(per);
Person5 p = (Person5) ois.readObject();
System.out.println(p.getName() + ":" + p.getAge());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
七、序列化机制对比
实现Serializable接口:
系统自动存储必要的信息;
Java内建支持,抑郁实现,只需要实现该接口即可,无需任何代码支持;
性能略差;
实现Externalizable接口:
程序员决定存储那些信息;
仅仅提供两个空方法,实现该接口必须为两个空方法提供实现;
性能略好;
八、对象序列化注意点
对象的类名、Field(包括基本类型、数组、对其它对象的引用)都会被序列化;方法、static Field(即静态Field)、transient Field(也被称为瞬态Field)都不会被序列化;
实现Serializable接口如果需要让某个Field不被序列化,则在该Field前加transient修饰符,而不是static关键字,虽然static关键字可以达到这个效果;
保证序列化对象的Field类型也是可序列化,否则需要使用transient关键字来修饰该Field,要不然该类是不可序列化的;
反序列化对象时必须有序列化对象的class文件;
当通过文件、网络来读取序列化的对象时,必须按实际写入顺序读取;
九、版本
反序列化Java对象时,必须提供该对象的class文件,随着项目的升级,系统的class文件也会升级。如果保持两个class文件兼容性:
Java的序列化机制允许为序列化类提供一个private static final的serialVersonUID值,该Field值用于标识该Java类的序列化版本;
最好在每个要序列化的类中加入private static final long serialVersionUID这个Field,具体数值自己定义。这样,即使对象被序列化之后,它所对应的类修改了,该对象也依然可以被正确反序列化;
如果不显示定义serialVersionUID值:
该Field值将由JVM根据类的相关信息计算,而修改后的类计算结果与修改前的类计算结果往往不同,从而造成对象的反序列化因为类版本的不兼容失败;
不利于程序在不同的JVM之间移植,因为不同的编译器计算该Field的值计算策略可能不同,从而造成虽然类完全没有改变,但是因为JVM不同,也会出现序列化版本不兼容而无法正确反序列化的现象;
Java:对象的序列化的更多相关文章
- Java对象的序列化与反序列化
序列化与反序列化 序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程.一般将一个对象存储至一个储存媒介,例如档案或是记亿体缓冲等.在网络传输过程中,可以是字节或是 ...
- Java对象的序列化和反序列化[转]
Java基础学习总结--Java对象的序列化和反序列化 一.序列化和反序列化的概念 把对象转换为字节序列的过程称为对象的序列化.把字节序列恢复为对象的过程称为对象的反序列化. 对象的序列化主要有两种用 ...
- Java对象的序列化与反序列化-Json篇
说到Java对象的序列化与反序列化,我们首先想到的应该是Java的Serializable接口,这玩意在两个系统之间的DTO对象里面可能会用到,用于系统之间的数据传输.或者在RPC(远程方法调用)时可 ...
- Java基础学习总结——Java对象的序列化和反序列化
一.序列化和反序列化的概念 把对象转换为字节序列的过程称为对象的序列化. 把字节序列恢复为对象的过程称为对象的反序列化. 对象的序列化主要有两种用途: 1) 把对象的字节序列永久地保存到硬盘上,通常存 ...
- Java对象的序列化和反序列化
对象的序列化是指将对象转换为字节序列的过程 对象的反序列化是指将字节序列恢复对象的过程 主要有两种用途: 1.把对象的字节序列永久地保存在硬盘上,通常放在一个文件中. 2.在网络上传输对象的字节序列. ...
- Java对象的序列化
1.概念 序列化:把Java对象转换为字节序列的过程. 反序列化:把字节序列恢复为Java对象的过程. 2.用途 对象的序列化主要有两种用途: 1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个 ...
- java对象的序列化与反序列化使用
1.Java序列化与反序列化 Java序列化是指把Java对象转换为字节序列的过程:而Java反序列化是指把字节序列恢复为Java对象的过程. 2.为什么需要序列化与反序列化 我们知道,当两个进程进 ...
- Java对象的序列化和反序列化实践
2013-12-20 14:58 对象序列化的目标是将对象保存在磁盘中,或者允许在网络中直接传输对象.对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久的保存 ...
- Java基础学习总结--Java对象的序列化和反序列化
一.序列化和反序列化的概念 把对象转换成字节序列的过程称之为对象的序列化 把字节序列恢复为对象的过程称之为对象的反序列化 对象序列化的主要用途: 1)把对象的字节序列永久的保存到硬盘上,通常放在一个文 ...
- 深入理解Java对象的序列化与反序列化的应用
当两个进程在进行远程通信时,彼此可以发送各种类型的数据.无论是何种类型的数据,都会以二进制序列的形式在网络上传送.发送方需要把这个Java对象转换为字节序列,才能在网络上传送:接收方则需要把字节序列再 ...
随机推荐
- Shell until循环
until 循环执行一系列命令直至条件为 true 时停止.until 循环与 while 循环在处理方式上刚好相反.一般while循环优于until循环,但在某些时候,也只是极少数情况下,until ...
- WPF ArrangeOverride与MeasureOverride
1.MeasureOverride 是FrameworkElement.MeasureOverride 方法 当重写在派生类中,单位是对于子元素所需要布局的大小并确定FrameworkElement ...
- javascript中通过className灵活查找元素 例如我们要把根据class来进行修改样式
一.背景:一个表单中,要修改一些li中有class=box的样式,将它的background设置为red红色.一般的做法是我们可以先找到父级元素 ,然后由父级元素找到所有相关tagName,最后,来一 ...
- java 递归函数
一.递归函数,通俗的说就是函数本身自己调用自己... 如:n!=n(n-1)! 你定义函数f(n)=nf(n-1) 而f(n-1)又是这个定义的函数..这就是递归 二.为什么要用递归:递归的目 ...
- [Webpack 2] Polyfill Promises for Webpack 2
If you're going to use code splitting with Webpack 2, you'll need to make sure the browser has suppo ...
- android 73 下载图片
package com.ithiema.imageviewer; import java.io.InputStream; import java.net.HttpURLConnection; impo ...
- oracle6
转换函数 介绍 转换函数用于将数据类型从一种转为另外一种.在某些情况下,oracle server允许值的数据类型和实际的不一样,这时oracle server会隐含的转化数据类型 比如: crea ...
- GDB 调试程序系列
http://blog.csdn.net/haoel/article/category/9197
- (亲测)设置myeclipse打开默认工作空间
亲测一: 1.找到D:\MyEclipse 8.5\configuration\ config.ini 这个文件 2.找到这一行instance.area.default 3.将后面的地址替换为你想要 ...
- JDBC操作数据库 封装好的工具类
mysql sqlserver oracle 数据库的驱动jar包http://download.csdn.net/download/csdn576038874/8833683package cn.h ...