<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                ## Chapter 12. Serialization(序列化) ### Item 87: Consider using a custom serialized form(考慮使用自定義序列化形式) When you are writing a class under time pressure, it is generally appropriate to concentrate your efforts on designing the best API. Sometimes this means releasing a “throwaway” implementation that you know you’ll replace in a future release. Normally this is not a problem, but if the class implements Serializable and uses the default serialized form, you’ll never be able to escape completely from the throwaway implementation. It will dictate the serialized form forever. This is not just a theoretical problem. It happened to several classes in the Java libraries, including BigInteger. 當你在時間緊迫的情況下編寫類時,通常應該將精力集中在設計最佳 API 上。有時,這意味著發布一個「一次性」實現,你也知道在將來的版本中會替換它。通常這不是一個問題,但是如果類實現 Serializable 接口并使用默認的序列化形式,你將永遠無法完全擺脫這個「一次性」的實現。它將永遠影響序列化的形式。這不僅僅是一個理論問題。這種情況發生在 Java 庫中的幾個類上,包括 BigInteger。 **Do not accept the default serialized form without first considering whether it is appropriate.** Accepting the default serialized form should be a conscious decision that this encoding is reasonable from the standpoint of flexibility, performance, and correctness. Generally speaking, you should accept the default serialized form only if it is largely identical to the encoding that you would choose if you were designing a custom serialized form. **在沒有考慮默認序列化形式是否合適之前,不要接受它。** 接受默認的序列化形式應該是一個三思而后行的決定,即從靈活性、性能和正確性的角度綜合來看,這種編碼是合理的。一般來說,設計自定義序列化形式時,只有與默認序列化形式所選擇的編碼在很大程度上相同時,才應該接受默認的序列化形式。 The default serialized form of an object is a reasonably efficient encoding of the physical representation of the object graph rooted at the object. In other words, it describes the data contained in the object and in every object that is reachable from this object. It also describes the topology by which all of these objects are interlinked. The ideal serialized form of an object contains only the logical data represented by the object. It is independent of the physical representation. 對象的默認序列化形式,相對于它的物理表示法而言是一種比較有效的編碼形式。換句話說,它描述了對象中包含的數據以及從該對象可以訪問的每個對象的數據。它還描述了所有這些對象相互關聯的拓撲結構。理想的對象序列化形式只包含對象所表示的邏輯數據。它獨立于物理表征。 **The default serialized form is likely to be appropriate if an object’s physical representation is identical to its logical content.** For example, the default serialized form would be reasonable for the following class, which simplistically represents a person’s name: **如果對象的物理表示與其邏輯內容相同,則默認的序列化形式可能是合適的。** 例如,默認的序列化形式對于下面的類來說是合理的,它簡單地表示一個人的名字: ``` // Good candidate for default serialized form public class Name implements Serializable { /** * Last name. Must be non-null. * @serial */ private final String lastName; /** * First name. Must be non-null. * @serial */ private final String firstName; /** * Middle name, or null if there is none. * @serial */ private final String middleName; ... // Remainder omitted } ``` Logically speaking, a name consists of three strings that represent a last name, a first name, and a middle name. The instance fields in Name precisely mirror this logical content. 從邏輯上講,名字由三個字符串組成,分別表示姓、名和中間名。Name 的實例字段精確地反映了這個邏輯內容。 **Even if you decide that the default serialized form is appropriate, you often must provide a readObject method to ensure invariants and security.** In the case of Name, the readObject method must ensure that the fields lastName and firstName are non-null. This issue is discussed at length in Items 88 and 90. **即使你認為默認的序列化形式是合適的,你通常也必須提供 readObject 方法來確保不變性和安全性。** 對于 Name 類而言, readObject 方法必須確保字段 lastName 和 firstName 是非空的。[Item-88](/Chapter-12/Chapter-12-Item-88-Write-readObject-methods-defensively.md) 和 [Item-90](/Chapter-12/Chapter-12-Item-90-Consider-serialization-proxies-instead-of-serialized-instances.md) 詳細討論了這個問題。 Note that there are documentation comments on the lastName, firstName, and middleName fields, even though they are private. That is because these private fields define a public API, which is the serialized form of the class, and this public API must be documented. The presence of the @serial tag tells Javadoc to place this documentation on a special page that documents serialized forms. 注意,雖然 lastName、firstName 和 middleName 字段是私有的,但是它們都有文檔注釋。這是因為這些私有字段定義了一個公共 API,它是類的序列化形式,并且必須對這個公共 API 進行文檔化。`@serial` 標記的存在告訴 Javadoc 將此文檔放在一個特殊的頁面上,該頁面記錄序列化的形式。 Near the opposite end of the spectrum from Name, consider the following class, which represents a list of strings (ignoring for the moment that you would probably be better off using one of the standard List implementations): 與 Name 類不同,考慮下面的類,它是另一個極端。它表示一個字符串列表(使用標準 List 實現可能更好,但此時暫不這么做): ``` // Awful candidate for default serialized form public final class StringList implements Serializable { private int size = 0; private Entry head = null; private static class Entry implements Serializable { String data; Entry next; Entry previous; } ... // Remainder omitted } ``` Logically speaking, this class represents a sequence of strings. Physically, it represents the sequence as a doubly linked list. If you accept the default serialized form, the serialized form will painstakingly mirror every entry in the linked list and all the links between the entries, in both directions. 從邏輯上講,這個類表示字符串序列。在物理上,它將序列表示為雙向鏈表。如果接受默認的序列化形式,該序列化形式將不遺余力地鏡像出鏈表中的所有項,以及這些項之間的所有雙向鏈接。 **Using the default serialized form when an object’s physical representation differs substantially from its logical data content has four disadvantages:** **當對象的物理表示與其邏輯數據內容有很大差異時,使用默認的序列化形式有四個缺點:** - **It permanently ties the exported API to the current internal representation.** In the above example, the private StringList.Entry class becomes part of the public API. If the representation is changed in a future release, the StringList class will still need to accept the linked list representation on input and generate it on output. The class will never be rid of all the code dealing with linked list entries, even if it doesn’t use them anymore. **它將導出的 API 永久地綁定到當前的內部實現。** 在上面的例子中,私有 `StringList.Entry` 類成為公共 API 的一部分。如果在將來的版本中更改了實現,StringList 類仍然需要接受鏈表形式的輸出,并產生鏈表形式的輸出。這個類永遠也擺脫不掉處理鏈表項所需要的所有代碼,即使不再使用鏈表作為內部數據結構。 - **It can consume excessive space.** In the above example, the serialized form unnecessarily represents each entry in the linked list and all the links. These entries and links are mere implementation details, not worthy of inclusion in the serialized form. Because the serialized form is excessively large, writing it to disk or sending it across the network will be excessively slow. **它會占用過多的空間。** 在上面的示例中,序列化的形式不必要地表示鏈表中的每個條目和所有鏈接關系。這些鏈表項以及鏈接只不過是實現細節,不值得記錄在序列化形式中。因為這樣的序列化形式過于龐大,將其寫入磁盤或通過網絡發送將非常慢。 - **It can consume excessive time.** The serialization logic has no knowledge of the topology of the object graph, so it must go through an expensive graph traversal. In the example above, it would be sufficient simply to follow the next references. **它會消耗過多的時間。** 序列化邏輯不知道對象圖的拓撲結構,因此必須遍歷開銷很大的圖。在上面的例子中,只要遵循 next 的引用就足夠了。 - **It can cause stack overflows.** The default serialization procedure performs a recursive traversal of the object graph, which can cause stack overflows even for moderately sized object graphs. Serializing a StringList instance with 1,000–1,800 elements generates a StackOverflowError on my machine. Surprisingly, the minimum list size for which serialization causes a stack overflow varies from run to run (on my machine). The minimum list size that exhibits this problem may depend on the platform implementation and command-line flags; some implementations may not have this problem at all. **它可能導致堆棧溢出。** 默認的序列化過程執行對象圖的遞歸遍歷,即使對于中等規模的對象圖,這也可能導致堆棧溢出。用 1000-1800 個元素序列化 StringList 實例會在我的機器上生成一個 StackOverflowError。令人驚訝的是,序列化導致堆棧溢出的最小列表大小因運行而異(在我的機器上)。顯示此問題的最小列表大小可能取決于平臺實現和命令行標志;有些實現可能根本沒有這個問題。 A reasonable serialized form for StringList is simply the number of strings in the list, followed by the strings themselves. This constitutes the logical data represented by a StringList, stripped of the details of its physical representation. Here is a revised version of StringList with writeObject and readObject methods that implement this serialized form. As a reminder, the transient modifier indicates that an instance field is to be omitted from a class’s default serialized form: StringList 的合理序列化形式就是列表中的字符串數量,然后是字符串本身。這構成了由 StringList 表示的邏輯數據,去掉了其物理表示的細節。下面是修改后的 StringList 版本,帶有實現此序列化形式的 writeObject 和 readObject 方法。提醒一下,transient 修飾符表示要從類的默認序列化表單中省略該實例字段: ``` // StringList with a reasonable custom serialized form public final class StringList implements Serializable { private transient int size = 0; private transient Entry head = null; // No longer Serializable! private static class Entry { String data; Entry next; Entry previous; } // Appends the specified string to the list public final void add(String s) { ... } /** * Serialize this {@code StringList} instance. ** @serialData The size of the list (the number of strings * it contains) is emitted ({@code int}), followed by all of * its elements (each a {@code String}), in the proper * sequence. */ private void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); s.writeInt(size); // Write out all elements in the proper order. for (Entry e = head; e != null; e = e.next) s.writeObject(e.data); } private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); int numElements = s.readInt(); // Read in all elements and insert them in list for (int i = 0; i < numElements; i++) add((String) s.readObject()); } ... // Remainder omitted } ``` The first thing writeObject does is to invoke defaultWriteObject, and the first thing readObject does is to invoke defaultReadObject, even though all of StringList’s fields are transient. You may hear it said that if all of a class’s instance fields are transient, you can dispense with invoking defaultWriteObject and defaultReadObject, but the serialization specification requires you to invoke them regardless. The presence of these calls makes it possible to add nontransient instance fields in a later release while preserving backward and forward compatibility. If an instance is serialized in a later version and deserialized in an earlier version, the added fields will be ignored. Had the earlier version’s readObject method failed to invoke defaultReadObject, the deserialization would fail with a StreamCorruptedException. writeObject 做的第一件事是調用 defaultWriteObject, readObject 做的第一件事是調用 defaultReadObject,即使 StringList 的所有字段都是 transient 的。你可能聽說過,如果一個類的所有實例字段都是 transient 的,那么你可以不調用 defaultWriteObject 和 defaultReadObject,但是序列化規范要求你無論如何都要調用它們。這些調用的存在使得在以后的版本中添加非瞬態實例字段成為可能,同時保留了向后和向前兼容性。如果實例在較晚的版本中序列化,在較早的版本中反序列化,則會忽略添加的字段。如果早期版本的 readObject 方法調用 defaultReadObject 失敗,反序列化將失敗,并出現 StreamCorruptedException。 Note that there is a documentation comment on the writeObject method, even though it is private. This is analogous to the documentation comment on the private fields in the Name class. This private method defines a public API, which is the serialized form, and that public API should be documented. Like the @serial tag for fields, the @serialData tag for methods tells the Javadoc utility to place this documentation on the serialized forms page. 注意,writeObject 方法有一個文檔注釋,即使它是私有的。這類似于 Name 類中私有字段的文檔注釋。這個私有方法定義了一個公共 API,它是序列化的形式,并且應該對該公共 API 進行文檔化。與字段的 `@serial` 標記一樣,方法的 `@serialData` 標記告訴 Javadoc 實用工具將此文檔放在序列化形式頁面上。 To lend some sense of scale to the earlier performance discussion, if the average string length is ten characters, the serialized form of the revised version of StringList occupies about half as much space as the serialized form of the original. On my machine, serializing the revised version of StringList is over twice as fast as serializing the original version, with a list length of ten. Finally, there is no stack overflow problem in the revised form and hence no practical upper limit to the size of StringList that can be serialized. 為了給前面的性能討論提供一定的伸縮性,如果平均字符串長度是 10 個字符,那么經過修改的 StringList 的序列化形式占用的空間大約是原始字符串序列化形式的一半。在我的機器上,序列化修訂后的 StringList 的速度是序列化原始版本的兩倍多,列表長度為 10。最后,在修改后的形式中沒有堆棧溢出問題,因此對于可序列化的 StringList 的大小沒有實際的上限。 While the default serialized form would be bad for StringList, there are classes for which it would be far worse. For StringList, the default serialized form is inflexible and performs badly, but it is correct in the sense that serializing and deserializing a StringList instance yields a faithful copy of the original object with all of its invariants intact. This is not the case for any object whose invariants are tied to implementation-specific details. 雖然默認的序列化形式對 StringList 不好,但是對于某些類來說,情況會更糟。對于 StringList,默認的序列化形式是不靈活的,并且執行得很糟糕,但是它是正確的,因為序列化和反序列化 StringList 實例會生成原始對象的無差錯副本,而所有不變量都是完整的。對于任何不變量綁定到特定于實現的細節的對象,情況并非如此。 For example, consider the case of a hash table. The physical representation is a sequence of hash buckets containing key-value entries. The bucket that an entry resides in is a function of the hash code of its key, which is not, in general, guaranteed to be the same from implementation to implementation. In fact, it isn’t even guaranteed to be the same from run to run. Therefore, accepting the default serialized form for a hash table would constitute a serious bug. Serializing and deserializing the hash table could yield an object whose invariants were seriously corrupt. 例如,考慮哈希表的情況。物理表示是包含「鍵-值」項的哈希桶序列。一個項所在的桶是其鍵的散列代碼的函數,通常情況下,不能保證從一個實現到另一個實現是相同的。事實上,它甚至不能保證每次運行都是相同的。因此,接受哈希表的默認序列化形式將構成嚴重的 bug。對哈希表進行序列化和反序列化可能會產生一個不變量嚴重損壞的對象。 Whether or not you accept the default serialized form, every instance field that isn’t labeled transient will be serialized when the defaultWriteObject method is invoked. Therefore, every instance field that can be declared transient should be. This includes derived fields, whose values can be computed from primary data fields, such as a cached hash value. It also includes fields whose values are tied to one particular run of the JVM, such as a long field representing a pointer to a native data structure. **Before deciding to make a field nontransient, convince yourself that its value is part of the logical state of the object.** If you use a custom serialized form, most or all of the instance fields should be labeled transient, as in the StringList example above. 無論你是否接受默認的序列化形式,當調用 defaultWriteObject 方法時,沒有標記為 transient 的每個實例字段都會被序列化。因此,可以聲明為 transient 的每個實例字段都應該做這個聲明。這包括派生字段,其值可以從主數據字段(如緩存的哈希值)計算。它還包括一些字段,這些字段的值與 JVM 的一個特定運行相關聯,比如表示指向本機數據結構指針的 long 字段。**在決定使字段非 transient 之前,請確信它的值是對象邏輯狀態的一部分。** 如果使用自定義序列化表單,大多數或所有實例字段都應該標記為 transient,如上面的 StringList 示例所示。 If you are using the default serialized form and you have labeled one or more fields transient, remember that these fields will be initialized to their default values when an instance is deserialized: null for object reference fields, zero for numeric primitive fields, and false for boolean fields [JLS, 4.12.5]. If these values are unacceptable for any transient fields, you must provide a readObject method that invokes the defaultReadObject method and then restores transient fields to acceptable values (Item 88). Alternatively, these fields can be lazily initialized the first time they are used (Item 83). 如果使用默認的序列化形式,并且標記了一個或多個字段為 transient,請記住,當反序列化實例時,這些字段將初始化為默認值:對象引用字段為 null,數字基本類型字段為 0,布爾字段為 false [JLS, 4.12.5]。如果這些值對于任何 transient 字段都是不可接受的,則必須提供一個 readObject 方法,該方法調用 defaultReadObject 方法,然后將 transient 字段恢復為可接受的值([Item-88](/Chapter-12/Chapter-12-Item-88-Write-readObject-methods-defensively.md))。或者,可以采用延遲初始化([Item-83](/Chapter-11/Chapter-11-Item-83-Use-lazy-initialization-judiciously.md)),在第一次使用這些字段時初始化它們。 Whether or not you use the default serialized form, **you must impose any synchronization on object serialization that you would impose on any other method that reads the entire state of the object.** So, for example, if you have a thread-safe object (Item 82) that achieves its thread safety by synchronizing every method and you elect to use the default serialized form, use the following write-Object method: 無論你是否使用默認的序列化形式,**必須對對象序列化強制執行任何同步操作,就像對讀取對象的整個狀態的任何其他方法強制執行的那樣。** 例如,如果你有一個線程安全的對象([Item-82](/Chapter-11/Chapter-11-Item-82-Document-thread-safety.md)),它通過同步每個方法來實現線程安全,并且你選擇使用默認的序列化形式,那么使用以下 write-Object 方法: ``` // writeObject for synchronized class with default serialized form private synchronized void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); } ``` If you put synchronization in the writeObject method, you must ensure that it adheres to the same lock-ordering constraints as other activities, or you risk a resource-ordering deadlock [Goetz06, 10.1.5]. 如果將同步放在 writeObject 方法中,則必須確保它遵守與其他活動相同的鎖排序約束,否則將面臨資源排序死鎖的風險 [Goetz06, 10.1.5]。 **Regardless of what serialized form you choose, declare an explicit serial version UID in every serializable class you write.** This eliminates the serial version UID as a potential source of incompatibility (Item 86). There is also a small performance benefit. If no serial version UID is provided, an expensive computation is performed to generate one at runtime. **無論選擇哪種序列化形式,都要在編寫的每個可序列化類中聲明顯式的序列版本 UID。** 這消除了序列版本 UID 成為不兼容性的潛在來源([Item-86](/Chapter-12/Chapter-12-Item-86-Implement-Serializable-with-great-caution.md))。這么做還能獲得一個小的性能優勢。如果沒有提供序列版本 UID,則需要執行高開銷的計算在運行時生成一個 UID。 Declaring a serial version UID is simple. Just add this line to your class: 聲明序列版本 UID 很簡單,只要在你的類中增加這一行: ``` private static final long serialVersionUID = randomLongValue; ``` If you write a new class, it doesn’t matter what value you choose for randomLongValue. You can generate the value by running the serialver utility on the class, but it’s also fine to pick a number out of thin air. It is not required that serial version UIDs be unique. If you modify an existing class that lacks a serial version UID, and you want the new version to accept existing serialized instances, you must use the value that was automatically generated for the old version. You can get this number by running the serialver utility on the old version of the class—the one for which serialized instances exist. 如果你編寫一個新類,為 randomLongValue 選擇什么值并不重要。你可以通過在類上運行 serialver 實用工具來生成該值,但是也可以憑空選擇一個數字。串行版本 UID 不需要是唯一的。如果修改缺少串行版本 UID 的現有類,并且希望新版本接受現有的序列化實例,則必須使用為舊版本自動生成的值。你可以通過在類的舊版本上運行 serialver 實用工具(序列化實例存在于舊版本上)來獲得這個數字。 If you ever want to make a new version of a class that is incompatible with existing versions, merely change the value in the serial version UID declaration. This will cause attempts to deserialize serialized instances of previous versions to throw an InvalidClassException. **Do not change the serial version UID unless you want to break compatibility with all existing serialized instances of a class.** 如果你希望創建一個新版本的類,它與現有版本不兼容,如果更改序列版本 UID 聲明中的值,這將導致反序列化舊版本的序列化實例的操作引發 InvalidClassException。**不要更改序列版本 UID,除非你想破壞與現有序列化所有實例的兼容性。** To summarize, if you have decided that a class should be serializable (Item 86), think hard about what the serialized form should be. Use the default serialized form only if it is a reasonable description of the logical state of the object; otherwise design a custom serialized form that aptly describes the object. You should allocate as much time to designing the serialized form of a class as you allocate to designing an exported method (Item 51). Just as you can’t eliminate exported methods from future versions, you can’t eliminate fields from the serialized form; they must be preserved forever to ensure serialization compatibility. Choosing the wrong serialized form can have a permanent, negative impact on the complexity and performance of a class. 總而言之,如果你已經決定一個類應該是可序列化的([Item-86](/Chapter-12/Chapter-12-Item-86-Implement-Serializable-with-great-caution.md)),那么請仔細考慮一下序列化的形式應該是什么。只有在合理描述對象的邏輯狀態時,才使用默認的序列化形式;否則,設計一個適合描述對象的自定義序列化形式。設計類的序列化形式應該和設計導出方法花的時間應該一樣多,都應該嚴謹對待([Item-51](/Chapter-8/Chapter-8-Item-51-Design-method-signatures-carefully.md))。正如不能從未來版本中刪除導出的方法一樣,也不能從序列化形式中刪除字段;必須永遠保存它們,以確保序列化兼容性。選擇錯誤的序列化形式可能會對類的復雜性和性能產生永久性的負面影響。 --- **[Back to contents of the chapter(返回章節目錄)](/Chapter-12/Chapter-12-Introduction.md)** - **Previous Item(上一條目):[Item 86: Implement Serializable with great caution(非常謹慎地實現 Serializable)](/Chapter-12/Chapter-12-Item-86-Implement-Serializable-with-great-caution.md)** - **Next Item(下一條目):[Item 88: Write readObject methods defensively(防御性地編寫 readObject 方法)](/Chapter-12/Chapter-12-Item-88-Write-readObject-methods-defensively.md)**
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看