Java IO系列2 InputStream之ObjectInputStream

(未整理...继续中)


一、对象序列化

Java.io.ObjectOutputStream

void writeObject( Object object) 

写出指定对象到ObjectOutputStream,这个方法将存储指定对象的类、类的签名以及这个类及其超类中所有的的非静态和非瞬时的域的值。

Java.io.ObjectInputStream

void readObject()

从ObjectInputStream中读入一个对象,读回对象的类、类的签名以及这个类及其超类中所有非静态和非瞬时域的值,它执行的反序列化允许恢复多个对象引用。


1.Serializable接口

1.当一个可序列化类有多个父类时(包括直接父类和间接父类),这些父类要么有无参数的构造器,要么也是可序列化的,否则反序列化时抛出InvalidClassException。如果父类是不可序列化的,只是带有无参数的构造器,则该父类中定义的Field值不会序列化到二进制流中

public class Computer {

	public String name;
	public int numKernel;
	private String ip;
	
	public  Computer(String name,int numKernel) {
		System.out.println("Computer  initialization ");
	}
	
	@Override
	public String toString() {
		return "Computer [name=" + name + ", numKernel=" + numKernel + ", ip=" + ip + "]";
	}
}

public class LenovoComputer extends Computer implements Serializable{
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
//	public LenovoComputer(){
//		System.out.println("LenovoComputer 无参数 initialization ");
//	}
	
	public LenovoComputer(String name,int kernel){
		super(name, kernel);
		this.name = name ;
		this.numKernel = kernel;
		System.out.println("LenovoComputer initialization ");
	}

	@Override
	public String toString() {
		super.toString();
		return "LenovoComputer [name=" + name + ", numKernel=" + numKernel +  "]";
	}
}

public class ObjectStreamDemo implements IDemo{

	@Override
	public void test() {
		try {
			ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("Object.txt"));
			LenovoComputer computer = new LenovoComputer("Lenovo", 4);
			outputStream.writeObject(computer);
			
			ObjectInputStream inputStream  = new ObjectInputStream(new FileInputStream("Object.txt"));
			try {
				computer = (LenovoComputer) inputStream.readObject();
				System.out.println(computer.toString());
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

Computer  initialization 
LenovoComputer initialization 
java.io.InvalidClassException: datastream.LenovoComputer; no valid constructor
	at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:147)
	at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:755)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1751)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1347)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:369)
	at datastream.ObjectStreamDemo.test(ObjectStreamDemo.java:23)

2.对象引用序列化

以下内容引用李刚的《疯狂Java讲义》

public class Person  implements Serializable  {
	private  String name;
	private  int  age;
	public Person(String name,  int  age) {
		super();
		this.name = name;
		this.age = age;
	}
}

public class Teacher implements Serializable {
	private String name;
	private Person student;
	public Teacher(String name, Person student) {
		super();
		this.name = name;
		this.student = student;
	}
}

Person per =  new Person("悟空", 500);
Teacher t1  = new Teacher("唐僧", per);
Teacher t2  = new Teacher("菩提老祖", per);


如果把上面的三个对象都序列化了,共享的Person怎么序列化呢!

其实Java序列化机制采用了一种特殊的序列化算法,

1、所有保存到磁盘中的对象都有有一个序列化编号。

2、当程序试图序列化一个对象时,程序将先检查该对象是否已经被序列化,只有该对象从未(在本次虚拟机中)被序列化过,系统才会讲过该对象转换成字节序列并输出。

3、如果某个对象已经序列化过,程序只是直接输出一个序列化编号,而不是再次重新序列化该对象。

oos.writeObject(t1);
oos.writeObject(t2);
oos.writeObject(per);


由于该机制,如果多次序列化同一个对象时,只有第一次序列化时才会把该对象转换成字节序列输出,但是序列化一个可变对象时,只有第一次使用writeObject( )方法输出时才会将该对象转换成字节序列并输出,当程序再次调用writeOject()方法时,程序只是输出前面的序列化编号,即使后面的Field值已经改变,改变的Field值也不会输出



2.writeObject()方法与readObject()方法

当某个字段被声明为transient后,默认序列化机制就会忽略该字段,除了将transitive关键字去掉之外,是否还有其它方法能使它再次可被序列化?方法之一就是在Person类中添加两个方法:writeObject()与readObject(),如下所示:

public class Person implements Serializable {  
    ...  
    transient private Integer age = null;  
    ...  
 
    private void writeObject(ObjectOutputStream out) throws IOException {  
        out.defaultWriteObject();  
        out.writeInt(age);  
    }  
 
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {  
        in.defaultReadObject();  
        age = in.readInt();  
    }  
} 

在writeObject()方法中会先调用ObjectOutputStream中的defaultWriteObject()方法,该方法会执行默认的序列化机制,如5.1节所述,此时会忽略掉age字段。然后再调用writeInt()方法显示地将age字段写入到ObjectOutputStream中。readObject()的作用则是针对对象的读取,其原理与writeObject()方法相同。再次执行SimpleSerial应用程序,则又会有如下输出:

arg constructor  
[John, 31, MALE] 
必须注意地是,writeObject()与readObject()都是private方法,那么它们是如何被调用的呢?毫无疑问,是使用反射。详情可以看看ObjectOutputStream中的writeSerialData方法,以及ObjectInputStream中的readSerialData方法。



序列化文件的格式

Java.io.ObjectStreamConstants



3.Externalizable接口

<span style="font-size:12px;">public interface Externalizable extends java.io.Serializable {
    /**
     * The object implements the writeExternal method to save its contents
     * by calling the methods of DataOutput for its primitive values or
     * calling the writeObject method of ObjectOutput for objects, strings,
     * and arrays.
     *
     * @serialData Overriding methods should use this tag to describe
     *             the data layout of this Externalizable object.
     *             List the sequence of element types and, if possible,
     *             relate the element to a public/protected field and/or
     *             method of this Externalizable class.
     *
     * @param out the stream to write the object to
     * @exception IOException Includes any I/O exceptions that may occur
     */
    void writeExternal(ObjectOutput out) throws IOException;

    /**
     * The object implements the readExternal method to restore its
     * contents by calling the methods of DataInput for primitive
     * types and readObject for objects, strings and arrays.  The
     * readExternal method must read the values in the same sequence
     * and with the same types as were written by writeExternal.
     *
     * @param in the stream to read data from in order to restore the object
     * @exception IOException if I/O errors occur
     * @exception ClassNotFoundException If the class for an object being
     *              restored cannot be found.
     */
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}</span>
实现Externalizable接口后,之前基于Serializable接口 的序列化机制就将失效,序列化的细节由程序员自己实现,当读取对象时,会

调用被序列化类的无参数构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中,所以,实现

Externalizable接口的类必须要提供一个无参的构造器,且它的访问权限为public。


4.readResolve()方法与writeReplace()方法

当我们使用Singleton模式时,应该是期望某个类的实例应该是唯一的,但如果该类是可序列化的,那么情况可能略有不同。此时对第2节使用的Person类进行修改,使其实现Singleton模式,如下所示:

public class Person implements Serializable {  
 
    private static class InstanceHolder {  
        private static final Person instatnce = new Person("John", 31, Gender.MALE);  
    }  
 
    public static Person getInstance() {  
        return InstanceHolder.instatnce;  
    }  
 
    private String name = null;  
 
    private Integer age = null;  
 
    private Gender gender = null;  
 
    private Person() {  
        System.out.println("none-arg constructor");  
    }  
 
    private Person(String name, Integer age, Gender gender) {  
        System.out.println("arg constructor");  
        this.name = name;  
        this.age = age;  
        this.gender = gender;  
    }  
    ...  
} 

同时要修改SimpleSerial应用,使得能够保存/获取上述单例对象,并进行对象相等性比较,如下代码所示:

public class SimpleSerial {  
 
    public static void main(String[] args) throws Exception {  
        File file = new File("person.out");  
        ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));  
        oout.writeObject(Person.getInstance()); // 保存单例对象  
        oout.close();  
 
        ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));  
        Object newPerson = oin.readObject();  
        oin.close();  
        System.out.println(newPerson);  
 
        System.out.println(Person.getInstance() == newPerson); // 将获取的对象与Person类中的单例对象进行相等性比较  
    }  
}

执行上述应用程序后会得到如下结果:


arg constructor  
[John, 31, MALE]  
false 
值得注意的是,从文件person.out中获取的Person对象与Person类中的单例对象并不相等。为了能在序列化过程仍能保持单例的特性,可以在Person类中添加一个readResolve()方法,在该方法中直接返回Person的单例对象,如下所示:

public class Person implements Serializable {  
 
    private static class InstanceHolder {  
        private static final Person instatnce = new Person("John", 31, Gender.MALE);  
    }  
 
    public static Person getInstance() {  
        return InstanceHolder.instatnce;  
    }  
 
    private String name = null;  
 
    private Integer age = null;  
 
    private Gender gender = null;  
 
    private Person() {  
        System.out.println("none-arg constructor");  
    }  
 
    private Person(String name, Integer age, Gender gender) {  
        System.out.println("arg constructor");  
        this.name = name;  
        this.age = age;  
        this.gender = gender;  
    }  
 
    private Object readResolve() throws ObjectStreamException {  
        return InstanceHolder.instatnce;  
    }  
    ...  
} 

再次执行本节的SimpleSerial应用后将如下输出:


arg constructor  
[John, 31, MALE]  
true 
无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象。


当进行序列化的时候:


首先JVM会先调用writeReplace方法,在这个阶段,我们可以进行张冠李戴,将需要进行序列化的对象换成我们指定的对象.


跟着JVM将调用writeObject方法,来将对象中的属性一个个进行序列化,我们可以在这个方法中控制住哪些属性需要序列化.


当反序列化的时候:


JVM会调用readObject方法,将我们刚刚在writeObject方法序列化好的属性,反序列化回来.


然后在readResolve方法中,我们也可以指定JVM返回我们特定的对象(不是刚刚序列化回来的对象).


注意到在writeReplace和readResolve,我们可以严格控制singleton的对象,在同一个JVM中完完全全只有唯一的对象,控制不让singleton对象产生副本.






  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值