序列化高級認識
1概述
兩個進程進行遠程通信時,彼此可以發送各種類型的數據,包括文本\圖片\音頻\視頻等,都會以二進制序列的形式在網絡上傳送.
當兩個java進程進行通信時,一個進程能否把一個java對象發送給另一個進程呢?
1.1
如何才能做到呢
1)發送方需要把這個java對象轉換爲字節序列,才能在網上傳送
2)接收方需要把字節序列再恢復爲java對象.
把java對象轉換爲字節序列的過程稱爲對象的序列化,
.
把字節序列恢復爲java對象的過程稱爲對象的反序列化.
2jdk類庫中的序列化API
2.1
java.io.ObjectOutputStream:代表對象輸出流它的writeObject(Object obj)方法可對參數指定的obj對象進行序列化,把得到的字節序列寫到一個目標輸出流中。
Java.io.ObjectInputStream代表對象輸入流,
它的readObject()方法從一個源輸入流中讀取字節,再把它們反序列化成一個對象,並將其返回。
2.2
哪些類可以被序列化呢?
只有實現了Serializable或Externalizable接口的類的對象才能被序列化,
否則ObjectOutputStream的writeObject(Object obj)方法會拋出IOException。
實現了Serializable或Externalizable接口的類也稱爲可序列化類。
Externalizable接口繼承Serializable接口,實現Externalizable接口的類完全由自身來控制序列化的行爲。
而僅實現Serializable接口的類可以採用默認的序列化方式。
Jdk的部分類 如String\Date等都實現了Serializable接口
3.1
序列化步驟:
》創建一個對象輸出流,它可以包裝一個其他類型的目標輸出流,
如文件輸出流:
ObjectOutputStream out = new ObjectOutputStream(new fileOutputStream(“D:\\objectfile.obj”));
》通過對象輸出流的writeObject()方法寫對象,
如:
Out.writeObject(“hello”);
Out.writeObject(new Date());
例:
import java.io.*;
import java.util.*;
public class ObjectSaver{
public static void main(String agrs[]) throws Exception {
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("D:\\objectFile.obj"));
String obj1="hello";
Date obj2=new Date();
Customer obj3=new Customer("Tom",20);
//序列化對象
out.writeObject(obj1);
out.writeObject(obj2);
out.writeObject(obj3);
out.writeInt(123);
out.close();
反序列化步驟:
》創建一個對象輸入流,它可以包裝一個其他類型的源輸入流,
如文件輸入流:
ObjectInputStream in = new ObjectInputStream (new fileIutputStream(“D:\\objectfile.obj”));
》通過對象輸入流的readObject()方法讀取對象,
如:
String obj1=(String)in.readObject();
Date obj2=(Date)in.readObject();
注意:
爲了能正確讀取數據,必須保證向對象輸出流寫對象的順序與從對象輸入流讀對象的順序一致
例:
ObjectInputStream in=new ObjectInputStream(new FileInputStream("D:\\objectFile.obj"));
String obj11 = (String)in.readObject();
System.out.println("obj11:"+obj11);
System.out.println("obj11==obj1:"+(obj11==obj1));
Date obj22 = (Date)in.readObject();
System.out.println("obj22:"+obj22);
System.out.println("obj22==obj2:"+(obj22==obj2));
Customer obj33 = (Customer)in.readObject();
System.out.println("obj33:"+obj33);
System.out.println("obj33==obj3:"+(obj33==obj3));
int var= in.readInt();
System.out.println("var:"+var);
in.close();
4:
注意事項:
1.
序列化的ID要一致,
private static final long serialVersionUID = 1L;
2.
序列化不保存靜態變量
因爲序列化保存的是對象的狀態,靜態變量屬於類的狀態,因此序列化並不保存靜態變量
3.父類的序列化與Transient關鍵字
要想讓父類序列化就要也實現Serializable接口,或者需要默認的無參構造函數
以爲反序列化時,爲了構造父類對象,值能調用父類的無參構造函數作爲默認的父對象,因此當我們取父類對象的變量值時,它的值是調用父類無參構造函數後的值,如果你考慮到這種情況,那麼在無參構造函數裏就要符默認值,否則就是0或null。
Transient關鍵字的作用是控制變量的序列化,在變量聲明前加上關鍵字,可以阻止該變量被序列化到文件中,在被序列化後,transient變量的值被設爲初始值0或null
4.對敏感字段加密
用戶自定義的writeObject()和readObject()方法可以運行控制序列化的過程,比如可以再序列化的過程中動態改變序列化的數值:
列:
public class EncryptTest implements Serializable {
private static final long serialVersionUID=1L;
private String password = "pass";
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public void readExternal(ObjectInputStream in) throws IOException,
ClassNotFoundException {
GetField readfileds=in.readFields();
Object object=readfileds.get("password", "");
System.out.println("要解密的字符串:"+object.toString());
password="pass";//模擬解密,需要獲取本地的密鑰
}
public void writeExternal(ObjectOutputStream out) throws IOException {
PutField putfileds=out.putFields();
System.out.println("原密碼:"+password);
password="encryption";//模擬加密
putfileds.put("password",password);
System.out.println("加密後的密碼"+password);
out.writeFields();
}
public static void main(String[] args) {
try {
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("result.obj"));
out.writeObject(new EncryptTest());
out.close();
ObjectInputStream in=new ObjectInputStream(new FileInputStream("result.obj"));
EncryptTest t=(EncryptTest) in.readObject();
System.out.println("解密後的字符串:"+t.getPassword());
in.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
5.java序列化具有特定的存儲規則:
當寫入文件爲同一個對象時,並不會在將對象的內容進行存儲,而只是再次存儲一份引用,從而增加5個字節,反序列化時,恢復引用關係,使得清單指向唯一對象,節省了空間
例:
public class Savexulihua implements Serializable {
public static void main(String[] args) {
try {
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("result.obj"));
Savexulihua save = new Savexulihua();
//試圖將對象兩次兩次寫人文件
out.writeObject(save);
out.flush();
System.out.println(new File("result.obj").length());
out.writeObject(save);
out.close();
System.out.println(new File("result.obj").length());
ObjectInputStream in = new ObjectInputStream(new FileInputStream(
"result.obj"));
try {
//從文件中依次讀出兩個文件
Savexulihua save1 = (Savexulihua) in.readObject();
Savexulihua save2 = (Savexulihua) in.readObject();
//判斷是否是同一個地址
System.out.println("save1==save2??"+(save1==save2));
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}