對象序列化(serialization)和反序列化(deserialization)是將對象轉(zhuǎn)化為便于傳輸?shù)母袷竭M(jìn)行發(fā)送和接收的兩個操作厢破。常見的序列化格式有字節(jié)數(shù)組,json字符串笆焰,xml字符串等。
本次討論的是java中的對象字節(jié)序列化嚷掠。
哪些東西可以是字節(jié)荞驴?圖片可以是字節(jié),文件可以是字節(jié)粟焊,一個字符串也可以是字節(jié)孙蒙,嗯悲雳,宇宙間的一切事物都可以用字節(jié)表示。當(dāng)然合瓢,對象也可以是字節(jié)。java的序列化就是將對象轉(zhuǎn)化為字節(jié)流顿苇,以便在進(jìn)程或網(wǎng)絡(luò)之間進(jìn)行傳輸税弃,而在接收方,需要以相同的方式對字節(jié)流進(jìn)行反序列化幔翰,得到傳輸?shù)膶ο蟆?/p>
jdk提供了序列化和反序列化相關(guān)實(shí)現(xiàn)。
1.序列化
使用此方法進(jìn)行序列化的對象必須實(shí)現(xiàn)Serializable接口遗增,不然在進(jìn)行序列化時(shí)會拋出NotSerializableException異常。
public static byte[] toBytes(Serializable obj) throws IOException {
try(ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
){
objectOutputStream.writeObject(obj);
return byteArrayOutputStream.toByteArray();
}
}
ObjectOutputStream的writeObject方法將對象序列化后寫入一個字節(jié)流中霍狰,而這個字節(jié)流就是在初始化ObjectOutputStream對象時(shí)傳入的字節(jié)流饰及,這里使用ByteArrayOutputStream,可以獲取到字節(jié)流中的字節(jié)數(shù)組步悠。
2.反序列化
對應(yīng)序列化,反序列化應(yīng)該是將字節(jié)數(shù)組轉(zhuǎn)化為對象鼎兽。
public static Serializable toObj(byte[] bytes) throws IOException, ClassNotFoundException {
try(ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
){
Object o = objectInputStream.readObject();
return (Serializable) o;
}
}
ByteArrayInputStream將字節(jié)數(shù)組轉(zhuǎn)化為字節(jié)流谚咬。而ObjectInputStream的readObject方法將字節(jié)流轉(zhuǎn)化為對象尚粘。
3.測試
建立一個Student類,并實(shí)現(xiàn)Serializable接口郎嫁。
public class Student implements Serializable{
private int id;
private String code;
private String name;
@Override
public String toString() {
return "Student{" +
"id=" + id +
", code='" + code + '\'' +
", name='" + name + '\'' +
'}';
}
//省略set/get方法
···
測試代碼:
//創(chuàng)建student對象
Student student = new Student(1,"2014213880","劉瑞杰");
//序列化
byte[] bytes = toBytes(student);
System.out.println(bytes.length);
//反序列化
Student student0 = (Student) toObj(bytes);
System.out.println(student0);
輸出結(jié)果:
111
Student{id=1, code='2014213880', name='劉瑞杰'}
4.關(guān)于serialVersionUID
我們可能經(jīng)常會看到泽铛,在一些類中實(shí)現(xiàn)了Serializable接口,同時(shí)還定義了一個private final static long型的變量杠茬,名字叫serialVersionUID。
private final static long serialVersionUID = 123456789L
那么這個變量有什么作用瓢喉?
上面的例子都是在對象序列化之后舀透,立即將結(jié)果反序列化,這樣肯定是沒任何問題的逗载。但是現(xiàn)在設(shè)想一個場景,如果將對象序列化后厉斟,并沒有馬上反序列化,如將對象保存到文件中擦秽,然后修改類的定義,比如在Student類中加一個字段缩搅。
然后再進(jìn)行反序列化触幼,此時(shí)會發(fā)生什么樣的事情。
首先編寫兩個方法置谦,分別是將對象保存到文件中,以及從文件中讀取之前保存的對象瘟栖。
寫文件:
public static void toFile(Serializable obj, String filePath) throws IOException {
try(FileOutputStream fileOutputStream = new FileOutputStream(filePath);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
){
objectOutputStream.writeObject(obj);
}
}
讀文件:
public static Serializable fromFile(String filePath) throws IOException, ClassNotFoundException {
try(FileInputStream fileInputStream = new FileInputStream(filePath);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
) {
return (Serializable) objectInputStream.readObject();
}
}
測試代碼:
首先保存Student對象:
String path = Test.class.getClassLoader().getResource("").getPath();
toFile(student, path+"student.obj");
執(zhí)行完成后半哟,
修改Student類的定義:
public class Student implements Serializable{
private int id;
private String code;
private String name;
private String clazz;//加個字段
//省略set/get方法
···
讀取文件中的Student對象:
Student student1 = (Student) fromFile(path+"student.obj");
System.out.println(student1);
執(zhí)行這段代碼签餐,意料之中,拋出了異常:
Exception in thread "main" java.io.InvalidClassException: serilize.Student;
local class incompatible:
stream classdesc serialVersionUID = 6643383736516292602,
local class serialVersionUID = -1315177638240754633
大概可以看出來缅茉,說是加載的類不匹配,當(dāng)然了,文件中的是舊的Student類译打,而程序中的是新的Student類,當(dāng)然會不匹配乔询。
不過韵洋,有時(shí)候黄锤,我們不希望每次修改了類的定義后食拜,以前保存的實(shí)例就不能夠使用了,這樣會導(dǎo)致每次更新程序會丟失很多數(shù)據(jù)负甸。
有些時(shí)候,修改后的類是完全可以兼容舊的類的打月。比如這里Student類只是加了一個clazz班級字段蚕捉,之前的所有字段都沒有變化。
而程序是通過比較新舊兩個類的serialVersionUID來判斷是否是不一樣的兩個類迫淹。所以我們只需要覆蓋serialVersionUID就可以做到兼容了。
覆蓋serialVersionUID需要滿足得條件:
1.static 靜態(tài)變量 2.final 常量 3.long 長整型 4.名稱為serialVersionUID
在Student類中加入這個變量:
public class Student implements Serializable{
private final static long serialVersionUID = 1L;
···
再次重復(fù)上面的操作:寫文件->類中加字段->讀文件
這次沒拋異常充易,成功輸出了正確的結(jié)果荸型。
Student{id=1, code='2014213880', name='劉瑞杰'}
當(dāng)然,并不是說稿静,覆蓋了serialVersionUID就可以做到完全兼容,數(shù)據(jù)完全不會丟失改备。如果修改已有的變量的類型蔓倍,如將Student類的id屬性改為字符串型,不用試都知道偶翅,反序列化時(shí)一定會拋異常。
InvalidClassException: serilize.Student; incompatible types for field id