transient HashMap使用目的分析
看HashSet源碼有這么一句:
private transient HashMap<E,Object> map;
再看HashSet的Add方法:
實際上HashSet是復(fù)用HashMap了。
而我們?nèi)タ纯碒ashMap也會發(fā)現(xiàn)一樣使用了transient
而不管是HashSet還是HashMapdou都要求是Serializable的:
public class HashMap<K,V> extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable {
public class HashSet<E>extends AbstractSet<E>implements Set<E>, Cloneable, java.io.Serializable
而transient實際上是不序列化的,這就好像有點“矛盾”,再回答這個問題之前先看看transient。
測試代碼如下:
import java.io.Serializable;public class EmployeeTransient implements Serializable {public String getConfidentialInfo() {return confidentialInfo;}public void setConfidentialInfo(String confidentialInfo) {this.confidentialInfo = confidentialInfo;}public String getFirstName() {return firstName;}public void setFirstName(String firstName) {this.firstName = firstName;}public String getLastName() {return lastName;}public void setLastName(String lastName) {this.lastName = lastName;}private transient String confidentialInfo;private String firstName;private String lastName;
}
?現(xiàn)在讓我們先序列化再反序列化回java對象,并驗證是否保存了“?confidentialInfo?”?
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;public class TransientTest {public static void main(String args[]) {try {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("empInfo.ser"));EmployeeTransient emp = new EmployeeTransient();emp.setFirstName("Lokesh");emp.setLastName("Gupta");emp.setConfidentialInfo("password");//Serialize the objectoos.writeObject(emp);oos.close();} catch (Exception e) {System.out.println(e);}try {ObjectInputStream ooi = new ObjectInputStream(new FileInputStream("empInfo.ser"));//Read the object backEmployeeTransient readEmpInfo = (EmployeeTransient) ooi.readObject();System.out.println(readEmpInfo.getFirstName());System.out.println(readEmpInfo.getLastName());System.out.println(readEmpInfo.getConfidentialInfo());ooi.close();} catch (Exception e) {System.out.println(e);}}
}
很明顯,“?confidentialInfo?”在序列化時沒有保存到持久狀態(tài),這正是我們在java中使用“?transient?”關(guān)鍵字的原因。?
什么時候應(yīng)該在java中使用transient關(guān)鍵字?
現(xiàn)在我們對“?transient??”關(guān)鍵字非常了解。讓我們通過確定您需要使用transient關(guān)鍵字的情況來擴展理解。
- 第一個也是非常合乎邏輯的情況是,您可能擁有從類實例中的其他字段派生/計算的字段。應(yīng)該每次都以編程方式計算它們,而不是通過序列化來保持狀態(tài)。一個例子可以是基于時間戳的價值;?例如人的年齡或時間戳和當(dāng)前時間戳之間的持續(xù)時間。在這兩種情況下,您將根據(jù)當(dāng)前系統(tǒng)時間而不是序列化實例來計算變量的值。
- 第二個邏輯示例可以是任何不應(yīng)以任何形式泄漏到JVM外部的安全信息(在數(shù)據(jù)庫或字節(jié)流中)。
- 另一個例子可能是在JDK或應(yīng)用程序代碼中未標(biāo)記為“Serializable”的字段。未實現(xiàn)Serializable接口并在任何可序列化類中引用的類無法序列化;?并將拋出“java.io.NotSerializableException”異常。在序列化主類之前,應(yīng)將這些不可序列化的引用標(biāo)記為“transient?”。
- 最后,有時候序列化某些字段根本沒有意義。期。例如,在任何類中,如果添加了記錄器引用,那么序列化該記錄器實例的用途是什么。絕對沒用。您可以邏輯地序列化表示實例狀態(tài)的信息。
Loggers永遠不要分享實例的狀態(tài)。它們只是用于編程/調(diào)試目的的實用程序。類似的例子可以是一個Thread類的引用。線程表示任何給定時間點的進程狀態(tài),并且沒有用于將線程狀態(tài)存儲到您的實例中;?僅僅因為它們不構(gòu)成你班級實例的狀態(tài)。
?
//final field 1
public final transient String confidentialInfo = "password";
?現(xiàn)在當(dāng)我再次運行序列化(寫/讀)時,會輸出出password。
原因是,只要任何最終字段/引用被評估為“?常量表達式?”,它就會被JVM序列化,忽略transient關(guān)鍵字的存在。
如果要保持非可序列化字段的狀態(tài),請使用readObject()和writeObject()方法。writeObject()/ readObject()通常在內(nèi)部鏈接到序列化/反序列化機制,因此自動調(diào)用。?:java中的SerialVersionUID及相關(guān)
?
HashMap如何使用transient關(guān)鍵字?
HashMap用于存儲鍵值對,我們都知道。并且我們還知道內(nèi)部密鑰的位置HashMap是基于例如密鑰獲得的哈希碼來計算的。現(xiàn)在當(dāng)我們序列化一個HashMap意味著內(nèi)部的所有鍵HashMap和鍵的各個值也將被序列化。序列化后,當(dāng)我們反序列化HashMap實例時,所有鍵實例也將被反序列化。我們知道在這個序列化/反序列化過程中,可能會丟失信息(用于計算哈希碼),最重要的是它本身就是一個新的實例。
在java中,任何兩個實例(即使是同一個類)都不能具有相同的哈希碼。這是一個很大的問題,因為根據(jù)新的哈希碼應(yīng)該放置鍵的位置不在正確的位置。檢索鍵的值時,您將在此新HashMap中引用錯誤的索引。
:在java中使用hashCode和equals方法
因此,當(dāng)序列化哈希映射時,這意味著哈希索引,因此表的順序不再有效,不應(yīng)保留。這是問題陳述。
HashMap類使用writeObject()和readObject()方法,如下所示:
/*** Save the state of the <tt>HashMap</tt> instance to a stream (i.e.,* serialize it).** @serialData The <i>capacity</i> of the HashMap (the length of the* bucket array) is emitted (int), followed by the* <i>size</i> (an int, the number of key-value* mappings), followed by the key (Object) and value (Object)* for each key-value mapping. The key-value mappings are* emitted in no particular order.*/private void writeObject(java.io.ObjectOutputStream s)throws IOException {int buckets = capacity();// Write out the threshold, loadfactor, and any hidden stuffs.defaultWriteObject();s.writeInt(buckets);s.writeInt(size);internalWriteEntries(s);}
// Callbacks to allow LinkedHashMap post-actionsvoid afterNodeAccess(Node<K,V> p) { }void afterNodeInsertion(boolean evict) { }void afterNodeRemoval(Node<K,V> p) { }// Called only from writeObject, to ensure compatible ordering.void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException {Node<K,V>[] tab;if (size > 0 && (tab = table) != null) {for (int i = 0; i < tab.length; ++i) {for (Node<K,V> e = tab[i]; e != null; e = e.next) {s.writeObject(e.key);s.writeObject(e.value);}}}}
下面是HashSet中的實現(xiàn),思路都是一樣的。都是把key,value都寫到序列化里去,
/*** Save the state of this <tt>HashSet</tt> instance to a stream (that is,* serialize it).** @serialData The capacity of the backing <tt>HashMap</tt> instance* (int), and its load factor (float) are emitted, followed by* the size of the set (the number of elements it contains)* (int), followed by all of its elements (each an Object) in* no particular order.*/private void writeObject(java.io.ObjectOutputStream s)throws java.io.IOException {// Write out any hidden serialization magics.defaultWriteObject();// Write out HashMap capacity and load factors.writeInt(map.capacity());s.writeFloat(map.loadFactor());// Write out sizes.writeInt(map.size());// Write out all elements in the proper order.for (E e : map.keySet())s.writeObject(e);}
然后反序列化的時候就是重新hash一次的過程
下面是HashMap的readObject:
下面是HashSet的readObject:
使用上面的代碼,HashMap仍然可以像通常那樣處理非transient字段,但是它們一個接一個地在字節(jié)數(shù)組的末尾寫入存儲的鍵值對。在反序列化時,它會通過默認的反序列化過程處理非transient變量,然后逐個讀取鍵值對。對于每個鍵,哈希和索引再次計算并插入到表中的正確位置,以便可以再次檢索它而不會出現(xiàn)任何錯誤。?
參考:Java transient關(guān)鍵字示例?
擴展閱讀:
為什么HashMap的哈希表標(biāo)記為transient,盡管該類是可序列化的
圖解集合4:HashMap?
總結(jié)
以上是生活随笔為你收集整理的transient HashMap使用目的分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 流萤是谁唱的啊?
- 下一篇: 黄山风景区看日出几点起床?