JR 精品文章 - 探讨对象次第读写(Serialization)
AD: jr (at) javaresearch.org


首页 | 动态 | 文章 | FAQ  | 新闻 | 下载 | 代码 | 工作 | 调查 | 术语 | 站点 | 图书 | 论坛 | 帮助 | 全部  

TOP | 交流 | 软件 | 专栏 | 开源 | 译/著 | 源码 | API  | 推荐 | FTP  | 积分 | 统计 | 搜索 | Blog | 我们  
首页 » 研究文集 » I/O及网络 搜索标题相关文章 搜索标题相关文章    评论此文章 发表评论     开始监控此文章 开始监控   加入收藏夹  加入收藏夹
探讨对象次第读写(Serialization)
nepalon 原创   更新:2003-11-17 10:03:37  版本: 1.0   

一.    对象次第读写
1.    实现对象的serialize操作
1.1.    Serializable接口
只要让一个类实现出Serializable接口,就可对它的对象进行serialize操作了。Serializable接口仅仅是一个标志性接口,不具有任何的函数。也就是说,只要一个类实现了Serializable(在类名后加“implements Serializable”),那么那个类就可以进行Serialize操作了。
1.2.    对象所包含的对象的Serialization
1)    Java库中的对象,如String,都实现了Serializable接口,所以它们都可进行serialization操作。
2)    对一个对象进行Serialize操作时,不仅会把对象在内存中的数据保存下来,还会把对象中所包含的可serialize成员对象也保存下来。如果对象中包含了没有实现Serializable接口的成员对象,那将在尝试对对象进行Serialize操作时,将发生错误。
3)    对一个Serializable对象进行次第读取时,并不会调用任何构造函数(包括default构造函数)。这是因为对象中的所有数据都是通过InputStream读取的数据来恢复的,所有不用通过构造函数来进行初始化。
import java.io.*;
class Data implements Serializable{//(1)
    private int i;
    Data(int x) { i = x; }
    public String toString(){
        return Integer.toString(i);
    }
}
class Worm implements Serializable{
    private static int r(){
        return (int)(Math.random() * 10);
}
//(2)
    private Data[] d = {
        new Data(r()), new Data(r()), new Data(r())
    };
    private Worm next;
    private char c;
    Worm(int i, char x){
        System.out.println(" Worm constructor: " + i);
        c = x;
        if(--i > 0)
            next = new Worm(i, (char)(x + 1)); //(3)
    }
    Worm(){
        System.out.println(" Default constructor " );
    }
    public String toString(){
        String s = ":" + c + "(";
        for(int i=0; i<d.length; i++)
            s += d[i].toString();
        s += ")";
        if(next!=null)
            s += next.toString();
        return s;
    }
}
public class TestSerialization {
    public static void main(String[] args)
        throws ClassNotFoundException, IOException{
        Worm w = new Worm(6, 'a');
        System.out.println("w = " + w);
        ObjectOutputStream out = 
            new ObjectOutputStream(
                new FileOutputStream("F:\\nepalon\\Worm.out"));
        out.writeObject("Worm storage");
        out.writeObject(w); //(5)
        out.close();
        ObjectInputStream in =
            new ObjectInputStream(
                new FileInputStream("F:\\nepalon\\Worm.out"));
        String s = (String)in.readObject(); //(4)
        Worm w2 = (Worm)in.readObject(); //(6)
        System.out.println(s + " , w2 = " + w2);
        ByteArrayOutputStream bout = 
new ByteArrayOutputStream();
        ObjectOutputStream out2 = new ObjectOutputStream(bout);
        out2.writeObject("Worm storage");
        out2.writeObject(w);
        out2.flush();
        ObjectInputStream in2 =
            new ObjectInputStream(
                new ByteArrayInputStream(
                    bout.toByteArray()));
        s = (String)in2.readObject();
        Worm w3 = (Worm)in2.readObject();
        System.out.println(s + " , w3 = " + w3);                
    }
}
运行结果为:
Worm constructor: 6
Worm constructor: 5
Worm constructor: 4
Worm constructor: 3
Worm constructor: 2
Worm constructor: 1
w = :a(993):b(769):c(379):d(532):e(151):f(481)
Worm storage , w2 = :a(993):b(769):c(379):d(532):e(151):f(481)
Worm storage , w3 = :a(993):b(769):c(379):d(532):e(151):f(481)
代码(2)合成了一个Date数组;代码(3)用类似递归的方法生成了一    个Worm对象链。由结果可见Worm对象的成员Data数组及由Worm对象所产生的Worm对象链都能被很好的保存了下来。代码(4)则证明了Java库的类型都实现了Serializable接口的。
如果去掉(1)处代码,不让class Data实现Serializable接口,那么当调用writeObject()(如代码(5))来保存对象时,将产生NotSerializableException异常,因为在保存Worm对象时会去保存它的所有成员对象,但Data是不能Serialize的,所以会产生异常。
4)    进行次第读取时,在执行读取操作的class中一定要能找到相应的class文件。
在上面的代码中,如果class TestSerialization和class Worm分别处于两个不同的文件中,那么在代码(6)中执行了次第读取操作来还原一个对象时,就要确保在TestSerialization所在的文件中能找到Worm所在的文件,否则会产生ClassNotFoundException异常。
1.3.    关键字transient
当一个成员被声明为transient时,在对象被保存的时候,该成员将不被保存。
    class Blip3 implements Serializable{
    int i;
    String s1;
    transient String s2;
    public Blip3(String x, int a){
        System.out.println("Blip3(String x, int a)");
        //在non-default构造函数中初始化i、s1和s2
        i = a;
        s1 = x;
        s2 = x;
    }
    public String toString(){
        String s22 = (s2==null)?"s2 is null":s2;
        return "s1 = " + s1 + " , s2 = " + s22 + ", i = " + i;
    }
}
public class TestSerialization{
    public static void main(String[] args)
        throws ClassNotFoundException, IOException{  
        System.out.println("Constructor Object: ");
        Blip3 b3 = new Blip3("A String", 47);
        System.out.println(b3);
        ObjectOutputStream out = 
            new ObjectOutputStream(
                new FileOutputStream("F:\\nepalon\\test\\Blip3.out"));
        out.writeObject(b3);
        out.close();
        ObjectInputStream in =
            new ObjectInputStream(
                new FileInputStream("F:\\nepalon\\test\\Blip3.out"));
        System.out.println("Recovering Object: ");
        b3 = (Blip3)in.readObject();
        System.out.println(b3);
    }
}
运行结果为:
Constructor Object: 
Blip3(String x, int a)
s1 = A String , s2 = A String, i = 47
Recovering Object: 
s1 = A String , s2 = s2 is null, i = 47
        由于s2被声明为transient,所以它将不被保存。
2.    通过Externalizable接口来实现次第读写
通过实现了Externalizable接口来产生Serialize功能,我们将取得更大的控制权。
2.1.    通过实现了Externalizable接口来产生Serialize功能
1)    在保存对象时不会保存任何成员对象。但我们可以手工保存成员对象(下面将讲到)。
2)    Default构造函数必须为public。因为在次第读取对象时,会调用default构造函数。因为对象中的成员对象不一定会被保存,所以要通过构造函数来进行初始化。
3)    Extrenalizable接口有writeExternal(ObjectOutput)和readExternal(ObjectInput)两个函数。在对实现了Externalizable接口的类进行次第读写时,会调用这两个函数。但是在恢复对象时,是先调用default构造函数再调用readExternal()函数的。
import java.io.*;
class Blip1 implements Externalizable{
    public Blip1(){ //(1)
        System.out.println("Blip1 Constructor");
    }
    public void writeExternal(ObjectOutput out)
        throws IOException{
        System.out.println("Blip1.writeExternal");
    }
    public void readExternal(ObjectInput in)
        throws IOException{
        System.out.println("Blip1.readexternal");
    }
}
class Blip2 implements Externalizable{
    Blip2(){ //(2)
        System.out.println("Blip2 Constructor");
    }
    public void writeExternal(ObjectOutput out)
        throws IOException{
        System.out.println("Blip2.writeExternal");
    }
    public void readExternal(ObjectInput in)
        throws IOException{
        System.out.println("Blip2.readexternal");
    }
}
public class TestSerialization{
    public static void main(String[] args)
        throws ClassNotFoundException, IOException{  
        System.out.println("Constructor Object: ");
        Blip1 b1 = new Blip1();
        Blip2 b2 = new Blip2();
        ObjectOutputStream out = 
            new ObjectOutputStream(
                new FileOutputStream("F:\\nepalon\\Blips.out"));
        System.out.println("Saveing Object: ");
        out.writeObject(b1);
        out.writeObject(b2);
        out.close();
        ObjectInputStream in =
            new ObjectInputStream(
                new FileInputStream("F:\\nepalon\\Blips.out"));
        System.out.println("Recovering Object: ");
        b1 = (Blip1)in.readObject();
        //b2 = (Blip2)in.readObject(); (3)
    }
}
运行结果为:
Constructor Object: 
Blip1 Constructor
Blip2 Constructor
Saveing Object: 
Blip1.writeExternal
Blip2.writeExternal
Recovering Object:
Blip1 Constructor
Blip1.readexternal
在上面的代码中,class Blip1的default构造函数为public(代码(1)),所以可以完成次第读写的操作。但是class Blip2的default构造函数不为public(代码(2)),所以在进行次第读取(代码(3))操作时,会产生InvalidClassException的异常。
从运行结果可看出,在恢复对象时会调用对象的defqult构造函数。
2.2.    Externalizable接口和Serializable接口的区别
1)    通过实现Serializable接口的方法,在保存对象时会把对象中所包含的成员对象也保存下来;而通过实现Externalizable接口的方法,在保存对象时不会保存任何成员对象。
2)    通过实现Serializable接口的方法,在恢复对象时不会调用任何构造函数(包括default构造函数);而通过实现Externalizable接口的方法,在恢复对象时会调用default构造函数。然后再调用readExternal()函数。
2.3.    利用writeExternal()和readExternal()函数来控制成员对象
由于通过实现Externalizable接口的方法,在保存对象时不会保存任何成员对象,但如果要在保存对象的时候要保存对象的成员对象时,我们可以通过这两个函数来实现。
        class Blip3 implements Externalizable{
    int i;
    String s1;
    String s2;
    public Blip3(){
        System.out.println("Blip3 Constructor");
        //在default构造函数中只对s2进行初始化
        s2 = "default String"; //(1)
    }
    public Blip3(String x, int a){
        System.out.println("Blip1(String x, int a)");
        //在non-default构造函数中初始化i、s1和s2
        i = a;
        s1 = x;
        s2 = x;
    }
    public String toString(){
        return "s1 = " + s1 + " , s2 = " + s2 + ", i = " + i;
    }
    public void writeExternal(ObjectOutput out)
        throws IOException{
        System.out.println("Blip3.writeExternal"); 
        out.writeObject(s1);
        out.writeInt(i);
    }
    public void readExternal(ObjectInput in)
        throws IOException, ClassNotFoundException{
        System.out.println("Blip2.readExternal");
        s1 = (String)in.readObject();
        i = in.readInt();
    }
}
public class TestSerialization{
    public static void main(String[] args)
        throws ClassNotFoundException, IOException{  
        System.out.println("Constructor Object: ");
        Blip3 b3 = new Blip3("A String", 47);
        System.out.println(b3);
        ObjectOutputStream out = 
            new ObjectOutputStream(
                new FileOutputStream("F:\\nepalon\\test\\Blip3.out"));
        System.out.println("Saveing Object: ");
        out.writeObject(b3);
        out.close();
        ObjectInputStream in =
            new ObjectInputStream(
                new FileInputStream("F:\\nepalon\\test\\Blip3.out"));
        System.out.println("Recovering Object: ");
        b3 = (Blip3)in.readObject();
        System.out.println(b3);
    }
}
运行结果为:
Constructor Object: 
Blip3(String x, int a)
s1 = A String , s2 = A String, i = 47
Saveing Object: 
Blip3.writeExternal
Recovering Object: 
Blip3 Constructor
Blip2.readExternal
s1 = A String , s2 = default String, i = 47 //(2)
在上面代码中,在writeExternal()函数中保存了i和s1,在readExternal()函数中恢复了i和s2;而在default构造函数中我们只初始化了s2(代码(1))。从结果(2)处可证明,s2在对象被恢复时在default构造函数中被初始化;而s1和i由于在保存对象时也被保存了,所以能恢复到原来的值。
3.    扩增Serializable接口以实现Externalizable接口的功能
虽然Serializable接口只是个标志封闭器,不具有任何函数,但我们可在实现了该接口的类中扩增writeObject()和readObject()函数,这是Java中最怪异的地方。
3.1.    实现扩增的语法
这两函数的定义为:
        private void writeObject(ObjectOutputStream stream)
        throws IOException{;
        private void readObject(ObjectInputStream stream)
        throws IOException, ClassNotFoundException;
3.2.    Java的怪异之处
1)    在下面的代码中我们可以看到,Blip3只实现了Serializable接口,而该接口只是个标志接口,所以这两个函数并不是接口的一部分。
2)    这两个函数被声明为private,按理只能在它们所在的类中被调用,但事实却是它们能被ObjectOutputStream和ObjectInputStream对象的writeObject()和readObject()函数调用。
3.3.    readObject()和writeObject()函数的使用及其工作过程
3.3.1    工作过程
当调用ObjectOutputStream.writeObject(Object)时,传入的Serializable对象会被检查是否实现自己的writeObject(ObjectOutputStream)。如果有,就会执行扩增的writeObject(ObjectOutputStream)函数。当调用ObjectInputStream.readObject()时的工作过程也一样。
3.3.2    在扩增的writeObject(ObjectOutputStream)中我们可以通过调用defaultWriteobject()函数来执行缺省的writeObject();在扩增的readObject(ObjectInputStream)函数中,我们可以通过调用defaultReadObject()函数来执行缺省的readObject()。在相应的缺省函数中都只对non-transient成员对象进行操作。
import java.io.*;
class Blip3 implements Serializable{
    String s1;
    transient String s2;
    public Blip3(String a, String b){
        s1 = "Not Transient: " + a;
        s2 = "Transient: " + b;
    }
    public String toString(){
        return "s1 = " + s1 + "\ns2 = " + s2 ;
}
//函数(1)
    private void writeObject(ObjectOutputStream stream)
        throws IOException{
        stream.defaultWriteObject();
        stream.writeObject(s2);
}
//函数(2)
    private void readObject(ObjectInputStream stream)
        throws IOException, ClassNotFoundException{
        stream.defaultReadObject();
        s2 = (String)stream.readObject();
    }
}
public class TestSerialization{
    public static void main(String[] args)
        throws ClassNotFoundException, IOException{  
        Blip3 b3 = new Blip3("Test1", "Test2");
        System.out.println("Before:\n" + b3);
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream out = 
            new ObjectOutputStream(bout);
        out.writeObject(b3); 
        out.close();
        ObjectInputStream in =
            new ObjectInputStream(
                new ByteArrayInputStream(bout.toByteArray()));
        System.out.println("Recovering Object: ");
        b3 = (Blip3)in.readObject();
        System.out.println("After:\n" + b3);
    }
}
运行结果为:
Before:
s1 = Not Transient: Test1
s2 = Transient: Test2
Recovering Object: 
After:
s1 = Not Transient: Test1
s2 = Transient: Test2
在代码的函数(1)中,先保存non-transient成员对象,再保存transient成员对象。在函数(2)中,先恢复non-transient成员对象,再恢复transient成员对象。保存的顺序和恢复的顺序必须相同,否则数据会出错。
4.    关于Serialize的扩充话题
4.1    当多个对象被保存到同一个Stream时,如果这些对象的成员对象中有指向相同的第三对象的reference,那么在恢复时也将指向同一个对象。但不相同的两个Stream中的对象的则不同。
import java.io.*;
import java.util.ArrayList;
class House implements Serializable {}
class Animal implements Serializable{
    String name;
    House house;
    Animal(String nm, House h){
        name = nm;
        house = h;
    }
    public String toString(){
        return name + "[" + super.toString() + 
            "], " + house + "\n";
    }
}
public class TestSerialization{
    public static void main(String[] args)
        throws ClassNotFoundException, IOException{  
        House house = new House();
        ArrayList animals = new ArrayList();
        animals.add(new Animal("dog", house));
        animals.add(new Animal("hamster", house));
        animals.add(new Animal("cat", house));
        System.out.println("animals: " + animals);
        //在同一个Stream(Stream1)中保存两次
        ByteArrayOutputStream buf1 = new ByteArrayOutputStream();
        ObjectOutputStream o1 = new ObjectOutputStream(buf1);
        o1.writeObject(animals);
        o1.writeObject(animals);
        //在另一个Stream(Stream2)中保存
        ByteArrayOutputStream buf2 = new ByteArrayOutputStream();
        ObjectOutputStream o2 = new ObjectOutputStream(buf2);
        o2.writeObject(animals);        
        //恢复Stream1中的两个对象
        ObjectInputStream in1 = 
            new ObjectInputStream(
                new ByteArrayInputStream(
                    buf1.toByteArray()));
        ArrayList animal1 = (ArrayList)in1.readObject();
        ArrayList animal2 = (ArrayList)in1.readObject();
        //恢复Stream2中的对象
        ObjectInputStream in2 = 
            new ObjectInputStream(
                new ByteArrayInputStream(
                    buf2.toByteArray()));
        ArrayList animal3 = (ArrayList)in2.readObject();
        System.out.println("animal1: " + animal1);
        System.out.println("animal2: " + animal2);
        System.out.println("animal3: " + animal3);
    }
}
运行结果为:
animals: [dog[Animal@1], House@2
, hamster[Animal@3], House@2
, cat[Animal@4], House@2
]
animal1: [dog[Animal@14], House@15
, hamster[Animal@16], House@15
, cat[Animal@17], House@15
]
animal2: [dog[Animal@14], House@15
, hamster[Animal@16], House@15
, cat[Animal@17], House@15
]
animal3: [dog[Animal@18], House@19
, hamster[Animal@1a], House@19
, cat[Animal@1b], House@19
]
当在同一个Stream中两次保存animals对象时,它们被恢复后是完全一样的,所以结果中animals1和animals2的对象的地址是一样的。但当把animals保存到另一个Stream中时,当它被恢复后可看到与前两个是不一样的。由结果可知animals1与animals2的信息一样,但这两个对象的信息与animals3的不一样。


版权声明   给作者写信
本篇文章对您是否有帮助?  投票:         投票结果:     25       1
作者其它文章: 作者全部文章
评论人:nepalon 发表时间: Mon Nov 17 17:56:04 CST 2003
4.2    关于静态成员数据的保存
在《Java编程思想第2版》中说到对象中的静态成员数据在对象被保存时不会被保存,必须手工编写保存静态成员数据的代码。但我在测试中并没发现这样的问题,我想可能是Java新的JDK已经改了这个问题了。
    class Animal implements Serializable{
    String s1 = "default String";
    static String s2 = "default static String";
    Animal(String s11, String s22 ){
        s1 = s11;
        s2 = s22;
    }
    public String toString(){
        return "s1 = " + s1 + " , s2 = " + s2 + "\n";
    }
}
public class TestSerialization{
    public static void main(String[] args)
        throws ClassNotFoundException, IOException{  
        House house = new House();
        ArrayList animals = new ArrayList();
        animals.add(new Animal("dog", "hello dog"));
        animals.add(new Animal("hamster", "hello hamster"));
        animals.add(new Animal("cat", "hello cat"));
        System.out.println("Saving animals:" );
        System.out.println(animals);
        ByteArrayOutputStream buf1 = new ByteArrayOutputStream();
        ObjectOutputStream o1 = new ObjectOutputStream(buf1);
        o1.writeObject(animals);
        ObjectInputStream in1 = 
            new ObjectInputStream(
                new ByteArrayInputStream(
                    buf1.toByteArray()));
        ArrayList animal1 = (ArrayList)in1.readObject();
        System.out.println("Covering animals: ");
        System.out.println(animal1);
    }
}
运行结果为:
Saving animals:
[s1 = dog , s2 = hello cat
, s1 = hamster , s2 = hello cat
, s1 = cat , s2 = hello cat
]
Covering animals: 
[s1 = dog , s2 = hello cat
, s1 = hamster , s2 = hello cat
, s1 = cat , s2 = hello cat
]
由于我们最后生成的是new Animal("cat", "hello cat"),所以静态成员数据s2的值就为hello cat。由结果可见,所有数据成员都能在对象被保存时被保存下来。
评论人:mosa 发表时间: Wed Nov 19 01:46:26 CST 2003
你的例子发生在同一个JVM进程的同一个class loader,所以读和写都是在同一个class上发生,其实读的是memory中同一个位置。即使读写在不同JVM进程中进行读写,你的例子仍然会造成static状态被serialize的假象,因为当class initializer被执行时仍然会初始化那个static field成指定的string。应该稍微修改一下例子,在class initializer之外对static状态进行修改,然后serialize/deserialize。
评论人:huhw 发表时间: Thu Jan 01 06:02:39 CST 2004
强烈要求取消楼主的参评资格!!!否则,其它优秀作者还有什么积极性,如此下去,JR被一些无耻小人的东拼西凑的文章充斥,我们也就没有有价值的文章可读了。像这样的文章竟然还都被投10甚至20多张赞成票,可以算是JR中超级5星级文章了,但大家的评论好像没有这么多人叫好啊?仔细看了他的其它文章,说一点价值也没有是有点偏激,因为垃圾也不能说是毫无价值的。连糊涂人也明白是怎么回事,nepalon(拿破轮?)做事要对得起自己的良心啊!你可以争取自己的利益,但是请不要以损伤大家的利益为代价!

建议你就自裁了吧,呵呵,免得在大家的一片叫骂声中丢尽脸面。不就是想领奖吗?我不在乎JR把奖品发给谁(声明:我是近期才发现JR的,没有发表过任何文章,但在JR读了很多好文章!没有靠攻击你来提高自己名次的目的。再说,这样有意义吗?),但是如果因此打击了优秀作者的写作积极性,那就损伤了大家的利益!难道JR的净土就要被你这样的人玷污了吗?

强烈要求取消楼主的参评资格!!!
强烈要求取消楼主的参评资格!!!
强烈要求取消楼主的参评资格!!!
强烈要求取消楼主的参评资格!!!
强烈要求取消楼主的参评资格!!!

----------- 写于2004年新年凌晨-----------
(同意者请多多转贴!)
评论人:vampire_315 发表时间: Sun Dec 17 19:54:51 CST 2006
好啊

这个文章共有 4 条评论
主题: 使用缓冲IO提高性能 上一篇文章
返回文章列表 返回〔I/O及网络〕
下一篇文章 主题: 发布与QQ互通的即时通讯系统源代码,有兴趣的来看看


文字广告链接
        自主、快速定制基于JAVA的B/S业务系统          重量级企业在线自定义WEB报表平台
        Excel制表、零代码发布、打印、图表结合——快逸报表,免费、稳定、功能强大的java工具
        技术圈: 关于Java、dotNet、PHP、Ruby、奇客、Web2.0等更多资讯博客精选文章

关于 JR  |  版权声明  |  联系我们 

©2002-2006 JR 版权所有 沪ICP备05019622号