首页 > 编程 > Java > 正文

java序列化学习

2019-11-08 01:54:58
字体:
来源:转载
供稿:网友

定义: java序列化是指把Java对象转换为字节序列的过程,保存在硬盘中;而Java反序列化是指把字节序列恢复为Java对象的过程。

优点: 好处一是实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里),二是,利用序列化实现远程通信,即在网络上传送对象的字节序列。

JDK类库中序列化API

java.io.ObjectOutputStream:表示对象输出流

它的writeObject(Object obj)方法可以对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。(将对象序列化保存到文件中)

java.io.ObjectInputStream:表示对象输入流

它的readObject()方法源输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回。

简单例子

1、先创建一个枚举类

public enum Gender { MALE,FEMALE}

2、创建一个Person类。 注意: 如果被写对象的类型是String,或数组,或Enum,或Serializable,那么就可以对该对象进行序列化,否则将抛出NotSerializableException。 序列化是指对象的序列化,只会关注对象的属性,不会关注类中的静态变量

public class Person implements Serializable{ PRivate String name; private Integer age; private 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; } 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; } @Override public String toString() { return "[" + name + ", " + age + ", " + gender + "]"; } }

3、创建一个测试类:

public class SimpleSerialTest { public static void main(String[] args) { //新建一个文件 File file = new File("person.out"); try { ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file)); Person person = new Person("john", 21, Gender.MALE); oout.writeObject(person); oout.close(); //但必须确保该读取程序的CLASSPATH中包含有Person.class //(哪怕在读取Person对象时并没有显示地使用Person类,如上例所示),否则会抛出ClassNotFoundException。 ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file)); Object per = oin.readObject(); oin.close(); System.out.println(per.toString()); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } }}

上述程序的输出结果: arg constructor [john, 21, MALE]

*此时必须注意的是,当重新读取被保存的Person对象时,并没有调用Person的任何构造器,看起来就像是直接使用字节将Person对象还原出来的。 当Person对象被保存到person.out文件中之后,我们可以在其它地方去读取该文件以还原对象,但必须确保该读取程序的CLASSPATH中包含有Person.class(哪怕在读取Person对象时并没有显示地使用Person类,如上例所示),否则会抛出ClassNotFoundException。*

默认的序列化机制 一个类被序列化,不仅他本身被序列化,还会对该对象引用的其他对象也将被序列化,所以当一个类对象引用了某个容器类对象,而容器对象所包含的元素也是容器对象,当被序列化的时候,会出现将容器对象也序列化,此时会带来很大的内存开销。

在现实应用中,有些时候不能使用默认序列化机制。比如,希望在序列化过程中忽略掉敏感数据,或者简化序列化过程。下面将介绍若干影响序列化的方法。如:transient关键字、writeObject()方法与readObject()方法指定那些属性被序列化和输出、Externalizable接口、readResolve()方法。

1、transient关键字 当某个字段被声明为transient后,默认序列化机制就会忽略该字段。

/** * 测试transient功能 * @author DELL * */public class Animal implements Serializable{ private String name; private int age; //使用transient,目的是为了避免敏感信息在序列化的时候 transient private String getter;//持有者 //测试static类熟悉是否被序列化 public static int weight; public void setGetter(String getter) { this.getter = getter; } ......省略set,get,构造方法 @Override public String toString() { return "Animal [name=" + name + ", age=" + age + ", getter=" + getter + "]"; }}

测试类

public class Test { public static void main(String[] args) { File fileAnimal = new File("animal.out"); writeFile(fileAnimal, new Animal("niuniu", 3, "张三")); readFile(fileAnimal); } //序列化公共处理方法 public static void writeFile(File file,Object obj){ try { ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file)); //Person person = new Person("zhangfei", 23, Gender.FEMALE); oout.writeObject(obj); oout.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //反序列化公共处理方法 public static void readFile(File file){ try { ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file)); Object obj = oin.readObject(); oin.close(); System.out.println(obj.toString()); //System.out.println(SinglePerson.getInstance() == ((SinglePerson)obj)); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } }}

测试结果: Animal [name=niuniu, age=3, getter=null] 结果可见:Animal类对象中被transient修饰的持有者未被序列化。

writeObject()方法与readObject()方法

public class Person implements Serializable{ ....... transient private Integer age; ...... //如果使用了transient关键字,则该属性在序列化时会被忽略,使用私有的writeObject,readObject方法,会让被忽略的属性,再次被序列化。 private void writeObject(ObjectOutputStream outputStream){ try { outputStream.defaultWriteObject(); System.out.println("this is a flag"); outputStream.writeInt(age); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private void readObject(ObjectInputStream inputStream){ try { inputStream.defaultReadObject(); age = inputStream.readInt(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } }}

测试代码:

public class Test { public static void main(String[] args) { File file = new File("test.out"); writeFile(file,new Person("zhangfei", 23, Gender.FEMALE)); readFile(file); } ......}

测试结果: arg constructor this is a flag [zhangfei, 23, FEMALE]

writeObject()方法中会先调用ObjectOutputStream中的defaultWriteObject()方法,该方法会执行默认的序列化机制,如5.1节所述,此时会忽略掉age字段。然后再调用writeInt()方法显示地将age字段写入到ObjectOutputStream中。readObject()的作用则是针对对象的读取,将值设置到属性中,其原理与writeObject()方法相同

Externalizable接口 无论是使用transient关键字,还是使用writeObject()和readObject()方法,其实都是基于Serializable接口的序列化。JDK中提供了另一个序列化接口–Externalizable,使用该接口之后,之前基于Serializable接口的序列化机制就将失效。

/** * 使用Externalizable接口,调用的是无参构造方法构造对象。 * @author DELL * */public class Project implements Externalizable{ private String projectName; private String PorjectLeader; private int teamPerson;//团队人数 ...... @Override public void writeExternal(ObjectOutput out) throws IOException { } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { } public Project() { System.out.println("this method had constructor"); } @Override public String toString() { ...... }}

测试代码: public class Test { public static void main(String[] args) { File fileProject = new File(“project.out”); writeFile(fileProject, new Project(“XXXXXX”, “xiaoming”, 6)); readFile(fileProject); } } 输出结果: this method had constructor Project [projectName=null, PorjectLeader=null, teamPerson=0]

从该结果方面,一个字段都没有被序列化,Externalizable继承于Serializable,当使用该接口时,序列化的细节需要由程序员去完成。如上所示的代码,由于writeExternal()与readExternal()方法未作任何处理,那么该序列化行为将不会保存/读取任何一个字段。这也就是为什么输出结果中所有字段的值均为空。 另外,使用Externalizable进行序列化时,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。这就是为什么在此次序列化过程中Project类的无参构造器会被调用。由于这个原因,实现Externalizable接口的类必须要提供一个无参的构造器,且它的访问权限为public。

因此对上面的project类做进一步修改:

public class Project implements Externalizable{ ......将writeExternal,readExternal进行补全。。 @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(projectName); out.writeInt(teamPerson); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { projectName = (String) in.readObject(); teamPerson = in.readInt(); } ......

再次执行Test类,执行结果如下: this method had constructor Project [projectName=xxxxx, PorjectLeader=null, teamPerson=6]

readResolve()方法

public class SinglePerson implements Serializable{ private String name; private String address; private int age; private static class InstanceHolder{ private static final SinglePerson person = new SinglePerson("jack", "tianfulu", 12); } public static SinglePerson getInstance(){ return InstanceHolder.person; } ...... @Override public String toString() { return "SinglePerson [name=" + name + ", address=" + address + ", age=" + age + "]"; }}

测试代码:

public class Test { public static void main(String[] args) { /*File file = new File("test.out"); writeFile(file,new Person("zhangfei", 23, Gender.FEMALE)); readFile(file); File fileAnimal = new File("animal.out"); writeFile(fileAnimal, new Animal("niuniu", 3, "张三")); readFile(fileAnimal); File fileProject = new File("project.out"); writeFile(fileProject, new Project("xxxxx", "xiaoming", 6)); readFile(fileProject);*/ File fileSinglePerson = new File("singlePerson.out"); writeFile(fileSinglePerson, SinglePerson.getInstance()); readFile(fileSinglePerson); } ...... //反序列化公共处理方法 public static void readFile(File file){ try { ....... //新增 System.out.println(SinglePerson.getInstance() == ((SinglePerson)obj)); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }

测试结果: SinglePerson [name=jack, address=tianfulu, age=12] false

新增readResolve方法

public class SinglePerson implements Serializable{ private String name; private String address; private int age; ..........省略 private Object readResolve() throws ObjectStreamException{ return SinglePerson.getInstance(); }}

再次执行测试代码: SinglePerson [name=jack, address=tianfulu, age=12] true

无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象。

参考文章:http://developer.51cto.com/art/201202/317181.htm http://blog.csdn.net/wangloveall/article/details/7992448/


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表