序列化:保存对象的状态
反序列化:把对象的状态再读出来
一般一下几种情况下,会用到序列化:
- 想把内存中的对象状态保存到文件中
- 想用套接字再网络上传输对象
- 通过RMI(remote method Invocation)传输对象
示例一:
简单序列化用法:
Box类:
public class Box implements Serializable { private static final long serialVersionUID = 1L; private int width; private int height; private String name; public Box(String name, int width, int height) { this.name = name; this.width = width; this.height = height; } @Override public String toString() { return "[" + name + ": (" + width + ", " + height + ") ]"; } }
public class SerialTest1 { private static final String PATH = "D:\baiduyun\filetest\ddd.txt"; public static void main(String[] args) throws Exception { // 将“对象”通过序列化保存 testWrite(); // 将序列化的“对象”读出来 testRead(); } private static void testRead() throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(PATH)); Box box = (Box) ois.readObject(); System.out.println(box); ois.close(); } private static void testWrite() throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(PATH)); Box box = new Box("box", 152, 132); oos.writeObject(box); oos.close(); } }
输出结果:[box: (152, 132) ]
通过上面的示例,我们知道:我们可以自定义类,让他支持序列化(实现Serializable接口),从而支持对象的保存和恢复.
其实"java基本类型和java自带的实现了serializable接口"的类都支持序列化,下面通过示例二来看.
示例二:
private static final String PATH = "D:\baiduyun\filetest\ddd.txt"; public static void main(String[] args) throws Exception { testWrite(); testRead(); } private static void testRead() throws Exception { ObjectInputStream in = new ObjectInputStream(new FileInputStream(PATH)); System.out.printf("boolean:%b ", in.readBoolean()); System.out.printf("byte:%d ", (in.readByte() & 0xff)); System.out.printf("char:%c ", in.readChar()); System.out.printf("int:%d ", in.readInt()); System.out.printf("float:%f ", in.readFloat()); System.out.printf("double:%f ", in.readDouble()); // 读取HashMap对象 HashMap map = (HashMap) in.readObject(); Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); System.out.printf("%-6s -- %s ", entry.getKey(), entry.getValue()); } in.close(); } private static void testWrite() throws Exception { ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(PATH)); out.writeBoolean(true); // 写入Boolean值 out.writeByte((byte) 65);// 写入Byte值 out.writeChar('a'); // 写入Char值 out.writeInt(20131015); // 写入Int值 out.writeFloat(3.14F); // 写入Float值 out.writeDouble(1.414D);// 写入Double值 // 写入HashMap对象 HashMap map = new HashMap(); map.put("one", "red"); map.put("two", "green"); map.put("three", "blue"); out.writeObject(map); out.close(); }
输出结果:
boolean:true
byte:65
char:a
int:20131015
float:3.140000
double:1.414000
one -- red
two -- green
three -- blue
上面两个示例我们知道了序列化和反序列化的简单应该,下面来看看序列化的高级应该.
- 序列化对static和transient变量,是不会自动进行状态保存的
- 对于Socket,Thread类,不支持序列化,若实现序列化的接口中,有Thread成员,对该类进行编译时会报错
下面演示序列化对static和transient的处理:
示例三:
public class Box implements Serializable { private static final long serialVersionUID = 1L; private static int width; private transient int height; private String name; public Box(String name, int width, int height) { this.name = name; this.width = width; this.height = height; } @Override public String toString() { return "[" + name + ": (" + width + ", " + height + ") ]"; } }
public class SerialTest3 { private static final String PATH = "D:\baiduyun\filetest\ddd.txt"; public static void main(String[] args) throws Exception { // 将“对象”通过序列化保存 testWrite(); // 将序列化的“对象”读出来 testRead(); } private static void testRead() throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(PATH)); Box box = (Box) ois.readObject(); System.out.println(box); ois.close(); } private static void testWrite() throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(PATH)); Box box = new Box("box", 15, 13); oos.writeObject(box); oos.close(); } }
输出结果:
[box: (15, 0) ]
为什么height=0呢?因为height是int类型,int的默认值是0.
为什么width=15呢,因为width是static类型,static意味着所有类共享该值,在testWrite()中我们初始化了box的width为15,所以取出来是15.
理解上面的内容之后,我们应该可以推断出下面的代码的运行结果。
示例四:
/** * 序列化的演示测试程序 * * */ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class SerialTest4 { private static final String TMP_FILE = ".serialtest4.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实现了Serializable序列化接口 Box box = new Box("desk", 80, 48); // 将box对象写入到对象输出流out中,即相当于将对象保存到文件TMP_FILE中 out.writeObject(box); // 打印“Box对象” System.out.println("testWrite box: " + box); // 修改box的值 box = new Box("room", 100, 50); 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类“支持序列化”。因为Box实现了Serializable接口。 * * 实际上,一个类只需要实现Serializable即可实现序列化,而不需要实现任何函数。 */ class Box implements Serializable { private static int width; private transient int height; private String name; public Box(String name, int width, int height) { this.name = name; this.width = width; this.height = height; } @Override public String toString() { return "["+name+": ("+width+", "+height+") ]"; } }
运行结果:
testWrite box: [desk: (80, 48) ]
testRead box: [desk: (100, 0) ]
现在我们更加确认"序列化不对static和transient变量进行保存"
下面演示怎么保存static和transient变量
示例五:
如果要保存static和transient变量,我们需要重写writeObject()和readObject()对象
Box5.java
public class Box5 implements Serializable { /** * @Fields field:field:{todo}(用一句话描述这个变量表示什么) */ private static final long serialVersionUID = 1L; private static int width; private transient int height; private String name; public Box5(String name, int width, int height) { this.name = name; this.width = width; this.height = height; } @Override public String toString() { return "[" + name + ": (" + width + ", " + height + ") ]"; } private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); out.writeInt(height); out.writeInt(width); } private void readObject(ObjectInputStream in) throws Exception { in.defaultReadObject(); height = in.readInt(); width = in.readInt(); } }
public class SerialTest5 { private static final String PATH = "D:\baiduyun\filetest\ddd.txt"; public static void main(String[] args) throws Exception { // 将“对象”通过序列化保存 testWrite(); // 将序列化的“对象”读出来 testRead(); } private static void testRead() throws Exception { ObjectInputStream in = new ObjectInputStream(new FileInputStream(PATH)); Box5 box5 = (Box5) in.readObject(); System.out.println("read:" + box5); in.close(); } private static void testWrite() throws Exception { ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(PATH)); Box5 box5 = new Box5("box5", 100, 200); out.writeObject(box5); System.out.println("write:" + box5); // 修改box的值 box5 = new Box5("room", 500, 50); System.out.println("modify:" + box5); out.close(); } }
输出结果:
write:[box5: (100, 200) ]
modify:[room: (500, 50) ]
read:[box5: (100, 200) ]
很明显我们把static和transient变量也序列化了.
接下来,我们来看看"对于socket,thread类,不支持序列化"
示例六:
box6中添加Thread变量
public class Box6 implements Serializable { /** * @Fields field:field:{todo}(用一句话描述这个变量表示什么) */ private static final long serialVersionUID = 1L; private static int width; private transient int height; private String name; private Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("Serializable test!"); } }); public Box6(String name, int width, int height) { this.name = name; this.width = width; this.height = height; } @Override public String toString() { return "[" + name + ": (" + width + ", " + height + ") ]"; } private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); out.writeInt(height); out.writeInt(width); } private void readObject(ObjectInputStream in) throws Exception { in.defaultReadObject(); height = in.readInt(); width = in.readInt(); } }
public class SerialTest6 { private static final String PATH = "D:\baiduyun\filetest\ddd.txt"; public static void main(String[] args) throws Exception { // 将“对象”通过序列化保存 testWrite(); // 将序列化的“对象”读出来 testRead(); } private static void testRead() throws Exception { ObjectInputStream in = new ObjectInputStream(new FileInputStream(PATH)); Box6 box6 = (Box6) in.readObject(); System.out.println("read:" + box6); in.close(); } private static void testWrite() throws Exception { ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(PATH)); Box6 box6 = new Box6("box6", 100, 200); out.writeObject(box6); System.out.println("write:" + box6); // 修改box的值 box6 = new Box6("room", 500, 50); System.out.println("modify:" + box6); out.close(); } }
运行结果:
Exception in thread "main" java.io.NotSerializableException: java.lang.Thread at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548) at java.io.ObjectOutputStream.defaultWriteObject(ObjectOutputStream.java:441) at com.zhangj.ymm.thinking.io.serial.Box6.writeObject(Box6.java:38) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:1140) at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496) at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432) at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) at com.zhangj.ymm.thinking.io.serial.SerialTest6.testWrite(SerialTest6.java:28) at com.zhangj.ymm.thinking.io.serial.SerialTest6.main(SerialTest6.java:13)
结果报错啦,事实证明我们不能对Thread进行序列化.若要编译通过我们可以在Thread变量前添加static或者transient修饰.
如果一个类要完全负责自己的序列化,则要实现Externalizable接口,然后重写writeExternal()和readExterbal()方法.声明类实现Externalizble接口会有重大的安全风险,writeExterbal()和readExternal()方法为public,恶意类可以用这些方法读取和写入对象.
示例七:
实现Externalizable接口
public class Box7 implements Externalizable { private int width; private int height; private String name; public Box7() { } public Box7(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(height); out.writeInt(width); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { name = (String) in.readObject(); height = in.readInt(); width = in.readInt(); } @Override public String toString() { return "[" + name + ": (" + width + ", " + height + ") ]"; } }
public class SerialTest7 { private static final String PATH = "D:\baiduyun\filetest\ddd.txt"; public static void main(String[] args) throws Exception { testWrite(); testRead(); } private static void testRead() throws Exception { ObjectInputStream in = new ObjectInputStream(new FileInputStream(PATH)); Box7 box7 = (Box7) in.readObject(); System.out.println(box7); in.close(); } private static void testWrite() throws Exception { ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(PATH)); Box7 box7 = new Box7("box7", 100, 200); out.writeObject(box7); box7 = new Box7("box77", 150, 250); out.close(); } }
运行结果:
[box7: (100, 200) ]