transient HashMap使用目的分析
看HashSet源碼有這么一句:
private transient HashMap<E,Object> map;
再看HashSet的Add方法:
實(shí)際上HashSet是復(fù)用HashMap了。
而我們?nèi)タ纯碒ashMap也會(huì)發(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實(shí)際上是不序列化的,這就好像有點(diǎn)“矛盾”,再回答這個(gè)問(wèn)題之前先看看transient。
測(cè)試代碼如下:
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對(duì)象,并驗(yàn)證是否保存了“?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?”在序列化時(shí)沒(méi)有保存到持久狀態(tài),這正是我們?cè)趈ava中使用“?transient?”關(guān)鍵字的原因。?
什么時(shí)候應(yīng)該在java中使用transient關(guān)鍵字?
現(xiàn)在我們對(duì)“?transient??”關(guān)鍵字非常了解。讓我們通過(guò)確定您需要使用transient關(guān)鍵字的情況來(lái)擴(kuò)展理解。
- 第一個(gè)也是非常合乎邏輯的情況是,您可能擁有從類實(shí)例中的其他字段派生/計(jì)算的字段。應(yīng)該每次都以編程方式計(jì)算它們,而不是通過(guò)序列化來(lái)保持狀態(tài)。一個(gè)例子可以是基于時(shí)間戳的價(jià)值;?例如人的年齡或時(shí)間戳和當(dāng)前時(shí)間戳之間的持續(xù)時(shí)間。在這兩種情況下,您將根據(jù)當(dāng)前系統(tǒng)時(shí)間而不是序列化實(shí)例來(lái)計(jì)算變量的值。
- 第二個(gè)邏輯示例可以是任何不應(yīng)以任何形式泄漏到JVM外部的安全信息(在數(shù)據(jù)庫(kù)或字節(jié)流中)。
- 另一個(gè)例子可能是在JDK或應(yīng)用程序代碼中未標(biāo)記為“Serializable”的字段。未實(shí)現(xiàn)Serializable接口并在任何可序列化類中引用的類無(wú)法序列化;?并將拋出“java.io.NotSerializableException”異常。在序列化主類之前,應(yīng)將這些不可序列化的引用標(biāo)記為“transient?”。
- 最后,有時(shí)候序列化某些字段根本沒(méi)有意義。期。例如,在任何類中,如果添加了記錄器引用,那么序列化該記錄器實(shí)例的用途是什么。絕對(duì)沒(méi)用。您可以邏輯地序列化表示實(shí)例狀態(tài)的信息。
Loggers永遠(yuǎn)不要分享實(shí)例的狀態(tài)。它們只是用于編程/調(diào)試目的的實(shí)用程序。類似的例子可以是一個(gè)Thread類的引用。線程表示任何給定時(shí)間點(diǎn)的進(jìn)程狀態(tài),并且沒(méi)有用于將線程狀態(tài)存儲(chǔ)到您的實(shí)例中;?僅僅因?yàn)樗鼈儾粯?gòu)成你班級(jí)實(shí)例的狀態(tài)。
?
//final field 1
public final transient String confidentialInfo = "password";
?現(xiàn)在當(dāng)我再次運(yùn)行序列化(寫(xiě)/讀)時(shí),會(huì)輸出出password。
原因是,只要任何最終字段/引用被評(píng)估為“?常量表達(dá)式?”,它就會(huì)被JVM序列化,忽略transient關(guān)鍵字的存在。
如果要保持非可序列化字段的狀態(tài),請(qǐng)使用readObject()和writeObject()方法。writeObject()/ readObject()通常在內(nèi)部鏈接到序列化/反序列化機(jī)制,因此自動(dòng)調(diào)用。?:java中的SerialVersionUID及相關(guān)
?
HashMap如何使用transient關(guān)鍵字?
HashMap用于存儲(chǔ)鍵值對(duì),我們都知道。并且我們還知道內(nèi)部密鑰的位置HashMap是基于例如密鑰獲得的哈希碼來(lái)計(jì)算的。現(xiàn)在當(dāng)我們序列化一個(gè)HashMap意味著內(nèi)部的所有鍵HashMap和鍵的各個(gè)值也將被序列化。序列化后,當(dāng)我們反序列化HashMap實(shí)例時(shí),所有鍵實(shí)例也將被反序列化。我們知道在這個(gè)序列化/反序列化過(guò)程中,可能會(huì)丟失信息(用于計(jì)算哈希碼),最重要的是它本身就是一個(gè)新的實(shí)例。
在java中,任何兩個(gè)實(shí)例(即使是同一個(gè)類)都不能具有相同的哈希碼。這是一個(gè)很大的問(wèn)題,因?yàn)楦鶕?jù)新的哈希碼應(yīng)該放置鍵的位置不在正確的位置。檢索鍵的值時(shí),您將在此新HashMap中引用錯(cuò)誤的索引。
:在java中使用hashCode和equals方法
因此,當(dāng)序列化哈希映射時(shí),這意味著哈希索引,因此表的順序不再有效,不應(yīng)保留。這是問(wèn)題陳述。
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中的實(shí)現(xiàn),思路都是一樣的。都是把key,value都寫(xiě)到序列化里去,
/*** 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);}
然后反序列化的時(shí)候就是重新hash一次的過(guò)程
下面是HashMap的readObject:
下面是HashSet的readObject:
使用上面的代碼,HashMap仍然可以像通常那樣處理非transient字段,但是它們一個(gè)接一個(gè)地在字節(jié)數(shù)組的末尾寫(xiě)入存儲(chǔ)的鍵值對(duì)。在反序列化時(shí),它會(huì)通過(guò)默認(rèn)的反序列化過(guò)程處理非transient變量,然后逐個(gè)讀取鍵值對(duì)。對(duì)于每個(gè)鍵,哈希和索引再次計(jì)算并插入到表中的正確位置,以便可以再次檢索它而不會(huì)出現(xiàn)任何錯(cuò)誤。?
參考:Java transient關(guān)鍵字示例?
擴(kuò)展閱讀:
為什么HashMap的哈希表標(biāo)記為transient,盡管該類是可序列化的
圖解集合4:HashMap?
總結(jié)
以上是生活随笔為你收集整理的transient HashMap使用目的分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 流萤是谁唱的啊?
- 下一篇: 最新技术选型解决方案列表