一、準備
在看ArrayList和LinkedList的 源碼 的時候蹋笼,發(fā)現(xiàn)ArrayList和LinkedList的一些成員變量變量被transient修飾了喂走,有點不解躬存,就查了一些資料。分享給大家息裸。
1.1 序列化與反序列化
序列化是java提供的一種將內(nèi)存中的對象信息轉(zhuǎn)化為二進制數(shù)組的方法蝇更,可以將數(shù)組保存和傳輸,然后使用原來的類模板恢復(fù)對象的信息呼盆。
轉(zhuǎn)化后的二進制數(shù)組中包含以下信息:序列化版本年扩,完整類名,serialVersionUID访圃,各個屬性的類型厨幻、名字和值、父類信息腿时。
1.2 怎么實現(xiàn)序列化和反序列化
實現(xiàn)Serializable接口况脆,使用ObjectOutputStream.writeObject(Object Object)寫對象信息,使用ObjectInputStream.readObject()讀對象信息批糟。
package Aug_05;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class SerializebleTest implements Serializable{
int num = 10;
private static final long serialVersionUID = -1L; //設(shè)置好serialVersionUID格了,減少反序列化失敗的機率
public static void main(String[] args) {
try {
//將對象序列化,并保存到本地文件中
FileOutputStream fileOutputStream = new FileOutputStream("MySerialize"); //創(chuàng)建文件輸出流
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); //創(chuàng)建對象輸出流
SerializebleTest oldObject = new SerializebleTest();
objectOutputStream.writeObject(oldObject); //寫對象信息
objectOutputStream.flush();
objectOutputStream.close();
fileOutputStream.close();
//從本地文件中讀取序列化信息徽鼎,恢復(fù)對象
FileInputStream fileInputStream = new FileInputStream("MySerialize");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
SerializebleTest newObject = (SerializebleTest)objectInputStream.readObject();
objectInputStream.close();
fileInputStream.close();
System.out.println("原始對象和恢復(fù)的對象是否是同一個對象:" + (newObject == oldObject));
System.out.println("原始對象和恢復(fù)的對象中的值是否相同:" + (newObject.num == oldObject.num));
} 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();
}
}
}
ObjectOutputStream.writeObject(ObjectA objectA)首先判斷ObjectA有沒有重寫writeObject方法盛末,如果有,反射調(diào)用ObjectA的writeObject方法完成序列化纬傲;否則满败,調(diào)用默認的序列化方法序列化。反序列化也一樣
1.2 transient關(guān)鍵字
被transient修飾的成員變量不會被序列化叹括。
哪些情況下可以不用序列化呢算墨?我認為有以下兩種情況。
1.節(jié)省空間
比如汁雷,一個長方形類有成員變量:長净嘀、寬、面積侠讯。那么面積就不用序列化挖藏,因為面積可以根據(jù)其他兩個計算出來,這樣節(jié)省存儲空間和傳輸空間厢漩。
2.持有對象的引用
比如膜眠,我們創(chuàng)建鏈表的結(jié)點如下。結(jié)點中持有前驅(qū)結(jié)點和后繼結(jié)點的引用,引用就是對象在內(nèi)存中的地址值宵膨。對于這樣的結(jié)點形成的鏈表架谎,我們序列化這個鏈表后,結(jié)點的前序和后繼引用都失效了辟躏,因為內(nèi)存地址變了谷扣。這種情況下我們需要重新連接鏈表。
private static class Node<E> {
E item; // 數(shù)據(jù)
Node<E> next; //后繼
Node<E> prev; //前驅(qū)
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
二捎琐、分析
首先說以下結(jié)論:
1.ArrayList中將elementData修飾成transient是為了節(jié)省空間
2.LinkedList中將first和last修飾成transient是為了節(jié)省空間和重新連接鏈表会涎。
2.1 ArrayList分析
查看 源碼 我們知道ArrayList中使用數(shù)組transient Object[] elementData保存數(shù)據(jù),當數(shù)組空間不夠時瑞凑,數(shù)組長度擴容為原來的1.5倍末秃。那么數(shù)組中可能有沒有使用的空間,比如elementData的長度時15拨黔,但是里面只裝了11個元素蛔溃,那么后面的4個元素都是空值。序列化的時候可以不把這4個元素序列化篱蝇。
ArrayList中定義了writeObject和readObject方法贺待,實現(xiàn)了自定義序列化。前面我們說了序列化的時候ObjectStream會判斷類中有沒有自定義序列化方法零截?如果有麸塞,使用自定義序列化方法:否則使用默認的序列化方法。
ArrayList自定義序列化方法如下
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// 先調(diào)用默認的序列化方法涧衙,將沒有transient修飾的成員變量序列化
int expectedModCount = modCount;
s.defaultWriteObject();
// 將元素容量序列化
s.writeInt(size);
// 將不為空的元素序列化
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// 先調(diào)用默認的反序列化方法
s.defaultReadObject();
// 反序列化元素容量
s.readInt(); // ignored
if (size > 0) {
// 檢查容量哪工,不過不夠進行擴容
ensureCapacityInternal(size);
Object[] a = elementData;
// 反序列化元素
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
2.2 LinkedList分析
查看 源碼 我們知道LinkedList中使用雙向鏈表保存數(shù)據(jù),結(jié)點中保存前驅(qū)和后繼的引用弧哎。但是序列化之后前序結(jié)點和后繼結(jié)點的地址都變了雁比,我們應(yīng)該連接新的結(jié)點。
下面看以下LinkedList是怎么自定義序列化的
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// 先調(diào)用默認的序列化方法
s.defaultWriteObject();
// 序列化容量
s.writeInt(size);
// 只把結(jié)點中的值序列化撤嫩,前序和后繼的引用不序列化
for (Node<E> x = first; x != null; x = x.next)
s.writeObject(x.item);
}
@SuppressWarnings("unchecked")
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// 先調(diào)用默認的反序列化方法
s.defaultReadObject();
// 讀容量
int size = s.readInt();
// 讀取每一個結(jié)點保存的值偎捎,創(chuàng)建新結(jié)點,重新連接鏈表序攘。
for (int i = 0; i < size; i++)
linkLast((E)s.readObject());
}
我們看到了茴她,LinkedList序列化的時候?qū)㈡湵戆错樞虿鸱珠_來,僅序列化結(jié)點中保存的數(shù)據(jù)程奠,反序列化的時候重新連接鏈表丈牢,保證了鏈表的有效性。