zoukankan      html  css  js  c++  java
  • java序列化

      在做了几年java之后,越来越感觉出java基础的重要性。很多时候,如果基础不好,我们就很难真正看懂一些代码,不知道为什么人家那么写,为什么那么设计;在系统出现问题的时候,也不知道如何去做系统调优,因为我们根本就不了解代码是如何解析的,jvm是如何运作的,一些设计跟优化的事情也就无从谈起。

      最近在读红楼梦,曹老先生回味自己的人生,觉得“实愧则有余,悔又无益”于是写了一本书;曹老先生我等自然比不了,但回味自己这几年,感觉就是瞎混过来的,一碰到啥深层次的知识,基本就是“不会”二字,吾虽不才,亦觉心中有愧,于是准备翻书、网查,准备补一下自己欠下的技术债。

    ---------------正文分割线--------------------------------------------------------------------------------------------------------------------------------------

      我们都知道,java的序列化就是把java对象转换成为字节码,反序列化就是把java对象由字节码文件转换为jvm中的java对象。貌似我们已经了解了其中的精髓,,,那么,问题来了:我们在把数据持久化到数据库的时候,实体类会实现Serializable接口,并且经常会有这么一行:

    private static final long serialVersionUID = 7439843002944792140L;
    

      为什么会这样,我们不实现Serializable接口行不行,我们不要这个序列化id行不行?是不是不实现这个接口就无法保存数据到数据库了?

      实诚人不卖关子,直接说答案,不是。经过测试(hibernate),即使没有实现这个接口,一样可以保存数据到数据库中,也也可正常查询数据。因为数据库的持久化实际是保存的单个属性到具体的数据库表字段,而不是存整个对象的二进制文件。

          那么这个序列化到底干什么用的呢?

      聪明的我想到了度娘,看了几篇文章后,得到如下信息:序列化就是我们开始理解的那样,把java对象转为字节码进行存储或者传输,反序列化我们理解的也对。而 serialVersionUID的作用就是兼容性校验。

      主要要了解的地方:serialVersionUID是根据类名、接口名、成员方法、以及属性等来生成的一个64位的哈希字段。也就是说如果我们改动了类名(接口名)、方法、属性等,那么这个字段的值也会变化,按这个道理来说都话,基本就是我们改了类的任何地方都会引起变化(注释改动是否会导致变化?稍后测试)。如果没有这个值,你在序列化一个对象之后,改动了该类的字段或者方法名之类的,那如果你再反序列化想取出之前的那个对象时,就会发现该类的serialVersionUID的值和之前保存的文件中的serialVersionUID的值不一样,就会抛出异常。而显式的设置这个serialVersionUID的值,就可以保证版本的兼容性。如果你在类中写上了这个值,就算类变动了,它发序列化的时候也能和文件中的原值匹配上。而新增的值则会设置成null,删除的值则不会显示。

          到此为止,序列化的基本内容已经结束了,接下来是测试,我们新建一个实体类: 

    public class SerializationTestClass implements Serializable{
        private String localId;
        private String localName;
        public SerializationTestClass(){}
    
        public String getLocalId() {
            return localId;
        }
        public void setLocalId(String localId) {
            this.localId = localId;
        }
        public String getLocalName() {
            return localName;
        }
        public void setLocalName(String localName) {
            this.localName = localName;
        }
    }

      然后测试类中单元测试:

    /**
     * 把java对象序列化到文件
     */
    @Test
    public void testSerialization(){
        SerializationTestClass st1 = new SerializationTestClass();
        st1.setLocalId( "myid 1" );
        st1.setLocalName( "myname 1" );
    
        //序列化st1到xuliehua.txt文件中
        try {
            FileOutputStream fos = new FileOutputStream(new File("/Users/nevermore/Desktop/xuliehua.txt"));
            ObjectOutputStream os = new ObjectOutputStream(fos);
    
            os.writeObject(st1);
            os.flush();
            os.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     *反序列化文件为java对象
     */
    @Test
    public void testDesSerialization(){
        //序列化st1到xuliehua.txt文件中
        try {
            FileInputStream fis = new FileInputStream(new File("/Users/nevermore/Desktop/xuliehua.txt"));
            ObjectInputStream ois = new ObjectInputStream(fis);
    
            SerializationTestClass st2 = (SerializationTestClass)ois.readObject();
            System.out.println(st2.getLocalId()+", "+st2.getLocalName());
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

      先后运行两个单元测试方法,可以看到反序列化打印出的值,跟我们序列化之前set进去的是一样的;

          注意的是此时的实体类中并没有serialVersionUID,它使用的是jdk默认生成的serialVersionUID。我们改一下这个持久化实体类,添加一个属性addNew,仍然用原来的序列化文件进行反序列化,会报异常:java.io.InvalidClassException: com.nevermore.serialization.SerializationTestClass; local class incompatible: stream classdesc serialVersionUID = -8732474590023544793, local class serialVersionUID = -475121565243726286 。说的是本地的serialVersionUID = -475121565243726286跟序列化文件的serialVersionUID = -8732474590023544793不一样,所以异常。

      这就是所谓的版本校验问题。如果我们想兼容同一类的序列化文件,只要手动设置这个serialVersionUID不变即可(不作测试)。注释的改动会引起serialVersionUID变动吗?本地测试,先序列化了一个SerializationTestClass,然后改动了addNew变量的注释,再运行反序列化测试方法,结果正常反序列化。说明修改注释并不能改变erialVersionUID的值。

      另外,瞬态属性跟静态属性也要注意:给类添加一个瞬态属性private transient String password,序列化:

    /**
     * 把java对象序列化到文件
     */
    @Test
    public void testSerialization(){
        SerializationTestClass st1 = new SerializationTestClass();
        st1.setLocalId( "myid 1" );
        st1.setLocalName( "myname 1" );
        st1.setPassword( "password 1" );
    
        //序列化st1到xuliehua.txt文件中
        try {
            FileOutputStream fos = new FileOutputStream(new File("/Users/nevermore/Desktop/xuliehua.txt"));
            ObjectOutputStream os = new ObjectOutputStream(fos);
    
            os.writeObject(st1);
            os.flush();
            os.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     *反序列化文件为java对象
     */
    @Test
    public void testDesSerialization(){
        //序列化st1到xuliehua.txt文件中
        try {
            FileInputStream fis = new FileInputStream(new File("/Users/nevermore/Desktop/xuliehua.txt"));
            ObjectInputStream ois = new ObjectInputStream(fis);
    
            SerializationTestClass st2 = (SerializationTestClass)ois.readObject();
            System.out.println(st2.getLocalId()+", "+st2.getLocalName()+", "+st2.getPassword());
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    

      运行结果为:

      

      可以看到,瞬态属性并没有被序列化。静态的属性实际也是不会序列化的,因为这个属性是从属于类的而不是具体的对象,即使序列化了也没用意义。测试设置一个静态属性country,序列化前值为“USA”,然后序列化,然后改动值为“CN”,再进行反序列化,会发现这个值是CN。

      ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

      回忆总结一下:就是java搞了一个可以把对象存到硬盘的东西叫序列化,当然我们还得能把它变成jvm里边的东西,这个叫反序列化。怎么确保不会把A序列化后的东西反序列化成了B呢,java搞了一个serialVersionUID,如果不一样我就报异常(默认同一个类,你改了都不行),怎么解决兼容问题呢,手动设置serialVersionUID。那么问题又来了,如果serialVersionUID相同,类名不一样,会如何?显然,不行,会报类型转换异常。其实序列化的数据是有结构的,类似于类名,serialVersionUID之类的信息都会保存在头消息中,用于校验,一旦校验不通过,就会报异常。

      还有个接口叫Externalizable,这个通过重写writeExternal 和 readExternal两个方法可以控制序列化的一些细节;没有进行深入学习。

  • 相关阅读:
    asp.net编程基础
    http://blog.csdn.net/pjw100/article/details/5261582
    英雄无敌3版的仙剑奇侠传
    另类DATAGRID数据编辑修改
    使用INDY组件发送邮件,DEMO程序没有讲到的一些内容.如邮件格式定议
    .net 简单实现MD5
    VB.NET 对于类型的传递按值或者按引用
    Start with Database Connection Pool 连接管理
    ASP页面之间传值
    正则表达式
  • 原文地址:https://www.cnblogs.com/nevermorewang/p/6197293.html
Copyright © 2011-2022 走看看