java对象序列化小结

百度百科上介绍序列化是这样的:

序列化 (Serialization):

对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。

序列化使其他代码可以查看或修改那些不序列化便无法访问的对象实例数据。确切地说,代码执行序列化需要特殊的权限:即指定了 SerializationFormatter 标志的 SecurityPermission。在默认策略下,通过 Internet
下载的代码或 Intranet 代码不会授予该权限;只有本地计算机上的代码才被授予该权限。

通常,对象实例的所有字段都会被序列化,这意味着数据会被表示为实例的序列化数据。这样,能够解释该格式的代码有可能能够确定这些数据的值,而不依赖于该成员的可访问性。类似地,反序列化从序列化的表示形式中提取数据,并直接设置对象状态,这也与可访问性规则无关。

对于任何可能包含重要的安全性数据的对象,如果可能,应该使该对象不可序列化。如果它必须为可序列化的,请尝试生成特定字段来保存不可序列化的重要数据。如果无法实现这一点,则应注意该数据会被公开给任何拥有序列化权限的代码,并确保不让任何恶意代码获得该权限。

序列化 (Serialization)目的:

1、以某种存储形式使自定义对象持久化

2、将对象从一个地方传递到另一个地方。

3、使程序更具维护性。

这里我们只讨论其中一个,就是把对象保存到文件的问题.假如我们对一个对象进行了多次修改,并且把该对象内容都保存到一个文件中,当我们再次读取该文件里对象信息的时候,JVM是如何识别的呢?或者能不能识别呢?

我们下面以一个例子来说明:假如我写了一个User类作为持久化保存的类,该类如下:

[java] view
plain
copy

  1. class User{
  2. @Override
  3. public String toString() {
  4. return "name:"+this.name+",age:"+this.age;
  5. }
  6. private String name;
  7. private int age;
  8. public String getName() {
  9. return this.name;
  10. }
  11. public void setName(String name) {
  12. this.name = name;
  13. }
  14. public int getAge() {
  15. return this.age;
  16. }
  17. public void setAge(int age) {
  18. this.age = age;
  19. }
  20. public User(String name, int age) {
  21. this.name = name;
  22. this.age = age;
  23. }
  24. }

该类只是保存了用户的姓名和年龄,注意此时该类并没有实现Serializable接口,我们尝试一下实例化一个该类的对象并保存到一个名称为felay.dat文件里,然后再从该文件中读取出来会出现什么效果.

这个时候我们编写一个SerializableDemo类,该类是用来保存和读取指定文件的对象信息的类.如下:

[java] view
plain
copy

  1. public class SeriazableDemo{
  2. ObjectInputStream inputStream = null;
  3. ObjectOutputStream outStream = null ;
  4. //保存对象
  5. public void saveObject(Object obj,String fileName) throws Exception{
  6. this.outStream = new ObjectOutputStream(new FileOutputStream(fileName));
  7. this.outStream.writeObject(obj);
  8. this.outStream.close();
  9. }
  10. //读取对象
  11. public User readObject(String fileName) throws Exception{
  12. this.inputStream = new ObjectInputStream(new FileInputStream(fileName));
  13. User user = (User) this.inputStream.readObject();
  14. this.inputStream.close();
  15. return user;
  16. }
  17. public static void main(String[] args) throws Exception {
  18. User user = new User("felay",22);
  19. System.out.println("保存前的用户信息:"+user);
  20. String fileName = "src/res/felay.dat";
  21. SeriazableDemo sb = new SeriazableDemo();
  22. //保存用户信息
  23. new SeriazableDemo().saveObject(user, fileName);
  24. //读取用户信息
  25. User theUser = sb.readObject(fileName);
  26. System.out.println("保存后再次读取的用户信息:"+theUser);
  27. }
  28. }

上述代码中我们尝试了把一个实例化的User对象保存到指定文件,并从中读取,运行程序我们发现出现一个异常.:

这个异常很清晰明了,就是告诉我们要读取的cn.felay.io.User类没有进行实例化,那么将User类实现Serizale接口试试,我们再次读取一次试试;

这个时候User类就修改如下:

[java] view
plain
copy

  1. <span style="color:#333333;">class User </span><span style="color:#ff0000;"><strong>implements Serializable</strong></span><span style="color:#333333;">{
  2. public String toString() {
  3. return "name:"+this.name+",age:"+this.age;
  4. }
  5. private String name;
  6. private int age;
  7. public String getName() {
  8. return this.name;
  9. }
  10. public void setName(String name) {
  11. this.name = name;
  12. }
  13. public int getAge() {
  14. return this.age;
  15. }
  16. public void setAge(int age) {
  17. this.age = age;
  18. }
  19. public User(String name, int age) {
  20. this.name = name;
  21. this.age = age;
  22. }
  23. }</span>

这个时候,注意上述的红色代码,我们实现了Serizable接口了,说明该 类可以序列化了.我们运行程序试试:结果很好:

我们能够顺利的读取到该实例的信息.

下面我们对这两个类进行修改,这里需要细心的观察:(这个例子需要从头开始)

还是创建一个User类,为了简单,把类减少了必要的代码,代码如下:

User.java

[java] view
plain
copy

  1. class User implements Serializable{
  2. private String name;
  3. public String getName() {
  4. return this.name;
  5. }
  6. public void setName(String name) {
  7. this.name = name;
  8. }
  9. public User(String name, int age) {
  10. this.name = name;
  11. }
  12. }

注意,这里没有覆写toString方法,然后再创建SeriazableDemo类,该类只有两个方法,保存对象,读取对象,代码如下:

SeriazableDemo.java

[java] view
plain
copy

  1. public class SeriazableDemo{
  2. ObjectInputStream inputStream = null;
  3. ObjectOutputStream outStream = null ;
  4. public void saveObject(Object obj,String fileName) throws Exception{
  5. this.outStream = new ObjectOutputStream(new FileOutputStream(fileName));
  6. this.outStream.writeObject(obj);
  7. this.outStream.close();
  8. }
  9. public User readObject(String fileName) throws Exception{
  10. this.inputStream = new ObjectInputStream(new FileInputStream(fileName));
  11. User user = (User) this.inputStream.readObject();
  12. this.inputStream.close();
  13. return user;
  14. }

这里我们没有像开始那样把测试代码进入,我们这次新建一个测试文件test.java

test.java

[java] view
plain
copy

  1. package cn.felay.io;
  2. public class Test {
  3. public static void main(String[] args) throws Exception {
  4. SeriazableDemo sd = new SeriazableDemo();
  5. String fileName = "src/res/felay.dat";
  6. User user1 = new User("felay1");
  7. sd.saveObject(user1, fileName);
  8. System.out.println("保存信息:"+user1);
  9. User theUser1 = sd.readObject(fileName);
  10. System.out.println("读取信息:"+theUser1);
  11. }
  12. }

测试结果很好,顺利读取出我们需要的信息,但是随之而来的问题来了,假如我们不想看到这个干巴巴的类的信息,我们想打印出能够更友好的信息,因此,此时我们需要对User类进行修改:

User.java(修改后)

[java] view
plain
copy

  1. class User implements Serializable{
  2. private String name;
  3. public String getName() {
  4. return this.name;
  5. }
  6. public void setName(String name) {
  7. this.name = name;
  8. }
  9. public User(String name) {
  10. this.name = name;
  11. }
  12. @Override
  13. public String toString(){
  14. return "name="+this.name;
  15. }
  16. }

修改后的代码覆写了toString()方法,此时,我们再次从felay.dat中读取对象的时候,就会出现下面的结果:

在运行测试代码前,我们需要注释掉这句代码:sd.saveObject(user1, fileName);否则,程序读取的就是新写入的.

这里显示的不全,主要异常信息为:

cn.felay.io.User; local class incompatible: stream classdesc serialVersionUID = -7549550075604522024,local
class serialVersionUID = -8650885904214976359

很意外吧,错误信息很明显,就是这个类的版本号不一致导致的,因为JVM在加载类的时候,该类的磁盘位置没有发现变化,因此JVM加载该类的时候会说这两个类是统一各类,因此无需再次加载,但是我们在读取实例对象信息的时候,JVM又发现读取的两个类不是一个类(其实是一个类,只是序列号或版本号不同),那么我们如何想再次读取该实例的信息呢?此时我们只需要在修改类的时候,将没有修改前的序列号ID也加入到修改后的类中代码中,就证明了这个类跟修改前的类是一个版本号,JVM在编译的时候就知道它们是同一个类只是被修改过.

修改后的代码:

User.java

[java] view
plain
copy

  1. class User implements Serializable{
  2. private static final long serialVersionUID = -7549550075604522024L;
  3. private String name;
  4. public String getName() {
  5. return this.name;
  6. }
  7. public void setName(String name) {
  8. this.name = name;
  9. }
  10. public User(String name) {
  11. this.name = name;
  12. }
  13. @Override
  14. public String toString(){
  15. return "name="+this.name;
  16. }
  17. }

然后再次运行测试代码,结果如下:

很好,这里再次读取修改前的序列化的对象的时候,就能够读取到了.

下面是对Java序列化的一些总结:(部分转载)

1.序列化是什么:

序列化就是将一个对象的状态(各个属性量)保存起来,然后在适当的时候再获得。

序列化分为两大部分:序列化和反序列化。序列化是这个过程的第一部分,将数据分解成字节流,以便存储在文件中或在网络上传输。反序列化就是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节表示,有时还要恢复数据。恢复数据要求有恢复数据的对象实例

2.序列化的什么特点:

如果某个类能够被序列化,其子类也可以被序列化。声明为static和transient类型的成员数据不能被序列化。因为static代表类的状态, transient代表对象的临时数据。

3.什么时候使用序列化:

一:对象序列化可以实现分布式对象。主要应用例如:RMI要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。

二:java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的"深复制",即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。

=======================

可以看看接口java.io.serializable的中文解释:

Serializable

public interface Serializable

类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。

要允许不可序列化类的子类型序列化,可以假定该子类型负责保存和还原超类型的公用 (public)、受保护的 (protected) 和(如果可访问)包 (package) 字段的状态。仅在子类型扩展的类有一个可访问的无参数构造方法来初始化该类的状态时,才可以假定子类型有此责任。如果不是这种情况,则声明一个类为可序列化类是错误的。该错误将在运行时检测到。

在反序列化过程中,将使用该类的公用或受保护的无参数构造方法初始化不可序列化类的字段。可序列化的子类必须能够访问无参数的构造方法。可序列化子类的字段将从该流中还原。

当遍历一个图形时,可能会遇到不支持可序列化接口的对象。在此情况下,将抛出 NotSerializableException,并将标识不可序列化对象的类。

在序列化和反序列化过程中需要特殊处理的类必须使用下列准确签名来实现特殊方法:

private void writeObject(java.io.ObjectOutputStream out)

throws IOException

private void readObject(java.io.ObjectInputStream in)

throws IOException, ClassNotFoundException;

writeObject 方法负责写入特定类的对象的状态,以便相应的 readObject 方法可以还原它。通过调用 out.defaultWriteObject 可以调用保存 Object 的字段的默认机制。该方法本身不需要涉及属于其超类或子类的状态。状态是通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream 来保存的。

readObject 方法负责从流中读取并还原类字段。它可以调用 in.defaultReadObject 来调用默认机制,以还原对象的非静态和非瞬态字段。defaultReadObject 方法使用流中的信息来分配流中通过当前对象中相应命名字段保存的对象的字段。这用于处理类发展后需要添加新字段的情形。该方法本身不需要涉及属于其超类或子类的状态。状态是通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream 来保存的。

将对象写入流时需要指定要使用的替代对象的可序列化类,应使用准确的签名来实现此特殊方法:

ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;

此 writeReplace 方法将由序列化调用,前提是如果此方法存在,而且它可以通过被序列化对象的类中定义的一个方法访问。因此,该方法可以拥有私有 (private)、受保护的 (protected) 和包私有 (package-private) 访问。子类对此方法的访问遵循 java 访问规则。

在从流中读取类的一个实例时需要指定替代的类应使用的准确签名来实现此特殊方法。

ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

此 readResolve 方法遵循与 writeReplace 相同的调用规则和访问规则。

序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException。可序列化类可以通过声明名为 "serialVersionUID" 的字段(该字段必须是静态 (static)、最终 (final) 的 long 型字段)显式声明其自己的
serialVersionUID:

ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值,如“Java(TM) 对象序列化规范”中所述。不过,强烈建议 所有可序列化类都显式声明 serialVersionUID 值,原因计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。因此,为保证 serialVersionUID
值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。还强烈建议使用 private 修改器显示声明 serialVersionUID(如果可能),原因是这种声明仅应用于立即声明类 -- serialVersionUID 字段作为继承成员没有用处。

java对象序列化小结,布布扣,bubuko.com

时间: 2024-12-26 12:43:07

java对象序列化小结的相关文章

Java对象序列化

深入理解Java对象序列化:http://developer.51cto.com/art/201202/317181.html

Java对象序列化和反序列化

Java对象序列化和反序列化 在Java中,我们如果要保存一个对象的瞬时状态值,以便在下次使用时能够得到这些值,或者持久化对象,或者使用RMI(远程方法调用),或在网络中传递对象时,此时我们就需要将对象序列化,实现序列化,我们只要实现Serializable接口,该接口是一个标记接口(Tag interface),即里面没有方法,其主要作用就是告诉JVM该类的对象可以进行序列化. 一般来说,很多类的对象都实现了Serializable接口,但是,有些对象是不能进行序列化的,比如与数据库相关的连接

java 对象序列化

上代码 /** * Person.java */ import java.io.*; public class Person implements Serializable { /** * */ private static final long serialVersionUID = 1L; private String name; private int age; public Person(String str,int num) { name=str; age=num; } public S

java 对象序列化与反序列化

Java序列化与反序列化是什么? 为什么需要序列化与反序列化? 如何实现Java序列化与反序列化? 本文围绕这些问题进行了探讨. 1.Java序列化与反序列化  Java序列化是指把Java对象转换为字节序列的过程: Java反序列化是指把字节序列恢复为Java对象的过程. 2.为什么需要序列化与反序列化 我们知道,当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本.图片.音频.视频等, 而这些数据都会以二进制序列的形式在网络上传送.那么当两个Java进程进行通信时,能否实现进程间的

Java对象序列化与反序列化

Java对象序列化与反序列化 对象序列化的目标是将对象保存在磁盘中或者在网络中进行传输.实现的机制是允许将对象转为与平台无关的二进制流. java中对象的序列化机制是将允许对象转为字节序列.这些字节序列可以使Java对象脱离程序存在,从而可以保存在磁盘上,也可以在网络间传输. 对象的序列化是将一个Java对象写入IO流:与此对应的,反序列化则是从IO流中恢复一个Java对象. 实现序列化 如果要将一个java对象序列化,那么对象的类需要是可序列化的.要让类可序列化,那么这个类需要实现如下两个接口

【java】理解java对象序列化

关于Java序列化的文章早已是汗牛充栋了,本文是对我个人过往学习,理解及应用Java序列化的一个总结.此文内容涉及Java序列化的基本原理,以及多种方法对序列化形式进行定制.在撰写本文时,既参考了Thinking in Java, Effective Java,JavaWorld,developerWorks中的相关文章和其它网络资料,也加入了自己的实践经验与理解,文.码并茂,希望对大家有所帮助.(2012.02.14最后更新) 1. 什么是Java对象序列化 Java平台允许我们在内存中创建可

java 对象序列化使用

java序列化的使用很简单的用法就是,在类定义时实现java.io.serializable即可.扩展这个接口不需要实现它的任何方法,Java会自动序列化.需要注意的,被标为transient和static的属性是不会被java自动序列化的. 在面对比较复杂的对象时,比如存在双向链接关系的对象时,如果实现该接口,java在序列化时会栈溢出,如果对象比较大,会出现堆溢出.那么这时候就需要我们自己动手写一个序列化的方法. 第一种自定义序列化方式: 1.类定义扩展java.io.serializabl

每天进步一点点-实例为导学-一个java对象序列化的例子

序列化和反序列化例子 如果我们想要序列化一个对象, (对象 转 序列)首先要创建某些OutputStream(如FileOutputStream.ByteArrayOutputStream等),然后将这些OutputStream封装在一个ObjectOutputStream中.这时候,只需要调用writeObject()方法就可以将对象序列化,并将其发送给OutputStream(记住:对象的序列化是基于字节(1字节8位)的,不能使用Reader和Writer等基于字符的层次结构).而反序列的过

(转)关于 Java 对象序列化您不知道的 5 件事

关于 Java 对象序列化您不知道的 5 件事 转自:http://developer.51cto.com/art/201506/479979.htm 数年前,当和一个软件团队一起用 Java 语言编写一个应用程序时,我体会到比一般程序员多知道一点关于 Java 对象序列化的知识所带来的好处. 关于本系列 您觉得自己懂 Java 编程?事实上,大多数程序员对于 Java 平台都是浅尝则止,只学习了足以完成手头上任务的知识而已.在本 系列 中,Ted Neward 深入挖掘 Java 平台的核心功