Java对象序列化
来源:互联网 发布:网络正常微信发不出去 编辑:程序博客网 时间:2024/06/07 01:57
参考:
http://www.blogjava.net/jiangshachina/archive/2012/02/13/369898.html
1、什么是对象序列化
(1)Java平台允许我们再内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,也就是说,这些对象的生命周期不会久于JVM的生命周期。现实生活中,可能要求在JVM停止运行之后能够持久化指定的对象,并在将来重新读取被保存的对象,Java序列化就可以完成这些工作
(2)Java序列化保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象,对象序列化保存的是这些对象的“状态”,也就是成员变量,所以序列化不会序列化静态变量
(3)序列化使用情况:远程调用方法(RMI)、网络中传递对象、持久化对象
2、序列化方法
第一种是实现java.io.Serializable接口,另一种是实现Externalizable接口(后面会有介绍),Externalizable继承于Serializable
1、简单使用示例
Gender类,是一个枚举类型,表示性别
public enum Gender{ MALE,FEMALE; }
每个枚举类型默认都继承类java.lang.Enum,该类实现了Serializable接口,所以枚举对象都是默认可以被序列化的。
Person类,实现了Serizlizable接口:
public class Person implements Serializable{ public enum Gender{ MALE,FEMALE; } private int age; private String name; private Gender gender; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Gender getGender() { return gender; } public void setGender(Gender gender) { this.gender = gender; } @Override public String toString() { return "Person [age=" + age + ", name=" + name + ", gender=" + gender + "]"; } public Person(int age, String name, Gender gender) { super(); this.age = age; this.name = name; this.gender = gender; } public Person() { super(); // TODO Auto-generated constructor stub }}
MainClass,序列化的测试程序:
public class MainClass { public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException { Person p=new Person(1,"1v",Gender.FEMALE); FileOutputStream fo=new FileOutputStream("person.out"); ObjectOutputStream oos=new ObjectOutputStream(fo); oos.writeObject(p); oos.close(); File file=new File("person.out"); ObjectInputStream ois=new ObjectInputStream(new FileInputStream(file)); Object np=ois.readObject(); System.out.println(np); ois.close(); }}
程序输出:
Person [age=1, name=1v, gender=FEMALE]
当Person对象被保存到person.out文件中之后,我们可以在其它地方去读取该文件以还原对象,但必须确保该读取程序的CLASSPATH中包含Person.class(哪怕在读取Person对象时并没有显示地使用Person类,如上例所示),否则会抛出ClassNotFoundException。
2、Serializable的作用
为什么一个类实现了Serializable接口,它就可以被序列化呢?在ObjectOutputStream类中有如下代码:
private void writeObject0(Object obj, boolean unshared) throws IOException { ... if (obj instanceof String) { writeString((String) obj, unshared); } else if (cl.isArray()) { writeArray(obj, desc, unshared); } else if (obj instanceof Enum) { writeEnum((Enum<?>) obj, desc, unshared); } else if (obj instanceof Serializable) { writeOrdinaryObject(obj, desc, unshared); } else { if (extendedDebugInfo) { throw new NotSerializableException( cl.getName() + "\n" + debugInfoStack.toString()); } else { throw new NotSerializableException(cl.getName()); } ... }
从上述代码可知,如果被写对象的类型是String,或数组,或Enum,或Serializable,那么就可以对该对象进行序列化,否则将抛出NotSerializableException。
3、默认序列化机制
如果一个类只是单纯的实现Serializable接口而没有做任何特殊处理,那么使用的是默认的序列化机制。使用默认机制,在序列化对象时,不仅会序列化当前对象本身,还会对该对象引用的其他对象也进行序列化,同样地,这些其他对象引用的另外对象也将被序列化,以此类推。所以,如果一个对象包含的成员变量是容器类对象,而这些容器所含有的 元素也是容器类对象,那么这个序列化的过程就会较复杂,开销比较大。
4、特殊序列化(非默认机制)
4.1 transient关键字
当某个字段声明为transient后,默认序列化机制就会忽略该字段。此例将Person类中的age字段用transient关键字修饰:
private transient int age;
再执行MainClass,输出:
Person [age=0, name=1v, gender=FEMALE]
age是0而不是1,age字段没有被序列化。
4.2 writeObject()方法和readObject()方法
对于已被声明为transitiive的字段age,除了去掉transitive关键字之外,还可以使用writeObject()方法和readObject()方法进行序列化,在Person类中重写这两个方法如下:
private void writeObject(ObjectOutputStream out) throws IOException { // TODO Auto-generated method stub out.defaultWriteObject(); out.writeInt(age); } private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException { // TODO Auto-generated method stub in.defaultReadObject(); age=in.readInt(); }
再次执行MainClass,结果为:
Person [age=1, name=1v, gender=FEMALE]
writeObject()方法首先调用defaultWriteObject()方法,该方法执行后会忽略age字段,然后调用writeInt()方法,又把age字段进行序列化。readObject()方法也是如此。值得注意的是,writeObject()与readObject()都是private方法,那么它们是如何被调用的呢?是ObjectOutputStream中的writeSerialData方法使用了反射机制对他们进行了调用。
4.3 Externalizable接口(使用这个接口的类必须要有无参构造器)
无论是使用transient关键字,还是使用writeObject()和readObject()方法,其实都是基于Serializable接口的序列化。JDK中提供了另一个序列化接口–Externalizable,使用该接口之后,之前基于Serializable接口的序列化机制就将失效。此时将Person类修改成如下:
public class Person implements Serializable,Externalizable{ public enum Gender{ MALE,FEMALE; } private transient int age; private String name; private Gender gender; public Person() { super(); System.out.println("无参构造器"); // TODO Auto-generated constructor stub } ...... private void writeObject(ObjectOutputStream out) throws IOException { // TODO Auto-generated method stub out.defaultWriteObject(); out.writeInt(age); } private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException { // TODO Auto-generated method stub in.defaultReadObject(); age=in.readInt(); } @Override public void writeExternal(ObjectOutput out) throws IOException { // TODO Auto-generated method stub } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { // TODO Auto-generated method stub }}
再次执行MainClass,结果为:
无参构造器
Person [age=0, name=null, gender=null]
Externalizable继承于Serializable,当使用该接口时,序列化的细节需要由程序员去完成。实现了Externalizable接口后,因为writeExternal()方法和readExternal()方法没有作为,输出的变量都没有序列化
另外,使用Externalizable进行序列化,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。这就是为什么在此次序列化过程中Person类的无参构造器会被调用。由于这个原因,实现Externalizable接口的类必须要提供一个无参的构造器,且它的访问权限为public。
Person类中重写这两个方法如下:
@Override public void writeExternal(ObjectOutput out) throws IOException { // TODO Auto-generated method stub out.writeInt(age); out.writeObject(gender); out.writeObject(name); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { // TODO Auto-generated method stub age=in.readInt(); gender=(Gender) in.readObject(); name=(String) in.readObject(); }
再次运行MainClass之后,结果为:
无参构造器
Person [age=1, name=1v, gender=FEMALE]
需要注意的是writeExternal中的out.writexx()和readExternal中的in.readxx()一定要对应,否则同一种类型的变量可能会出现赋值错位,比如在person类中,我新增了一个String类型变量fav,然后进行如下更改:
@Override public void writeExternal(ObjectOutput out) throws IOException { // TODO Auto-generated method stub out.writeInt(age); out.writeObject(gender); out.writeObject(name); //这里先write了name out.writeObject(fav); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { // TODO Auto-generated method stub age=in.readInt(); gender=(Gender) in.readObject(); fav=(String) in.readObject();//然后先read了fav name=(String) in.readObject(); }
输出结果:
无参构造器
Person [age=1, name=唱歌, gender=FEMALE fav=1v]
发现fav和name赋值错位了!
4.4 readResolve()方法
当我们使用Singleton模式时,应该是期望某个类的实例是唯一的,但是如果该类是可序列化的,情况会略有不同,对person类进行修改:
public class Person implements Serializable,Externalizable{ public enum Gender{ MALE,FEMALE; } private transient int age; private String name; private Gender gender; private static class InstanceHolder{ private static final Person instance=new Person(1,"1v",Gender.FEMALE); } public static Person getInstance(){ return InstanceHolder.instance; } ......}
然后在MainClass也进行修改:
public class MainClass { public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException { FileOutputStream fo=new FileOutputStream("person.out"); ObjectOutputStream oos=new ObjectOutputStream(fo); oos.writeObject(Person.getInstance()); oos.close(); File file=new File("person.out"); ObjectInputStream ois=new ObjectInputStream(new FileInputStream(file)); Object np=ois.readObject(); System.out.println(np==Person.getInstance()); ois.close(); }
最后输出:
无参构造器
false
反序列化的对象和getInstance得到的对象并不相等,因为反序列化实际上是new了一个新对象
现在在person类中添加readResolve()方法:
public class Person implements Serializable,Externalizable{ public enum Gender{ MALE,FEMALE; } private transient int age; private String name; private Gender gender; private static class InstanceHolder{ private static final Person instance=new Person(1,"1v",Gender.FEMALE); } public static Person getInstance(){ return InstanceHolder.instance; } ...... private Object readResolve() { // TODO Auto-generated method stub return InstanceHolder.instance; }}
再次执行MainClass,结果为:
无参构造器
true
无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象,而被创建的对象则会被垃圾回收掉。
- Java对象序列化
- Java对象序列化
- Java对象序列化
- Java对象序列化
- java对象序列化
- Java 对象序列化
- Java 对象序列化
- Java对象序列化
- Java对象序列化
- java对象序列化
- Java 对象序列化
- java对象序列化
- Java-对象序列化
- java对象序列化
- JAVA对象序列化
- java对象序列化
- Java对象序列化
- Java 对象序列化
- TortoiseSVN 锁的使用
- 专业程序员路上用到的各种优秀框架、神器、资料
- cocos 报错:
- R-FCN+ResNet-50用自己的数据集训练模型(python版本)
- 1068. Find More Coins 解析
- Java对象序列化
- 静态链接和动态链接
- 不需要jni目录使用ndk
- 如何在ASP.NET MVC5不用EF框架完成对数据库的连接操作
- 洛谷 P1433 吃奶酪 (dfs)
- intellj 在线生成工具2
- spring cloud eureka服务发现(高可用)
- Unity接入应用宝YSDK
- android5.1添加android长按power键重启功能