[Java开发之路](9)对象序列化与反序列化

1. 对象序列化

当你创建对象时,只要你需要,它会一直存在,但是程序终止时,无论何时它都不会继续存在。尽管这样做是非常有意义的,但是在某些情况下,如果程序不运行时扔能存在并且保存其信息,那将对我们非常有用。这样,在下次程序运行时,该对象将被重建并且拥有的信息与程序上次运行时它所拥有的信息相同。当然,我们也可以通过将信息写入文件或者数据库,但是如果能将一个对象声明为是"持久性"的,并为我们处理掉所有的细节,这将会显得十分方便。

Java的序列化是将那些实现了Serializable接口的对象转换为一个字节序列,并能够在以后需要的时候将这个字节序列完全恢复为原来的对象。我们可以在windows系统机器上创建一个对象,将其序列化,通过网络将它发送给一台运行Unix系统的计算机,然后在那里准确的重新组装恢复为原来的对象,所以说不必担心数据在不同机器上的表示会不同,也不必关心字节的顺序或者其他任何细节。这意味着序列化机制能自动弥补不同操作系统之间的差异。

对象序列化是为了支持两种特性。一是Java的远程方法调用(RMI),它使存活于其他计算机上的对象使用起来就像存活于本机上一样。当向远程对象发送消息时,需要通过对象序列化来传输参数和返回值。二是Java Beans,使用一个bean时,一般情况下是在设计阶段对它的状态信息进行配置。这种状态信息必须保存下来,并在程序启动时进行后期恢复(这种具体工作就是由对象序列化完成的)。

要序列化一个对象,首先要创建一个OutputStream对象,然后将其封装在一个ObjectOutputStream对象内。这时,只需调用writeObject()即可将对象序列化,并将其发送给 OutputStream(对象序列化是基于字节的,因要使用InputStream和OutputStream继承层次结构)。要反向进行该过程(将一个序列化还原为一个对象),需要将一个InputStream封装在ObjectInputStream内,然后调用readObject()。和往常一样,我们最后获得的是一个引用,它指向一个向上转型的Object,所以必须向下转型才能直接设置它们。


package com.qunar.bean;

 

import java.io.Serializable;

/**

* 学生实体类

* @author sjf0115

*

*/

public class Student implements Serializable{

 

/**

*

*/

private static final long serialVersionUID = 1L;

// 姓名

private String name;

// 学号

private String ID;

// 年龄

private int age;

// 学校

private String school;

 

/**

* @param name

* @param iD

* @param age

* @param school

*/

public Student(String name, String id, int age, String school) {

super();

this.name = name;

ID = id;

this.age = age;

this.school = school;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public String getID() {

return ID;

}

public void setID(String iD) {

ID = iD;

}

public int getAge() {

return age;

}

public void setAge(int age) {

this.age = age;

}

public String getSchool() {

return school;

}

public void setSchool(String school) {

this.school = school;

}

@Override

public String toString() {

return "姓名:" + name + " 学号:" + ID + " 年龄:" + age + " 学校:" + school;

}

}


package com.qunar.io;

 

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

 

import com.qunar.bean.Student;

 

public class SeriaCode {

 

public static void main(String[] args) {

// 对象序列化数据保存位置

String path = "D:\\seria.dat";

try {

// 创建FileOutputStream对象

FileOutputStream fileOutputStream = new FileOutputStream(path);

// 创建ObjectOutputStream对象

ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

// 序列化对象

Student student = new Student("xiaosi","130346",25,"西安电子科技大学");

// 进行对象序列化 Student对象要实现序列化接口

objectOutputStream.writeObject(student);

objectOutputStream.flush();

objectOutputStream.close();

// 创建FileInputStream对象

FileInputStream fileInputStream = new FileInputStream(path);

// 创建ObjectInputStream对象

ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

// 反序列化为对象

Student stu = (Student)objectInputStream.readObject();

objectInputStream.close();

System.out.println("Stu->"+stu);

} catch (FileNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

} catch (ClassNotFoundException e) {

e.printStackTrace();

}

}

}


2.寻找类

将一个对象从它的序列化状态中恢复出来,有哪些工作是必须的?举例来说,假如我们将一个对象序列化,并通过网络将其作为文件传送给另一台计算机,那么另一台计算机上的程序可以只利用该文件内容来还原这个对象吗?


package com.qunar.io.serial;

 

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.IOException;

import java.io.ObjectInputStream;

 

public class SerialCode2 {

 

public static void main(String[] args) {

try {

// 创建FileInputStream对象

FileInputStream fileInputStream = new FileInputStream("seria.dat");

// 创建ObjectInputStream对象

ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

// 反序列化为对象

Object object = objectInputStream.readObject();

objectInputStream.close();

System.out.println(object.getClass());

} catch (FileNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

} catch (ClassNotFoundException e) {

e.printStackTrace();

}

}

}

假设在运行上面程序之前,将Student类删除,在运行,会得到:

java.lang.ClassNotFoundException: com.qunar.io.Student

    at java.net.URLClassLoader$1.run(Unknown Source)

    at java.net.URLClassLoader$1.run(Unknown Source)

    at java.security.AccessController.doPrivileged(Native Method)

    at java.net.URLClassLoader.findClass(Unknown Source)

    at java.lang.ClassLoader.loadClass(Unknown Source)

    at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)

    at java.lang.ClassLoader.loadClass(Unknown Source)

    at java.lang.Class.forName0(Native Method)

    at java.lang.Class.forName(Unknown Source)

    at java.io.ObjectInputStream.resolveClass(Unknown Source)

    at java.io.ObjectInputStream.readNonProxyDesc(Unknown Source)

    at java.io.ObjectInputStream.readClassDesc(Unknown Source)

    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)

    at java.io.ObjectInputStream.readObject0(Unknown Source)

    at java.io.ObjectInputStream.readObject(Unknown Source)

    at com.qunar.io.serial.SerialCode2.main(SerialCode2.java:17)

打开文件和读取Student对象中内容都需要Student的class对象,而虚拟机找不到Student.calss,这样会导致抛出ClassNotFoundException异常。所以必须保证虚拟机能够找到相关的.class文件。

从这里就可以证明得到:不能只利用序列化字节数据文件来得到原先对象,还必须对应类的.class文件。

3. 序列化控制

默认的序列化机制并不难控制。然而,如果有特殊的需要那又该怎么办?例如,也许考虑特殊的安全问题,而且你不希望对象的某一部分被序列化;或者一个对象还原以后,某子对象需要重新创建,从而不必将该子对象序列化。

为了应对这些特殊的情况,可通过Externalizable接口(代替实现Serializable接口)来对序列化过程进行控制。这个Externalizable接口继承了Serializable接口,同时还增添了两个方法:writeExternal()和readExternal()。这两个方法会在序列化和反序列化还原过程中自动调用,以便执行一些特殊操作。


package com.qunar.io;

 

import java.io.Externalizable;

import java.io.IOException;

import java.io.ObjectInput;

import java.io.ObjectOutput;

 

public class Fruit implements Externalizable{

 

public Fruit(){

System.out.println("Fruit constructor...");

}

@Override

public void writeExternal(ObjectOutput out) throws IOException {

System.out.println("Fruit writeExternal...");

}

@Override

public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

System.out.println("Fruit readExternal...");

}

 

}


package com.qunar.io;

 

import java.io.Externalizable;

import java.io.IOException;

import java.io.ObjectInput;

import java.io.ObjectOutput;

 

public class Fruit2 implements Externalizable{

 

Fruit2(){

System.out.println("Fruit2 constuctor...");

}

@Override

public void writeExternal(ObjectOutput out) throws IOException {

System.out.println("Fruit writeExternal...");

}

@Override

public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

System.out.println("Fruit readExternal...");

}

 

}


package com.qunar.io;

 

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

 

public class FruitSerialCode {

 

public static void main(String[] args) {

try {

// 创建FileOutputStream对象

FileOutputStream fileOutputStream = new FileOutputStream("fruit.out");

// 创建ObjectOutputStream对象

ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

// 序列化对象

Fruit fruit = new Fruit();

Fruit2 fruit2 = new Fruit2();

// 进行对象序列化 Fruit对象要实现序列化接口

System.out.println("writeObject...");

objectOutputStream.writeObject(fruit);

objectOutputStream.writeObject(fruit2);

objectOutputStream.flush();

objectOutputStream.close();

// 创建FileInputStream对象

FileInputStream fileInputStream = new FileInputStream("fruit.out");

// 创建ObjectInputStream对象

ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

// 反序列化为对象

System.out.println("readFruit...");

fruit = (Fruit)objectInputStream.readObject();

System.out.println("readFruit2...");

fruit2 = (Fruit2)objectInputStream.readObject();

objectInputStream.close();

} catch (FileNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

} catch (ClassNotFoundException e) {

e.printStackTrace();

}

}

}

运行结果:

Fruit constructor...

Fruit2 constuctor...

writeObject...

Fruit writeExternal...

Fruit writeExternal...

readFruit...

Fruit constructor...

Fruit readExternal...

readFruit2...

java.io.InvalidClassException: com.qunar.io.Fruit2; no valid constructor

    at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(Unknown Source)

    at java.io.ObjectStreamClass.checkDeserialize(Unknown Source)

    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)

    at java.io.ObjectInputStream.readObject0(Unknown Source)

    at java.io.ObjectInputStream.readObject(Unknown Source)

    at com.qunar.io.FruitSerialCode.main(FruitSerialCode.java:36)

Fruit和Fruit2除了细微的差别之外,几乎完全一致。上例中没有反序列化后Fruit2对象,并且导致了一个异常。主要是Fruit的构造函数是public的,而Fruit2的构造函数却不是,这样就会在反序列时抛出异常。

反序列化fruit后,会调用Fruit的默认构造函数。这与反序列一个Serializable对象不同。对于一个Serializable对象,对象完全以它存储的二进制位为基础,而不用调用构造函数。而对于一个Externalizable对象,所有普通的构造函数都会被调用(包括在字段定义时的初始化),然后调用readExternal()。

注意:


      所有默认的构造函数都会被调用,才能使Externalizable对象产生正确的行为。

下面例子示范如何正确的序列化和反序列一个Externalizable对象:


package com.qunar.io;

 

import java.io.Externalizable;

import java.io.IOException;

import java.io.ObjectInput;

import java.io.ObjectOutput;

 

public class Fruit implements Externalizable{

private String name;

private int num;

// 必须有默认构造函数 反序列时使用

public Fruit(){

System.out.println("Fruit default constructor...");

}

/**

* @param name

* @param num

*/

public Fruit(String name, int num) {

System.out.println("Fruit constructor...");

this.name = name;

this.num = num;

}

 

@Override

public void writeExternal(ObjectOutput out) throws IOException {

System.out.println("Fruit writeExternal...");

// 必须做如下操作

out.writeObject(name);

out.writeInt(num);

}

@Override

public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

System.out.println("Fruit readExternal...");

// 必须做如下操作

name = (String)in.readObject();

num = in.readInt();

}

 

@Override

public String toString() {

return "name:" + name + " num:" + num;

}

}


package com.qunar.io;

 

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

 

public class FruitSerialCode {

 

public static void main(String[] args) {

try {

// 创建FileOutputStream对象

FileOutputStream fileOutputStream = new FileOutputStream("fruit.out");

// 创建ObjectOutputStream对象

ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

// 序列化对象

Fruit fruit = new Fruit("苹果",20);

// 进行对象序列化 Fruit对象要实现序列化接口

System.out.println("writeObject...");

objectOutputStream.writeObject(fruit);

objectOutputStream.flush();

objectOutputStream.close();

// 创建FileInputStream对象

FileInputStream fileInputStream = new FileInputStream("fruit.out");

// 创建ObjectInputStream对象

ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

// 反序列化为对象

System.out.println("readFruit...");

fruit = (Fruit)objectInputStream.readObject();

System.out.println("Fruit->[" + fruit + "]");

objectInputStream.close();

} catch (FileNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

} catch (ClassNotFoundException e) {

e.printStackTrace();

}

}

}

运行结果:

Fruit constructor...

writeObject...

Fruit writeExternal...

readFruit...

Fruit default constructor...

Fruit readExternal...

Fruit->[name:苹果  num:20]

可以看出,name和num只在第二个构造函数中初始化,而不是在默认的构造函数中初始化。所以说,假如不在readExternal初始化name和num,name就会为null,age就会为0,如果注释掉代码中"必须做如下操作"之后的代码,反序列化之后的对象的name为null,num为0。

Fruit constructor...

writeObject...

Fruit writeExternal...

readFruit...

Fruit default constructor...

Fruit readExternal...

Fruit->[name:null  num:0]

如果我们从一个Externalizable对象继承,通常需要调用基类版本的writeExternal()和readExternal()来为基类组件提供恰当的序列化和反序列化功能。因此,为了正常运行,我们不仅需要在writeExternal()方法(没有任何默认行为来为Externalizable对象写入任何成员对象)中将来自对象的重要信息写入,还必须在readExternal()方法中恢复数据。

4. transient关键字

但我们对序列化进行控制时,可能某个特定属性不想让Java序列化机制自动保存与恢复。如果属性表示的是我们不希望将其序列化的敏感信息(如密码),就会遇到这种问题。即使对象中的这些信息是private属性,一经序列化处理,人们就可以通过读取文件或者拦截网络传输的方式访问到它。

(1)防止对象敏感信息被序列化,可以将类实现为Externalizable,像前面一样。这样就没有任何东西可以自动序列化,并且可以在writeExternal()内部只对所需部分进行显示的序列化。

(2)如果我们操作的是Serializable对象,那么所有序列化操作都会自动进行。为了进行控制,使用transient关键字关闭序列化操作,它的意思"不用麻烦你序列化或者反序列化数据,我自己会处理的"。

假设我们用Login类保存某个特定的登录会话信息。登录的合法性得到检验之后,我们想把数据保存下来,但不包括密码。


package com.qunar.io;

 

import java.io.Serializable;

import java.util.Date;

 

public class Login implements Serializable{

/**

*

*/

private static final long serialVersionUID = 1L;

private Date date = new Date();

private String userName;

// 防止被序列化transient

private transient String password;

/**

* @param date

* @param userName

* @param password

*/

public Login(String userName, String password) {

super();

this.userName = userName;

this.password = password;

}

 

@Override

public String toString() {

return "Date:" + date + " UserName:" + userName + " Password:" + password;

}

}


package com.qunar.io;

 

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

 

public class LoginSerialCode {

 

public static void main(String[] args) {

try {

// 创建FileOutputStream对象

FileOutputStream fileOutputStream = new FileOutputStream("login.out");

// 创建ObjectOutputStream对象

ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

// 序列化对象

Login login = new Login("xiaosi", "123");

// 进行对象序列化 Fruit对象要实现序列化接口

System.out.println("writeObject...");

objectOutputStream.writeObject(login);

objectOutputStream.flush();

objectOutputStream.close();

// 创建FileInputStream对象

FileInputStream fileInputStream = new FileInputStream("login.out");

// 创建ObjectInputStream对象

ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

// 反序列化为对象

System.out.println("readFruit...");

login = (Login)objectInputStream.readObject();

System.out.println("LoginInfo->[" + login + "]");

objectInputStream.close();

} catch (FileNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

} catch (ClassNotFoundException e) {

e.printStackTrace();

}

}

}

运行结果:

writeObject...

readFruit...

LoginInfo->[Date:Thu Dec 31 00:16:13 CST 2015  UserName:xiaosi Password:null]

date和useName(不是transient),所以它们会自动被序列化,而password是transient的,所以不会被自动保存到磁盘;另外自动序列化机制也不会去恢复它,当对象恢复时,password就会变成null。同时我们发现date字段被存储在磁盘并且从磁盘上恢复出来,而不是重新生成。

(3)Externalizable的替代方法

如果你不使用Externalizable,我们还有一种方法。我们可以实现Serializable接口,并添加writeObject()和readObject()方法。这样一旦进行序列化和反序列化,就会自动的分别调用这两个方法,来代替默认的序列化机制。


package com.qunar.io;

 

import java.io.IOException;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

import java.io.Serializable;

import java.util.Date;

 

public class Login implements Serializable{

/**

*

*/

private static final long serialVersionUID = 1L;

private Date date = new Date();

private String userName;

// 防止被序列化transient

private transient String password;

/**

* @param date

* @param userName

* @param password

*/

public Login(String userName, String password) {

super();

this.userName = userName;

this.password = password;

}

// 必须有

private void writeObject(ObjectOutputStream stream) throws IOException{

// 默认的序列化

stream.defaultWriteObject();

// 手动完成序列化

stream.writeObject(password);

}

// 必须有

private void readObject(ObjectInputStream stream) throws ClassNotFoundException, IOException{

// 默认的反序列化

stream.defaultReadObject();

// 手动完成反序列化

password = (String)stream.readObject();

}

@Override

public String toString() {

return "Date:" + date + " UserName:" + userName + " Password:" + password;

}

}


package com.qunar.io;

 

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

 

public class LoginSerialCode {

 

public static void main(String[] args) {

try {

// 创建FileOutputStream对象

FileOutputStream fileOutputStream = new FileOutputStream("login.out");

// 创建ObjectOutputStream对象

ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

// 序列化对象

Login login = new Login("xiaosi", "123");

// 进行对象序列化 Fruit对象要实现序列化接口

System.out.println("writeObject...");

objectOutputStream.writeObject(login);

objectOutputStream.flush();

objectOutputStream.close();

// 创建FileInputStream对象

FileInputStream fileInputStream = new FileInputStream("login.out");

// 创建ObjectInputStream对象

ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

// 反序列化为对象

System.out.println("readFruit...");

login = (Login)objectInputStream.readObject();

System.out.println("LoginInfo->[" + login + "]");

objectInputStream.close();

} catch (FileNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

} catch (ClassNotFoundException e) {

e.printStackTrace();

}

}

}

运行结果:

writeObject...

readFruit...

LoginInfo->[Date:Fri Jan 01 15:57:53 CST 2016  UserName:xiaosi Password:123]

相比上面实验,password恢复出来了。在这个例子中,password字段是transient字段,用来证明非transient字段是由defaultWriteObject()方法保存,而transient字段是必须在程序中明确保存和恢复。

5. 使用"持久化"

一个诱人的使用序列化技术的想法:存储程序的一些状态,以便我们随后可以很容易的将程序恢复到当前的状态。但是在我们能够这样做之前,必须回答几个问题。如果我们将两个对象(都具有指向第三个对象的引用)进行序列化,会发生什么状况?当我们从它们的序列化状态恢复这两个对象时,第三个对象会只出现一次吗?


package com.qunar.io;

 

import java.io.Serializable;

 

public class House implements Serializable{

 

/**

*

*/

private static final long serialVersionUID = 1L;

private String name;

private String location;

/**

* @param name

* @param location

*/

public House(String name, String location) {

super();

this.name = name;

this.location = location;

}

}


package com.qunar.io;

 

import java.io.Serializable;

 

public class Animal implements Serializable{

 

/**

*

*/

private static final long serialVersionUID = 1L;

private String name;

private House house;

/**

* 构造函数

* @param name

* @param house

*/

public Animal(String name, House house) {

super();

this.name = name;

this.house = house;

}

 

@Override

public String toString() {

return name + " " + house;

}

}


package com.qunar.io;

 

import java.io.ByteArrayInputStream;

import java.io.ByteArrayOutputStream;

import java.io.FileNotFoundException;

import java.io.IOException;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

import java.util.ArrayList;

import java.util.List;

 

 

public class AnimalSerialCode {

 

@SuppressWarnings("unchecked")

public static void main(String[] args) {

House house = new House("水立方","北京海淀区");

List<Animal> animals = new ArrayList<Animal>();

animals.add(new Animal("狗", house));

animals.add(new Animal("鸡", house));

animals.add(new Animal("羊", house));

System.out.println("Animals->" + animals);

try {

ByteArrayOutputStream buf = new ByteArrayOutputStream();

ObjectOutputStream objectOutputStream = new ObjectOutputStream(buf);

// 序列化

objectOutputStream.writeObject(animals);

objectOutputStream.writeObject(animals);

ByteArrayOutputStream buf2 = new ByteArrayOutputStream();

ObjectOutputStream objectOutputStream2 = new ObjectOutputStream(buf2);

// 序列化

objectOutputStream2.writeObject(animals);

ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()));

List<Animal> ani1 = (List<Animal>)objectInputStream.readObject();

List<Animal> ani2 = (List<Animal>)objectInputStream.readObject();

ObjectInputStream objectInputStream2 = new ObjectInputStream(new ByteArrayInputStream(buf2.toByteArray()));

List<Animal> ani3 = (List<Animal>)objectInputStream2.readObject();

System.out.println("Animals1->"+ani1);

System.out.println("Animals2->"+ani2);

System.out.println("Animals3->"+ani3);

} catch (FileNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

} catch (ClassNotFoundException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

运行结果:

Animals->[狗   com.qunar.io.House@1b15692, 鸡   com.qunar.io.House@1b15692, 羊   com.qunar.io.House@1b15692]

Animals1->[狗   com.qunar.io.House@1aaf0b3, 鸡   com.qunar.io.House@1aaf0b3, 羊   com.qunar.io.House@1aaf0b3]

Animals2->[狗   com.qunar.io.House@1aaf0b3, 鸡   com.qunar.io.House@1aaf0b3, 羊   com.qunar.io.House@1aaf0b3]

Animals3->[狗   com.qunar.io.House@1a082e2, 鸡   com.qunar.io.House@1a082e2, 羊   com.qunar.io.House@1a082e2]

我们可以通过字节数组来使用对象序列化,从而实现任何可Serializable对象的"深度复制"(意味着复制的是整个对象网,而不仅仅是基本对象及其引用)。在这个例子中,Animal对象包含House类型字段。我们创建Animals列表并将其两次序列化,分别送至不同的流。当期被反序列化还原被打印时,我们可以看到:每次运行时对象将会处在不同的内存地址。

当然我们期望这些反序列还原后的对象地址与原来的对象地址不同,但是Animals1和Animals2却出现了相同的地址。当恢复Animals3时,系统无法知道另一个流内的对象是第一个流内对象额别名,因此会产生完全不同的对象网。

只要将任何对象序列化到单一流中,就可以会付出与我们写出时一样的对象网,并且没有任何意外重复复制的对象。如果想保存系统状态,最安全的做法就是将其作为"原子"操作进行序列化。

时间: 2024-10-31 13:38:45

[Java开发之路](9)对象序列化与反序列化的相关文章

详解Java中对象序列化与反序列化_java

        序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程.一般将一个对象存储至一个储存媒介,例如档案或是记亿体缓冲等.在网络传输过程中,可以是字节或是XML等格式.而字节的或XML编码格式可以还原完全相等的对象.这个相反的过程又称为反序列化.Java对象的序列化与反序列化 在Java中,我们可以通过多种方式来创建对象,并且只要对象没有被回收我们都可以复用该对象.但是,我们创建出来的这些Java对象都是存在于JVM的堆内存中的.只有JVM处于运行状态

Java中对象序列化与反序列化详解_java

本文实例讲述了Java中对象序列化与反序列化.分享给大家供大家参考.具体如下: 一.简介 对象序列化(Serializable)是指将对象转换为字节序列的过程,而反序列化则是根据字节序列恢复对象的过程. 序列化一般用于以下场景: 1.永久性保存对象,保存对象的字节序列到本地文件中: 2.通过序列化对象在网络中传递对象: 3.通过序列化在进程间传递对象. 对象所属的类必须实现Serializable或是Externalizable接口才能被序列化.对实现了Serializable接口的类,其序列化

C#对象序列化和反序列化

C#对象序列化和反序列化,如下代码示例: using System;  using System.Text;  using System.Collections.Generic;  using System.IO;  using System.Runtime.Serialization.Formatters.Binary;    class SerializableOperate  {      private static void ObjectSerializable(object obj,

[Java开发之路](10)DOM解析XML文档

对象序列化的一个重要限制是它只是Java的解决方案:只有Java程序才能反序列化这种对象.一种更具操作性的解决方案是将数据转化为XML格式,这可以使其被各种各样的平台和语言使用. 1. 简介 DOM 是用与平台和语言无关的方式表示XML文档的官方 W3C 标准.DOM 是以层次结构组织的节点或信息片断的集合.这个层次结构允许开发人员在树中寻找特定信息.分析该结构通常需要加载整个文档和构造层次结构, 然后才能做任何工作. 由于它是基于信息层次的,因而 DOM 被认为是基于树或基于对象的.DOM 以

java与c++中的对象序列化 分析。

有时候我们在开发项目的时候,对于数据的保存 我们通常是直接将数据保存到磁盘上面 ,但是这样操作起来非常的不方便 ,尤其是在大型的项目开发中.  对象的序列化 可以将对象以数据的形式存储到文件中:反之我们也可以从文件中加载一个对象 ,也叫反序列化. 对于多个数据的操作封装在一起,写入文件,在反序列化的时候我们只需要读取这个对象就可以了  ,我么就不必关心内部数据和方法了 .因为所有数据都包含在对象中. 对象的序列化规则 不同的语言 不一样 ,java中有java的规则 ,c++中有自己的规则 .可

[Java开发之路](5)异常详解

1. 异常分类 在Java程序设计语言中,异常对象都是派生于Throwable类的一个实例.其是如果Java中的异常类不能满足需求,用户可以创建自己的异常类. 下图是Java异常层次结构的一个简化示意图. 从图上可以看出,所有的异常都是继承于Throwable类,但是在下一层立即分解为两个分支:Error和Exception. (1)Error Error描述了Java运行时系统的内部错误和资源耗尽错误.应用程序不应该抛出这种类型的错误,如果出现了这样的内部错误,除了通告用户,并尽力使程序安全的

[Java开发之路](2)Java字符串

1.什么是Java中的字符串 在 Java 中,字符串被作为 String 类型的对象处理. String 类位于 java.lang 包中.默认情况下,该包被自动导入所有的程序. 创建 String 对象的方法: package com.qunar.test;   public class HelloWorld { public static void main(String[] args){ // 创建一个字符串对象 String str = "xiaosi"; // 创建一个空字

java开发之路的困惑

问题描述 已经毕业快一年了,算上大四也已经有了2年左右的开发经验.现在的公司是一个比较正规软件公司,名字就不说了,每个项目大概在400万左右,目前这个项目已经验收.由于这个项目的特殊性还有比较多的开发需要完成,加上项目组上3个项目经理离职,在现场只有我一个人,需要同时兼任项目经理的职责,目前公司给我的待遇算上全部的补贴在6500左右.在开发上,能够使用公司的框架正常开发,现在的开发全部都是一些业务逻辑上的开发,总感觉没有学习到什么技术,除了写sql语句熟练了.由于在大学基础没有学好,总感觉自己的

Python pickle类库介绍(对象序列化和反序列化)_python

一.pickle pickle模块用来实现python对象的序列化和反序列化.通常地pickle将python对象序列化为二进制流或文件.   python对象与文件之间的序列化和反序列化: 复制代码 代码如下: pickle.dump() pickle.load() 如果要实现python对象和字符串间的序列化和反序列化,则使用: 复制代码 代码如下: pickle.dumps() pickle.loads()   可以被序列化的类型有: * None,True 和 False; * 整数,浮