一、知识预备
根据使用Serializable的使用场景,可以发现所涉及的场景都是跨进程的,就是要做的事情不是在一个java进程中完成的,我们都知道java进程是基于jvm跑起来的,而每一个被创建出来的对象都是放在堆里面的,如对象实例中的属性值,但是跨进程时每个Java进程都有一个jvm,也就是各自jvm中都有一个堆用来存放对象信息(很多博客上讲的是对象存放于内存之中,简直云里雾里,新人根本搞不清楚,一定要讲到java的根本jvm)。这时候问题引入,我们如何将A进程的Student实例,传输给本机B进程呢?又或者说如何传输给另外一台机器上的C进程呢?
二、引入序列化
我们为什么需要序列化呢?或者说什么情况下需要用到序列化呢?或者说如果没有序列化的话,哪些场景是我们处理不了的呢?
第一种情况是:一般情况下Java对象的声明周期都比Java虚拟机的要短,实际应用中我们希望在JVM停止运行之后能够持久化指定的对象,这时候就需要把对象进行序列化之后保存。
也就是说,在Java进程启动后,new出来放到内存中的对象(JVM中的堆空间),当JVM停止之后内存空间就被释放掉了,刚才创造的java进程中的世界都消失了,再次启动又会重新创建出来一个新的空间。
那么如果说是一个单机游戏进程,比如说单机版梦幻西游,人物初始属性中的攻击、防御、魔法都是0,你练了几级之后,这几个属性就会增加,那么今天你玩够了,把游戏进程给关了,那么你刚刚创建的游戏人物的相关信息都要保存下来的(很多游戏里面,在你退出的时候会提示你让你存档,就是这个意思),下次再打开游戏的时候就是将你刚才存档的信息读入到内存,在游戏界面再次的显示出来。
三、过程解析
Java序列化是指把Java对象保存为二进制字节码的过程,Java反序列化是指把二进制码重新转换成Java对象的过程。
当然这句话也让人云里雾里,因为对象就是类的实例对象生成之后就是在内存中,这句话应该这样表达,Java序列化是指把位于堆空间的Java对象以二进制字节码的形式保存为文件,反序列化就是将文件中的二进制字节码读取解析到内存中重新转化为Java对象的过程。
举个例子
package test; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class SerializableTest { public static void main(String[] args) throws IOException, ClassNotFoundException { FileOutputStream fos = new FileOutputStream("game-person.info"); ObjectOutputStream oos = new ObjectOutputStream(fos); GamePerson personIn = new GamePerson(); personIn.setName("abcde"); personIn.setLevel(1); personIn.setForceValue(2); personIn.setDefenseValue(3); oos.writeObject(personIn); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream("game-person.info"); ObjectInputStream ois = new ObjectInputStream(fis); GamePerson personOut = (GamePerson) ois.readObject(); System.out.println(personOut.getName()); System.out.println(personOut.getLevel()); System.out.println(personOut.getForceValue()); System.out.println(personOut.getDefenseValue()); } } class GamePerson implements Serializable { private static final long serialVersionUID = 1L; private String name; private int level; private int forceValue; private int defenseValue; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getLevel() { return level; } public void setLevel(int level) { this.level = level; } public int getForceValue() { return forceValue; } public void setForceValue(int forceValue) { this.forceValue = forceValue; } public int getDefenseValue() { return defenseValue; } public void setDefenseValue(int defenseValue) { this.defenseValue = defenseValue; } }
运行之后,在workspce目录下会生成game-person.info二进制文件,我们打开看看
四、修改序列化后的文件
重点来了,现在人物属性由内存持久化到了本地,那么我们是不是可以做一些手脚?
嘿嘿,我们把十六进制中各个属性尝试改一下,999(十进制) = 3E7(十六进制),我们改序列化后的文件见下图。
然后在从文件读取看看看
public class SerializableTest { public static void main(String[] args) throws IOException, ClassNotFoundException { FileInputStream fis = new FileInputStream("game-person.info"); ObjectInputStream ois = new ObjectInputStream(fis); GamePerson personOut = (GamePerson) ois.readObject(); System.out.println(personOut.getName()); System.out.println(personOut.getLevel()); System.out.println(personOut.getForceValue()); System.out.println(personOut.getDefenseValue()); } }
一刀999,惊不惊喜,意不意外!
五、总结
除了本地--内存,这种序列化的应用场景外。另外一种也就是第二种情况是:需要把Java对象通过网络进行传输的时候。因为数据只能够以二进制的形式在网络中进行传输,因此当把对象通过网络发送出去之前需要先序列化成二进制数据,在接收端读到二进制数据之后反序列化成Java对象。