所謂的JAVA序列化與反序列化,序列化就是將JAVA 對象以一種的形式保持,好比存放到硬盤,或是用於傳輸。反序列化是序列化的一個逆過程。html
JAVA規定被序列化的對象必須實現java.io.Serializable這個接口,而咱們分析的目標ArrayList一樣實現了該接口。java
經過對ArrayList源碼的分析,能夠知道ArrayList的數據存儲都是依賴於elementData數組,它的聲明爲:數組
transient Object[] elementData;
注意transient修飾着elementData這個數組。
咱們都知道一個對象只要實現了Serilizable接口,這個對象就能夠被序列化,java的這種序列化模式爲開發者提供了不少便利,咱們能夠沒必要關係具體序列化的過程,只要這個類實現了Serilizable接口,這個類的全部屬性和方法都會自動序列化。緩存
然而在實際開發過程當中,咱們經常會遇到這樣的問題,這個類的有些屬性須要序列化,而其餘屬性不須要被序列化,打個比方,若是一個用戶有一些敏感信息(如密碼,銀行卡號等),爲了安全起見,不但願在網絡操做(主要涉及到序列化操做,本地序列化緩存也適用)中被傳輸,這些信息對應的變量就能夠加上 transient關鍵字。換句話說,這個字段的生命週期僅存於調用者的內存中而不會寫到磁盤裏持久化。安全
總之,java 的transient關鍵字爲咱們提供了便利,你只須要實現Serilizable接口,將不須要序列化的屬性前添加關鍵字transient,序列化對象的時候,這個屬性就不會序列化到指定的目的地中。網絡
具體詳見:Java transient關鍵字使用小記app
既然elementData被transient修飾,按理來講,它不能被序列化的,那麼ArrayList又是如何解決序列化這個問題的呢?源碼分析
類經過實現java.io.Serializable接口能夠啓用其序列化功能。要序列化一個對象,必須與必定的對象輸出/輸入流聯繫起來,經過對象輸出流將對象狀態保存下來,再經過對象輸入流將對象狀態恢復。this
在序列化和反序列化過程當中須要特殊處理的類必須使用下列準確簽名來實現特殊方法:.net
private void writeObject(java.io.ObjectOutputStream out) throws IOException
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
a) 寫入
b)讀取
舉例說明:
public class Box implements Serializable {
private static final long serialVersionUID = -3450064362986273896L;
private int width;
private int height;
public static void main(String[] args) {
Box myBox=new Box();
myBox.setWidth(50);
myBox.setHeight(30);
try {
FileOutputStream fs=new FileOutputStream("F:\\foo.ser");
ObjectOutputStream os=new ObjectOutputStream(fs);
os.writeObject(myBox);
os.close();
FileInputStream fi=new FileInputStream("F:\\foo.ser");
ObjectInputStream oi=new ObjectInputStream(fi);
Box box=(Box)oi.readObject();
oi.close();
System.out.println(box.height+","+box.width);
} catch (Exception e) {
e.printStackTrace();
}
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
}
從上面序列化的工做流程能夠看出,要想序列化對象,使用ObjectOutputStream對象輸出流的writeObject()方法寫入對象狀態信息,便可使用readObject()方法讀取信息。
那是否是能夠在ArrayList中調用ObjectOutputStream對象的writeObject()方法將elementData的值寫入輸出流呢?
見源碼:
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException
{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i = 0; i < size; i++)
{
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount)
{
throw new ConcurrentModificationException();
}
}
雖然elementData被transient修飾,不能被序列化,可是咱們能夠將它的值取出來,而後將該值寫入輸出流。
// 片斷1 它的功能等價於片斷2
s.writeObject(elementData[i]); // 傳值時,是將實參elementData[i]賦給s.writeObject()的形參
// 片斷2
Object temp = new Object(); // temp並無被transient修飾
temp = elementData[i];
s.writeObject(temp);
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException
{
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0)
{
// be like clone(), allocate array based upon size not capacity
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i = 0; i < size; i++)
{
a[i] = s.readObject();
}
}
}
ObjectOutputStream會調用這個類的writeObject方法進行序列化,ObjectInputStream會調用相應的readObject方法進行反序列化。
事情究竟是這樣的嗎?咱們作個小實驗,來驗明正身。
實驗1:
public class TestSerialization implements Serializable
{
private transient int num;
public int getNum()
{
return num;
}
public void setNum(int num)
{
this.num = num;
}
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException
{
s.defaultWriteObject();
s.writeObject(num);
System.out.println("writeObject of "+this.getClass().getName());
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException
{
s.defaultReadObject();
num = (Integer) s.readObject();
System.out.println("readObject of "+this.getClass().getName());
}
public static void main(String[] args)
{
TestSerialization test = new TestSerialization();
test.setNum(10);
System.out.println("序列化以前的值:"+test.getNum());
// 寫入
try
{
ObjectOutputStream outputStream = new ObjectOutputStream(
new FileOutputStream("D:\\test.tmp"));
outputStream.writeObject(test);
} catch (FileNotFoundException e)
{
e.printStackTrace();
} catch (IOException e)
{
e.printStackTrace();
}
// 讀取
try
{
ObjectInputStream oInputStream = new ObjectInputStream(
new FileInputStream("D:\\test.tmp"));
try
{
TestSerialization aTest = (TestSerialization) oInputStream.readObject();
System.out.println("讀取序列化後的值:"+aTest.getNum());
} catch (ClassNotFoundException e)
{
e.printStackTrace();
}
} catch (FileNotFoundException e)
{
e.printStackTrace();
} catch (IOException e)
{
e.printStackTrace();
}
}
}
序列化以前的值:10
writeObject of TestSerialization
readObject of TestSerialization
讀取序列化後的值:10
writeObjectMethod.invoke(obj, new Object[]{ out });
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class[] { ObjectOutputStream.class },
Void.TYPE);
private static Method getPrivateMethod(Class cl, String name,
Class[] argTypes, Class returnType)
{
try
{
Method meth = cl.getDeclaredMethod(name, argTypes);
// *****經過反射訪問對象的private方法
meth.setAccessible(true);
int mods = meth.getModifiers();
return ((meth.getReturnType() == returnType)
&& ((mods & Modifier.STATIC) == 0) && ((mods & Modifier.PRIVATE) != 0)) ? meth
: null;
} catch (NoSuchMethodException ex)
{
return null;
}
}
readObject(ObjectInputStream o)
and
writeObject(ObjectOutputStream o)
以前呢?
non transient
fields of the class respectively.
non-transient
field to the class and you are trying to deserialize it by the older version of class then the defaultReadObject() method will neglect the newly added field, similarly if you deserialize the old serialized object by the new version then the new non transient field will take default value from JVM
既然要將ArrayList的字段序列化(即將elementData序列化),那爲何又要用transient修飾elementData呢?
回想ArrayList的自動擴容機制,elementData數組至關於容器,當容器不足時就會再擴充容量,可是容器的容量每每都是大於或者等於ArrayList所存元素的個數。
好比,如今實際有了8個元素,那麼elementData數組的容量多是8x1.5=12,若是直接序列化elementData數組,那麼就會浪費4個元素的空間,特別是當元素個數很是多時,這種浪費是很是不合算的。
因此ArrayList的設計者將elementData設計爲transient,而後在writeObject方法中手動將其序列化,而且只序列化了實際存儲的那些元素,而不是整個數組。
見源碼:
// Write out all elements in the proper order.從源碼中,能夠觀察到 循環時是使用i<size而不是 i<elementData.length,說明序列化時,只需實際存儲的那些元素,而不是整個數組。
for (int i=0; i<size; i++)
{
s.writeObject(elementData[i]);
}
參考: