Java对象表示方式1:序列化、反序列化的做用

1.序列化是的做用和用途html

序列化:把对象转换为字节序列的过程称为对象的序列化java

反序列化:把字节序列恢复为对象的过程称为对象的反序列化安全

对象的序列化主要有两种用途:
  1) 把对象的字节序列永久地保存到硬盘上,一般存放在一个文件中;
  2) 在网络上传送对象的字节序列。
网络

2.序列化的步骤ide

       java.io.ObjectOutputStream表明对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把获得的字节序列写到一个目标输出流中。
  java.io.ObjectInputStream表明对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
  只有实现了Serializable和Externalizable接口的类的对象才能被序列化。Externalizable接口继承自 Serializable接口,实现Externalizable接口的类彻底由自身来控制序列化的行为,而仅实现Serializable接口的类能够 采用默认的序列化方式 。
  对象序列化包括以下步骤:
  1) 建立一个对象输出流,它能够包装一个其余类型的目标输出流,如文件输出流;
  2) 经过对象输出流的writeObject()方法写对象。

  对象反序列化的步骤以下:
  1) 建立一个对象输入流,它能够包装一个其余类型的源输入流,如文件输入流;
  2) 经过对象输入流的readObject()方法读取对象。
函数

3.默认的序列化测试

序列化只须要实现java.io.Serializable接口就能够了。序列化的时候有一个serialVersionUID参数,Java序列化机制是经过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化,Java虚拟机会把传过来的字节流中的serialVersionUID和本地相应实体类的serialVersionUID进行比较,若是相同就认为是一致的实体类,能够进行反序列化,不然Java虚拟机会拒绝对这个实体类进行反序列化并抛出异常。serialVersionUID有两种生成方式:this

一、默认的1L加密

二、根据类名、接口名、成员方法以及属性等来生成一个64位的Hash字段spa

若是实现java.io.Serializable接口的实体类没有显式定义一个名为serialVersionUID、类型为long的变量时,Java序列化机制会根据编译的.class文件自动生成一个serialVersionUID,若是.class文件没有变化,那么就算编译再屡次,serialVersionUID也不会变化。换言之,Java为用户定义了默认的序列化、反序列化方法,其实就是ObjectOutputStream的defaultWriteObject方法和ObjectInputStream的defaultReadObject方法看一个例子:

复制代码
 1 public class SerializableObject implements Serializable
 2 {
 3     private static final long serialVersionUID = 1L;
 4     
 5     private String str0;
 6     private transient String str1;
 7     private static String str2 = "abc";
 8     
 9     public SerializableObject(String str0, String str1)
10     {
11         this.str0 = str0;
12         this.str1 = str1;
13     }
14     
15     public String getStr0()
16     {
17         return str0;
18     }
19 
20     public String getStr1()
21     {
22         return str1;
23     }
24 }
复制代码
复制代码
 1 public static void main(String[] args) throws Exception
 2 {
 3     File file = new File("D:" + File.separator + "s.txt");
 4     OutputStream os = new FileOutputStream(file);  
 5     ObjectOutputStream oos = new ObjectOutputStream(os);
 6     oos.writeObject(new SerializableObject("str0", "str1"));
 7     oos.close();
 8         
 9     InputStream is = new FileInputStream(file);
10     ObjectInputStream ois = new ObjectInputStream(is);
11     SerializableObject so = (SerializableObject)ois.readObject();
12     System.out.println("str0 = " + so.getStr0());
13     System.out.println("str1 = " + so.getStr1());
14     ois.close();
15 }
复制代码

先不运行,用一个二进制查看器查看一下s.txt这个文件,并详细解释一下每一部分的内容。

第1部分是序列化文件头

◇AC ED:STREAM_MAGIC序列化协议

◇00 05:STREAM_VERSION序列化协议版本

◇73:TC_OBJECT声明这是一个新的对象

第2部分是要序列化的类的描述,在这里是SerializableObject类

◇72:TC_CLASSDESC声明这里开始一个新的class

◇00 1F:十进制的31,表示class名字的长度是31个字节

◇63 6F 6D ... 65 63 74:表示的是“com.xrq.test.SerializableObject”这一串字符,能够数一下确实是31个字节

◇00 00 00 00 00 00 00 01:SerialVersion,序列化ID,1

◇02:标记号,声明该对象支持序列化

◇00 01:该类所包含的域的个数为1个

第3部分是对象中各个属性项的描述

◇4C:字符"L",表示该属性是一个对象类型而不是一个基本类型

◇00 04:十进制的4,表示属性名的长度

◇73 74 72 30:字符串“str0”,属性名

◇74:TC_STRING,表明一个new String,用String来引用对象

第4部分是该对象父类的信息,若是没有父类就没有这部分。有父类和第2部分差很少

◇00 12:十进制的18,表示父类的长度

◇4C 6A 61 ... 6E 67 3B:“L/java/lang/String;”表示的是父类属性

◇78:TC_ENDBLOCKDATA,对象块结束的标志

◇70:TC_NULL,说明没有其余超类的标志

第5部分输出对象的属性项的实际值,若是属性项是一个对象,这里还将序列化这个对象,规则和第2部分同样

◇00 04:十进制的4,属性的长度

◇73 74 72 30:字符串“str0”,str0的属性值

从以上对于序列化后的二进制文件的解析,咱们能够得出如下几个关键的结论:

 

一、序列化以后保存的是类的信息

二、被声明为transient的属性不会被序列化,这就是transient关键字的做用

三、被声明为static的属性不会被序列化,这个问题能够这么理解,序列化保存的是对象的状态,可是static修饰的变量是属于类的而不是属于变量的,所以序列化的时候不会序列化它

接下来运行一下上面的代码看一下

str0 = str0
str1 = null

由于str1是一个transient类型的变量,没有被序列化,所以反序列化出来也是没有任何内容的,显示的null,符合咱们的结论。

 4.defaultWriteObject和defaultReadObject(手动指定序列化)

4.1手动指定序列化

Java并不强求用户非要使用默认的序列化方式,用户也能够按照本身的喜爱本身指定本身想要的序列化方式----只要你本身能保证序列化先后能获得想要的数据就行了。手动指定序列化方式的规则是:

进行序列化、反序列化时,虚拟机会首先试图调用对象里的writeObject和readObject方法,进行用户自定义的序列化和反序列化。若是没有这样的方法,那么默认调用的是ObjectOutputStream的defaultWriteObject以及ObjectInputStream的defaultReadObject方法。换言之,利用自定义的writeObject方法和readObject方法,用户能够本身控制序列化和反序列化的过程。

这是很是有用的。好比:

一、有些场景下,某些字段咱们并不想要使用Java提供给咱们的序列化方式,而是想要以自定义的方式去序列化它,好比ArrayList的elementData、HashMap的table(至于为何在以后写这两个类的时候会解释缘由),就能够经过将这些字段声明为transient,而后在writeObject和readObject中去使用本身想要的方式去序列化它们

二、由于序列化并不安全,所以有些场景下咱们须要对一些敏感字段进行加密再序列化,而后再反序列化的时候按照一样的方式进行解密,就在必定程度上保证了安全性了。要这么作,就必须本身写writeObject和readObject,writeObject方法在序列化前对字段加密,readObject方法在序列化以后对字段解密

上面的例子SerializObject这个类修改一下,主函数不须要修改:

复制代码
 1 public class SerializableObject implements Serializable
 2 {
 3     private static final long serialVersionUID = 1L;
 4     
 5     private String str0;
 6     private transient String str1;
 7     private static String str2 = "abc";
 8     
 9     public SerializableObject(String str0, String str1)
10     {
11         this.str0 = str0;
12         this.str1 = str1;
13     }
14     
15     public String getStr0()
16     {
17         return str0;
18     }
19 
20     public String getStr1()
21     {
22         return str1;
23     }
24     
25     private void writeObject(java.io.ObjectOutputStream s) throws Exception
26     {
27         System.out.println("我想本身控制序列化的过程");
28         s.defaultWriteObject();
29         s.writeInt(str1.length());
30         for (int i = 0; i < str1.length(); i++)
31             s.writeChar(str1.charAt(i));
32     }
33     
34     private void readObject(java.io.ObjectInputStream s) throws Exception
35     {
36         System.out.println("我想本身控制反序列化的过程");
37         s.defaultReadObject();
38         int length = s.readInt();
39         char[] cs = new char[length];
40         for (int i = 0; i < length; i++)
41             cs[i] = s.readChar();
42         str1 = new String(cs, 0, length);
43     }
44 }
复制代码

直接看一下运行结果:

我想本身控制序列化的过程
我想本身控制反序列化的过程
str0 = str0
str1 = str1

看到,程序走到了咱们本身写的writeObject和readObject中,并且被transient修饰的str1也成功序列化、反序列化出来了----由于手动将str1写入了文件和从文件中读了出来。不妨再看一下s.txt文件的二进制:

看到橘黄色的部分就是writeObject方法追加的str1的内容。至此,总结一下writeObject和readObject的一般用法:

先经过defaultWriteObject和defaultReadObject方法序列化、反序列化对象,而后在文件结尾追加须要额外序列化的内容/从文件的结尾读取额外须要读取的内容。 

 4.2 经过这种方式达到 序列化static和transient变量的目的

 

1 /**
  2  * 序列化的演示测试程序
  3  *
  4  * @author skywang
  5  */
  6 
  7 import java.io.FileInputStream;   
  8 import java.io.FileOutputStream;   
  9 import java.io.ObjectInputStream;   
 10 import java.io.ObjectOutputStream;   
 11 import java.io.Serializable;   
 12 import java.io.IOException;   
 13 import java.lang.ClassNotFoundException;   
 14   
 15 public class SerialTest5 { 
 16     private static final String TMP_FILE = ".serialtest5.txt";
 17   
 18     public static void main(String[] args) {   
 19         // 将“对象”经过序列化保存
 20         testWrite();
 21         // 将序列化的“对象”读出来
 22         testRead();
 23     }
 24   
 25 
 26     /**
 27      * 将Box对象经过序列化,保存到文件中
 28      */
 29     private static void testWrite() {   
 30         try {
 31             // 获取文件TMP_FILE对应的对象输出流。
 32             // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
 33             ObjectOutputStream out = new ObjectOutputStream(
 34                     new FileOutputStream(TMP_FILE));
 35             // 建立Box对象,Box实现了Serializable序列化接口
 36             Box box = new Box("desk", 80, 48);
 37             // 将box对象写入到对象输出流out中,即至关于将对象保存到文件TMP_FILE中
 38             out.writeObject(box);
 39             // 打印“Box对象”
 40             System.out.println("testWrite box: " + box);
 41             // 修改box的值
 42             box = new Box("room", 100, 50);
 43 
 44             out.close();
 45         } catch (Exception ex) {
 46             ex.printStackTrace();
 47         }
 48     }
 49  
 50     /**
 51      * 从文件中读取出“序列化的Box对象”
 52      */
 53     private static void testRead() {
 54         try {
 55             // 获取文件TMP_FILE对应的对象输入流。
 56             ObjectInputStream in = new ObjectInputStream(
 57                     new FileInputStream(TMP_FILE));
 58             // 从对象输入流中,读取先前保存的box对象。
 59             Box box = (Box) in.readObject();
 60             // 打印“Box对象”
 61             System.out.println("testRead  box: " + box);
 62             in.close();
 63         } catch (Exception e) {
 64             e.printStackTrace();
 65         }
 66     }
 67 }
 68 
 69 
 70 /**
 71  * Box类“支持序列化”。由于Box实现了Serializable接口。
 72  *
 73  * 实际上,一个类只须要实现Serializable便可实现序列化,而不须要实现任何函数。
 74  */
 75 class Box implements Serializable {
 76     private static int width;   
 77     private transient int height; 
 78     private String name;   
 79 
 80     public Box(String name, int width, int height) {
 81         this.name = name;
 82         this.width = width;
 83         this.height = height;
 84     }
 85 
 86     private void writeObject(ObjectOutputStream out) throws IOException{ 
 87         out.defaultWriteObject();//使定制的writeObject()方法能够利用自动序列化中内置的逻辑。 
 88         out.writeInt(height); 
 89         out.writeInt(width); 
 90         //System.out.println("Box--writeObject width="+width+", height="+height);
 91     }
 92 
 93     private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{ 
 94         in.defaultReadObject();//defaultReadObject()补充自动序列化 
 95         height = in.readInt(); 
 96         width = in.readInt(); 
 97         //System.out.println("Box---readObject width="+width+", height="+height);
 98     }
 99 
100     @Override
101     public String toString() {
102         return "["+name+": ("+width+", "+height+") ]";
103     }
104 }

 

  

运行结果

testWrite box: [desk: (80, 48) ]
testRead  box: [desk: (80, 48) ]

程序说明

“序列化不会自动保存static和transient变量”,所以咱们若要保存它们,则须要经过writeObject()和readObject()去手动读写。
(01) 经过writeObject()方法,写入要保存的变量。writeObject的原始定义是在ObjectOutputStream.java中,咱们按照以下示例覆盖便可:

private void writeObject(ObjectOutputStream out) throws IOException{ 
    out.defaultWriteObject();// 使定制的writeObject()方法能够利用自动序列化中内置的逻辑。 
    out.writeInt(ival);      // 若要保存“int类型的值”,则使用writeInt()
    out.writeObject(obj);    // 若要保存“Object对象”,则使用writeObject()
}

(02) 经过readObject()方法,读取以前保存的变量。readObject的原始定义是在ObjectInputStream.java中,咱们按照以下示例覆盖便可:

private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{ 
    in.defaultReadObject();       // 使定制的readObject()方法能够利用自动序列化中内置的逻辑。 
    int ival = in.readInt();      // 若要读取“int类型的值”,则使用readInt()
    Object obj = in.readObject(); // 若要读取“Object对象”,则使用readObject()
}

至此,咱们就介绍完了“序列化对static和transient变量的处理”。

5.Externalizable 

若是一个类要彻底负责本身的序列化,则实现Externalizable接口,而不是Serializable接口。

Externalizable:他是Serializable接口的子类,有时咱们不但愿序列化那么多,可使用这个接口,这个接口的writeExternal()和readExternal()方法能够指定序列化哪些属性。

须要注意的是:声明类实现Externalizable接口会有重大的安全风险。writeExternal()与readExternal()方法声明为public,恶意类能够用这些方法读取和写入对象数据。若是对象包含敏感信息,则要格外当心。

/**
 * 序列化的演示测试程序
 *
 * @author skywang
 */

import java.io.FileInputStream;   
import java.io.FileOutputStream;   
import java.io.ObjectInputStream;   
import java.io.ObjectOutputStream;   
import java.io.ObjectOutput;   
import java.io.ObjectInput;   
import java.io.Serializable;   
import java.io.Externalizable;   
import java.io.IOException;   
import java.lang.ClassNotFoundException;   
  
public class ExternalizableTest2 { 
    private static final String TMP_FILE = ".externalizabletest2.txt";
  
    public static void main(String[] args) {   
        // 将“对象”经过序列化保存
        testWrite();
        // 将序列化的“对象”读出来
        testRead();
    }
  

    /**
     * 将Box对象经过序列化,保存到文件中
     */
    private static void testWrite() {   
        try {
            // 获取文件TMP_FILE对应的对象输出流。
            // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
            ObjectOutputStream out = new ObjectOutputStream(
                    new FileOutputStream(TMP_FILE));
            // 建立Box对象
            Box box = new Box("desk", 80, 48);
            // 将box对象写入到对象输出流out中,即至关于将对象保存到文件TMP_FILE中
            out.writeObject(box);
            // 打印“Box对象”
            System.out.println("testWrite box: " + box);

            out.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
 
    /**
     * 从文件中读取出“序列化的Box对象”
     */
    private static void testRead() {
        try {
            // 获取文件TMP_FILE对应的对象输入流。
            ObjectInputStream in = new ObjectInputStream(
                    new FileInputStream(TMP_FILE));
            // 从对象输入流中,读取先前保存的box对象。
            Box box = (Box) in.readObject();
            // 打印“Box对象”
            System.out.println("testRead  box: " + box);
            in.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


/**
 * Box类实现Externalizable接口
 */
class Box implements Externalizable {
    private int width;   
    private int height; 
    private String name;   

    public Box() {
    }

    public Box(String name, int width, int height) {
        this.name = name;
        this.width = width;
        this.height = height;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
        out.writeInt(width);
        out.writeInt(height);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = (String) in.readObject();
        width = in.readInt();
        height = in.readInt();
    }

    @Override
    public String toString() {
        return "["+name+": ("+width+", "+height+") ]";
    }
}

  

运行结果

testWrite box: [desk: (80, 48) ]
testRead  box: [null: (0, 0) ]

注意事项:

(01) 实现Externalizable接口的类,不会像实现Serializable接口那样,会自动将数据保存。
(02) 实现Externalizable接口的类,必须实现writeExternal()和readExternal()接口!不然,程序没法正常编译!
(03) 实现Externalizable接口的类,必须定义不带参数的构造函数!会默认的调用构造函数,不然,程序没法正常编译!
(04) writeExternal() 和 readExternal() 的方法都是public的,不是很是安全!

 

6.复杂序列化状况总结

虽然Java的序列化可以保证对象状态的持久保存,可是遇到一些对象结构复杂的状况仍是比较难处理的,最后对一些复杂的对象状况做一个总结:

一、当父类继承Serializable接口时,全部子类均可以被序列化

二、子类实现了Serializable接口,父类没有,父类中的属性不能序列化(不报错,数据丢失),可是在子类中属性仍能正确序列化

三、若是序列化的属性是对象,则这个对象也必须实现Serializable接口,不然会报错

四、反序列化时,若是对象的属性有修改或删减,则修改的部分属性会丢失,但不会报错

五、反序列化时,若是serialVersionUID被修改,则反序列化时会失败

 

转载:http://www.cnblogs.com/xrq730/p/4821958.html

http://www.cnblogs.com/skywang12345/p/io_06.html

相关文章
相关标签/搜索