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

简介: 本章,我们对序列化进行深入的学习和探讨。学习内容,包括序列化的作用、用途、用法,以及对实现序列化的2种方式Serializable和Externalizable的深入研究。 转载请注明出处:http://www.cnblogs.com/skywang12345/p/io_06.html 1. 序列化是的作用和用途 序列化,就是为了保存对象的状态;而与之对应的反序列化,则可以把保存的对象状态再读出来。

本章,我们对序列化进行深入的学习和探讨。学习内容,包括序列化的作用、用途、用法,以及对实现序列化的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 }
复制代码

运行结果

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\n" , in.readBoolean()); 58 System.out.printf("byte:%d\n" , (in.readByte()&0xff)); 59 System.out.printf("char:%c\n" , in.readChar()); 60 System.out.printf("int:%d\n" , in.readInt()); 61 System.out.printf("float:%f\n" , in.readFloat()); 62 System.out.printf("double:%f\n" , 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\n" , entry.getKey(), entry.getValue()); 69  } 70 71  in.close(); 72 } catch (Exception e) { 73  e.printStackTrace(); 74  } 75  } 76 }
复制代码

运行结果

复制代码
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 }
复制代码

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 }
复制代码

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 }
复制代码

运行结果

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 }
复制代码

结果是,编译出错!
事实证明,不能对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 }
复制代码

至此,关于“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 }
复制代码

运行结果

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 }
复制代码

运行结果

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

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

相关文章
|
3月前
|
存储 网络协议 安全
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
143 23
|
4月前
|
缓存 网络协议 Java
JAVA网络IO之NIO/BIO
本文介绍了Java网络编程的基础与历史演进,重点阐述了IO和Socket的概念。Java的IO分为设备和接口两部分,通过流、字节、字符等方式实现与外部的交互。
146 0
|
8月前
|
Java
Java 中 IO 流的分类详解
【10月更文挑战第10天】不同类型的 IO 流具有不同的特点和适用场景,我们可以根据具体的需求选择合适的流来进行数据的输入和输出操作。在实际应用中,还可以通过组合使用多种流来实现更复杂的功能。
249 57
|
7月前
|
Java
java 中 IO 流
Java中的IO流是用于处理输入输出操作的机制,主要包括字节流和字符流两大类。字节流以8位字节为单位处理数据,如FileInputStream和FileOutputStream;字符流以16位Unicode字符为单位,如FileReader和FileWriter。这些流提供了读写文件、网络传输等基本功能。
126 10
|
8月前
|
存储 缓存 Java
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
这篇文章详细介绍了Java中的IO流,包括字符与字节的概念、编码格式、File类的使用、IO流的分类和原理,以及通过代码示例展示了各种流的应用,如节点流、处理流、缓存流、转换流、对象流和随机访问文件流。同时,还探讨了IDEA中设置项目编码格式的方法,以及如何处理序列化和反序列化问题。
222 1
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
|
7月前
|
存储 安全 Java
🌟Java零基础-反序列化:从入门到精通
【10月更文挑战第21天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
197 5
|
7月前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
7月前
|
存储 缓存 安全
🌟Java零基础:深入解析Java序列化机制
【10月更文挑战第20天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
92 3
|
8月前
|
存储 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第9天】在Java的世界里,对象序列化是连接数据持久化与网络通信的桥梁。本文将深入探讨Java对象序列化的机制、实践方法及反序列化过程,通过代码示例揭示其背后的原理。从基础概念到高级应用,我们将一步步揭开序列化技术的神秘面纱,让读者能够掌握这一强大工具,以应对数据存储和传输的挑战。
|
8月前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第3天】在Java编程的世界里,对象序列化与反序列化是实现数据持久化和网络传输的关键技术。本文将深入探讨Java序列化的原理、应用场景以及如何通过代码示例实现对象的序列化与反序列化过程。从基础概念到实践操作,我们将一步步揭示这一技术的魅力所在。