Java 对象序列化机制详解
来源:互联网 发布:影像测量仪软件 编辑:程序博客网 时间:2024/06/07 03:25
对象序列化的目标:将对象保存到磁盘中,或允许在网络中直接传输对象。
对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久的保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点。其他程序一旦获得了这种二进制流,都可以讲这种二进制流恢复成原来的Java对象。
如果需要让某个对象支持序列化机制,则必须让它的类是可序列化的,则这个类必须实现如下两个接口之一:
· Serializable
· Externalizable
Serializable是一个标志接口,它只是表明该类的实例是可序列化的。
一、 使用对象流实现序列化
一旦某个类实现了Serializable接口,则该类的对象就是可序列化的,程序通过如下步骤创建可序列化对象:
1) 创建一个ObjectOutStream,这个输出流是一个处理流:
ObjectOutputStream oos = new ObjectOutputStream("object.txt");
2) 调用ObjectOutputStream对象的writeObject()方法输出可序列化对象:
public class Person implements java.io.Serializable{ public String name; public int age; // 构造方法 // setter和getter方法}
使用ObjectOutputStream将一个Person对象写入磁盘文件:
public class WriteObject {public static void main(String[] args) throws Exception{ObjectOutputStream oos = new ObjectOutputStream("object.txt");Person per = new Person("沉缘",25);oos.writeObject(per);}}
通过ObjectOutputStream,我们将Person对象保存到了文件中(硬盘),我们可以看到在当前目录中已经有了object.txt文件。
如果希望从二进制流中恢复对象,则可以通过反序列化机制,步骤如下:
1) 创建一个ObjectInputStream输入流,这个输入流是一个处理流,所以必须建立在其他节点流的基础上。
FileInputStream fis = new FileInputStream("object.txt"); ObjectInputStream ois = new ObjectInputStream(fis);
2) 调用ObjectInputStream对象的readObject()方法读取流中的对象,该方法返回一个Object类型的Java对象,可对该对象进行强制转换:
Person per= (Person) ois.readObject();ois.close();
实例:
public class ReadObject{public static void main(String[] args){try(// 创建一个ObjectInputStream输入流ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"))){// 从输入流中读取一个Java对象,并将其强制类型转换为Person类Person p = (Person)ois.readObject();System.out.println("名字为:" + p.getName()+ "\n年龄为:" + p.getAge());}catch (Exception ex){ex.printStackTrace();}}}
反序列化读取的仅仅是Java对象的数据,而不是Java类,因此采用反序列化恢复Java对象时,必须提供该对象所属类的class文件,否则将会引发ClassNotFoundException异常。
反序列化机制无须通过构造器来初始化Java对象。
二、 对象引用的序列化
在对象的嵌套过程中,比如Teacher类中有Person对象,如果希望Teacher对象是可序列化的,则Person对象也必须是可序列化的。
class Teacher implements java.io.Serializable{private String name;private Person student;//构造方法//setter、getter方法}
· 所有保存到磁盘中的对象都有一个序列化编号。
· 当程序试图序列化一个对象时,程序将先检查该对象是否已经被序列化过,只有该对象从未被序列化过,系统才会将该对象转换成字节序列输出。
· 如果某个对象已经被序列化过,程序将只是直接输出一个序列化编号,而不是再次重新序列化该对象。
下面程序序列化两个Teacher对象,两个Teacher对象都持有一个引用到同一个Person对象的引用,而且程序两次调用writeObject()方法输出同一个Teacher对象。
public class WriteTeacher{public static void main(String[] args) throws Exception{ObjectOutputStream oos = new ObjectOutputStream("object.txt");Person per = new Person("沉缘",25);Teacher t1 = new Teacher("无情",<span style="font-family: SimSun;">per</span>);Teacher t2 = new Teacher("无缘",per);oos.writeObject(t1);oos.writeObject(t2);oos.writeObject(per);oos.writeObject(t2);oos.close();}}上述程序,我们看着是序列化了四个对象,实际上只有三个,而且序列中的两个Teacher对象的student引用实际上时用一个Person对象。
接下来,我们读取引用:
public class ReadTeacher{public static void main(String[] args) {try(// 创建一个ObjectInputStream输出流ObjectInputStream 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();// 输出trueSystem.out.println("t1的student引用和p是否相同:"+ (t1.getStudent() == p));// 输出trueSystem.out.println("t2的student引用和p是否相同:"+ (t2.getStudent() == p));// 输出trueSystem.out.println("t2和t3是否是同一个对象:"+ (t2 == t3));}catch (Exception ex){ex.printStackTrace();}}}
此时,我们应该注意到一个问题,那就是,序列化一个可变对象时,只有第一次使用writeObject()方法输出时才会将该对象转换成字节序列并输出,当程序再次调用writeObject()方法时,程序只是输出前面的序列化编号,及时后面对象的Field值已改变,改变的Field值也不会被输出。
public class SerializeMutable{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对象的name Fieldper.setName("猪八戒");// 系统只是输出序列化编号,所以改变后的name不会被序列化oos.writeObject(per);Person p1 = (Person)ois.readObject(); //①Person p2 = (Person)ois.readObject(); //②// 下面输出true,即反序列化后p1等于p2System.out.println(p1 == p2);// 下面依然看到输出"孙悟空",即改变后的Field没有被序列化System.out.println(p2.getName());}catch (Exception ex){ex.printStackTrace();}}}
三、 自定义序列化
通过在Field(属性)前使用transient关键字,可以指定Java序列化时无须理会该Field。
public class Personimplements java.io.Serializable{private String name;private transient int age;// 注意此处没有提供无参数的构造器!public Person(String name , int age){System.out.println("有参数的构造器");this.name = name;this.age = age;}// 省略name与age的setter和getter方法}测试该Person对象:
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"))){Person per = new Person("孙悟空", 500);// 系统会per对象转换字节序列并输出oos.writeObject(per);Person p = (Person)ois.readObject();System.out.println(p.getName());System.out.println(p.getAge());}catch (Exception ex){ex.printStackTrace();}}}
输出:
有参数的构造器
孙悟空0
观察输出,获取的age值为0, 说明在序列化时,该age属性并未被序列化。
四、 Externalizable接口
该接口提供的序列化机制,完全由程序员决定存储和恢复对象数据。Externalizable接口中两个需实现的方法。
void
readExternal(ObjectInput in)
void
writeExternal(ObjectOutput out)
我们举个例子,看下如何使用该接口来序列化对象。
public class Personimplements java.io.Externalizable{private String name;private int age;// 注意此处没有提供无参数的构造器!public Person(String name , int age){System.out.println("有参数的构造器");this.name = name;this.age = age;}// 省略name与age的setter和getter方法// name的setter和getter方法public void setName(String name){this.name = name;}public String getName(){return this.name;}// age的setter和getter方法public void setAge(int age){this.age = age;}public int getAge(){return this.age;}public void writeExternal(java.io.ObjectOutput out)throws IOException{// 将name Field的值反转后写入二进制流out.writeObject(new StringBuffer(name).reverse());out.writeInt(age);}public void readExternal(java.io.ObjectInput in)throws IOException, ClassNotFoundException{// 将读取的字符串反转后赋给name Fieldthis.name = ((StringBuffer)in.readObject()).reverse().toString();this.age = in.readInt();}}
两种序列化机制对比:
对象序列化需要注意:
1. 对象的类名、Field都会被序列化; 方法、static Field、transient Field都不会被序列化。
2. 实现Serializable接口的类如果需要让某个Field不被序列化,则可以在该Field前添加transient私事符。
3. 保证序列化对象的Field类型也是可序列化的。
4. 反序列化对象时必须有序列化对象的class文件。
5. 当通过文件、网络来读取序列化后的对象时,必须按实际写入的顺序读取。
五、 版本
在对象进行序列化或者反序列化操作的时候,要考虑JDK版本问题。如果序列化的JDK版本和反序列化的版本不一致,则可能出现异常。
因此,可以在序列化操作中引入一个serialVersionUID的长了,通过此常量验证版本的一致性。
import java.io.Serializable ;public class Person implements Serializable{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 ;}};
- Java 对象序列化机制详解
- JAVA对象序列化机制
- JAVA对象序列化机制
- java序列化机制详解
- 详解java对象序列化
- Java对象序列化详解
- Java对象序列化详解
- Java对象序列化详解
- Java对象序列化机制摘录
- Java反射机制和对象序列化
- Java序列化机制详解
- Java重要机制——对象序列化
- java序列化机制
- Java序列化机制
- java序列化机制
- Java序列化机制
- Java 序列化机制
- Java序列化机制
- hightmaps 按地图显示统计量
- 微信公众平台Js API(WeixinApi)
- 用Linux命令行修图——缩放、编辑、转换格式——一切皆有可能
- 第十八章 18.1.3节练习 & 18.1.4节练习
- 异常
- Java 对象序列化机制详解
- CRC英文维基百科
- cookie和session的总结
- 【费用流】 HDU 1853 Cyclic Tour 费用流 完备匹配(裸题)
- RuntimeExcetion
- Could not create the view: An unexpected exception was thrown. 电脑突然断电,myeclipse非正常关闭,出现错误
- matlab中repmat函数的用法
- 开始学习Linux操作系统了
- 内部类