1. 程式人生 > >Java序列化總結(最全)

Java序列化總結(最全)

概念

 

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

&n