序列化的理解与应用
1. 什么是序列化
如果我们需要在网络上传输文件,我们不可能直接传输给对方;有人会问:为什么我在本机上可以直接存储数据
列如:我们需要操作数据库,我们是把数据直接储存在数据库里面,并没有进行序列化;在这里需要说明一点是:
在本机上储存数据,我们是直接通过计算服务与计算机的内存打交道;
但是,不同电脑通过同一个网络进行连接传输数据时,是没有办法直接传输数据的,网络之间传输数据,是通过编码
进行传输的,因此就需要使用序列化;
2. 实列化与没有实例化
列如:
(1)未实列化时,person是保存到堆中的一个实例;
1.Person person = new Person();
2.person.setWidth(37);
(2)实例化:通过实例化,我们可以把person会被保存到一个.ser文件里面
1.FileOutPutStream out = new FieOutPutStream("p.ser");
2.ObjectOutputStream os = new ObjectOutputStream(out);
3. os.write(person);
4. os.close();
3.如何进行实例化
第一步:创建文件输出流:FileOutPutStream out = new FieOutPutStream();
第二步:创建对象输出流:ObjectOutStream
os
= new ObjectOutPutStream
()
第三步:调用输出流的write()方法;
第四步:关闭流:close()
4.需要注意的事项
1)序列化时,只对对象的状态进行保存,而不管对象的方法;
2)当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
3)当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
4)并非所有的对象都可以序列化,,至于为什么不可以,有很多原因了,比如:
1.安全方面的原因,比如一个对象拥有private,public等field,对于一个要传输的对象,比如写到文件,或者进行rmi传输 等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的。
5.java序列化的过程
Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。
使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意地是,对象序列化保存的是对象的"状态",即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。
除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。
实列:
1)定义一个枚举类型(枚举类型可以被序列化):
public enum Gender {
MALE, FEMALE
}
2)定义一个person类;
public class Person implements Serializable {
private String name = null;
private Integer age = null;
private Gender gender = null;
public Person() {
System.out.println("none-arg constructor");
}
public Person(String name, Integer age, Gender gender) {
System.out.println("arg constructor");
this.name = name;
this.age = age;
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Gender getGender() {
return gender;
}
public void setGender(Gender gender) {
this.gender = gender;
}
@Override
public String toString() {
return "[" + name + ", " + age + ", " + gender + "]";
}
}
3)定义一个main()方法实现序列化
public class SimpleSerial {
public static void main(String[] args) throws Exception {
File file = new File("person.out");
ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));
Person person = new Person("John", 101, Gender.MALE);
oout.writeObject(person);
oout.close();
ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
Object newPerson = oin.readObject(); // 没有强制转换到Person类型
oin.close();
System.out.println(newPerson);
}
}
/*上述程序的输出的结果为:
arg constructor
[John, 31, MALE]
此时必须注意的是,当重新读取被保存的Person对象时,并没有调用Person的任何构造器,看起来就像是直接使用字节将Person对象还原出来的。
当Person对象被保存到person.out文件中之后,我们可以在其它地方去读取该文件以还原对象,但必须确保该读取程序的CLASSPATH中包含有Person.class(哪怕在读取Person对象时并没有显示地使用Person类,如上例所示),否则会抛出ClassNotFoundException。*/
6.与Serializable有关的关键字以及方法
- transient关键字
当某个字段被声明为transient后,默认序列化机制就会忽略该字段。此处将Person类中的age字段声明为transient,如下所示,
public class Person implements Serializable {
transient private Integer age = null;
}
再执行SimpleSerial
应用程序,会有如下输出:
arg constructor
[John, null, MALE]
可见,age字段未被序列化。
2. writeObject()方法与readObject()方法
对于上述已被声明为transitive的字段age,除了将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()方法,该方法会执行默认的序列化机制,此时会忽略掉age字段。然后再调用writeInt()方法显示地将age字段写入到ObjectOutputStream中。readObject()的作用则是针对对象的读取,其原理与writeObject()方法相同。
再次执行SimpleSerial应用程序,则又会有如下输出:arg constructor [John, 31, MALE]
必须注意地是,writeObject()与readObject()都是private方法,那么它们是如何被调用的呢?毫无疑问,是使用反射。详情可见ObjectOutputStream中的writeSerialData方法,以及ObjectInputStream中的readSerialData方法。