Java序列化總結(最全)

  • 2019 年 10 月 20 日
  • 筆記

概念

 

  • 實現 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;                  }