概念
- 實現 Serializable 接口, 它只是一個標記接口,不實現也能夠進行序列化
- RMI: 遠程方法調用
- RPC: 遠程過程調用
序列化ID
解決了序列化與反序列出現代碼不一致的問題, 不一致將導致序列化失敗 private static final long serialVersionUID = 1L; // 便於進行代碼版本控制 private static final long serialVersionUID = -5453781658505116230L; //便於控制代碼結構
靜態變量序列化
- x*- 序列化的是對象,而不是類,靜態變量屬於類級別,所以序列化不會保存靜態變量
父類序列化與Trancient關鍵字
- 一個子類實現了 Serializable 接口,它的父類沒有實現 Serializable 接口,那么序列化子類時,父類的值都不會進行保存
- 需要父類保存值 ,就需要讓父類也實現Serializable 接口
- 取父對象的變量值時,它的值是調用父類無參構造函數后的值,出現如 int 型的默認是 0,string 型的默認是 null, 要指定值,那么需要在父類構造方法中進行指定
- Trancient關鍵字指定的內容將不會被保存(阻止序列化)
- 在被反序列化后,transient 變量的值被設為初始值,如 int 型的是 0,對象型的是 nul
- 使用繼承關系同樣可以實現,Trancient一樣的效果,,即為父類不需要實現Serializable接口
利用PutField getPutField字段進行加密
原理:
- 1: 進行序列化時,JVM試圖調用對象的writeObject() readObject() 方法(允許自己私有化實現)
- 2:默認調用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法
private void writeObject(ObjectOutputStream out) { try { PutField putFields = out.putFields(); //放到 System.out.println("原密碼:" + password); password = "encryption";// 模擬加密 putFields.put("password", password); System.out.println("加密后的密碼" + password); out.writeFields(); } catch (IOException e) { e.printStackTrace(); } } private void readObject(ObjectInputStream in) { try { GetField readFields = in.readFields(); Object object = readFields.get("password", ""); System.out.println("要解密的字符串:" + object.toString()); password = "pass";// 模擬解密,需要獲得本地的密鑰 } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } // 調用的時候直接調用 out的writeObject(),或者in的readObject() 即可
序列化存儲規則
- 對同一對象兩次寫入文件, 第一次寫入實際對象的序列化后的數據,第二次寫入同一個對象的引用數據.(即為指向同一個對象)
- 1: 節約了磁盤存儲空間
- 2: 反序列化后的數據的值,應該是第一次保存的數據的值,(對於同一個對象第二次序列化,值是不會進行保存的)
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("result.obj"));
Test test = new Test();
test.i = ; // 有效
out.writeObject(test);
out.flush();
test.i = ; //無效 第二次反序列化 只寫出對象的引用關系 表示為同一個 引用對象,節約了磁盤空間
out.writeObject(test);
out.close();
ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
"result.obj"));
Test t = (Test) oin.readObject();
Test t = (Test) oin.readObject();
System.out.println(t.i);//
System.out.println(t.i);//
Serializable接口的定義:
public interface Serializable {} // 可以知道這個只是 一個標記接口, 並且JVM 並沒有實現相應的反射代碼,真的據說是起到標記作用! 那么這個標記 是在哪里進行判斷的?
標記的具體定義地方:
writeObject0方法中有這么一段代碼:
if (obj instanceof String) { 2 writeString((String) obj, unshared); 3 } else if (cl.isArray()) { 4 writeArray(obj, desc, unshared); 5 } else if (obj instanceof Enum) { 6 writeEnum((Enum<?>) obj, desc, unshared); 7 } else if (obj instanceof Serializable) { 8 writeOrdinaryObject(obj, desc, unshared); 9 } else { 10 if (extendedDebugInfo) { 11 throw new NotSerializableException( 12 cl.getName() + "/n" + debugInfoStack.toString()); 13 } else { 14 throw new NotSerializableException(cl.getName()); 15 } 16 }
可以看出: 在進行序列化操作時,會判斷要被序列化的類是否是Enum、Array和Serializable類型,如果不是則直接拋出 NotSerializableException
ArrayList分析
- 要實現序列化 必須實現Serializable接口,ArrayList 也實現了這個接口
transient Object[] elementData; //為什么要讓ArrayList 存儲數據的結構丟棄呢?
答案:
ArrayList實際上是動態數組,每次在放滿以后自動增長設定的長度值,如果數組自動增長長度設為100,
而實際只放了一個元素,那就會序列化99個null元素。為了保證在序列化的時候不會將這么多null同時進行序列化,
ArrayList把元素數組設置為transient (一句話只對實際有效的值進行保存)
- -* 實現策略:
ArrayList 對writeObject readObject 方法進行了重寫, 對NULL值數據進行了過濾
具體分析:
在ArrayList中定義了來個方法: writeObject 和 readObject
private void readObject(java.io.ObjectInputStream s) 2 throws java.io.IOException, ClassNotFoundException { 3 elementData = EMPTY_ELEMENTDATA; 4 // Read in size, and any hidden stuff 5 s.defaultReadObject(); 6 // Read in capacity 7 s.readInt(); // ignored 8 if (size > 0) { 9 // be like clone(), allocate array based upon size not capacity 10 ensureCapacityInternal(size); 11 Object[] a = elementData; 12 // Read in all elements in the proper order. 13 for (int i=0; i<size; i++) { 14 a[i] = s.readObject(); 15 } 16 } 17 }
private void writeObject(java.io.ObjectOutputStream s) 2 throws java.io.IOException{ 3 // Write out element count, and any hidden stuff 4 int expectedModCount = modCount; 5 s.defaultWriteObject(); 6 // Write out size as capacity for behavioural compatibility with clone() 7 s.writeInt(size); 8 // Write out all elements in the proper order. 9 for (int i=0; i<size; i++) { 10 s.writeObject(elementData[i]); 11 } 12 if (modCount != expectedModCount) { 13 throw new ConcurrentModificationException(); 14 } 15 }
總結; 如何自定義的序列化和反序列化策略 重寫 writeObject 和 readObject 方法,
這兩個方法是怎么被調用的?
void invokeWriteObject(Object obj, ObjectOutputStream out) 2 throws IOException, UnsupportedOperationException 3 { 4 if (writeObjectMethod != null) { 5 try { 6 writeObjectMethod.invoke(obj, new Object[]{ out }); 7 } catch (InvocationTargetException ex) { 8 Throwable th = ex.getTargetException(); 9 if (th instanceof IOException) { 10 throw (IOException) th; 11 } else { 12 throwMiscException(th); 13 } 14 } catch (IllegalAccessException ex) { 15 // should not occur, as access checks have been suppressed 16 throw new InternalError(ex); 17 } 18 } else { 19 throw new UnsupportedOperationException(); 20 } 21 }
其中 writeObjectMethod.invoke(obj, new Object[]{ out }); 是關鍵,通過反射的方式調用writeObjectMethod方法。官方是這么解釋這個writeObjectMethod的:
class-defined writeObject method, or null if none
在我們的例子中,這個方法就是我們在ArrayList中定義的writeObject方法。通過反射的方式被調用了
那么怎么反射的呢?
在 ObjectStreamClass這個方法中 有這么一段代碼: 這樣 readObjectMethod readObjectNoDataMethod 就拿到 了
if (externalizable) { cons = getExternalizableConstructor(cl); } else { cons = getSerializableConstructor(cl); writeObjectMethod = getPrivateMethod(cl, "writeObject", new Class<?>[] { ObjectOutputStream.class }, Void.TYPE); readObjectMethod = getPrivateMethod(cl, "readObject", new Class<?>[] { ObjectInputStream.class }, Void.TYPE); readObjectNoDataMethod = getPrivateMethod( cl, "readObjectNoData", null, Void.TYPE); hasWriteObjectData = (writeObjectMethod != null); } domains = getProtectionDomains(cons, cl); writeReplaceMethod = getInheritableMethod( cl, "writeReplace", null, Object.class); readResolveMethod = getInheritableMethod( cl, "readResolve", null, Object.class); return null; }