應(yīng)用Hash函數(shù)

           應(yīng)用Hash函數(shù)

          作者:沖處宇宙

          時(shí)間:2007.1.25

          計(jì)算理論中,沒(méi)有Hash函數(shù)的說(shuō)法,只有單向函數(shù)的說(shuō)法。所謂的單向函數(shù),是一個(gè)復(fù)雜的定義,大家可以去看計(jì)算理論或者密碼學(xué)方面的數(shù)據(jù)。用“人類(lèi)”的語(yǔ)言描述單向函數(shù)就是:如果某個(gè)函數(shù)在給定輸入的時(shí)候,很容易計(jì)算出其結(jié)果來(lái);而當(dāng)給定結(jié)果的時(shí)候,很難計(jì)算出輸入來(lái),這就是單項(xiàng)函數(shù)。各種加密函數(shù)都可以被認(rèn)為是單向函數(shù)的逼近。Hash函數(shù)(或者成為散列函數(shù))也可以看成是單向函數(shù)的一個(gè)逼近。即它接近于滿(mǎn)足單向函數(shù)的定義。

          Hash函數(shù)還有另外的含義。實(shí)際中的Hash函數(shù)是指把一個(gè)大范圍映射到一個(gè)小范圍。把大范圍映射到一個(gè)小范圍的目的往往是為了節(jié)省空間,使得數(shù)據(jù)容易保存。除此以外,Hash函數(shù)往往應(yīng)用于查找上。所以,在考慮使用Hash函數(shù)之前,需要明白它的幾個(gè)限制:

          1. Hash的主要原理就是把大范圍映射到小范圍;所以,你輸入的實(shí)際值的個(gè)數(shù)必須和小范圍相當(dāng)或者比它更小。不然沖突就會(huì)很多。
          2. 由于Hash逼近單向函數(shù);所以,你可以用它來(lái)對(duì)數(shù)據(jù)進(jìn)行加密。
          3. 不同的應(yīng)用對(duì)Hash函數(shù)有著不同的要求;比如,用于加密的Hash函數(shù)主要考慮它和單項(xiàng)函數(shù)的差距,而用于查找的Hash函數(shù)主要考慮它映射到小范圍的沖突率。

          應(yīng)用于加密的Hash函數(shù)已經(jīng)探討過(guò)太多了,在作者的博客里面有更詳細(xì)的介紹。所以,本文只探討用于查找的Hash函數(shù)。

          Hash函數(shù)應(yīng)用的主要對(duì)象是數(shù)組(比如,字符串),而其目標(biāo)一般是一個(gè)int類(lèi)型。以下我們都按照這種方式來(lái)說(shuō)明。

          一般的說(shuō),Hash函數(shù)可以簡(jiǎn)單的劃分為如下幾類(lèi):
          1. 加法Hash;
          2. 位運(yùn)算Hash;
          3. 乘法Hash;
          4. 除法Hash;
          5. 查表Hash;
          6. 混合Hash;
          下面詳細(xì)的介紹以上各種方式在實(shí)際中的運(yùn)用。
          一 加法Hash

          所謂的加法Hash就是把輸入元素一個(gè)一個(gè)的加起來(lái)構(gòu)成最后的結(jié)果。標(biāo)準(zhǔn)的加法Hash的構(gòu)造如下:

          static int additiveHash(String key, int prime)
          {
          int hash, i;
          for (hash = key.length(), i = 0; i < key.length(); i++)
          hash += key.charAt(i);
          return (hash % prime);
          }
          這里的prime是任意的質(zhì)數(shù),看得出,結(jié)果的值域?yàn)閇0,prime-1]。
          二 位運(yùn)算Hash

          這類(lèi)型Hash函數(shù)通過(guò)利用各種位運(yùn)算(常見(jiàn)的是移位和異或)來(lái)充分的混合輸入元素。比如,標(biāo)準(zhǔn)的旋轉(zhuǎn)Hash的構(gòu)造如下:

          static int rotatingHash(String key, int prime)
          {
          int hash, i;
          for (hash=key.length(), i=0; i<key.length(); ++i)
          hash = (hash<<4)^(hash>>28)^key.charAt(i);
          return (hash % prime);
          }

          先移位,然后再進(jìn)行各種位運(yùn)算是這種類(lèi)型Hash函數(shù)的主要特點(diǎn)。比如,以上的那段計(jì)算hash的代碼還可以有如下幾種變形:
          1. hash = (hash<<5)^(hash>>27)^key.charAt(i);
          2. hash += key.charAt(i);
          hash += (hash << 10);
          hash ^= (hash >> 6);
          3. if((i&1) == 0)
          {
          hash ^= (hash<<7) ^ key.charAt(i) ^ (hash>>3);
          }
          else
          {
          hash ^= ~((hash<<11) ^ key.charAt(i) ^ (hash >>5));
          }
          4. hash += (hash<<5) + key.charAt(i);
          5. hash = key.charAt(i) + (hash<<6) + (hash>>16) – hash;
          6. hash ^= ((hash<<5) + key.charAt(i) + (hash>>2));
          三 乘法Hash

          這種類(lèi)型的Hash函數(shù)利用了乘法的不相關(guān)性(乘法的這種性質(zhì),最有名的莫過(guò)于平方取頭尾的隨機(jī)數(shù)生成算法,雖然這種算法效果并不好)。比如,

          static int bernstein(String key)
          {
          int hash = 0;
          int i;
          for (i=0; i<key.length(); ++i) hash = 33*hash + key.charAt(i);
          return hash;
          }

          jdk5.0里面的String類(lèi)的hashCode()方法也使用乘法Hash。不過(guò),它使用的乘數(shù)是31。推薦的乘數(shù)還有:131, 1313, 13131, 131313等等。

          使用這種方式的著名Hash函數(shù)還有:
          // 32位FNV算法
          int M_SHIFT = 0;
          public int FNVHash(byte[] data)
          {
          int hash = (int)2166136261L;
          for(byte b : data)
          hash = (hash * 16777619) ^ b;
          if (M_SHIFT == 0)
          return hash;
          return (hash ^ (hash >> M_SHIFT)) & M_MASK;
          }

          以及改進(jìn)的FNV算法:
          public static int FNVHash1(String data)
          {
          final int p = 16777619;
          int hash = (int)2166136261L;
          for(int i=0;i<data.length();i++)
          hash = (hash ^ data.charAt(i)) * p;
          hash += hash << 13;
          hash ^= hash >> 7;
          hash += hash << 3;
          hash ^= hash >> 17;
          hash += hash << 5;
          return hash;
          }

          除了乘以一個(gè)固定的數(shù),常見(jiàn)的還有乘以一個(gè)不斷改變的數(shù),比如:
          static int RSHash(String str)
          {
          int b = 378551;
          int a = 63689;
          int hash = 0;

          for(int i = 0; i < str.length(); i++)
          {
          hash = hash * a + str.charAt(i);
          a = a * b;
          }
          return (hash & 0x7FFFFFFF);
          }

          雖然Adler32算法的應(yīng)用沒(méi)有CRC32廣泛,不過(guò),它可能是乘法Hash里面最有名的一個(gè)了。關(guān)于它的介紹,大家可以去看RFC 1950規(guī)范。
          四 除法Hash

          除法和乘法一樣,同樣具有表面上看起來(lái)的不相關(guān)性。不過(guò),因?yàn)槌ㄌ@種方式幾乎找不到真正的應(yīng)用。需要注意的是,我們?cè)谇懊婵吹降膆ash的結(jié)果除以一個(gè)prime的目的只是為了保證結(jié)果的范圍。如果你不需要它限制一個(gè)范圍的話(huà),可以使用如下的代碼替代”hash%prime”: hash = hash ^ (hash>>10) ^ (hash>>20)。
          五 查表Hash

          查表Hash最有名的例子莫過(guò)于CRC系列算法。雖然CRC系列算法本身并不是查表,但是,查表是它的一種最快的實(shí)現(xiàn)方式。下面是CRC32的實(shí)現(xiàn):

          static int crctab[256] = {
          省略
          };
          int crc32(String key, int hash)
          {
          int i;
          for (hash=key.length(), i=0; i<key.length(); ++i)
          hash = (hash >> 8) ^ crctab[(hash & 0xff) ^ k.charAt(i)];
          return hash;
          }

          查表Hash中有名的例子有:Universal Hashing和Zobrist Hashing。他們的表格都是隨機(jī)生成的。
          六 混合Hash

          混合Hash算法利用了以上各種方式。各種常見(jiàn)的Hash算法,比如MD5、Tiger都屬于這個(gè)范圍。它們一般很少在面向查找的Hash函數(shù)里面使用。
          七 對(duì)Hash算法的評(píng)價(jià)

          http://www.burtleburtle.net/bob/hash/doobs.html 這個(gè)頁(yè)面提供了對(duì)幾種流行Hash算法的評(píng)價(jià)。我們對(duì)Hash函數(shù)的建議如下:

          1. 字符串的Hash。最簡(jiǎn)單可以使用基本的乘法Hash,當(dāng)乘數(shù)為33時(shí),對(duì)于英文單詞有很好的散列效果(小于6個(gè)的小寫(xiě)形式可以保證沒(méi)有沖突)。復(fù)雜一點(diǎn)可以使用FNV算法(及其改進(jìn)形式),它對(duì)于比較長(zhǎng)的字符串,在速度和效果上都不錯(cuò)。

          2. 長(zhǎng)數(shù)組的Hash。可以使用http://burtleburtle.net/bob/c/lookup3.c這種算法,它一次運(yùn)算多個(gè)字節(jié),速度還算不錯(cuò)。
          八 后記

          本文簡(jiǎn)略的介紹了一番實(shí)際應(yīng)用中的用于查找的Hash算法。Hash算法除了應(yīng)用于這個(gè)方面以外,另外一個(gè)著名的應(yīng)用是巨型字符串匹配(這時(shí)的Hash算法叫做:rolling hash,因?yàn)樗仨毧梢詽L動(dòng)的計(jì)算)。設(shè)計(jì)一個(gè)真正好的Hash算法并不是一件容易的事情。做為應(yīng)用來(lái)說(shuō),選擇一個(gè)適合的算法是最重要的。

          posted on 2008-08-02 11:40 anyStar 閱讀(1168) 評(píng)論(0)  編輯  收藏


          只有注冊(cè)用戶(hù)登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          <2025年7月>
          293012345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          導(dǎo)航

          統(tǒng)計(jì)

          常用鏈接

          留言簿(2)

          隨筆分類(lèi)

          隨筆檔案

          文章檔案

          搜索

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 南陵县| 三河市| 宜春市| 营山县| 江门市| 二连浩特市| 多伦县| 云梦县| 天柱县| 舞钢市| 鹤峰县| 昭通市| 尼玛县| 财经| 三江| 柯坪县| 葫芦岛市| 武邑县| 芷江| 理塘县| 上林县| 武平县| 巴彦淖尔市| 宁津县| 土默特左旗| 宣武区| 南漳县| 慈溪市| 景泰县| 蒙山县| 清苑县| 贵港市| 五峰| 全椒县| 辰溪县| 都江堰市| 舟山市| 永新县| 新化县| 巩义市| 内江市|