[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时,系统无法知道另一个流内的对象是第一个流内对象额别名,因此会产生完全不同的对象网。

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




目录
相关文章
|
2天前
|
安全 Java 编译器
Java对象一定分配在堆上吗?
本文探讨了Java对象的内存分配问题,重点介绍了JVM的逃逸分析技术及其优化策略。逃逸分析能判断对象是否会在作用域外被访问,从而决定对象是否需要分配到堆上。文章详细讲解了栈上分配、标量替换和同步消除三种优化策略,并通过示例代码说明了这些技术的应用场景。
Java对象一定分配在堆上吗?
|
1天前
|
存储 安全 Java
🌟Java零基础-反序列化:从入门到精通
【10月更文挑战第21天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
12 5
|
1天前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
6 2
|
5天前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
1天前
|
监控 Java 数据库连接
在Java开发中,数据库连接管理是关键问题之一
在Java开发中,数据库连接管理是关键问题之一。本文介绍了连接池技术如何通过预创建和管理数据库连接,提高数据库操作的性能和稳定性,减少资源消耗,并简化连接管理。通过示例代码展示了HikariCP连接池的实际应用。
6 1
WK
|
1天前
|
安全 Java 编译器
C++和Java哪个更适合开发web网站
在Web开发领域,C++和Java各具优势。C++以其高性能、低级控制和跨平台性著称,适用于需要高吞吐量和低延迟的场景,如实时交易系统和在线游戏服务器。Java则凭借其跨平台性、丰富的生态系统和强大的安全性,广泛应用于企业级Web开发,如企业管理系统和电子商务平台。选择时需根据项目需求和技术储备综合考虑。
WK
7 0
|
5天前
|
存储 缓存 NoSQL
一篇搞懂!Java对象序列化与反序列化的底层逻辑
本文介绍了Java中的序列化与反序列化,包括基本概念、应用场景、实现方式及注意事项。序列化是将对象转换为字节流,便于存储和传输;反序列化则是将字节流还原为对象。文中详细讲解了实现序列化的步骤,以及常见的反序列化失败原因和最佳实践。通过实例和代码示例,帮助读者更好地理解和应用这一重要技术。
6 0
|
3月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
18天前
|
存储 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第9天】在Java的世界里,对象序列化是连接数据持久化与网络通信的桥梁。本文将深入探讨Java对象序列化的机制、实践方法及反序列化过程,通过代码示例揭示其背后的原理。从基础概念到高级应用,我们将一步步揭开序列化技术的神秘面纱,让读者能够掌握这一强大工具,以应对数据存储和传输的挑战。
|
24天前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第3天】在Java编程的世界里,对象序列化与反序列化是实现数据持久化和网络传输的关键技术。本文将深入探讨Java序列化的原理、应用场景以及如何通过代码示例实现对象的序列化与反序列化过程。从基础概念到实践操作,我们将一步步揭示这一技术的魅力所在。