java IO笔记(序列化与ObjectInputStream、ObjectOutputStream)

25 篇文章 0 订阅

本篇讲的内容是序列化,以及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接口:


下面先用一个最简单的例子来让大家熟悉序列化和反序列化的操作:

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;
	}
	

	@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 {

	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属性。

以上为本篇内容。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

moonfish0607

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值