【資料結構】30、hashmap=》hash 計算方式

cutter_point發表於2019-06-26

前提知識

寫在前面,為什麼num&(length - 1) 在length是2的n次冪的時候等價於num%length

n - 1意味著比n最高位小的位都為1,而高的位都為0,因此通過與可以剔除位數比n最高位更高的部分,只保留比n最高位小的部分,也就是取餘了。

而且用位運算取代%,效率會比較高。

 

基於以上幾點,我們再看看hashmap中如何計算hash值得

 

這裡吧key的hashcode取出來,然後把它右移16位,然後取異或

這裡從我Google得到的資訊是,int是4個位元組,也就是32位,我們右移16位也即是把高位的資料右移到低位的16位,然後做異或,那就是把高位和低位的資料進行重合

同時保留了低位和高位的資訊

但是為什麼是右移16位,這邊保留疑問,我要是右移8位,4位,2位呢???

不做右移肯定不是,不做右移直接異或,那不就是0麼

 

我們直接做個測試

public static int hash(Object key) {
        int h;
        //也就將key的hashCode無符號右移16位然後與hashCode異或從而得到hash值在putVal方法中(n - 1)& hash計算得到桶的索引位置
        //注意,這裡h是int值,也就是32位,然後無符號又移16位,那麼就是折半,折半之後和原來的資料做異或操作,正好整合了高位和低位的資料
        //混合原始雜湊碼的高位和低位,以此來加大低位的隨機性,而且混合後的低位摻雜了高位的部分特徵,這樣高位的資訊也被變相保留下來。
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

    //測試,如果我們不做高位低位的操作看看hash衝突是大還是小
    public static int hash2(Object key) {
        return (int) key;
    }

    public static int hash3(Object key) {
        int h = key.hashCode();
        //我們不做右移試試,那就自己跟自己異或。。。沒意義,只能是0了
        return (key == null) ? 0 : h ^ h;
    }

    public static int hash4(Object key) {
        int h;
        //我們不做右移試試,或者右移8位試試
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 8);
    }

    public static int hash5(Object key) {
        int h;
        //我們不做右移試試,或者右移8位試試
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 4);
    }

    public static int hash6(Object key) {
        int h;
        //我們不做右移試試,或者右移8位試試
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 2);
    }

hash3的測試可以去除不用考慮了

public void testHash() {
        //產生隨機數
        int init = 64 * 64;
        int size = 64 * 64;

        for(int j = 0; j < 10; ++j) {
//            int size = init * (j+1);
            size *= 2;

            int hash1[] = new int[size];
            int hash2[] = new int[size];
            int hash3[] = new int[size];
            int hash4[] = new int[size];
            int hash5[] = new int[size];
            int hash6[] = new int[size];

            int testCount = size;
            int exist1 = 0;
            int exist2 = 0;
            int exist3 = 0;
            int exist4 = 0;
            int exist5 = 0;
            int exist6 = 0;

            for(int i = 0; i < testCount; ++i) {

                int key = (int) ((Math.random() * (size - 1)) + 1);

                if(hash1[MyHashMap.hash(key)&(size - 1)] != 0) {
                    exist1++;
                } else {
                    hash1[MyHashMap.hash(key)&(size - 1)] = 1;
                }
            }

            for(int i = 0; i < testCount; ++i) {

                int key = (int) ((Math.random() * (size - 1)) + 1);

                if(hash2[MyHashMap.hash2(key)&(size - 1)] != 0) {
                    exist2++;
                } else {
                    hash2[MyHashMap.hash2(key)&(size - 1)] = 1;
                }
            }

//        for(int i = 0; i < testCount; ++i) {
//
//            int key = (int) ((Math.random() * (size - 1)) + 1);
//
//            if(hash3[MyHashMap.hash3(key)&(size - 1)] != 0) {
//                exist3++;
//            } else {
//                hash3[MyHashMap.hash3(key)&(size - 1)] = 1;
//            }
//        }

            for(int i = 0; i < testCount; ++i) {

                int key = (int) ((Math.random() * (size - 1)) + 1);

                if(hash4[MyHashMap.hash4(key)&(size - 1)] != 0) {
                    exist4++;
                } else {
                    hash4[MyHashMap.hash4(key)&(size - 1)] = 1;
                }
            }

            for(int i = 0; i < testCount; ++i) {

                int key = (int) ((Math.random() * (size - 1)) + 1);

                if(hash5[MyHashMap.hash5(key)&(size - 1)] != 0) {
                    exist5++;
                } else {
                    hash5[MyHashMap.hash5(key)&(size - 1)] = 1;
                }
            }

            for(int i = 0; i < testCount; ++i) {

                int key = (int) ((Math.random() * (size - 1)) + 1);

                if(hash6[MyHashMap.hash6(key)&(size - 1)] != 0) {
                    exist6++;
                } else {
                    hash6[MyHashMap.hash6(key)&(size - 1)] = 1;
                }
            }

            System.out.println("衝突比較:\t1:" + exist1 + "\t2:" + exist2 + "\t4:" + exist4 + "\t5:" + exist5 + "\t6:" + exist6);
        }

    }

開始測試:

上面是size會遞增的,現在我們先測size不變的情況看看效果

size=64 * 64

從結果上看明顯是右移8位衝突比較少!!!

 我們把size擴大一倍

再擴大一倍

這次還比較平價

 

 

有人會說這是因為每次隨機的數不一樣的,每次都是產生新的隨機數,沒有可比性

那麼我們每次用一個固定的數去進行hash碰撞

還是64*64開始,依次乘以2,4

@org.junit.jupiter.api.Test
    public void testHash2() throws InterruptedException {
        //產生隨機數
        int init = 64 * 64;
        int size = 8;

        System.out.println("衝突比較:\t1:jdk1.8\t2:沒有操作\t\t4:右移8位\t\t5:右移4位\t\t6:右移2位");

        for(int j = 0; j < 10; ++j) {
//            int size = init * (j+1);
            size = 8 * j + size;
            int hash1[] = new int[size];
            int hash2[] = new int[size];
            int hash4[] = new int[size];
            int hash5[] = new int[size];
            int hash6[] = new int[size];

            int testCount = size / 3;
            int exist1 = 0;
            int exist2 = 0;
            int exist4 = 0;
            int exist5 = 0;
            int exist6 = 0;

            for(int i = 0; i < testCount; ++i) {
                Thread.sleep(i + 1);
                int key = (int) ((Math.random() * (size - 1)) + 1) * j * (i + 1);

                if(hash1[MyHashMap.hash(key)&(size - 1)] != 0) {
                    exist1++;
                } else {
                    hash1[MyHashMap.hash(key)&(size - 1)] = 1;
                }

                if(hash2[MyHashMap.hash2(key)&(size - 1)] != 0) {
                    exist2++;
                } else {
                    hash2[MyHashMap.hash2(key)&(size - 1)] = 1;
                }

                if(hash4[MyHashMap.hash4(key)&(size - 1)] != 0) {
                    exist4++;
                } else {
                    hash4[MyHashMap.hash4(key)&(size - 1)] = 1;
                }

                if(hash5[MyHashMap.hash5(key)&(size - 1)] != 0) {
                    exist5++;
                } else {
                    hash5[MyHashMap.hash5(key)&(size - 1)] = 1;
                }

                if(hash6[MyHashMap.hash6(key)&(size - 1)] != 0) {
                    exist6++;
                } else {
                    hash6[MyHashMap.hash6(key)&(size - 1)] = 1;
                }
            }

            System.out.println("衝突比較:\t1:" + exist1 + "\t\t2:" + exist2 + "\t\t4:" + exist4 + "\t\t\t5:" + exist5 + "\t\t6:" + exist6);
        }

    }

我們還是執行三次比較:

 

 從第一次看結果好像jdk自帶的方式衝突還比較多。。。

 

 

 我最後再來一次,怎麼感覺越來越不對勁。。。

 

 

 

 總結:我懵逼了啊!!!為什麼啊,我測試出來感覺jdk自帶的右移16位的方式,並不能有效減少衝突,反而右移4或者8位測試效果比較好!!!

 

求大神解答!!!

 

相關文章