麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁(yè) > 學(xué)院 > 開發(fā)設(shè)計(jì) > 正文

Java數(shù)據(jù)結(jié)構(gòu)-HashMap

2019-11-14 20:52:02
字體:
供稿:網(wǎng)友
java數(shù)據(jù)結(jié)構(gòu)-HashMap一直以來似乎都有一個(gè)錯(cuò)覺,認(rèn)為map跟其他的集合類一樣繼承自Collection,其實(shí)不然,Map和Collection在結(jié)構(gòu)層次上是沒有任何關(guān)系的,通過查看源碼可以發(fā)現(xiàn)map所有操作都是基于key-value對(duì),而不是單獨(dú)的元素。下面以HashMap為例子,深入對(duì)Map的實(shí)現(xiàn)機(jī)制進(jìn)行了解,在這個(gè)過程中,請(qǐng)打開jdk源碼。Hash算法HashMap使用Hash算法,所以在解剖HashMap之間,需要先簡(jiǎn)單的了解Hash算法,Hash算法一般也成為散列算法,通過散列算法將任意的值轉(zhuǎn)化成固定的長(zhǎng)度輸出,該輸出就是散列值,這是一種壓縮映射,也就是,散列值的空間遠(yuǎn)遠(yuǎn)小于輸入的值空間。簡(jiǎn)單的說,hash算法的意義在于提供了一種快速存取數(shù)據(jù)的方法,它用一種算法建立鍵值與真實(shí)值之間的對(duì)應(yīng)關(guān)系,(每一個(gè)真實(shí)值只能有一個(gè)鍵值,但是一個(gè)鍵值可以對(duì)應(yīng)多個(gè)真實(shí)值),這樣可以快速在數(shù)組等里面存取數(shù)據(jù)。下面我們建立一個(gè)HashMap,然后往里面放入12對(duì)key-value,這個(gè)HashMap的默認(rèn)數(shù)組長(zhǎng)度為16,我們的key分別存放在該數(shù)組的格子中,每個(gè)格子下面存放的元素又是以鏈表的方式存放元素。publicstaticvoidmain(String[]args){Mapmap=newHashMap();map.put("What","chenyz");map.put("You","chenyz");map.put("Don't","chenyz");map.put("Know","chenyz");map.put("About","chenyz");map.put("Geo","chenyz");map.put("APIs","chenyz");map.put("Can't","chenyz");map.put("Hurt","chenyz");map.put("you","chenyz");map.put("google","chenyz");map.put("map","chenyz");map.put("hello","chenyz");}當(dāng)我們新添加一個(gè)元素時(shí),首先我們通過Hash算法計(jì)算出這個(gè)元素的Hash值的hashcode,通過這個(gè)hashcode的值,我們就可以計(jì)算出這個(gè)新元素應(yīng)該存放在這個(gè)hash表的哪個(gè)格子里面,如果這個(gè)格子中已經(jīng)存在元素,那么就把新的元素加入到已經(jīng)存在格子元素的鏈表中。運(yùn)行上面的程序,我們對(duì)HashMap源碼進(jìn)行一點(diǎn)修改,打印出每個(gè)key對(duì)象的hash值What-->hash值:8You-->hash值:3Don't-->hash值:7Know-->hash值:13About-->hash值:11Geo-->hash值:12APIs-->hash值:1Can't-->hash值:7Hurt-->hash值:1you-->hash值:10google-->hash值:3map-->hash值:8hello-->hash值:0計(jì)算出來的Hash值分別代表該key應(yīng)該存放在Hash表中對(duì)應(yīng)數(shù)字的格子中,如果該格子已經(jīng)有元素存在,那么該key就以鏈表的方式依次放入格子中從上表可以看出,Hash表是線性表和鏈表的綜合所得,根據(jù)數(shù)據(jù)結(jié)構(gòu)的定義,可以得出粗劣的結(jié)論,Hash算法的存取速度要比數(shù)組差一些,但是比起單純的鏈表,在查找和存取方面卻要好多。如果要查找一個(gè)元素時(shí),同樣的方式,通過Hash函數(shù)計(jì)算出這個(gè)元素的Hash值hashcode,然后通過這個(gè)hashcode值,直接找到跟這個(gè)hash值相對(duì)應(yīng)的線性格子,進(jìn)如該格子后,對(duì)這個(gè)格子存放的鏈表元素逐個(gè)進(jìn)行比較,直到找到對(duì)應(yīng)的hash值。在簡(jiǎn)單了解完Hash算法后,我們打開HashMap源碼初始化HashMap下面我們看看Map map = new HashMap();這段代碼究竟做了什么,發(fā)生了什么數(shù)據(jù)結(jié)構(gòu)的變化。HashMap中幾個(gè)重要的屬性transient Entry[] table;用來保存key-value的對(duì)象Entry數(shù)組,也就是Hash表transient int size;返回HashMap的鍵值對(duì)個(gè)數(shù)final float loadFactor;負(fù)載因子,用來決定Entry數(shù)組是否擴(kuò)容的因子,HashMap默認(rèn)是0.75fint threshold;重構(gòu)因子,(capacity * load factor)負(fù)載因子與Entry[]數(shù)組容積的乘值publicclassHashMap<K,V>extendsAbstractMap<K,V>implementsMap<K,V>,Cloneable,Serializable{intthreshold;finalfloatloadFactor;transientEntry[]table;staticfinalfloatDEFAULT_LOAD_FACTOR=0.75f;staticfinalintDEFAULT_INITIAL_CAPACITY=16;publicHashMap(intinitialCapacity,floatloadFactor){if(initialCapacity<0)thrownewIllegalArgumentException("Illegalinitialcapacity:"+initialCapacity);if(initialCapacity>MAXIMUM_CAPACITY)initialCapacity=MAXIMUM_CAPACITY;if(loadFactor<=0||Float.isNaN(loadFactor))thrownewIllegalArgumentException("Illegalloadfactor:"+loadFactor);//Findapowerof2>=initialCapacityintcapacity=1;while(capacity<initialCapacity)capacity<<=1;this.loadFactor=loadFactor;threshold=(int)(capacity*loadFactor);table=newEntry[capacity];init();}以public HashMap(int initialCapacity, float loadFactor)構(gòu)造函數(shù)為例,另外兩個(gè)構(gòu)造函數(shù)實(shí)際上也是以同種方式來構(gòu)建HashMap.首先是要確定hashMap的初始化的長(zhǎng)度,這里使用的策略是循環(huán)查出一個(gè)大于initialCapacity的2的次方的數(shù),例如initialCapacity的值是10,那么大于10的數(shù)是2的4次方,也就是16,capacity的值被賦予了16,那么實(shí)際上table數(shù)組的長(zhǎng)度是16,之所以采用這樣的策略來構(gòu)建Hash表的長(zhǎng)度,是因?yàn)?的次方運(yùn)算對(duì)于計(jì)算機(jī)來說是有相當(dāng)?shù)男省oadFactor,被稱為負(fù)載因子,HashMap的默認(rèn)負(fù)載因子是0.75fthreshold,接下來是重構(gòu)因子,由負(fù)載因子和容量的乘機(jī)組成,它表示當(dāng)HashMap元素被存放了多少個(gè)之后,需要對(duì)HashMap進(jìn)行重構(gòu)。通過這一系列的計(jì)算和定義后,初始化Entry[] table;put(key,value)接下來看一對(duì)key-value是如何被存放到HashMap中:put(key,value)publicVput(Kkey,Vvalue){if(key==null)returnputForNullKey(value);inthash=hash(key.hashCode());inti=indexFor(hash,table.length);System.out.access(this);returnoldValue;}}modCount++;addEntry(hash,key,value,i);returnnull;}staticinthash(inth){h^=(h>>>20)^(h>>>12);returnh^(h>>>7)^(h>>>4);}staticintindexFor(inth,intlength){returnh&(length-1);}這里是整個(gè)hash的關(guān)鍵,請(qǐng)打開源碼查看一步一步查看。hash(key.hashCode())計(jì)算出key的hash碼 //對(duì)于hash()的算法,這里有一篇分析很透徹的文章<HashMap hash方法分析>indexFor(hash, table.length)通過一個(gè)與算法計(jì)算出來,該key應(yīng)在存放在Hash表的哪個(gè)格子中。for (Entry<K,V> e = table[i]; e != null; e = e.next)然后再遍歷table[i]格中的鏈表,判斷是否已經(jīng)存在一樣的key,如果存在一樣的key值,那么就用新的value覆蓋舊的value,并把舊的value值返回。addEntry(hash, key, value, i)如果經(jīng)過遍歷鏈表沒有發(fā)現(xiàn)同樣的key,那么進(jìn)行addEntry函數(shù)的操作,增加當(dāng)前key到hash表中的第i個(gè)格子中的鏈表中voidaddEntry(inthash,Kkey,Vvalue,intbucketIndex){Entry<K,V>e=table[bucketIndex];table[bucketIndex]=newEntry<K,V>(hash,key,value,e);if(size++>=threshold)resize(2*table.length);}Entry<K,V> e = table[bucketIndex]; 創(chuàng)建一個(gè)Entry對(duì)象來存放鍵值(ps:Entry對(duì)象是一個(gè)鏈表對(duì)象)table[bucketIndex] = new Entry<K,V>(hash, key, value, e);將Entry對(duì)象添加到鏈表中if (size++ >= threshold) resize(2 * table.length);最后將size進(jìn)行自增,判斷size值是否大于重構(gòu)因子,如果大于那么就是用resize進(jìn)行擴(kuò)容重構(gòu)。voidresize(intnewCapacity){Entry[]oldTable=table;intoldCapacity=oldTable.length;if(oldCapacity==MAXIMUM_CAPACITY){threshold=Integer.MAX_VALUE;return;}Entry[]newTable=newEntry[newCapacity];transfer(newTable);table=newTable;threshold=(int)(newCapacity*loadFactor);}這里為什么是否需要擴(kuò)容重構(gòu),其實(shí)是涉及到負(fù)載因子的性能問題loadFactor負(fù)載因子上面說過loadFactor是一個(gè)hashMap的決定性屬性,HashSet和HashMap的默認(rèn)負(fù)載因子都是0.75,它表示,如果哈希表的容量超過3/4時(shí),將自動(dòng)成倍的增加哈希表的容量,這個(gè)值是權(quán)衡了時(shí)間和空間的成本,如果負(fù)載因子較高,雖然會(huì)減少對(duì)內(nèi)存空間的需求,但也會(huì)增加查找數(shù)據(jù)的時(shí)間開銷,無(wú)論是put()和get()都涉及到對(duì)數(shù)據(jù)進(jìn)行查找的動(dòng)作,所以負(fù)載因子是不適宜設(shè)置過高get(key)接下來看看get(key)做了什么publicVget(Objectkey){if(key==null)returngetForNullKey();inthash=hash(key.hashCode());for(Entry<K,V>e=table[indexFor(hash,table.length)];e!=null;e=e.next){Objectk;if(e.hash==hash&&((k=e.key)==key||key.equals(k)))returne.value;}returnnull;}這些動(dòng)作似乎是跟put(key,value)相識(shí),通過hash算法獲取key的hash碼,再通過indexFor定位出該key存在于table的哪一個(gè)下表,獲取該下標(biāo)然后對(duì)下標(biāo)中的鏈表進(jìn)行遍歷比對(duì),如果有符合就直接返回該key的value值。keySet()這里還涉及另一個(gè)問題,上面說了HashMap是跟set沒有任何親屬關(guān)系,但map也一樣實(shí)現(xiàn)了keySet接口,下面譜析一下keySet在hashMap中是如何實(shí)現(xiàn)的,這里給出部分代碼,請(qǐng)結(jié)合源碼查看publicKnext(){returnnextEntry().getKey();}finalEntry<K,V>nextEntry(){if(modCount!=expectedModCount)thrownewConcurrentModificationException();Entry<K,V>e=next;if(e==null)thrownewNoSuchElementException();if((next=e.next)==null){Entry[]t=table;while(index<t.length&&(next=t[index++])==null);}current=e;returne;}代碼很簡(jiǎn)單,就是對(duì)每個(gè)格子里面的鏈表進(jìn)行遍歷,也正是這個(gè)原因,當(dāng)我們依次將key值put進(jìn)hashMap中,但在使用map.entrySet().iterator()進(jìn)行遍歷時(shí)候卻不是put時(shí)候的順序。擴(kuò)容在前面說到put函數(shù)的時(shí)候,已經(jīng)提過了擴(kuò)容的問題if(size++>=threshold)resize(2*table.length);這里一個(gè)是否擴(kuò)容的判斷,當(dāng)數(shù)據(jù)達(dá)到了threshold所謂的重構(gòu)因子,而不是HashMap的最大容量,就進(jìn)行擴(kuò)容。voidresize(intnewCapacity){Entry[]oldTable=table;intoldCapacity=oldTable.length;if(oldCapacity==MAXIMUM_CAPACITY){threshold=Integer.MAX_VALUE;return;}Entry[]newTable=newEntry[newCapacity];transfer(newTable);table=newTable;threshold=(int)(newCapacity*loadFactor);}voidtransfer(Entry[]newTable){Entry[]src=table;intnewCapacity=newTable.length;for(intj=0;j<src.length;j++){Entry<K,V>e=src[j];if(e!=null){src[j]=null;do{Entry<K,V>next=e.next;inti=indexFor(e.hash,newCapacity);e.next=newTable[i];newTable[i]=e;e=next;}while(e!=null);}}}transfer方法實(shí)際上是將所有的元素重新進(jìn)行一些hash,這是因?yàn)槿萘孔兓耍總€(gè)元素相對(duì)應(yīng)的hash值也會(huì)不一樣。使用HashMap1.不要再高并發(fā)中使用HashMap,HashMap是線程不安全,如果被多個(gè)線程共享之后,將可能發(fā)生不可預(yù)知的問題。2.如果數(shù)據(jù)大小事固定的,最好在初始化的時(shí)候就給HashMap一個(gè)合理的容量值,如果使用new HashMap()默認(rèn)構(gòu)造函數(shù),重構(gòu)因子的值是16*0.75=12,當(dāng)HashMap的容量超過了12后,就會(huì)進(jìn)行一系列的擴(kuò)容運(yùn)算,重建一個(gè)原來成倍的數(shù)組,并且對(duì)原來存在的元素進(jìn)行重新的hash運(yùn)算,如果你的數(shù)據(jù)是有成千上萬(wàn)的,那么你的成千上萬(wàn)的數(shù)據(jù)也要跟這你的擴(kuò)容不斷的hash,這將產(chǎn)生高額的內(nèi)存和cpu的大量開銷。當(dāng)然啦,HashMap的函數(shù)還有很多,不過都是基于table的鏈表進(jìn)行操作,當(dāng)然也就是hash算法,Map & hashMap在平時(shí)我們的應(yīng)用非常多,最重要的是我們要對(duì)每句代碼中每塊數(shù)據(jù)結(jié)構(gòu)變化心中有數(shù)。上面主要是參考了jdk源碼,數(shù)據(jù)結(jié)構(gòu)和一些相關(guān)資料本著好記性不如爛博客的精神記錄下來,希望朋友們?nèi)绻l(fā)覺哪里不對(duì)請(qǐng)指出來,虛心請(qǐng)教
發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 黑人一区二区 | 天天色图片| av在线不卡免费 | 免费a视频 | 一级视频在线播放 | 午夜精品在线视频 | 成年人小视频在线观看 | 免费久久精品 | 色操网| 九草在线视频 | 成人免费影院 | 黄色片网站免费 | 91精选视频在线观看 | 国产一区二区三区网站 | 免费毛片观看 | 午夜精品成人 | av成人在线免费观看 | 精品一区二区在线播放 | 看免费5xxaaa毛片 | 久久国产精品无码网站 | 九九热视频免费 | 91九色精品国产 | 亚洲射情 | 精品国产高清一区二区三区 | 久久精品男人 | 在线成人免费网站 | 无码专区aaaaaa免费视频 | 欧美视频黄色 | 黑人日比视频 | 欧美18—19sex性hd按摩 | 极品美女一级毛片 | 精品人伦一区二区三区蜜桃网站 | 国产亚洲精品久久 | 免费观看黄色影片 | 亚洲视屏在线观看 | 国产成人观看 | 在线观看va | 全黄裸片武则天一级第4季 九色p | 中文字幕欧美一区二区三区 | 成人视屏在线观看 | 特大黑人videos与另类娇小 |