murmurhash源码分析

分析levelDB源码的时候遇到的...发现是一个广泛应用的hash算法,而且是纯c写的,于是找来了源码看。

**MurmurHash** 是一种非[加密](https://zh.wikipedia.org/wiki/)型[哈希函数](https://zh.wikipedia.org/wiki/),适用于一般的哈希检索操作。[[1]](https://zh.wikipedia.org/wiki/Murmur#cite_note-Hadoop-1)[[2]](https://zh.wikipedia.org/wiki/Murmur#cite_note-2)[[3]](https://zh.wikipedia.org/wiki/Murmur#cite_note-3)由Austin Appleby在2008年发明,[[4]](https://zh.wikipedia.org/wiki/Murmur#cite_note-4)[[5]](https://zh.wikipedia.org/wiki/Murmur#cite_note-5) 并出现了多个变种,[[6]](https://zh.wikipedia.org/wiki/Murmur#cite_note-Murmur160-6) 都已经发布到了[公有领域](https://zh.wikipedia.org/wiki/)(public domain)。与其它流行的哈希函数相比,对于规律性较强的key,MurmurHash的随机分布特征表现更良好。[[7]](https://zh.wikipedia.org/wiki/Murmur#cite_note-StackExchange-7)

最初的实现是C++的,但是被移植到了其他的流行语言上,包括 Python,[11]C,[12]C#,[9][13]Perl,[14]Ruby,[15]PHP,[16]Haskell,[17]Scala[18]Java[19][20]JavaScript[21][22]等。

这个算法已经被若干开源计划所采纳,最重要的有libstdc++ (4.6版)、Perl[23]nginx (不早于1.0.1版)[24]Rubinius[25]、 libmemcached (MemcachedC语言客户端驱动)[26]、maatkit[27]Hadoop[1]、Kyoto Cabinet[28]以及RaptorDB[29]

虽然说破天就是一个hash函数。。似乎没什么好分析的?

不过由于是第一次分析有现实意义的代码,所以简单一点也不是罪过吧orz

以及这次分析代码的重点不在hash算法本身...而是算法之外的其他东西...

大概感受下有现实意义的工程代码的布局之类orz

hash函数本身没有分析...这个没什么好分析的吧...应该是类似一种构造,看懂每一步很容易,但是你还是想不出来啊?而且一堆"magic number"

代码很短,也就200行,分析见注释。

  1    /**
  2     * `main.c' - murmurhash
  3     *
  4     * copyright (c) 2014 joseph werle <joseph.werle@gmail.com>
  5     */
  6    
  7    #include <stdio.h>
  8    #include <stdlib.h>
  9    #include <string.h>
 10    #include <unistd.h>
 11    #include <inttypes.h>
 12    #include "murmurhash.h"
 13    
 14    static void
 15    usage () {
 16      fprintf(stderr, "usage: murmur [-hV] [options]\n");
 17    }
 18    
 19    static void   //函数类型和函数名不一行写是什么风格orz...
 20    help () {
 21      fprintf(stderr, "\noptions:\n");
 22      fprintf(stderr, "\n  --seed=[seed]  hash seed (optional)");
 23      fprintf(stderr, "\n");
 24    }
 25    
 26    static char *
 27    read_stdin () {
 28      size_t bsize = 1024;
 29      size_t size = 1;
 30      char buf[bsize];
 31      char *res = (char *) malloc(sizeof(char) * bsize);
 32      char *tmp = NULL;
 33    
 34      // memory issue
 35      if (NULL == res) { return NULL; } //申请内存失败了...
 36    
 37      // cap
 38      res[0] = '\0';
 39    
 40      // read
 41      if (NULL != fgets(buf, bsize, stdin)) {
 42        // store
 43        tmp = res; //异常安全? 
 44        // resize
 45        size += (size_t) strlen(buf);
 46        // realloc
 47        res = (char *) realloc(res, size);
 48    
 49        // memory issues
 50        if (NULL == res) {
 51          free(tmp);
 52          return NULL;
 53        }
 54    
 55        // yield
 56        strcat(res, buf); //将buf连接到res后面...
 57      //  printf("buf:%s res:%s\n",buf,res);
 58        return res;
 59      }
 60    
 61      free(res);
 62    
 63    
 64      return NULL;
 65    }
 66    
 67    #define isopt(opt, str) (0 == strncmp(opt, str, strlen(str))) 
 68    //比较opt和str前strlen(str)个字符。
 69    
 70    #define setopt(opt, key, var) {                       \
 71        char tmp = 0;                                     \
 72        size_t len = strlen(key) + 1;                     \
 73        for (int i = 0; i < len; ++i) { tmp = *opt++; }   \
 74        var = opt;                                        \
 75    }
 76    // key是"seed",让len = strlen(key)+1,是为了把seed和‘=’跳过去。。。
 77    // 因此seed,‘=’,后面的数字之间不支持有空格。
 78    // 得到的var是*char型的seed.
 79    
 80    
 81    int
 82    main (int argc, char **argv) { //argc是命令行的参数个数,**argv是字符串数组
 83      char *buf = NULL;
 84      char *key = NULL;
 85      char *seed = NULL;
 86      uint32_t h = 0;
 87    
 88      // parse opts
 89      {
 90        char *opt = NULL;
 91        char tmp = 0;
 92    
 93     //   for ( int i = 0 ;  i < argc ; i++)  printf("Argument %d is %s.\n", i, argv[i]);
 94        opt = *argv++; // unused,因为第0个参数就是“murmur”,跳过。
 95        
 96    
 97        while ((opt = *argv++)) { //argv参数最后会变成null,退出循环...
 98    //	printf("opt: %s argv:%s\n",opt,*argv);
 99    
100          // flags
101          if ('-' == *opt++) {
102            switch (*opt++) {
103              case 'h':
104                return usage(), help(), 0; //直接用了return和逗号表达式...避免了break,代码简洁了(?
105    
106              case 'V':
107                fprintf(stderr, "%s\n", MURMURHASH_VERSION);
108                return 0;
109    
110              case '-': //参数不要带空格。。。
111                if (isopt(opt, "seed")) {
112                  setopt(opt, "seed", seed);
113                }
114                break; //是break而不是return,说明在--seed参数后面还可以跟其他参数。。
115    		    //如果跟了多了--seed参数,前面的参数会被最有一个覆盖掉,hash结果与最后一个seed对应。
116              default:
117                tmp = *opt--;//这个tmp好像没什么用啊?
118                // error
119                fprintf(stderr, "unknown option: `%s'\n", opt);
120                usage();
121                return 1;
122            }
123          }
124        }
125      }
126    
127      if (NULL == seed) { //赞! 避免了手残把等号写成赋值号的可能,下面也都用了这种写法。
128        seed = "0";
129      }
130    
131    #define hash(key) murmurhash(key, (uint32_t) strlen(key), (uint32_t) atoi(seed));
132    						//atoi,把字符串转成了int型,然后被强转了。。。
133      if (1 == isatty(0)) { return 1; } //isatty 判断文件描述词是否位终端机(?,是为1,不是为0
134      else if (ferror(stdin)) { return 1; }
135      else {
136        buf = read_stdin();
137        if (NULL == buf) { return 1; }
138        else if (0 == strlen(buf)) { buf = ""; }
139        h = hash(buf);
140        printf("%" PRIu32 "\n", h);
141        //PRIu32是头文件inttypes.h包含的宏,用于平台独立的printf编码。。
142        //会自动根据平台去对应相应的类型说明符。。。
143        do {
144          key = read_stdin();
145          if (NULL == key) { break; }
146          else if (0 == strlen(buf)) { buf = ""; }
147          h = hash(buf);
148          printf("%d" PRIu32 "\n", h);
149        } while (NULL != key);
150      }
151    
152    #undef hash
153    
154      return 0;
155    }
156
157
158
159    
160    /**
161     * `murmurhash.h' - murmurhash
162     *
163     * copyright (c) 2014 joseph werle <joseph.werle@gmail.com>
164     */
165    
166    #include <stdlib.h>
167    #include <stdio.h>
168    #include <stdint.h>
169    #include "murmurhash.h"
170    
171    uint32_t
172    murmurhash (const char *key, uint32_t len, uint32_t seed) {
173      uint32_t c1 = 0xcc9e2d51;
174      uint32_t c2 = 0x1b873593;
175      uint32_t r1 = 15;
176      uint32_t r2 = 13;
177      uint32_t m = 5;
178      uint32_t n = 0xe6546b64;
179      uint32_t h = 0;
180      uint32_t k = 0;
181      uint8_t *d = (uint8_t *) key; // 32 bit extract from `key'
182      const uint32_t *chunks = NULL;
183      const uint8_t *tail = NULL; // tail - last 8 bytes
184      int i = 0;
185      int l = len / 4; // chunk length
186    
187      h = seed;
188    
189      chunks = (const uint32_t *) (d + l * 4); // body
190      tail = (const uint8_t *) (d + l * 4); // last 8 byte chunk of `key'
191    
192      // for each 4 byte chunk of `key'
193      for (i = -l; i != 0; ++i) {
194        // next 4 byte chunk of `key'
195        k = chunks[i];
196    
197        // encode next 4 byte chunk of `key'
198        k *= c1;
199        k = (k << r1) | (k >> (32 - r1));
200        k *= c2;
201    
202        // append to hash
203        h ^= k;
204        h = (h << r2) | (h >> (32 - r2));
205        h = h * m + n;
206      }
207    
208      k = 0;
209    
210      // remainder
211      switch (len & 3) { // `len % 4'
212        case 3: k ^= (tail[2] << 16);
213        case 2: k ^= (tail[1] << 8);
214    
215        case 1:
216          k ^= tail[0];
217          k *= c1;
218          k = (k << r1) | (k >> (32 - r1));
219          k *= c2;
220          h ^= k;
221      }
222    
223      h ^= len;
224    
225      h ^= (h >> 16);
226      h *= 0x85ebca6b;
227      h ^= (h >> 13);
228      h *= 0xc2b2ae35;
229      h ^= (h >> 16);
230    
231      return h;
232    }
233
234

Posts in this Series