我爱学Java之对象序列化
2016-03-09 21:46
381 查看
Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,当JVM停止运行时,这些对象就不复存在,但在实际应用中,我们需要持久化的保存指定的对象,并在特定的时间重新读取被保存的对象,Java对象序列化就能够帮助我们实现该功能。
在进行Java对象序列化的时候,会把其状态保存为一组字节,在对这些对象反序列化的时候再将这些字节组装成对象。但值得注意的事,对象序列化保存的是对象的”状态“,即它的成员变量,因此类中的静态变量不会被序列化。
除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化,下面我们分别通过文件和MySQL理解Java对象的序列化。
首先定义需要被序列化的类,通过实现Serializable接口:
接下来,先测试针对MySQL的对象序列化的测试:
先建立数据库表,字段对应为“blog”:
接下来,写测试用例:
输出结果:
![](http://img.blog.csdn.net/20160309210049976)
再来针对文件的对象序列化
输出结果:
![](http://img.blog.csdn.net/20160309211029359)
再提一下transient关键字,当某个字段被声明为transient后,默认序列化机制就会忽略该字段。此处将Student类中的id字段声明为transient:
再运行得到的结果如下:
![](http://img.blog.csdn.net/20160309211516263)
可见name字段未被序列化;如想再恢复该字段的序列化能力,除了删除transient关键字以外,还有另外一种方式:即在Student类中加两个方法,如下所示:
直接在此运行得到结果如图:
![](http://img.blog.csdn.net/20160309213114007)
还有另一种序列化接口-Externalizable
将之前两个类修改如下:
此时在此运行程序,得到结果如下:
![](http://img.blog.csdn.net/20160309213636494)
从该结果可看出对象中任何一个字段都没有被序列化,但是可以看出调用了类的无参构造器。Externalizable继承于Serializable,当使用该接口时,序列化的细节需要由通过实现writeExternal()与readExternal()方法。另外,若使用Externalizable进行序列化,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。这就是为什么在此次序列化过程中类的无参构造器会被调用。由于这个原因,实现Externalizable接口的类必须要提供一个无参的构造器,且它的访问权限为public。
对上述类作进一步的修改,使其能够对name与age字段进行序列化:
在运行得到如下结果:
![](http://img.blog.csdn.net/20160309214447794)
最后说一下serialVersionUID的作用:
Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的,在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,如不同,就会出现序列化版本不一致的异常,如下图:
![](http://img.blog.csdn.net/20160310092302572)
serialVersionUID有两种生成方式:一个是指定一个默认值,比如:private static final long serialVersionUID = 1L; 另一个是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:private static final long serialVersionUID = 8107332608515657739L。
在实现序列化的过程当中,如果我们没有显示的指定serialVersionUID,Java序列化机制会根据编译的class自动生成一个serialVersionUID(不同的编译器对于同一个类可能会产生不同的serialVersionUID),如果我们在序列化期间没有对类进行修改,那么无论编译多少次serialVersionUID都不会变,也就是说反序列化的时候不会出现问题,但是我们一旦修改了类的某一部分,反序列化就会出现如上所说的版本不一致的异常。
所以需要序列化的时候,我们最好显示的指定serialVersionUID(但要注意的是,如果使用第一种方式即指定默认值,需要对不同的类指定程不同的值,否则可能会出现把不同项目的相同类名的类当作一样的类去处理),这样当序列化一个类实例的时候,如添加修改删除一个字段,反序列化的时候对于添加和修改的字段会设定对于其类型的默认值,而删除的字段将不设置。
在进行Java对象序列化的时候,会把其状态保存为一组字节,在对这些对象反序列化的时候再将这些字节组装成对象。但值得注意的事,对象序列化保存的是对象的”状态“,即它的成员变量,因此类中的静态变量不会被序列化。
除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化,下面我们分别通过文件和MySQL理解Java对象的序列化。
首先定义需要被序列化的类,通过实现Serializable接口:
class Student implements Serializable{ private static final long serialVersionUID = 1L; //多种类型测试 private int id; private String name; private Address address; private ArrayList<String> course; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } public ArrayList<String> getCourse() { return course; } public void setCourse(ArrayList<String> course) { this.course = course; } } //被引用的类也需要实现Serializable接口,否则会报错 class Address implements Serializable{ private static final long serialVersionUID = 1L; private String street; private String road; public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } public String getRoad() { return road; } public void setRoad(String road) { this.road = road; } }
接下来,先测试针对MySQL的对象序列化的测试:
先建立数据库表,字段对应为“blog”:
mysql> create table student( -> id int auto_increment primary key, -> stu blob -> );
接下来,写测试用例:
public static void main(String[] args) throws IOException{ String url = "jdbc:mysql://localhost:3306/test"; String user = "root"; String pass = "root"; Student student = new Student(); student.setId(2); student.setName("gaoya"); Address address = new Address(); address.setStreet("aa"); address.setRoad("bb"); student.setAddress(address); ArrayList<String> course = new ArrayList<String>(); course.add("math"); course.add("english"); student.setCourse(course); try { Class.forName("com.mysql.jdbc.Driver"); Connection conn = DriverManager.getConnection(url, user, pass); String sql = "insert into student(stu) values(?)"; PreparedStatement pre = conn.prepareStatement(sql); pre.setObject(1, student); pre.executeUpdate(); sql = "select stu from student"; pre = conn.prepareStatement(sql); ResultSet rs = pre.executeQuery(); while(rs.next()){ Blob blob = rs.getBlob(1); InputStream is = blob.getBinaryStream(); BufferedInputStream buff = new BufferedInputStream(is); byte[] b = new byte[(int)blob.length()]; while(buff.read(b) != -1); ObjectInputStream obj = new ObjectInputStream(new ByteArrayInputStream(b)); Student stu = (Student)obj.readObject(); System.out.println(stu.getName()); System.out.println(stu.getId()); System.out.println(stu.getAddress().getRoad() + " " + stu.getAddress().getStreet()); System.out.println(stu.getCourse()); ; } } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
输出结果:
再来针对文件的对象序列化
public static void main(String[] args){ Student student = new Student(); student.setId(2); student.setName("gaoya"); Address address = new Address(); address.setStreet("aa"); address.setRoad("bb"); student.setAddress(address); ArrayList<String> course = new ArrayList<String>(); course.add("math"); course.add("english"); student.setCourse(course); try { //将对象序列化到文件 File file = new File("d:\\out.txt");//输出目录 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file)); out.writeObject(student); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream(file)); Student stu = (Student)in.readObject(); System.out.println(stu.getId()); System.out.println(stu.getName()); System.out.println(stu.getAddress().getRoad() + " " + stu.getAddress().getStreet()); System.out.println(stu.getCourse()); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
输出结果:
再提一下transient关键字,当某个字段被声明为transient后,默认序列化机制就会忽略该字段。此处将Student类中的id字段声明为transient:
private transient String name;
再运行得到的结果如下:
可见name字段未被序列化;如想再恢复该字段的序列化能力,除了删除transient关键字以外,还有另外一种方式:即在Student类中加两个方法,如下所示:
//需将这两个方法定义为private,序列化的时候会自动被调用 private void writeObject(ObjectOutputStream out) throws IOException{ out.defaultWriteObject();//执行默认化序列化机制 out.writeUTF(name);//将name字段写入ObjectOutputStream中 } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{ in.defaultReadObject();//执行默认反序列化机制 name = in.readUTF();//将name字段读入ObjectInputStream中 }
直接在此运行得到结果如图:
还有另一种序列化接口-Externalizable
将之前两个类修改如下:
class Student implements Externalizable{ private static final long serialVersionUID = 1L; //多种类型测试 private int id; private transient String name; private Address address; private ArrayList<String> course; public Student(){ System.out.println("student"); } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } public ArrayList<String> getCourse() { return course; } public void setCourse(ArrayList<String> course) { this.course = course; } private void writeObject(ObjectOutputStream out) throws IOException{ out.defaultWriteObject();//执行默认化序列化机制 out.writeUTF(name);//将name字段写入ObjectOutputStream中 } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{ in.defaultReadObject(); name = in.readUTF(); } @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 } } class Address implements Externalizable{ private static final long serialVersionUID = 1L; private String street; private String road; public Address(){ System.out.println("address"); } public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } public String getRoad() { return road; } public void setRoad(String road) { this.road = road; } @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 } }
此时在此运行程序,得到结果如下:
从该结果可看出对象中任何一个字段都没有被序列化,但是可以看出调用了类的无参构造器。Externalizable继承于Serializable,当使用该接口时,序列化的细节需要由通过实现writeExternal()与readExternal()方法。另外,若使用Externalizable进行序列化,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。这就是为什么在此次序列化过程中类的无参构造器会被调用。由于这个原因,实现Externalizable接口的类必须要提供一个无参的构造器,且它的访问权限为public。
对上述类作进一步的修改,使其能够对name与age字段进行序列化:
@Override public void writeExternal(ObjectOutput out) throws IOException { // TODO Auto-generated method stub out.writeInt(id); out.writeObject(name); out.writeObject(address); out.writeObject(course); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { // TODO Auto-generated method stub id = in.readInt(); name = (String)in.readObject(); address = (Address) in.readObject(); course = (ArrayList<String>) in.readObject(); } @Override public void writeExternal(ObjectOutput out) throws IOException { // TODO Auto-generated method stub out.writeObject(street); out.writeObject(road); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { // TODO Auto-generated method stub street = (String) in.readObject(); road = (String) in.readObject(); }
在运行得到如下结果:
最后说一下serialVersionUID的作用:
Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的,在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,如不同,就会出现序列化版本不一致的异常,如下图:
serialVersionUID有两种生成方式:一个是指定一个默认值,比如:private static final long serialVersionUID = 1L; 另一个是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:private static final long serialVersionUID = 8107332608515657739L。
在实现序列化的过程当中,如果我们没有显示的指定serialVersionUID,Java序列化机制会根据编译的class自动生成一个serialVersionUID(不同的编译器对于同一个类可能会产生不同的serialVersionUID),如果我们在序列化期间没有对类进行修改,那么无论编译多少次serialVersionUID都不会变,也就是说反序列化的时候不会出现问题,但是我们一旦修改了类的某一部分,反序列化就会出现如上所说的版本不一致的异常。
所以需要序列化的时候,我们最好显示的指定serialVersionUID(但要注意的是,如果使用第一种方式即指定默认值,需要对不同的类指定程不同的值,否则可能会出现把不同项目的相同类名的类当作一样的类去处理),这样当序列化一个类实例的时候,如添加修改删除一个字段,反序列化的时候对于添加和修改的字段会设定对于其类型的默认值,而删除的字段将不设置。
相关文章推荐
- elasticsearch结合spring springmvc jest 使用做成web架构
- Java 折半查找
- java线程3
- eclipse打包jar时包含第三方jar包的相关问题
- Java初级工程师应该具备的知识点
- 【转】人生如梦游戏间,RPG游戏开源开发讲座(JAVA篇)[5]——一树双花
- java 设计模式
- Java 进阶 之 抽象类与接口 比较(二)
- 【转】人生如梦游戏间,RPG游戏开源开发讲座(JAVA篇)[4]——一步莲华
- leetcode:Add Two Numbers 【Java】
- 礼拜三log~java web框架探索&baidu地图
- 2016年3月8_spring
- java 线程2
- Java之泛型进阶——泛型代码转化为普通代码
- java线程一
- Eclipse中自动import包
- Java反射的学习
- Java 入门 之 抽象类
- Spring 的容器
- Java多线程实现异步调用