服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

探讨java对象次第读写

一.    对象次第读写
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            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的不一样。


扫描关注微信公众号