<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    于吉吉的技術(shù)博客

    建造高性能門(mén)戶(hù)網(wǎng)

      BlogJava :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
      65 隨筆 :: 6 文章 :: 149 評(píng)論 :: 0 Trackbacks
    一直以來(lái)似乎都有一個(gè)錯(cuò)覺(jué),認(rèn)為map跟其他的集合類(lèi)一樣繼承自Collection,其實(shí)不然,Map和Collection在結(jié)構(gòu)層次上是沒(méi)有任何關(guān)系的,通過(guò)查看源碼可以發(fā)現(xiàn)map所有操作都是基于key-value對(duì),而不是單獨(dú)的元素。

    下面以HashMap為例子,深入對(duì)Map的實(shí)現(xiàn)機(jī)制進(jìn)行了解,在這個(gè)過(guò)程中,請(qǐng)打開(kāi)jdk源碼。

    Hash算法

    HashMap使用Hash算法,所以在解剖HashMap之間,需要先簡(jiǎn)單的了解Hash算法,Hash算法一般也成為散列算法,通過(guò)散列算法將任意的值轉(zhuǎn)化成固定的長(zhǎng)度輸出,該輸出就是散列值,這是一種壓縮映射,也就是,散列值的空間遠(yuǎn)遠(yuǎn)小于輸入的值空間。
    簡(jiǎn)單的說(shuō),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è)格子下面存放的元素又是以鏈表的方式存放元素。

        public static void main(String[] args) {
            Map map 
    = new HashMap();
            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í),首先我們通過(guò)Hash算法計(jì)算出這個(gè)元素的Hash值的hashcode,通過(guò)這個(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值:8
    You-->hash值:3
    Don't-->hash值:7
    Know-->hash值:13
    About-->hash值:11
    Geo-->hash值:12
    APIs-->hash值:1
    Can't-->hash值:7
    Hurt-->hash值:1
    you-->hash值:10
    google-->hash值:3
    map-->hash值:8
    hello-->hash值:0

    計(jì)算出來(lái)的Hash值分別代表該key應(yīng)該存放在Hash表中對(duì)應(yīng)數(shù)字的格子中,如果該格子已經(jīng)有元素存在,那么該key就以鏈表的方式依次放入格子中



    從上表可以看出,Hash表是線(xiàn)性表和鏈表的綜合所得,根據(jù)數(shù)據(jù)結(jié)構(gòu)的定義,可以得出粗劣的結(jié)論,Hash算法的存取速度要比數(shù)組差一些,但是比起單純的鏈表,在查找和存取方面卻要好多。

    如果要查找一個(gè)元素時(shí),同樣的方式,通過(guò)Hash函數(shù)計(jì)算出這個(gè)元素的Hash值hashcode,然后通過(guò)這個(gè)hashcode值,直接找到跟這個(gè)hash值相對(duì)應(yīng)的線(xiàn)性格子,進(jìn)如該格子后,對(duì)這個(gè)格子存放的鏈表元素逐個(gè)進(jìn)行比較,直到找到對(duì)應(yīng)的hash值。

    在簡(jiǎn)單了解完Hash算法后,我們打開(kāi)HashMap源碼

    初始化HashMap

    下面我們看看Map map = new HashMap();這段代碼究竟做了什么,發(fā)生了什么數(shù)據(jù)結(jié)構(gòu)的變化。

    HashMap中幾個(gè)重要的屬性

    transient Entry[] table;
    用來(lái)保存key-value的對(duì)象Entry數(shù)組,也就是Hash表

    transient int size;
    返回HashMap的鍵值對(duì)個(gè)數(shù)

    final float loadFactor;
    負(fù)載因子,用來(lái)決定Entry數(shù)組是否擴(kuò)容的因子,HashMap默認(rèn)是0.75f

    int threshold;
    重構(gòu)因子,(capacity * load factor)負(fù)載因子與Entry[]數(shù)組容積的乘值

    public class HashMap<K,V>
        
    extends AbstractMap<K,V>
        
    implements Map<K,V>, Cloneable, Serializable
    {
        
    int threshold;    

        
    final float loadFactor;

        
    transient Entry[] table;

        
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

        
    static final int DEFAULT_INITIAL_CAPACITY = 16;

        
    public HashMap(int initialCapacity, float loadFactor) {
            
    if (initialCapacity < 0)
                
    throw new IllegalArgumentException("Illegal initial capacity: " +
                                                   initialCapacity);
            
    if (initialCapacity > MAXIMUM_CAPACITY)
                initialCapacity 
    = MAXIMUM_CAPACITY;
            
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
                
    throw new IllegalArgumentException("Illegal load factor: " +
                                                   loadFactor);

            
    // Find a power of 2 >= initialCapacity
            int capacity = 1;
            
    while (capacity < initialCapacity)
                capacity 
    <<= 1;

            
    this.loadFactor = loadFactor;
            threshold 
    = (int)(capacity * loadFactor);
            table 
    = new Entry[capacity];
            init();
        }
    以public HashMap(int initialCapacity, float loadFactor)構(gòu)造函數(shù)為例,另外兩個(gè)構(gòu)造函數(shù)實(shí)際上也是以同種方式來(lái)構(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,之所以采用這樣的策略來(lái)構(gòu)建Hash表的長(zhǎng)度,是因?yàn)?的次方運(yùn)算對(duì)于計(jì)算機(jī)來(lái)說(shuō)是有相當(dāng)?shù)男省?br />
    loadFactor,被稱(chēng)為負(fù)載因子,HashMap的默認(rèn)負(fù)載因子是0.75f

    threshold,接下來(lái)是重構(gòu)因子,由負(fù)載因子和容量的乘機(jī)組成,它表示當(dāng)HashMap元素被存放了多少個(gè)之后,需要對(duì)HashMap進(jìn)行重構(gòu)。

    通過(guò)這一系列的計(jì)算和定義后,初始化Entry[] table;

    put(key,value)

    接下來(lái)看一對(duì)key-value是如何被存放到HashMap中:put(key,value)

        public V put(K key, V value) {
            
    if (key == null)
                
    return putForNullKey(value);
            
    int hash = hash(key.hashCode());
            
            
    int i = indexFor(hash, table.length);
            System.out.println(key
    +"-->hash值:"+i);//這就是剛才程序打印出來(lái)的key對(duì)應(yīng)hash值
            for (Entry<K,V> e = table[i]; e != null; e = e.next) {
                Object k;
                
    if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                    V oldValue 
    = e.value;
                    e.value 
    = value;
                    e.recordAccess(
    this);
                    
    return oldValue;
                }
            }

            modCount
    ++;
            addEntry(hash, key, value, i);
            
    return null;
        }

        
    static int hash(int h) {
            h 
    ^= (h >>> 20^ (h >>> 12);
            
    return h ^ (h >>> 7^ (h >>> 4);
        }

        
    static int indexFor(int h, int length) {
            
    return h & (length-1);
        }

    這里是整個(gè)hash的關(guān)鍵,請(qǐng)打開(kāi)源碼查看一步一步查看。

    hash(key.hashCode()) 計(jì)算出key的hash碼 //對(duì)于hash()的算法,這里有一篇分析很透徹的文章<HashMap hash方法分析>
    indexFor(hash, table.length) 通過(guò)一個(gè)與算法計(jì)算出來(lái),該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)過(guò)遍歷鏈表沒(méi)有發(fā)現(xiàn)同樣的key,那么進(jìn)行addEntry函數(shù)的操作,增加當(dāng)前key到hash表中的第i個(gè)格子中的鏈表中

        void addEntry(int hash, K key, V value, int bucketIndex) {
            Entry
    <K,V> e = table[bucketIndex];
            table[bucketIndex] 
    = new Entry<K,V>(hash, key, value, e);
            
    if (size++ >= threshold)
                resize(
    2 * table.length);
        }

    Entry<K,V> e = table[bucketIndex];
      創(chuàng)建一個(gè)Entry對(duì)象來(lái)存放鍵值(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)。

        void resize(int newCapacity) {
            Entry[] oldTable 
    = table;
            
    int oldCapacity = oldTable.length;
            
    if (oldCapacity == MAXIMUM_CAPACITY) {
                threshold 
    = Integer.MAX_VALUE;
                
    return;
            }

            Entry[] newTable 
    = new Entry[newCapacity];
            transfer(newTable);
            table 
    = newTable;
            threshold 
    = (int)(newCapacity * loadFactor);
        }

    這里為什么是否需要擴(kuò)容重構(gòu),其實(shí)是涉及到負(fù)載因子的性能問(wèn)題

    loadFactor負(fù)載因子
    上面說(shuō)過(guò)loadFactor是一個(gè)hashMap的決定性屬性,HashSet和HashMap的默認(rèn)負(fù)載因子都是0.75,它表示,如果哈希表的容量超過(guò)3/4時(shí),將自動(dòng)成倍的增加哈希表的容量,這個(gè)值是權(quán)衡了時(shí)間和空間的成本,如果負(fù)載因子較高,雖然會(huì)減少對(duì)內(nèi)存空間的需求,但也會(huì)增加查找數(shù)據(jù)的時(shí)間開(kāi)銷(xiāo),無(wú)論是put()和get()都涉及到對(duì)數(shù)據(jù)進(jìn)行查找的動(dòng)作,所以負(fù)載因子是不適宜設(shè)置過(guò)高



    get(key)

    接下來(lái)看看get(key)做了什么

        public V get(Object key) {
            
    if (key == null)
                
    return getForNullKey();
            
    int hash = hash(key.hashCode());
            
    for (Entry<K,V> e = table[indexFor(hash, table.length)];
                 e 
    != null;
                 e 
    = e.next) {
                Object k;
                
    if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                    
    return e.value;
            }
            
    return null;
        }

    這些動(dòng)作似乎是跟put(key,value)相識(shí),通過(guò)hash算法獲取key的hash碼,再通過(guò)indexFor定位出該key存在于table的哪一個(gè)下表,獲取該下標(biāo)然后對(duì)下標(biāo)中的鏈表進(jìn)行遍歷比對(duì),如果有符合就直接返回該key的value值。

    keySet()

    這里還涉及另一個(gè)問(wèn)題,上面說(shuō)了HashMap是跟set沒(méi)有任何親屬關(guān)系,但map也一樣實(shí)現(xiàn)了keySet接口,下面譜析一下keySet在hashMap中是如何實(shí)現(xiàn)的,這里給出部分代碼,請(qǐng)結(jié)合源碼查看

    public K next() {
                
    return nextEntry().getKey();
            }

        
    final Entry<K,V> nextEntry() {
                
    if (modCount != expectedModCount)
                    
    throw new ConcurrentModificationException();
                Entry
    <K,V> e = next;
                
    if (e == null)
                    
    throw new NoSuchElementException();

                
    if ((next = e.next) == null) {
                    Entry[] t 
    = table;
                    
    while (index < t.length && (next = t[index++]) == null)
                        ;
                }
            current 
    = e;
                
    return e;
            }

    代碼很簡(jiǎn)單,就是對(duì)每個(gè)格子里面的鏈表進(jìn)行遍歷,也正是這個(gè)原因,當(dāng)我們依次將key值put進(jìn)hashMap中,但在使用map.entrySet().iterator()進(jìn)行遍歷時(shí)候卻不是put時(shí)候的順序。

    擴(kuò)容
    在前面說(shuō)到put函數(shù)的時(shí)候,已經(jīng)提過(guò)了擴(kuò)容的問(wèn)題

    if (size++ >= threshold)
    resize(
    2 * table.length);


    這里一個(gè)是否擴(kuò)容的判斷,當(dāng)數(shù)據(jù)達(dá)到了threshold所謂的重構(gòu)因子,而不是HashMap的最大容量,就進(jìn)行擴(kuò)容。

        
    void resize(int newCapacity) {
            Entry[] oldTable 
    = table;
            
    int oldCapacity = oldTable.length;
            
    if (oldCapacity == MAXIMUM_CAPACITY) {
                threshold 
    = Integer.MAX_VALUE;
                
    return;
            }

            Entry[] newTable 
    = new Entry[newCapacity];
            transfer(newTable);
            table 
    = newTable;
            threshold 
    = (int)(newCapacity * loadFactor);
        }

        
    void transfer(Entry[] newTable) {
            Entry[] src 
    = table;
            
    int newCapacity = newTable.length;
            
    for (int j = 0; j < src.length; j++) {
                Entry
    <K,V> e = src[j];
                
    if (e != null) {
                    src[j] 
    = null;
                    
    do {
                        Entry
    <K,V> next = e.next;
                        
    int i = 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ì)不一樣。

    使用HashMap

    1.不要再高并發(fā)中使用HashMap,HashMap是線(xiàn)程不安全,如果被多個(gè)線(xiàn)程共享之后,將可能發(fā)生不可預(yù)知的問(wèn)題。
    2.如果數(shù)據(jù)大小事固定的,最好在初始化的時(shí)候就給HashMap一個(gè)合理的容量值,如果使用new HashMap()默認(rèn)構(gòu)造函數(shù),重構(gòu)因子的值是16*0.75=12,當(dāng)HashMap的容量超過(guò)了12后,就會(huì)進(jìn)行一系列的擴(kuò)容運(yùn)算,重建一個(gè)原來(lái)成倍的數(shù)組,并且對(duì)原來(lái)存在的元素進(jìn)行重新的hash運(yùn)算,如果你的數(shù)據(jù)是有成千上萬(wàn)的,那么你的成千上萬(wàn)的數(shù)據(jù)也要跟這你的擴(kuò)容不斷的hash,這將產(chǎn)生高額的內(nèi)存和cpu的大量開(kāi)銷(xiāo)。

    當(dāng)然啦,HashMap的函數(shù)還有很多,不過(guò)都是基于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)資料本著好記性不如爛博客的精神記錄下來(lái),希望朋友們?nèi)绻l(fā)覺(jué)哪里不對(duì)請(qǐng)指出來(lái),虛心請(qǐng)教

    ----------------------------------------

    by 陳于喆
    QQ:34174409
    Mail: dongbule@163.com
    posted on 2011-02-15 19:18 陳于喆 閱讀(10182) 評(píng)論(6)  編輯  收藏 所屬分類(lèi): java數(shù)據(jù)結(jié)構(gòu)

    評(píng)論

    # re: java數(shù)據(jù)結(jié)構(gòu)-HashMap 2011-02-16 11:18 xylz
    我以前也寫(xiě)了一篇http://m.tkk7.com/xylz/archive/2010/07/20/326584.html

    不過(guò)我覺(jué)得HashMap的精髓在于這里:
    static int hash(int h) {
    // This function ensures that hashCodes that differ only by
    // constant multiples at each bit position have a bounded
    // number of collisions (approximately 8 at default load factor).
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
    }  回復(fù)  更多評(píng)論
      

    # re: java數(shù)據(jù)結(jié)構(gòu)-HashMap 2011-02-16 12:31 陳于喆
    @xylz
    你分析得相當(dāng)透析,學(xué)習(xí)了  回復(fù)  更多評(píng)論
      

    # re: java數(shù)據(jù)結(jié)構(gòu)-HashMap 2011-02-16 16:52 HiMagic!
    不錯(cuò),很好的分析  回復(fù)  更多評(píng)論
      

    # re: java數(shù)據(jù)結(jié)構(gòu)-HashMap 2011-02-17 14:42 Xuzhengsong
    @xylz
    同意上面的說(shuō)法。
    一直以來(lái)就是不明白它的hash算法為什么要這樣構(gòu)造?
    jdk5中
    還有一個(gè)老式的寫(xiě)法
    private static int oldHash(int h) {
    h += ~(h << 9);
    h ^= (h >>> 14);
    h += (h << 4);
    h ^= (h >>> 10);
    return h;
    }  回復(fù)  更多評(píng)論
      

    # re: java數(shù)據(jù)結(jié)構(gòu)-HashMap 2011-02-20 17:20 淘寶網(wǎng)女裝2011春裝
    一樓看來(lái)也是hash達(dá)人。。。。  回復(fù)  更多評(píng)論
      

    # re: java數(shù)據(jù)結(jié)構(gòu)-HashMap 2011-04-28 10:49 sioumi
    店二百淘寶網(wǎng) www.di28.com
    幫你比 www.80bi.com
    淘寶網(wǎng)女裝 www.sioumi.com   回復(fù)  更多評(píng)論
      

    主站蜘蛛池模板: 亚洲中文精品久久久久久不卡| 亚洲日本在线观看网址| 国产成人精品日本亚洲| 久久亚洲国产精品| 亚洲乱码一区av春药高潮| 中国亚洲呦女专区| 亚洲精品视频久久久| 亚洲色无码一区二区三区| 精品无码一区二区三区亚洲桃色| 亚洲va成无码人在线观看| MM1313亚洲精品无码久久| 一级毛片大全免费播放| 99re这里有免费视频精品| 免费看的成人yellow视频| 中文字幕亚洲图片| 久久精品国产亚洲5555| 亚洲AV日韩精品久久久久久久| 亚洲a级片在线观看| 美女视频黄a视频全免费网站一区| 伊人久久大香线蕉免费视频| 国产黄色免费网站| 亚洲av区一区二区三| 亚洲精品一级无码鲁丝片 | 国产亚洲大尺度无码无码专线 | 中文字幕免费在线看线人| 在线观看免费成人| 亚洲中文字幕无码日韩| 亚洲一卡2卡4卡5卡6卡残暴在线| 美女被艹免费视频| 99国产精品免费视频观看| 日韩亚洲精品福利| 亚洲色av性色在线观无码| 特级一级毛片免费看| 69视频在线观看高清免费| 91香焦国产线观看看免费| 国产精品嫩草影院免费| 亚洲不卡av不卡一区二区| 国产午夜亚洲精品| 国产精品视频白浆免费视频| 日本成人免费在线| 亚洲网址在线观看|