Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会 比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java对象序 列化就能够帮助我们实现该功能。
使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意地是,对象序列化保存的是对象的"状态",即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。
我们可以使用java的IO流中的对象流ObjectOutputStream和ObjectInputStream来实现序列化和反序列化的操作。
java对象序列化就是将对象转换为字节流,然后可以通过这些值再生成相同状态的对象。对象序列化是对象持久化的一种实现方法,它是将一个对象的属性和方法转化为一种序列化的格式以用于存储和传输,反序列化就是根据这些保存的信息重建对象的过程。
序列化的实现:将需要被序列化的类实现Serializable接口,然后使用一个输出(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。所以使用ObjectOutputStream类实现序列化,ObjectInputStream类实现反序列化。
首先定义一个Person类,并实现Serializable接口:
public class Person implements Serializable {
public String name;
public int age;
public Person(String name,int age){
this.name=name;
this.age=age;
}
@Override
public String toString() {
return "person [name=" + name + ", age=" + age + "]";
}
}
上面代码来看我们没有实现任何Serializable接口的方法,其实Serializable接口是一个标记接口,用于启动其序列化功能,此接口没有定义任何方法,只是作为一个标记。如图
1.ObjectOutputStream实现序列化
示例代码
public static void main(String[] args) {
try {
//序列化文件輸出流
OutputStream outputStream=new FileOutputStream("L:\\myfile.tmp");
//构建缓冲流
BufferedOutputStream buf=new BufferedOutputStream(outputStream);
//构建字符输出的对象流
ObjectOutputStream obj=new ObjectOutputStream(buf);
//序列化数据写入
obj.writeUTF("好好学习,天天向上");//字符串
obj.writeInt(18);//int类型
obj.writeObject(new Person("小明", 21));//Person对象
//关闭流
obj.close();
buf.close();
outputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
运行结果:
数据已经被序列化,无法正常显示出来。这个tmp的文件格式在我们很多应用程序中都可以看到。比如qq等,那怎么知道我们有没有序列化成功呢,我们来反序列化看看。
2.ObjectInputStream实现反序列化
示例代码:
public static void main(String[] args) {
try {
InputStream inputStream=new FileInputStream("L:\\myfile.tmp");
//构建缓冲流
BufferedInputStream buf=new BufferedInputStream(inputStream);
//构建字符输入的对象流
ObjectInputStream obj=new ObjectInputStream(buf);
//数据读取--只能按存入的顺序读取
String info=obj.readUTF();
int ok=obj.readInt();
Person tempPerson=(Person)obj.readObject();
System.out.println("str: "+info+"\n"+"整数为:"+ok+"\n"+"Person对象为:"+tempPerson);
//关闭流
obj.close();
buf.close();
inputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
运行结果:
切记:读取的顺序要和写入的顺序一样,不然会读取出错,如把String info=obj.readUTF();和int ok=obj.readInt();换个位置就会出错
3.序列化一组对象
上面的读取只能按照顺序读取,如果我们不知道有多少和对象需要读取,那么将无法读取出来,为了解决这问题,可以使用序列化一组对象,然后在反序列这一组对象出来。反序列也必须按顺序来读取
如:序列化一组对象
public static void main(String[] args) {
try {
//序列化文件輸出流
OutputStream outputStream=new FileOutputStream("L:\\myfile.tmp");
//构建缓冲流
BufferedOutputStream buf=new BufferedOutputStream(outputStream);
//构建字符输出的对象流
ObjectOutputStream obj=new ObjectOutputStream(buf);
//序列化数据写入
obj.writeInt(18);//int类型
Person []persons={new Person("小明", 21),new Person("小黑", 20),
new Person("小红", 22),new Person("小白", 18)};
obj.writeObject(persons);//一组Person对象
//关闭流
obj.close();
buf.close();
outputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
读取一组序列化,也要按顺序读取,要先读取int
public static void main(String[] args) {
try {
InputStream inputStream = new FileInputStream("L:\\myfile.tmp");
// 构建缓冲流
BufferedInputStream buf = new BufferedInputStream(inputStream);
// 构建字符输入的对象流
ObjectInputStream obj = new ObjectInputStream(buf);
// 数据读取--只能按存入的顺序读取
int ok = obj.readInt();
Person []tempPerson = (Person[]) obj.readObject();
System.out.println("整数为:" + ok );
for(int i=0;i<tempPerson.length;i++)
System.out.println(tempPerson[i]);
// 关闭流
obj.close();
buf.close();
inputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
运行结果:
4.关键字:transient
如果把Person的age属性前面加上关键字transient,则Person的age属性将不会被序列化。即如果用transient声明一个变量,当对象存储时,它的值不需要维持。
代码还是使用序列化和反序列化一组对象的代码,只是在Person类的age属性前加上关键字transient
运行结果: