• java io系列06之 序列化总结(Serializable 和 Externalizable)


    本章,我们对序列化进行深入的学习和探讨。学习内容,包括序列化的作用、用途、用法,以及对实现序列化的2种方式SerializableExternalizable的深入研究。

    转载请注明出处:http://www.cnblogs.com/skywang12345/p/io_06.html

    1. 序列化是的作用和用途

    序列化,就是为了保存对象的状态;而与之对应的反序列化,则可以把保存的对象状态再读出来
    简言之:序列化/反序列化,是Java提供一种专门用于的保存/恢复对象状态的机制。

    一般在以下几种情况下,我们可能会用到序列化:
    a)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
    b)当你想用套接字在网络上传送对象的时候;
    c)当你想通过RMI传输对象的时候。

    2. 演示程序1

    下面,我们先通过一则简单示例来查看序列化的用法。

    源码如下(SerialTest1.java) 

     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   
    13 public class SerialTest1 { 
    14     private static final String TMP_FILE = ".serialtest1.txt";
    15   
    16     public static void main(String[] args) {   
    17         // 将“对象”通过序列化保存
    18         testWrite();
    19         // 将序列化的“对象”读出来
    20         testRead();
    21     }
    22   
    23 
    24     /**
    25      * 将Box对象通过序列化,保存到文件中
    26      */
    27     private static void testWrite() {   
    28         try {
    29             // 获取文件TMP_FILE对应的对象输出流。
    30             // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
    31             ObjectOutputStream out = new ObjectOutputStream(
    32                     new FileOutputStream(TMP_FILE));
    33             // 创建Box对象,Box实现了Serializable序列化接口
    34             Box box = new Box("desk", 80, 48);
    35             // 将box对象写入到对象输出流out中,即相当于将对象保存到文件TMP_FILE中
    36             out.writeObject(box);
    37             // 打印“Box对象”
    38             System.out.println("testWrite box: " + box);
    39 
    40             out.close();
    41         } catch (Exception ex) {
    42             ex.printStackTrace();
    43         }
    44     }
    45  
    46     /**
    47      * 从文件中读取出“序列化的Box对象”
    48      */
    49     private static void testRead() {
    50         try {
    51             // 获取文件TMP_FILE对应的对象输入流。
    52             ObjectInputStream in = new ObjectInputStream(
    53                     new FileInputStream(TMP_FILE));
    54             // 从对象输入流中,读取先前保存的box对象。
    55             Box box = (Box) in.readObject();
    56             // 打印“Box对象”
    57             System.out.println("testRead  box: " + box);
    58             in.close();
    59         } catch (Exception e) {
    60             e.printStackTrace();
    61         }
    62     }
    63 }
    64 
    65 
    66 /**
    67  * Box类“支持序列化”。因为Box实现了Serializable接口。
    68  *
    69  * 实际上,一个类只需要实现Serializable即可实现序列化,而不需要实现任何函数。
    70  */
    71 class Box implements Serializable {
    72     private int width;   
    73     private int height; 
    74     private String name;   
    75 
    76     public Box(String name, int width, int height) {
    77         this.name = name;
    78         this.width = width;
    79         this.height = height;
    80     }
    81 
    82     @Override
    83     public String toString() {
    84         return "["+name+": ("+width+", "+height+") ]";
    85     }
    86 }
    View Code

    运行结果

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

    源码说明

    (01) 程序的作用很简单,就是演示:先将Box对象,通过对象输出流保存到文件中;之后,再通过对象输入流,将文件中保存的Box对象读取出来。

    (02) Box类说明。Box是我们自定义的演示类,它被用于序列化的读写。Box实现了Serialable接口,因此它支持序列化操作;即,Box支持通过ObjectOutputStream去写入到输出流中,并且支持通过ObjectInputStream从输入流中读取出来。

    (03) testWrite()函数说明。testWrite()的作用就是,新建一个Box对象,然后将该Box对象写入到文件中。
           首先,新建文件TMP_FILE的文件输出流对象(即FileOutputStream对象),再创建该文件输出流的对象输出流(即ObjectOutputStream对象)。
           a) 关于FileInputStream和FileOutputStream的内容,可以参考“java io系列07之 FileInputStream和FileOutputStream”。
           b) 关于ObjectInputStream和ObjectOutputStream的的更多知识,可以参考“java io系列05之 ObjectInputStream 和 ObjectOutputStream
           然后,新建Box对象。
           最后,通过out.writeObject(box) 将box写入到对象输出流中。实际上,相当于将box写入到文件TMP_FILE中。

    (04) testRead()函数说明。testRead()的作用就是,从文件中读出Box对象。
           首先,新建文件TMP_FILE的文件输入流对象(即FileInputStream对象),再创建该文件输入流的对象输入流(即ObjectInputStream对象)。
           然后,通过in.readObject() 从对象输入流中读取出Box对象。实际上,相当于从文件TMP_FILE中读取Box对象。

    通过上面的示例,我们知道:我们可以自定义类,让它支持序列化(即实现Serializable接口),从而能支持对象的保存/恢复。
    若要支持序列化,除了“自定义实现Serializable接口的类”之外;java的“基本类型”和“java自带的实现了Serializable接口的类”,都支持序列化。我们通过下面的示例去查看一下。

    3. 演示程序2

    源码如下(SerialTest2.java)

     1 /**
     2  * “基本类型” 和 “java自带的实现Serializable接口的类” 对序列化的支持
     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.util.Map;
    13 import java.util.HashMap;
    14 import java.util.Iterator;
    15   
    16 public class SerialTest2 { 
    17     private static final String TMP_FILE = ".serialabletest2.txt";
    18   
    19     public static void main(String[] args) {   
    20         testWrite();
    21         testRead();
    22     }
    23   
    24     /**
    25      * ObjectOutputStream 测试函数
    26      */
    27     private static void testWrite() {   
    28         try {
    29             ObjectOutputStream out = new ObjectOutputStream(
    30                     new FileOutputStream(TMP_FILE));
    31             out.writeBoolean(true);    // 写入Boolean值
    32             out.writeByte((byte)65);// 写入Byte值
    33             out.writeChar('a');     // 写入Char值
    34             out.writeInt(20131015); // 写入Int值
    35             out.writeFloat(3.14F);  // 写入Float值
    36             out.writeDouble(1.414D);// 写入Double值
    37             // 写入HashMap对象
    38             HashMap map = new HashMap();
    39             map.put("one", "red");
    40             map.put("two", "green");
    41             map.put("three", "blue");
    42             out.writeObject(map);
    43 
    44             out.close();
    45         } catch (Exception ex) {
    46             ex.printStackTrace();
    47         }
    48     }
    49  
    50     /**
    51      * ObjectInputStream 测试函数
    52      */
    53     private static void testRead() {
    54         try {
    55             ObjectInputStream in = new ObjectInputStream(
    56                     new FileInputStream(TMP_FILE));
    57             System.out.printf("boolean:%b
    " , in.readBoolean());
    58             System.out.printf("byte:%d
    " , (in.readByte()&0xff));
    59             System.out.printf("char:%c
    " , in.readChar());
    60             System.out.printf("int:%d
    " , in.readInt());
    61             System.out.printf("float:%f
    " , in.readFloat());
    62             System.out.printf("double:%f
    " , in.readDouble());
    63             // 读取HashMap对象
    64             HashMap map = (HashMap) in.readObject();
    65             Iterator iter = map.entrySet().iterator();
    66             while (iter.hasNext()) {
    67                 Map.Entry entry = (Map.Entry)iter.next();
    68                 System.out.printf("%-6s -- %s
    " , entry.getKey(), entry.getValue());
    69             }
    70 
    71             in.close();
    72         } catch (Exception e) {
    73             e.printStackTrace();
    74         }
    75     }
    76 }
    View Code

    运行结果

    boolean:true
    byte:65
    char:a
    int:20131015
    float:3.140000
    double:1.414000
    two    -- green
    one    -- red
    three  -- blue

    源码说明

    (01) 程序的作用很简单,就是演示:先将“基本类型数据”和“HashMap对象”,通过对象输出流保存到文件中;之后,再通过对象输入流,将这些保存的数据读取出来。

    (02) testWrite()函数说明。testWrite()的作用就是,先将“基本类型数据”和“HashMap对象”,通过对象输出流保存到文件中。
           首先,新建文件TMP_FILE的文件输出流对象(即FileOutputStream对象),再创建该文件输出流的对象输出流(即ObjectOutputStream对象)。
           然后,通过 writeBoolean(), writeByte(), ... , writeDouble() 等一系列函数将“Boolean, byte, char, ... , double等基本数据类型”写入到对象输出流中。实际上,相当于将这些内容写入到文件TMP_FILE中。
          最后,新建HashMap对象map,并通过out.writeObject(map) 将map写入到对象输出流中。实际上,相当于map写入到文件TMP_FILE中。
    关于HashMap的更多知识,可以参考“Java 集合系列10之 HashMap详细介绍(源码解析)和使用示例”。

    (03) testRead()函数说明。testRead()的作用就是,从文件中读出testWrite()写入的对象。
           首先,新建文件TMP_FILE的文件输入流对象(即FileInputStream对象),再创建该文件输入流的对象输入流(即ObjectInputStream对象)。
           然后,通过in.readObject() 从对象输入流中读取出testWrite()对象。实际上,相当于从文件TMP_FILE中读取出这些对象。

    在前面,我们提到过:若要支持序列化,除了“自定义实现Serializable接口的类”之外;java的“基本类型”和“java自带的实现了Serializable接口的类”,都支持序列化。为了验证这句话,我们看看HashMap是否实现了Serializable接口。
    HashMap是java.util包中定义的类,它的接口声明如下:

    public class HashMap<K,V> extends AbstractMap<K,V>
        implements Map<K,V>, Cloneable, Serializable {} 

    至此,我们对序列化的认识已经比较深入了:即知道了“序列化的作用和用法”,也知道了“基本类型”、“java自带的支持Serializable接口的类”和“自定义实现Serializable接口的类”都能支持序列化。
    应付序列化的简单使用应该足够了。但是,我们的目的是对序列化有更深层次的了解!更何况,写此文的作者(也就是区区在下),应该比各位看官要累(既要写代码,又要总结,还得注意排版和用词,讲的通俗易懂,让各位看得轻松自在);我这个菜鸟都能做到这些,何况对知识极其渴望的您呢?所以,请深吸一口气,然后继续……

    我们在介绍序列化定义时,说过“序列化/反序列化,是专门用于的保存/恢复对象状态的机制”。
    从中,我们知道:序列化/反序列化,只支持保存/恢复对象状态,即仅支持保存/恢复类的成员变量,但不支持保存类的成员方法!
    但是,序列化是不是对类的所有的成员变量的状态都能保存呢?
    答案当然是否定的
    (01) 序列化对static和transient变量,是不会自动进行状态保存的。
            transient的作用就是,用transient声明的变量,不会被自动序列化。
    (02) 对于Socket, Thread类,不支持序列化。若实现序列化的接口中,有Thread成员;在对该类进行序列化操作时,编译会出错!
            这主要是基于资源分配方面的原因。如果Socket,Thread类可以被序列化,但是被反序列化之后也无法对他们进行重新的资源分配;再者,也是没有必要这样实现。

    下面,我们还是通过示例来查看“序列化对static和transient的处理”。

    4. 演示程序3

    我们对前面的SerialTest1.java进行简单修改,得到源文件(SerialTest3.java)如下:

     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   
    13 public class SerialTest3 { 
    14     private static final String TMP_FILE = ".serialtest3.txt";
    15   
    16     public static void main(String[] args) {   
    17         // 将“对象”通过序列化保存
    18         testWrite();
    19         // 将序列化的“对象”读出来
    20         testRead();
    21     }
    22   
    23 
    24     /**
    25      * 将Box对象通过序列化,保存到文件中
    26      */
    27     private static void testWrite() {   
    28         try {
    29             // 获取文件TMP_FILE对应的对象输出流。
    30             // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
    31             ObjectOutputStream out = new ObjectOutputStream(
    32                     new FileOutputStream(TMP_FILE));
    33             // 创建Box对象,Box实现了Serializable序列化接口
    34             Box box = new Box("desk", 80, 48);
    35             // 将box对象写入到对象输出流out中,即相当于将对象保存到文件TMP_FILE中
    36             out.writeObject(box);
    37             // 打印“Box对象”
    38             System.out.println("testWrite box: " + box);
    39 
    40             out.close();
    41         } catch (Exception ex) {
    42             ex.printStackTrace();
    43         }
    44     }
    45  
    46     /**
    47      * 从文件中读取出“序列化的Box对象”
    48      */
    49     private static void testRead() {
    50         try {
    51             // 获取文件TMP_FILE对应的对象输入流。
    52             ObjectInputStream in = new ObjectInputStream(
    53                     new FileInputStream(TMP_FILE));
    54             // 从对象输入流中,读取先前保存的box对象。
    55             Box box = (Box) in.readObject();
    56             // 打印“Box对象”
    57             System.out.println("testRead  box: " + box);
    58             in.close();
    59         } catch (Exception e) {
    60             e.printStackTrace();
    61         }
    62     }
    63 }
    64 
    65 
    66 /**
    67  * Box类“支持序列化”。因为Box实现了Serializable接口。
    68  *
    69  * 实际上,一个类只需要实现Serializable即可实现序列化,而不需要实现任何函数。
    70  */
    71 class Box implements Serializable {
    72     private static int width;   
    73     private transient int height; 
    74     private String name;   
    75 
    76     public Box(String name, int width, int height) {
    77         this.name = name;
    78         this.width = width;
    79         this.height = height;
    80     }
    81 
    82     @Override
    83     public String toString() {
    84         return "["+name+": ("+width+", "+height+") ]";
    85     }
    86 }
    View Code

    SerialTest3.java 相比于 SerialTest1.java。仅仅对Box类中的 width 和 height 变量的定义进行了修改。
    SerialTest1.java 中width和height定义

    private int width;   
    private int height; 

    SerialTest3.java 中width和height定义

    private static int width; 
    private transient int height; 

    在看后面的结果之前,我们建议大家对程序进行分析,先自己得出一个结论。

    运行结果

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

    结果分析

    我们前面说过,“序列化不对static和transient变量进行状态保存”。因此,testWrite()中保存Box对象时,不会保存width和height的值。这点是毋庸置疑的!但是,为什么testRead()中读取出来的Box对象的width=80,而height=0呢?
    先说,为什么height=0。因为Box对象中height是int类型,而int类型的默认值是0。
    再说,为什么width=80。这是因为height是static类型,而static类型就意味着所有的Box对象都共用一个height值;而在testWrite()中,我们已经将height初始化为80了。因此,我们通过序列化读取出来的Box对象的height值,也被就是80。

    理解上面的内容之后,我们应该可以推断出下面的代码的运行结果。

    源码如下(SerialTest4.java): 

     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   
    13 public class SerialTest4 { 
    14     private static final String TMP_FILE = ".serialtest4.txt";
    15   
    16     public static void main(String[] args) {   
    17         // 将“对象”通过序列化保存
    18         testWrite();
    19         // 将序列化的“对象”读出来
    20         testRead();
    21     }
    22   
    23 
    24     /**
    25      * 将Box对象通过序列化,保存到文件中
    26      */
    27     private static void testWrite() {   
    28         try {
    29             // 获取文件TMP_FILE对应的对象输出流。
    30             // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
    31             ObjectOutputStream out = new ObjectOutputStream(
    32                     new FileOutputStream(TMP_FILE));
    33             // 创建Box对象,Box实现了Serializable序列化接口
    34             Box box = new Box("desk", 80, 48);
    35             // 将box对象写入到对象输出流out中,即相当于将对象保存到文件TMP_FILE中
    36             out.writeObject(box);
    37             // 打印“Box对象”
    38             System.out.println("testWrite box: " + box);
    39             // 修改box的值
    40             box = new Box("room", 100, 50);
    41 
    42             out.close();
    43         } catch (Exception ex) {
    44             ex.printStackTrace();
    45         }
    46     }
    47  
    48     /**
    49      * 从文件中读取出“序列化的Box对象”
    50      */
    51     private static void testRead() {
    52         try {
    53             // 获取文件TMP_FILE对应的对象输入流。
    54             ObjectInputStream in = new ObjectInputStream(
    55                     new FileInputStream(TMP_FILE));
    56             // 从对象输入流中,读取先前保存的box对象。
    57             Box box = (Box) in.readObject();
    58             // 打印“Box对象”
    59             System.out.println("testRead  box: " + box);
    60             in.close();
    61         } catch (Exception e) {
    62             e.printStackTrace();
    63         }
    64     }
    65 }
    66 
    67 
    68 /**
    69  * Box类“支持序列化”。因为Box实现了Serializable接口。
    70  *
    71  * 实际上,一个类只需要实现Serializable即可实现序列化,而不需要实现任何函数。
    72  */
    73 class Box implements Serializable {
    74     private static int width;   
    75     private transient int height; 
    76     private String name;   
    77 
    78     public Box(String name, int width, int height) {
    79         this.name = name;
    80         this.width = width;
    81         this.height = height;
    82     }
    83 
    84     @Override
    85     public String toString() {
    86         return "["+name+": ("+width+", "+height+") ]";
    87     }
    88 }
    View Code

    SerialTest4.java 相比于 SerialTest3.java,在testWrite()中添加了一行代码box = new Box("room", 100, 50);

    运行结果

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

    现在,我们更加确认“序列化不对static和transient变量进行状态保存”。但是,若我们想要保存static或transient变量,能不能办到呢?
    当然可以!我们在类中重写两个方法writeObject()和readObject()即可。下面程序演示了如何手动保存static和transient变量。

    5. 演示程序4

    我们对前面的SerialTest4.java进行简单修改,以达到:序列化存储static和transient变量的目的。

    源码如下(SerialTest5.java): 

      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 }
    View Code

    运行结果

    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变量的处理”。
    接下来,我们来研究“对于Socket, Thread类,不支持序列化”。还是通过示例来查看。

    6. 演示程序5

    我们修改SerialTest5.java的源码,在Box类中添加一个Thread成员。

    源码如下(SerialTest6.java)

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

    结果是,编译出错!
    事实证明,不能对Thread进行序列化。若希望程序能编译通过,我们对Thread变量添加static或transient修饰即可!如下,是对Thread添加transient修饰的源码(SerialTest7.java)

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

    至此,关于“Serializable接口”来实现序列化的内容,都说完了。为什么这么说?因为,实现序列化,除了Serializable之外,还有其它的方式,就是通过实现Externalizable来实现序列化。整理下心情,下面继续对Externalizable进行了解。

    7. Externalizable和完全定制序列化过程

    如果一个类要完全负责自己的序列化,则实现Externalizable接口,而不是Serializable接口。

    Externalizable接口定义包括两个方法writeExternal()与readExternal()。需要注意的是:声明类实现Externalizable接口会有重大的安全风险。writeExternal()与readExternal()方法声明为public,恶意类可以用这些方法读取和写入对象数据。如果对象包含敏感信息,则要格外小心。

    下面,我们修改之前的SerialTest1.java测试程序;将其中的Box由“实现Serializable接口” 改为 “实现Externalizable接口”。
    修改后的源码如下( ExternalizableTest1.java)

      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.ObjectOutput;   
     12 import java.io.ObjectInput;   
     13 import java.io.Serializable;   
     14 import java.io.Externalizable;   
     15 import java.io.IOException;   
     16 import java.lang.ClassNotFoundException;   
     17   
     18 public class ExternalizableTest1 { 
     19     private static final String TMP_FILE = ".externalizabletest1.txt";
     20   
     21     public static void main(String[] args) {   
     22         // 将“对象”通过序列化保存
     23         testWrite();
     24         // 将序列化的“对象”读出来
     25         testRead();
     26     }
     27   
     28 
     29     /**
     30      * 将Box对象通过序列化,保存到文件中
     31      */
     32     private static void testWrite() {   
     33         try {
     34             // 获取文件TMP_FILE对应的对象输出流。
     35             // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
     36             ObjectOutputStream out = new ObjectOutputStream(
     37                     new FileOutputStream(TMP_FILE));
     38             // 创建Box对象
     39             Box box = new Box("desk", 80, 48);
     40             // 将box对象写入到对象输出流out中,即相当于将对象保存到文件TMP_FILE中
     41             out.writeObject(box);
     42             // 打印“Box对象”
     43             System.out.println("testWrite box: " + box);
     44 
     45             out.close();
     46         } catch (Exception ex) {
     47             ex.printStackTrace();
     48         }
     49     }
     50  
     51     /**
     52      * 从文件中读取出“序列化的Box对象”
     53      */
     54     private static void testRead() {
     55         try {
     56             // 获取文件TMP_FILE对应的对象输入流。
     57             ObjectInputStream in = new ObjectInputStream(
     58                     new FileInputStream(TMP_FILE));
     59             // 从对象输入流中,读取先前保存的box对象。
     60             Box box = (Box) in.readObject();
     61             // 打印“Box对象”
     62             System.out.println("testRead  box: " + box);
     63             in.close();
     64         } catch (Exception e) {
     65             e.printStackTrace();
     66         }
     67     }
     68 }
     69 
     70 
     71 /**
     72  * Box类实现Externalizable接口
     73  */
     74 class Box implements Externalizable {
     75     private int width;   
     76     private int height; 
     77     private String name;   
     78 
     79     public Box() {
     80     }
     81 
     82     public Box(String name, int width, int height) {
     83         this.name = name;
     84         this.width = width;
     85         this.height = height;
     86     }
     87 
     88     @Override
     89     public void writeExternal(ObjectOutput out) throws IOException {
     90     }
     91 
     92     @Override
     93     public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
     94     }
     95 
     96     @Override
     97     public String toString() {
     98         return "["+name+": ("+width+", "+height+") ]";
     99     }
    100 }
    View Code

    运行结果

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

    说明

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


    接着,我们修改上面的ExternalizableTest1.java测试程序;实现Box类中的writeExternal()和readExternal()接口!
    修改后的源码如下( ExternalizableTest2.java)

      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.ObjectOutput;   
     12 import java.io.ObjectInput;   
     13 import java.io.Serializable;   
     14 import java.io.Externalizable;   
     15 import java.io.IOException;   
     16 import java.lang.ClassNotFoundException;   
     17   
     18 public class ExternalizableTest2 { 
     19     private static final String TMP_FILE = ".externalizabletest2.txt";
     20   
     21     public static void main(String[] args) {   
     22         // 将“对象”通过序列化保存
     23         testWrite();
     24         // 将序列化的“对象”读出来
     25         testRead();
     26     }
     27   
     28 
     29     /**
     30      * 将Box对象通过序列化,保存到文件中
     31      */
     32     private static void testWrite() {   
     33         try {
     34             // 获取文件TMP_FILE对应的对象输出流。
     35             // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
     36             ObjectOutputStream out = new ObjectOutputStream(
     37                     new FileOutputStream(TMP_FILE));
     38             // 创建Box对象
     39             Box box = new Box("desk", 80, 48);
     40             // 将box对象写入到对象输出流out中,即相当于将对象保存到文件TMP_FILE中
     41             out.writeObject(box);
     42             // 打印“Box对象”
     43             System.out.println("testWrite box: " + box);
     44 
     45             out.close();
     46         } catch (Exception ex) {
     47             ex.printStackTrace();
     48         }
     49     }
     50  
     51     /**
     52      * 从文件中读取出“序列化的Box对象”
     53      */
     54     private static void testRead() {
     55         try {
     56             // 获取文件TMP_FILE对应的对象输入流。
     57             ObjectInputStream in = new ObjectInputStream(
     58                     new FileInputStream(TMP_FILE));
     59             // 从对象输入流中,读取先前保存的box对象。
     60             Box box = (Box) in.readObject();
     61             // 打印“Box对象”
     62             System.out.println("testRead  box: " + box);
     63             in.close();
     64         } catch (Exception e) {
     65             e.printStackTrace();
     66         }
     67     }
     68 }
     69 
     70 
     71 /**
     72  * Box类实现Externalizable接口
     73  */
     74 class Box implements Externalizable {
     75     private int width;   
     76     private int height; 
     77     private String name;   
     78 
     79     public Box() {
     80     }
     81 
     82     public Box(String name, int width, int height) {
     83         this.name = name;
     84         this.width = width;
     85         this.height = height;
     86     }
     87 
     88     @Override
     89     public void writeExternal(ObjectOutput out) throws IOException {
     90         out.writeObject(name);
     91         out.writeInt(width);
     92         out.writeInt(height);
     93     }
     94 
     95     @Override
     96     public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
     97         name = (String) in.readObject();
     98         width = in.readInt();
     99         height = in.readInt();
    100     }
    101 
    102     @Override
    103     public String toString() {
    104         return "["+name+": ("+width+", "+height+") ]";
    105     }
    106 }
    View Code

    运行结果

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

    至此,序列化的内容就全部讲完了。更多相关的内容,请参考:

    1. java 集合系列目录(Category)

    2. java io系列01之 IO框架

    3. java io系列02之 ByteArrayInputStream的简介,源码分析和示例(包括InputStream)

    4. java io系列03之 ByteArrayOutputStream的简介,源码分析和示例(包括OutputStream)

    5. java io系列04之 管道(PipedOutputStream和PipedInputStream)的简介,源码分析和示例

    6. java io系列05之 ObjectInputStream 和 ObjectOutputStream

  • 相关阅读:
    ABP记录被删除调用Repository.Get报错
    C# 中在对象后面跟“?” 以及在类型后面跟问号
    list转table
    ling groupby多字段分组统计
    linq一堆多再对多
    ABP下mvc的libs还原
    EF数据迁移
    ABP密码规则设置
    ABP中table时间格式化
    ABP读取appseting.json
  • 原文地址:https://www.cnblogs.com/skywang12345/p/io_06.html
Copyright © 2020-2023  润新知