標籤:查詢 auto java trie 實驗環境 enables 斷點 實驗 結構
轉載請註明源出處:http://www.cnblogs.com/lighten/p/7003536.html
1.前言
本章介紹Java位元組流中重要的成員,物件流程ObjectInputStream和ObjectOutputStream。之前的DataInputStream和DataOutputStream只是對一些基礎資料型別 (Elementary Data Type)進行瞭解析和儲存,但是在Java中更常見的是一個對象執行個體,一個對象執行個體是十分複雜的,如何儲存一個對象呢?這個就是物件流程的作用了。物件流程最終是要有歸屬地的,通常也就是檔案流。
2.ObjectInputStream
ObjectInputStream雖然需要接受一個輸入源,但是並不是繼承自FilterInputStream,其繼承自抽象父類InputStream,實現了ObjectInput介面,這個介面實現了之前所接觸過的DataInput介面,也就是基礎資料型別 (Elementary Data Type)。還實現了ObjectStreamConstants介面,這個介面中只有一些常量,是對象轉換的時候的一系列情況約定位元組。
其只有一個建構函式:
這個類很複雜,先看建構函式具體做了些什麼事情。
第一個verifySubclass()方法是一個安全檢驗,其作用是校正此類或子類的執行個體可以在不違反安全約束的情況下構建,子類不能覆寫安全敏感的非final方法,否則將檢查enableSubclassImplementation的序列化許可。
後面三個類都是ObjectInputStream的內部類,其作用之後介紹。
enableOverride決定後面調用readObjectOverride()還是readObject()方法,true時調用前者。
readStreamHeader()方法用於校正所讀流的頭,校正的內容是魔方數和版本號碼。
2.1 BlockDataInputStream
輸入資料流有兩種模式:在預設模式下,輸入寫入的資料與DataOutputStream格式相同; 在“塊資料”模式下,輸入資料由塊資料標記括起來(參見對象序列化規範詳情)。 緩衝取決於塊資料模式:預設情況下模式,沒有預先快取資料; 當在塊資料模式下,所有資料被讀入一次(和緩衝)。
BlockDataInputStream中構造了一個PeekInputStream,這個流每次唯讀一個位元組,儲存在peekb欄位,擷取的時候如果這個欄位>0就直接返回了,如果小於0就從封裝的流中取出一個欄位,peek()方法讀取的位元組,用read()方法還可以讀取,但只限一個,如果連續調用兩次peek(),再調用read()方法,第一個位元組就會丟失,這就是這個流的特點。
欄位如下:
從這裡可以看出BlockData塊資料的緩衝大小,預設是非block模式。而我們主要關註上面設定的true,block模式。
BlockDataInputStream裡面方法眾多,還是通過InputStream的相關方法實現來看吧。
read()方法根據end和pos標識判斷是否讀完緩衝,讀取完畢就會重新填充緩衝,在setBlockDataMode的時候,如果和預設的模式(false)不一樣就會將pos、end、unread都重設為0。然後就返回當前位置的資料,pos+1。讀取完了就是返回-1。
refill()方法都過一個unread欄位和pos==end的迴圈判斷,來讀取。一個塊資料,首先會讀取blockheader資料,返回block資料的長度,賦值給unread。具體解析block頭資料格式過程不做講解。unread是當前資料區塊在流中剩餘未讀取的資料,只要讀取到了資料,或無資料可讀即end!=0就會跳出迴圈,下次unread依舊可能不為0,就是當前資料區塊還沒有讀完,讀完時就是0,又回到瞭解析block頭資料的過程。
currentBlockRemaining()返回的是當前資料區塊未讀的資料,包括緩衝中未讀的和流中未讀取的剩餘資料區塊資料。
BlockDataInputStream小結:看到這裡,BlockDataInputStream的其它方法也沒有看的必要了,看起來應該也簡單。這個類的作用主要就是按塊解析資料流,一個資料區塊包括塊頭和塊身,塊頭中有一些標識和塊身的長度。每次讀取都是一個資料區塊一個個讀的,不保證讀完了一個資料區塊。每個塊身的大小是1024個位元組。看Java規範,這麼設計主要是為了對象序列化的前後相容,以塊的形式儲存和讀取可以相容1.2版本之前的1.1的次要版本和1.2之後的擴充相容。
2.2HandleTable
這個類是一個非同步的表,用來追蹤對象關聯的控制代碼。在還原序列化期間,一個對象首先通過調用assign方法分配一個控制代碼。這個方法使得分配的控制代碼處於開啟狀態,依賴於其它控制代碼的異常狀態,則可以通過markDependency方法註冊,或者一個異常可以通過調用markException直接關聯。當控制代碼被標記為異常,HandleTable承擔將異常傳播到依賴於被標記對象的其它對象的職責。以上是JDK上的部分注釋了,可能翻譯不到位,再看源碼:
根據上面的解釋再來看源碼,這裡三個數組就很清楚了,一個是存對象handler的狀態,一個存對象或異常,一個存所有依賴於這個對象的handler。HandlerList這個類很簡單,就是一個記錄對象依賴控制代碼序號的數組,add(int handler)方法添加。HandlerTable中的方法如下:
clear():將數組重設成null,byte是0;size()方法就是返回而已;grow()方法是擴容數組,將原資料拷貝。主要關注之前提到的三個方法:
一個對象先被標記成未知狀態,將其存入entries中。
根據依賴的狀態來選擇,從注釋的順序來看,一個對象先被assign再調其它方法,這個時候的狀態時unknow。目標是正常的情況下,依賴也就沒必要改變,目標有異常,根據傳播原則,要將目標的異常傳播到本對象。如果目標也是未知狀態,就會為目標建立一個list,以後再判斷,傳導。
這個markException方法也就清楚了,就是將目標的異常傳導到其依賴,以及依賴的依賴。finish()則是判斷終結後還有未知狀態的改成OK狀態,所以之前判斷異常狀態就break,因為處理完了,而ok狀態就會拋出異常,因為這個狀態應該是最後才會確定的。
HandlerTable小結:對象的構成可能是十分複雜的,在還原序列化的步驟中,如果出錯了,應該要傳導到所有依賴上,比如A對象有個B,B中的C還原序列化失敗了,B肯定也是異常的,A自然也是異常的了。HandlerTable就是用於計算這個異常傳導的。
2.3ValidationList
一旦對象圖完成還原序列化,就要執行回調的優先列表。
將obj註冊到ValidationList,帶上優先順序,插入鏈表的相關位置。Callback是一個可以構成鏈式結構的結構,有下一個的指標。
最後就會按照順序執行回調了
2.4 readObject()
使用物件流程當然是為了將位元組讀成一個對象了,而不關心一些基本的讀取方法,當然這些方法也很簡單,都是用到了之前將的BlockDataInputStream的相關方法,看了之前的理解起來也不難。
這個不是子類,enableOverride為false。所以我們需要關注的是後面的流程。初始時,passHandle為-1,即outerHandle就是-1了。後面邏輯之前handlerTable理解了也比較容易,就是查詢如果有異常就拋出,沒有執行回調,返回obj。finally的時候又將passhandle重設會原值了。下面看readObject0(false)做了什麼。
一開始這段是相容舊的模式,沒有看的必要。
讀到重設符,重設控制代碼。
這個就是根據標記,使用不同的方式進行解析了。裡面比較複雜,不再進行詳細介紹。
2.5 小結讀取過程
不考慮舊版本(1.1次要版本)的讀取,即非block模式。讀取一個物件流程,最先讀到的四個位元組是恒定的,為魔方數[-84, -19]和版本號碼[0,5],short類型。這個讀取就已經是開始讀取一個塊1024個位元組。第5個位元組就是上面的readObject0()的switch選項。
3.ObjectOutputStream
略,反向步驟(看的很累-。-!)。反向過程主要就是writeObject方法了。
4.後記
本文對ObjectInputStream進行了詳細介紹,但還是有些不明之處,個人對於readObject方法中的handler.markDependency有所疑問,因為最初passHandle應該是-1,這句應該沒什麼作用才對。eclipse斷點不能看到此處的變數值,難驗證。ObjectInputStream大致也就這些內容了,只是分拆分析了一下。writeObject沒有進行介紹,肯定是和readObject相對應的內容了。下面是一個小例子。
public class Student implements Serializable{/** * */private static final long serialVersionUID = 4484961141733473265L;private int id;private String name;private int age;public Student(int id, String name, int age) {this.id = id;this.name = name;this.age = age;}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Student [id=" + id + ", name=" + name + ", age=" + age + "]";}}
public class Teacher implements Serializable{/** * */private static final long serialVersionUID = -312001656457242689L;private int age;private String name;private Student student;public Teacher(int age, String name, Student student) {this.age = age;this.name = name;this.student = student;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Student getStudent() {return student;}public void setStudent(Student student) {this.student = student;}@Overridepublic String toString() {return "Teacher [age=" + age + ", name=" + name + ", student=" + student + "]";}}
@Testpublic void test() throws IOException {Student student = new Student(10, "張三", 12);Teacher teacher = new Teacher(32, "李四", student);ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(teacher);byte[] obj = baos.toByteArray();System.out.println(obj.length);System.out.println(Arrays.toString(obj));ByteArrayInputStream bais = new ByteArrayInputStream(obj);ObjectInputStream ois = new ObjectInputStream(bais);try {Teacher t = (Teacher) ois.readObject();System.out.println(t);} catch (ClassNotFoundException e) {e.printStackTrace();}}
這裡面需要序列化的對象必須實現Serializable介面,或者Externalizable介面。這個也給一個例子:
public class Classroom implements Externalizable {private Teacher teacher;private List<Student> students;@Overridepublic void writeExternal(ObjectOutput out) throws IOException {out.writeObject(teacher);out.writeObject(students);}@SuppressWarnings("unchecked")@Overridepublic void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {this.teacher = (Teacher) in.readObject();this.students = (List<Student>) in.readObject();}public Teacher getTeacher() {return teacher;}public void setTeacher(Teacher teacher) {this.teacher = teacher;}public List<Student> getStudents() {return students;}public void setStudents(List<Student> students) {this.students = students;}@Overridepublic String toString() {return "Classroom [teacher=" + teacher + ", students=" + students + "]";}}
@Testpublic void test2() throws IOException {Student student = new Student(10, "張三", 12);Teacher teacher = new Teacher(32, "李四", student);Classroom classroom = new Classroom();List<Student> students = new ArrayList<Student>();students.add(student);classroom.setStudents(students);classroom.setTeacher(teacher);ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(classroom);byte[] obj = baos.toByteArray();System.out.println(obj.length);System.out.println(Arrays.toString(obj));ByteArrayInputStream bais = new ByteArrayInputStream(obj);ObjectInputStream ois = new ObjectInputStream(bais);try {Classroom c = (Classroom) ois.readObject();System.out.println(c);} catch (ClassNotFoundException e) {e.printStackTrace();}}
本文講的有些不清楚,或許有所差錯,如果發現了請指出。下面兩篇也可以參考一下。
ObjectStreamField與ObjectStreamClass:http://blog.csdn.net/silentbalanceyh/article/details/8250096
ObjectIuputStream與ObjectOutputStream: http://blog.csdn.net/silentbalanceyh/article/details/8294269
5.補充5.1 物件流程寫檔案只能寫一個對象的問題
先不寫入檔案,寫入一個數組來看看現象:
@Testpublic void test3() throws IOException {Student student = new Student(10, "張三", 12);Teacher teacher = new Teacher(32, "李四", student);ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(student);oos.writeObject(teacher);byte[] obj = baos.toByteArray();System.out.println(obj.length);System.out.println(Arrays.toString(obj));ByteArrayInputStream bais = new ByteArrayInputStream(obj);ObjectInputStream ois = new ObjectInputStream(bais);try {Student s = (Student) ois.readObject();System.out.println(s);Teacher t = (Teacher) ois.readObject();System.out.println(t);} catch (ClassNotFoundException e) {e.printStackTrace();}}
這裡寫入了兩個對象,實際上實驗了兩次,第一次唯寫了一個Student對象,第二次寫入了Student和Teacher對象,對比位元組碼,前面Student的完全一樣。區別如下:
115在再一次調用readObject的時候是一個標識符,意味著讀取一個對象Object。上述寫兩個對象通過ByteArray流是沒有問題的,執行如下:
那麼再實驗一下將兩個對象寫入檔案,再讀取。
@Testpublic void test4() throws IOException {String path = ObjectStreamTest.class.getClassLoader().getResource("").getPath() + "objectstream.txt";//System.out.println(path);File file = new File(path);Student student = new Student(10, "張三", 12);Teacher teacher = new Teacher(32, "李四", student);FileOutputStream fos = new FileOutputStream(file);ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(student);oos.writeObject(teacher);oos.flush();oos.close();FileInputStream fis = new FileInputStream(new File(path));@SuppressWarnings("resource")ObjectInputStream ois = new ObjectInputStream(fis);try {Student s = (Student) ois.readObject();System.out.println(s);Teacher t = (Teacher) ois.readObject();System.out.println(t);} catch (ClassNotFoundException e) {e.printStackTrace();}}
實驗結果如下:
並沒有問題,實驗環境是JDK8,可能在這個版本解決了這個問題,也許是由於其它問題導致的。總而言之,沒有測試出問題。所有測試程式都忘記關閉輸入輸出資料流了,各位讀者自己補一下,懶的修改了。
Java之IO(七)ObjectInputStream和ObjectOutputStream