本篇讲的内容是序列化,以及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属性。
以上为本篇内容。