JAVA 对象序列化——Serializable

1、序列化是干什么的?
       简单说就是为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来。虽然你可以用你自己的各种各样的方法来保存object states,但是Java给你提供一种应该比你自己好的保存对象状态的机制,那就是序列化。

2、什么情况下需要序列化   
    a)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
    b)当你想用套接字在网络上传送对象的时候;
    c)当你想通过RMI传输对象的时候;

3、当对一个对象实现序列化时,究竟发生了什么?
    在没有序列化前,每个保存在堆(Heap)中的对象都有相应的状态(state),即实例变量(instance ariable)比如:

java 代码

  1. Foo  myFoo = new Foo();
  2. myFoo .setWidth(37);
  3. myFoo.setHeight(70);

当 通过下面的代码序列化之后,MyFoo对象中的width和Height实例变量的值(37,70)都被保存到foo.ser文件中,这样以后又可以把它 从文件中读出来,重新在堆中创建原来的对象。当然保存时候不仅仅是保存对象的实例变量的值,JVM还要保存一些小量信息,比如类的类型等以便恢复原来的对 象。

java 代码

  1. FileOutputStream fs = new FileOutputStream("foo.ser");
  2. ObjectOutputStream os = new ObjectOutputStream(fs);
  3. os.writeObject(myFoo);

4、实现序列化(保存到一个文件)的步骤
       
a)Make a FileOutputStream

java 代码

  1. FileOutputStream fs = new FileOutputStream("foo.ser");    

b)Make a ObjectOutputStream

java 代码

  1. ObjectOutputStream os =  new ObjectOutputStream(fs);

c)write the object

java 代码

  1. os.writeObject(myObject1);
  2. os.writeObject(myObject2);
  3. os.writeObject(myObject3);

d) close the ObjectOutputStream

java 代码

  1. os.close();


5、举例说明

java 代码

import java.io.*;

public class  Box implements Serializable
{
    private int width;
    private int height;  

    public void setWidth(int width){
        this.width  = width;
    }
    public void setHeight(int height){
        this.height = height;
    }  

    public static void main(String[] args){
        Box myBox = new Box();
        myBox.setWidth(50);
        myBox.setHeight(30);  

        try{
            FileOutputStream fs = new FileOutputStream("foo.ser");
            ObjectOutputStream os =  new ObjectOutputStream(fs);
            os.writeObject(myBox);
            os.close();
        }catch(Exception ex){
            ex.printStackTrace();
        }
    }  

}  

6、相关注意事项
    a)序列化时,只对对象的状态进行保存,而不管对象的方法;
    b)当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
    c)当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
    d)并非所有的对象都可以序列化,,至于为什么不可以,有很多原因了,比如:
        1.安全方面的原因,比如一个对象拥有private,public等field,对于一个要传输的对象,比如写到文件,或者进行rmi传输  等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的。
       2. 资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分  配,而且,也是没有必要这样实现。

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Java的对象序列化是指将那些实现了Serializable接口的对象转换成一个字符序列,并能够在以后将这个字节序列完全恢复为原来的对象。这一过程甚至可通过网络进行,这意味着序列化机制能自动弥补不同操作系统之间的差异。 只要对象实现了Serializable接口(记住,这个接口只是一个标记接口,不包含任何的方法

如果我们想要序列化一个对象,首先要创建某些OutputStream(如FileOutputStream、ByteArrayOutputStream等),然后将这些OutputStream封装在一个ObjectOutputStream中。这时候,只需要调用writeObject()方法就可以将对象序列化,并将其发送给OutputStream(记住:对象的序列化是基于字节的,不能使用Reader和Writer等基于字符的层次结构)。而饭序列的过程(即将一个序列还原成为一个对象),需要将一个InputStream(如FileInputstream、ByteArrayInputStream等)封装在ObjectInputStream内,然后调用readObject()即可。

对象序列化过程不仅仅保存单个对象,还能追踪对象内所包含的所有引用,并保存那些对象(这些对象也需实现了Serializable接口)。下面这段代码演示了此过程:

package test.serializable;

/**
 *@chenfeic
 *
 *只是一个简单的类,用于测试序列化
 */
import java.io.Serializable;

public class Data implements Serializable {
    private static final long serialVersionUID = 7247714666080613254L;
    public int n;
    public Data(int n) {
        this.n = n;
    }
    public String toString(){
        return Integer.toString(n);
    }
}
package test.serializable;

import java.io.Serializable;
import java.util.Random;

/**
 *
 * @author chenfei
 *
 * 用于测试序列化,每个对象Worm对象都与worm中的下一段链接,同时又有属于不同类(Data)的对象引用数组链接
 */
public class Worm implements Serializable {
    private static final long serialVersionUID = 5468335797443850679L;
    private Data[] d = {
            new Data(random.nextInt(10)),
            new Data(random.nextInt(10)),
            new Data(random.nextInt(10))
    };
    private static Random random = new Random(47);
    private Worm next;
    private char c;

    public Worm(int i , char x) {
        System.out.println("Worm constructor:" +i);
        c = x;
        if(--i > 0) {
            next = new Worm(i , (char)(x+1));
        }
    }
    public Worm() {
        System.out.println("Default constructor!");
    }

    public String toString() {
        StringBuilder sb = new StringBuilder(":");
        sb.append(c);
        sb.append("(");
        for(Data data : d) {
            sb.append(data);
        }
        sb.append(")");
        if(next!=null) {
            sb.append(next);
        }
        return sb.toString();
    }
}
package test.serializable;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
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 SerializableTest {

    public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
        Worm w = new Worm(6 ,‘a‘);
        System.out.println("序列化操纵之前");
        System.out.println("w="+w);

        //序列化操作1--FileOutputStream
        ObjectOutputStream oos1 = new ObjectOutputStream(new FileOutputStream("worm.out"));
        oos1.writeObject("Worm storage By FileOutputStream ");
        oos1.writeObject(w);//必须所有引用的对象都实现序列化(本例终究是Data这个类),否则抛出有java.io.NotSerializableException:这个异常
        oos1.close();

        //反序列化操作1---FileInputStream
        ObjectInputStream ois1 = new ObjectInputStream(new FileInputStream("worm.out"));
        String s1 = (String)ois1.readObject();
        Worm w1 = (Worm)ois1.readObject();
        ois1.close();
        System.out.println("反序列化操作1之后");
        System.out.println(s1);
        System.out.println("w1:"+w1);

        //序列化操作2--ByteArrayOutputStream
        ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream();
        ObjectOutputStream oos2 = new ObjectOutputStream(byteOutStream);
        oos2.writeObject("Worm storage By ByteOutputStream ");
        oos2.writeObject(w);
        oos2.flush();

        //反序列操作2--ByteArrayInputStream
        ByteArrayInputStream byteInStream = new ByteArrayInputStream(byteOutStream.toByteArray());
        ObjectInputStream ois2 = new ObjectInputStream(byteInStream);
        String s2 = (String)ois2.readObject();
        Worm w2 = (Worm)ois2.readObject();
        ois2.close();
        System.out.println("反序列化操作2之后");
        System.out.println(s2);
        System.out.println("w2:"+w2);
    }

}

运行的结果如下:

Worm constructor:6
Worm constructor:5
Worm constructor:4
Worm constructor:3
Worm constructor:2
Worm constructor:1
序列化操纵之前
w=:a(853):b(119):c(802):d(788):e(199):f(881)
反序列化操作1之后
Worm storage By FileOutputStream
w1::a(853):b(119):c(802):d(788):e(199):f(881)
反序列化操作2之后
Worm storage By ByteOutputStream
w2::a(853):b(119):c(802):d(788):e(199):f(881)

思考:

1)反序列化后的对象,需要调用构造函数重新构造吗?

答案:不需要。对于Serializable对象,对象完全以它存储的二进制位作为基础来构造,而不调用构造器。

请看下面这段代码

package test.serializable;

import java.io.Serializable;
import java.util.Date;

/**
 *
 * @author chenfei
 *
 * 用于测试序列化时的deep copy
 *
 */
public class House implements Serializable {
    private static final long serialVersionUID = -6091530420906090649L;

    private Date date = new Date(); //记录当前的时间

    public String toString() {
        return "House:" + super.toString() + ".Create Time is:" + date;
    }

}
package test.serializable;

import java.io.Serializable;

public class Animal implements Serializable {
    private static final long serialVersionUID = -213221189192962074L;

    private String name;

    private House house;

    public Animal(String name , House house) {
        this.name = name;
        this.house = house;
        System.out.println("调用了构造器");
    }

    public String toString() {
        return  name + "[" +super.toString() + "‘]" + house;
    }

}
package test.serializable;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Myworld {

    /**
     * @param args
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        House house = new House();
        System.out.println("序列化前");
        Animal animal = new Animal("test",house);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(out);
        oos.writeObject(animal);
        oos.flush();
        oos.close();

        System.out.println("反序列化后");
        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(in);
        Animal animal1 = (Animal)ois.readObject();
        ois.close();
    }

}

    运行结果如下所示:

序列化前
调用了构造器
反序列化后

从上面的结果中可以看到,在序列化前,当我们使用

        Animal animal = new Animal("test",house);

时,调用了Animal的构造器(打印了输出语句),但是反序列后并没有再打印任何语句,说明并没有调用构造器。

2)序列前的对象与序列化后的对象是什么关系?是("=="还是equal?是浅复制还是深复制?)

答案:深复制,反序列化还原后的对象地址与原来的的地址不同。 我们还是看上面思考1)中给出的代码,前两个类不变化,修改第三个类(MyWorld.java)的部分代码,修改后的代码如下:

package test.serializable;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Myworld {

    /**
     * @param args
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        House house = new House();
        System.out.println("序列化前");
        Animal animal = new Animal("test",house);
        System.out.println(animal);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(out);
        oos.writeObject(animal);
        oos.writeObject(animal);//在写一次,看对象是否是一样,
        oos.flush();
        oos.close();

        ByteArrayOutputStream out2 = new ByteArrayOutputStream();//换一个输出流
        ObjectOutputStream oos2 = new ObjectOutputStream(out2);
        oos2.writeObject(animal);
        oos2.flush();
        oos2.close();

        System.out.println("反序列化后");
        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(in);
        Animal animal1 = (Animal)ois.readObject();
        Animal animal2 = (Animal)ois.readObject();
        ois.close();

        ByteArrayInputStream in2 = new ByteArrayInputStream(out2.toByteArray());
        ObjectInputStream ois2 = new ObjectInputStream(in2);
        Animal animal3 = (Animal)ois2.readObject();
        ois2.close();

        System.out.println("out流:" +animal1);
        System.out.println("out流:" +animal2);
        System.out.println("out2流:" +animal3);

        System.out.println("测试序列化前后的对象 == :"+ (animal==animal1));
        System.out.println("测试序列化后同一流的对象:"+ (animal1 == animal2));
        System.out.println("测试序列化后不同流的对象==:" + (animal1==animal3));

    }

}

 运行结果如下:

序列化前
调用了构造器
test[[email protected]‘]House:[email protected] Time is:Sat Apr 06 00:11:30 CST 2013
反序列化后
out流:test[[email protected]‘]House:[email protected] Time is:Sat Apr 06 00:11:30 CST 2013
out流:test[[email protected]‘]House:[email protected] Time is:Sat Apr 06 00:11:30 CST 2013(与上面的相同)
out2流:test[[email protected]‘]House:[email protected] Time is:Sat Apr 06 00:11:30 CST 2013(与上面只是值相同,但是地址不一样。)
测试序列化前后的对象 == :false
测试序列化后同一流的对象:true
测试序列化后不同流的对象==:false

从结果可以看到

序列化前后对象的地址不同了,但是内容是一样的,而且对象中包含的引用也相同。换句话说,通过序列化操作,我们可以实现对任何可Serializable对象的”深度复制(deep copy)"——这意味着我们复制的是整个对象网,而不仅仅是基本对象及其引用。对于同一流的对象,他们的地址是相同,说明他们是同一个对象,但是与其他流的对象地址却不相同。也就说,只要将对象序列化到单一流中,就可以恢复出与我们写出时一样的对象网,而且只要在同一流中,对象都是同一个。

补充:

serialVersionUID 的作用?

在Java中,软件的兼容性是一个大问题,尤其在使用到对象串行性的时候,那么在某一个对象已经被串行化了,可是这个对象又被修改后重新部署了,那么在这种情况下, 用老软件来读取新文件格式虽然不是什么难事,但是有可能丢失一些信息。 serialVersionUID来解决这些问题,新增的serialVersionUID必须定义成下面这种形式:static final long serialVersionUID=-2805284943658356093L;。其中数字后面加上的L表示这是一个long值。 通过这种方式来解决不同的版本之间的串行话问题。

Java串行化机制定义的文件格式似乎很脆弱,只要稍微改动一下类的定义,原来保存的对象就可能无法读取。例如,下面是一个简单的类定义:

public class Save implements Serializable
{
String name;

public void save() throws IOException
{
FileOutputStream f = new FileOutputStream("foo");
ObjectOutputStream oos = new ObjectOutputStream(f);
oos.writeObject(this);
oos.close();
}
}

如果在这个类定义中增加一个域,例如final int val = 7;,再来读取原来保存的对象,就会出现下面的异常:

java.io.InvalidClassException:
Save; local class incompatible:
stream classdesc serialVersionUID = -2805284943658356093,
local class serialVersionUID = 3419534311899376629

上例异常信息中的数字串表示类定义里各种属性的编码值:

●类的名字(Save)。

●域的名字(name)。

●方法的名字(Save)。

●已实现的接口(Serializable)。

改动上述任意一项内容(无论是增加或删除),都会引起编码值变化,从而引起类似的异常警报。这个数字序列称为“串行化版本统一标识符”(serial version universal identifier),简称UID。解决这个问题的办法是在类里面新增一个域serialVersionUID,强制类仍旧使用原来的UID。新增的域必须是:

●static:该域定义的属性作用于整个类,而非特定的对象。

●final:保证代码运行期间该域不会被修改。

●long:它是一个64位的数值。

也就是说,新增的serialVersionUID必须定义成下面这种形式:static final long serialVersionUID=-2805284943658356093L;。其中数字后面加上的L表示这是一个long值。

当然,改动之后的类不一定能够和原来的对象兼容。例如,如果把一个域的定义从String改成了int,执行逆-串行化操作时系统就不知道如何处理该值,显示出错误信息:java.io.InvalidClassException: Save; incompatible types for field name。

原文地址:https://www.cnblogs.com/xiarongjin/p/8331607.html

时间: 2024-08-03 14:39:23

JAVA 对象序列化——Serializable的相关文章

Java对象序列化和反序列化

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

java对象序列化小结

百度百科上介绍序列化是这样的: 序列化 (Serialization): 将对象的状态信息转换为可以存储或传输的形式的过程.在序列化期间,对象将其当前状态写入到临时或持久性存储区.以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象. 序列化使其他代码可以查看或修改那些不序列化便无法访问的对象实例数据.确切地说,代码执行序列化需要特殊的权限:即指定了 SerializationFormatter 标志的 SecurityPermission.在默认策略下,通过 Internet 下载

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 平台的核心功