java IO笔记(序列化与ObjectInputStream、ObjectOutputStream)
2017-08-15 17:38
816 查看
本篇讲的内容是序列化,以及ObjectInputStream和ObjectOutputStream流。
我们知道java是基于对象编程的,我们前面在进行流传输数据时,要么是以字节传输,要么是以字符传输,如果能在流中传输对象岂不是更为方便,幸运的是java在这个方面提供了支持,序列化和反序列技术帮我我们实现了该功能。
序列化指的是将对象转换为字节序列,而反序列化就是将字节序列转换为对象了,java io中的ObjectInputStream中的readObject()方法对应着反序列化,ObjuectOutputStream中的writeObjext(Object object)对应着序列化,当然所序列化的对象object必须是可被序列化的。
序列化反序列化一般的应用场景分为两种,一种是将信息写入硬盘之中需要用的时候再从硬盘中还原,这样可以节省很多的操作空间,如web中的session,当并发数量很高时,可能会将一些存储到硬盘中,等需要时再还原。
那么如何能让对象可以被序列化呢,其实很简单,那就是该对象必须实现java.io.Serializable接口或者java.io.Externlizabel接口,其中Externlizable接口继承了Serializable接口。如果采用默认的序列化方式实现Serializable接口就行了,如果想自己控制序列化,那么就需要实现Externlizable接口。像我们常用的String,Number对象本身都实现了Serializable接口:
下面先用一个最简单的例子来让大家熟悉序列化和反序列化的操作:
可以看出成功的从文件中还原了对象,打开test.txt文件可以看到如下情况:
虽然大部分是乱码但依稀能看出一点儿Person对象的样子~
看上去序列化和反序列化是不是很简单呢,其实它们也有很多要注意的地方,首先我们在Person类中看到了一个变量,serialVersionUID,这个是什么呢?其实它相当于一序列化和反序列化的一个标识符。
比如当你序列化和反序列化不在同一台机器时,那么反序列化时,除了两点的对象本身要完全一样意外,还要对比两点的serialVersionUID是否相同,如果相同才能进行反序列化,否则将会失败。
serialVersionUID有两种生成方式:
第一种为默认的值为1L,就如上面使用的那样。
第二种则是会根据实体类来生成一个不重复的long类型数据。jvm不同,即使是相同的实体类,也可能获得不同的值,如果实体类有过更改,那么生成的ID值肯定会发生变化,这点要注意。
一般没有特殊要求,使用第一种即可,如果你不显示的声明这个值,它会默认以第二种方式来帮你生成。
下面将上面的案例进行修改,来加深一下对serialVersionUID的认识:
上述的实体类Person没有显示的定义serialVersionUID,所以默认以第二种方式生成。执行完上述打印可以得到如下打印:
然后执行反序列化代码:
因为第二次我们修改了实体类,所以当再次自动生成serialVersionUID的值与之前的不同,所以反序列化无法达成,现在修改代码,显示的定义该值:
然后修改实体类:
可以看出反序列化成功,实体类修改过后,对于未赋过值的属性为其初始化默认值。
对于serialVersionUID的具体使用方式还是要根据实际使用场景来觉定,比如你做了一个c/s程序,当你服务器代码升级过后,如果你希望用户也升级对应的客户端,那么你可以修改该值,使得客户端无法使用,强制用户升级(好像有点儿强盗啊。。),这种时候显然就不需要用一个定值了,当然如果你希望客户端能一直使用,那么定义一个不变的值是个很好的选择。
那么对象序列化是所有的属性都可以被序列化吗?答案是否定的。序列化是记录对象的状态,那么当定一个静态属性的时候,因为其属于类的状态,所以它不会被序列化。同时,被Transient关键字修饰过的属性也不可以被序列化,下面举例说明:
按照所说的静态属性和被transient修饰过的属性应该都无法被序列化,从控制台可以看出,被transient修饰过的numbers确实没有数据,为什么静态属性hobby有值呢?其实它并不是反序列化得到的,因为其是静态属性,所以当jvm在对象中无法找到该值的时候会继续扩大寻找范围,此时内存中的hobby因为被赋过值且一直存在,所以此时打印出来的是内存中的数据。
当序列化操作和反序列化操作分开执行的时候,可以看到如下打印:
事实证明静态属性是不可以被序列化的。
除这些还有一种情况需要考虑,那就是父子类继承的情况,我们知道,当我们创建一个子类对象时,需要先创建其父类对象。那么问题来了,当子类实现了Serializable接口,父类却没有实现时,序列化时,便不会对其父类进行序列化,那么当反序列化时,便会调用其父类的无参构造函数来创建其父类对象,此时父类特有的那些属性值时,便会被赋予初始化值(如果无参构造中没有进行过赋值的话),举例说明:
从控制台输出可以看出,父类的属性确实没有被序列化,如果想要实现的话,那么父类也必须实现Serializable接口。
通过这种特性也可以实现transient关键字的效果。
最后要讲述的则是序列化中的序列化和反序列化方法了,一般情况下我们使用对象流中的readObject和writeObject就可以了,但有些时候我们需要自行控制序列化和反序列化的过程,这样可以提升数据的安全性,这时我们就就要自己重写这两个方法了,在Serializable接口中也有说明:
像前面说的Externalizable接口就是继承了Serializable接口,其中定义了两个方法,源码如下:
下面举两个例子,一个是实现Serialiabel接口,重写readObject,writeObject方法,来实现加密,另一个是实现Externalizable接口,重写writeExternal,readExternal方法,实现自主控制序列化反序列化流程。
例子1:
例子2:
执行上述代码打印如下:
由上可见,只序列化了name属性。
以上为本篇内容。
我们知道java是基于对象编程的,我们前面在进行流传输数据时,要么是以字节传输,要么是以字符传输,如果能在流中传输对象岂不是更为方便,幸运的是java在这个方面提供了支持,序列化和反序列技术帮我我们实现了该功能。
序列化指的是将对象转换为字节序列,而反序列化就是将字节序列转换为对象了,java io中的ObjectInputStream中的readObject()方法对应着反序列化,ObjuectOutputStream中的writeObjext(Object object)对应着序列化,当然所序列化的对象object必须是可被序列化的。
序列化反序列化一般的应用场景分为两种,一种是将信息写入硬盘之中需要用的时候再从硬盘中还原,这样可以节省很多的操作空间,如web中的session,当并发数量很高时,可能会将一些存储到硬盘中,等需要时再还原。
那么如何能让对象可以被序列化呢,其实很简单,那就是该对象必须实现java.io.Serializable接口或者java.io.Externlizabel接口,其中Externlizable接口继承了Serializable接口。如果采用默认的序列化方式实现Serializable接口就行了,如果想自己控制序列化,那么就需要实现Externlizable接口。像我们常用的String,Number对象本身都实现了Serializable接口:
下面先用一个最简单的例子来让大家熟悉序列化和反序列化的操作:
package objectIO; import java.io.ByteArrayInputStream; 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 java.io.Serializable; public class ObjectIOTest { public static void main(String[] args) { Person person = new Person("tom", 19); try { ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("./src/objectIO/test.txt")); oos.writeObject(person); oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream( "./src/objectIO/test.txt")); Person temp = (Person) ois.readObject(); System.out.println(temp); ois.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } class Person implements Serializable { private static final long serialVersionUID = 1L; private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "[name: " + name + " age: " + age + "]"; } }执行上述代码可以在控制台得到如下打印:
可以看出成功的从文件中还原了对象,打开test.txt文件可以看到如下情况:
虽然大部分是乱码但依稀能看出一点儿Person对象的样子~
看上去序列化和反序列化是不是很简单呢,其实它们也有很多要注意的地方,首先我们在Person类中看到了一个变量,serialVersionUID,这个是什么呢?其实它相当于一序列化和反序列化的一个标识符。
比如当你序列化和反序列化不在同一台机器时,那么反序列化时,除了两点的对象本身要完全一样意外,还要对比两点的serialVersionUID是否相同,如果相同才能进行反序列化,否则将会失败。
serialVersionUID有两种生成方式:
第一种为默认的值为1L,就如上面使用的那样。
第二种则是会根据实体类来生成一个不重复的long类型数据。jvm不同,即使是相同的实体类,也可能获得不同的值,如果实体类有过更改,那么生成的ID值肯定会发生变化,这点要注意。
一般没有特殊要求,使用第一种即可,如果你不显示的声明这个值,它会默认以第二种方式来帮你生成。
下面将上面的案例进行修改,来加深一下对serialVersionUID的认识:
package objectIO; import java.io.FileOutputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class ObjectIOTest { public static void main(String[] args) { Person person = new Person("tom", 19); try { ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("./src/objectIO/test.txt")); oos.writeObject(person); oos.close(); System.out.println("序列化成功"); } catch (Exception e) { e.printStackTrace(); } } } class Person implements Serializable { private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "[name: " + name + " age: " + age + "]"; } }
上述的实体类Person没有显示的定义serialVersionUID,所以默认以第二种方式生成。执行完上述打印可以得到如下打印:
然后执行反序列化代码:
package objectIO; import java.io.FileOutputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class ObjectIOTest { public static void main(String[] args) { Person person = new Person("tom", 19); try { ObjectInputStream ois = new ObjectInputStream(new FileInputStream( "./src/objectIO/test.txt")); Person temp = (Person) ois.readObject(); System.out.println(temp); ois.close(); } catch (Exception e) { e.printStackTrace(); } } } class Person implements Serializable { private String name; private int age; private String sex; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public Person(String name, int age, String sex){ this.name = name; this.age = age; this.sex = sex; } @Override public String toString() { return "[name: " + name + " age: " + age +" sex: "+sex+ "]"; } }执行后发现反序列化失败,控制台输出如下内容:
因为第二次我们修改了实体类,所以当再次自动生成serialVersionUID的值与之前的不同,所以反序列化无法达成,现在修改代码,显示的定义该值:
package objectIO; import java.io.FileOutputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class ObjectIOTest { public static void main(String[] args) { Person person = new Person("tom", 19); try { ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("./src/objectIO/test.txt")); oos.writeObject(person); oos.close(); System.out.println("序列化成功"); } catch (Exception e) { e.printStackTrace(); } } } class Person implements Serializable { private static final long serialVersionUID = 1L; private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } @ 4000 Override public String toString() { return "[name: " + name + " age: " + age +"]"; } }执行代码后,控制台输出如下:
然后修改实体类:
package objectIO; import java.io.FileInputStream; import java.io.ObjectInputStream; import java.io.Serializable; public class ObjectIOTest { public static void main(String[] args) { try { ObjectInputStream ois = new ObjectInputStream(new FileInputStream( "./src/objectIO/test.txt")); Person temp = (Person) ois.readObject(); System.out.println(temp); ois.close(); } catch (Exception e) { e.printStackTrace(); } } } class Person implements Serializable { private static final long serialVersionUID = 1L; private String name; private int age; private String sex; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public Person(String name, int age, String sex){ this.name = name; this.age = age; this.sex = sex; } @Override public String toString() { return "[name: " + name + " age: " + age +" sex: "+sex+ "]"; } }执行上述代码后,控制台输出如下:
可以看出反序列化成功,实体类修改过后,对于未赋过值的属性为其初始化默认值。
对于serialVersionUID的具体使用方式还是要根据实际使用场景来觉定,比如你做了一个c/s程序,当你服务器代码升级过后,如果你希望用户也升级对应的客户端,那么你可以修改该值,使得客户端无法使用,强制用户升级(好像有点儿强盗啊。。),这种时候显然就不需要用一个定值了,当然如果你希望客户端能一直使用,那么定义一个不变的值是个很好的选择。
那么对象序列化是所有的属性都可以被序列化吗?答案是否定的。序列化是记录对象的状态,那么当定一个静态属性的时候,因为其属于类的状态,所以它不会被序列化。同时,被Transient关键字修饰过的属性也不可以被序列化,下面举例说明:
package objectIO; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class ObjectIOTest { public static void main(String[] args) { Person person = new Person("tom", 19,"123456789"); Person.hobby = "swim"; try { ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("./src/objectIO/test.txt")); oos.writeObject(person); oos.close(); System.out.println("序列化成功"); ObjectInputStream ois = new ObjectInputStream(new FileInputStream( "./src/objectIO/test.txt")); Person temp = (Person) ois.readObject(); System.out.println("反序列化成功"); System.out.println(temp); ois.close(); } catch (Exception e) { e.printStackTrace(); } } } class Person implements Serializable { private static final long serialVersionUID = 1L; private String name; private int age; private String sex; public static String hobby; private transient String numbers; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public Person(String name, int age, String sex){ this.name = name; this.age = age; this.sex = sex; } public Person(String name, int age, String sex, String numbers) { this.name = name; this.age = age; this.sex = sex; this.numbers = numbers; } @Override public String toString() { return "[name: " + name + " age: " + age +" sex: "+sex+" hobby: "+hobby+" numbers: "+numbers+ "]"; } }执行上述代码后,控制台输出如下打印:
按照所说的静态属性和被transient修饰过的属性应该都无法被序列化,从控制台可以看出,被transient修饰过的numbers确实没有数据,为什么静态属性hobby有值呢?其实它并不是反序列化得到的,因为其是静态属性,所以当jvm在对象中无法找到该值的时候会继续扩大寻找范围,此时内存中的hobby因为被赋过值且一直存在,所以此时打印出来的是内存中的数据。
当序列化操作和反序列化操作分开执行的时候,可以看到如下打印:
事实证明静态属性是不可以被序列化的。
除这些还有一种情况需要考虑,那就是父子类继承的情况,我们知道,当我们创建一个子类对象时,需要先创建其父类对象。那么问题来了,当子类实现了Serializable接口,父类却没有实现时,序列化时,便不会对其父类进行序列化,那么当反序列化时,便会调用其父类的无参构造函数来创建其父类对象,此时父类特有的那些属性值时,便会被赋予初始化值(如果无参构造中没有进行过赋值的话),举例说明:
package objectIO; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class ObjectIOTest { public static void main(String[] args) { Person person = new Person("hi","tom", 19,"boy","123456789"); Person.hobby = "swim"; try { ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("./src/objectIO/test.txt")); oos.writeObject(person); oos.close(); System.out.println("序列化成功"); ObjectInputStream ois = new ObjectInputStream(new FileInputStream( "./src/objectIO/test.txt")); Person temp = (Person) ois.readObject(); System.out.println("反序列化成功"); System.out.println(temp); ois.close(); } catch (Exception e) { e.printStackTrace(); } } } class Person extends PersonFather implements Serializable { private static final long serialVersionUID = 1L; private String name; private int age; private String sex; public static String hobby; private transient String numbers; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public Person(String name, int age, String sex){ this.name = name; this.age = age; this.sex = sex; } public Person(String name, int age, String sex, String numbers) { this.name = name; this.age = age; this.sex = sex; this.numbers = numbers; } public Person(String greet,String name, int age, String sex, String numbers){ super(greet); this.name = name; this.age = age; this.sex = sex; this.numbers = numbers; } @Override public String toString() { return "[name: " + name + " age: " + age +" sex: "+sex+" hobby: "+hobby+" numbers: "+numbers+" greet: "+greet+"]"; } } class PersonFather{ public String greet; public PersonFather(){ } public PersonFather(String greet){ this.greet = greet; } }执行上述代码可以得到如下打印:
从控制台输出可以看出,父类的属性确实没有被序列化,如果想要实现的话,那么父类也必须实现Serializable接口。
通过这种特性也可以实现transient关键字的效果。
最后要讲述的则是序列化中的序列化和反序列化方法了,一般情况下我们使用对象流中的readObject和writeObject就可以了,但有些时候我们需要自行控制序列化和反序列化的过程,这样可以提升数据的安全性,这时我们就就要自己重写这两个方法了,在Serializable接口中也有说明:
/** * <PRE> * private void writeObject(java.io.ObjectOutputStream out) * throws IOException * private void readObject(java.io.ObjectInputStream in) * throws IOException, ClassNotFoundException; * private void readObjectNoData() * throws ObjectStreamException; * </PRE> * * <p>The writeObject method is responsible for writing the state of the * object for its particular class so that the corresponding * readObject method can restore it. The default mechanism for saving * the Object's fields can be invoked by calling * out.defaultWriteObject. The method does not need to concern * itself with the state belonging to its superclasses or subclasses. * State is saved by writing the individual fields to the * ObjectOutputStream using the writeObject method or by using the * methods for primitive data types supported by DataOutput. * * <p>The readObject method is responsible for reading from the stream and * restoring the classes fields. It may call in.defaultReadObject to invoke * the default mechanism for restoring the object's non-static and * non-transient fields. The defaultReadObject method uses information in * the stream to assign the fields of the object saved in the stream with the * correspondingly named fields in the current object. This handles the case * when the class has evolved to add new fields. The method does not need to * concern itself with the state belonging to its superclasses or subclasses. * State is saved by writing the individual fields to the * ObjectOutputStream using the writeObject method or by using the * methods for primitive data types supported by DataOutput. * * <p>The readObjectNoData method is responsible for initializing the state of * the object for its particular class in the event that the serialization * stream does not list the given class as a superclass of the object being * deserialized. This may occur in cases where the receiving party uses a * different version of the deserialized instance's class than the sending * party, and the receiver's version extends classes that are not extended by * the sender's version. This may also occur if the serialization stream has * been tampered; hence, readObjectNoData is useful for initializing * deserialized objects properly despite a "hostile" or incomplete source * stream. * * <p>Serializable classes that need to designate an alternative object to be * used when writing an object to the stream should implement this * special method with the exact signature: */
像前面说的Externalizable接口就是继承了Serializable接口,其中定义了两个方法,源码如下:
package java.io; import java.io.ObjectOutput; import java.io.ObjectInput; public interface Externalizable extends java.io.Serializable { void writeExternal(ObjectOutput out) throws IOException; void readExternal(ObjectInput in) throws IOException, ClassNotFoundException; }
下面举两个例子,一个是实现Serialiabel接口,重写readObject,writeObject方法,来实现加密,另一个是实现Externalizable接口,重写writeExternal,readExternal方法,实现自主控制序列化反序列化流程。
例子1:
package objectIO; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectInputStream.GetField; import java.io.ObjectOutputStream; import java.io.ObjectOutputStream.PutField; import java.io.Serializable; public class ObjectIOTest { public static void main(String[] args) { Person1 person = new Person1("tom", 19, "boy", "administrator"); Person1.hobby = "swim"; try { ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("./src/objectIO/test.txt")); oos.writeObject(person); oos.close(); System.out.println("序列化成功"); ObjectInputStream ois = new ObjectInputStream(new FileInputStream( "./src/objectIO/test.txt")); Person1 temp = (Person1) ois.readObject(); System.out.println("反序列化成功"); System.out.println(temp); ois.close(); } catch (Exception e) { e.printStackTrace(); } } } class Person1 implements Serializable { private static final long serialVersionUID = 1L; private String name; private int age; private String sex; private String password; public static String hobby; public Person1() { } public Person1(String name, int age) { this.name = name; this.age = age; } public Person1(String name, int age, String sex) { this.name = name; this.age = age; this.sex = sex; } public Person1(String name, int age, String sex, String numbers) { this.name = name; this.age = age; this.sex = sex; this.password = numbers; } @Override public String toString() { return "[name: " + name + " age: " + age + " sex: " + sex + " hobby: " + hobby + " password: " + password + "]"; } private String getSecretNumber(String password){ byte[] bytes = password.getBytes(); for(int i = 0; i < bytes.length; i++){ bytes[i] += 1; } return new String(bytes,0,bytes.length); } private String explainSecretNumber(String password){ byte[] bytes = password.getBytes(); for(int i = 0; i < bytes.length; i++){ bytes[i] -= 1; } return new String(bytes,0,bytes.length); } private void writeObject(java.io.ObjectOutputStream out) { try { PutField putField = out.putFields(); putField.put("name", name); putField.put("age", age); putField.put("sex", sex); putField.put("password", getSecretNumber(password)); System.out.println(password+"加密后为"+getSecretNumber(password)); out.writeFields(); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } private void readObject(java.io.ObjectInputStream in) { try { GetField getField = in.readFields(); Object object1 = getField.get("name", null); int object2 = getField.get("age", 0); Object object3 = getField.get("sex", null); Object object4 = getField.get("password", null); name = object1.toString(); age = object2; sex = object3.toString(); password = explainSecretNumber(object4.toString()); } catch (Exception e) { e.printStackTrace(); } } }执行上述代码可以得到如下打印:
例子2:
package objectIO; import java.io.Externalizable; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectInputStream; import java.io.ObjectInputStream.GetField; import java.io.ObjectOutput; import java.io.ObjectOutputStream; import java.io.ObjectOutputStream.PutField; import java.io.Serializable; public class ObjectIOTest1 { public static void main(String[] args) { Person person = new Person("tom", 19, "boy", "administrator"); Person.hobby = "swim"; try { ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("./src/objectIO/test1.txt")); oos.writeObject(person); oos.close(); System.out.println("序列化成功"); ObjectInputStream ois = new ObjectInputStream(new FileInputStream( "./src/objectIO/test1.txt")); Person temp = (Person) ois.readObject(); System.out.println("反序列化成功"); System.out.println(temp); ois.close(); } catch (Exception e) { e.printStackTrace(); } } } class Person implements Externalizable ad96 { private static final long serialVersionUID = 1L; private String name; private int age; private String sex; private String password; public static String hobby; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public Person(String name, int age, String sex) { this.name = name; this.age = age; this.sex = sex; } public Person(String name, int age, String sex, String numbers) { this.name = name; this.age = age; this.sex = sex; this.password = numbers; } @Override public String toString() { return "[name: " + name + " age: " + age + " sex: " + sex + " hobby: " + hobby + " password: " + password + "]"; } @Override public void writeExternal(ObjectOutput out) throws IOException { // TODO Auto-generated method stub out.writeObject(name); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { // TODO Auto-generated method stub name = (String) in.readObject(); } }
执行上述代码打印如下:
由上可见,只序列化了name属性。
以上为本篇内容。
相关文章推荐
- Java IO--对象序列化Serializable、ObjectOutputStream、ObjectInputStream、transient
- java序列化与ObjectOutputStream和ObjectInputStream的实例详解
- java IO笔记(FileterInputStream/FilterOutputStream)
- Java IO ObjectInputStream和ObjectOutputStream
- Java-IO之对象输入流输出流(ObjectInputStream和ObjectOutputStream)
- Java使用ObjectOutputStream和ObjectInputStream序列号对象报java.io.EOFException异常的解决方法
- java语言编程IO流之对象序列化和ObjectInputStream与ObjectOutputStream
- Java对象序列化ObjectOutputStream和ObjectInputStream示例
- Java对象序列化ObjectOutputStream和ObjectInputStream示例
- Java-IO之对象输入流输出流(ObjectInputStream和ObjectOutputStream)
- Java的IO操作(三) - 对象的序列化、ObjectInputStream和ObjectOutputStream类
- Java IO之对象的序列化、ObjectInputStream和ObjectOutputStream类
- Java-IO之对象输入流输出流(ObjectInputStream和ObjectOutputStream)
- Java的IO操作(三) - 对象的序列化、ObjectInputStream和ObjectOutputStream类
- java文件传输基础:序列化和反序列化ObjectInputStream/ObjectOutputStream
- Java_io体系之ObjectInputStream、ObjectOutputStream简介、走进源码及示例——11
- 学习笔记之java.io包中的字节流(上)—— 基本的InputStream和OutputStream
- java IO笔记(PipedInputStream/PipedOutputStream)
- Java IO ---学习笔记(InputStream 和 OutputStream)
- java.io.ObjectOutputStream.putFields()和java.io.ObjectInputStream. readFields()