美文网首页
OC 中NSString的hash方法

OC 中NSString的hash方法

作者: Peter杰 | 来源:发表于2019-09-29 15:12 被阅读0次

对象的hash

oc对象的hash,就是对象地址本身

Person *p1 = [Person new];
NSLog(@"p1.hash:%lu\np1:%ld",p1.hash, (long)p1);
//打印结果
p1.hash:4333796016
p1:4333796016

NSString中的hash函数

NSString 中hash是如何处理的呢?我在CFString中找到了如下部分代码:


翻译如下

/ *字符串散列:无论编码如何,都应该给出相同的结果;所以我们哈希UniChars。
如果长度小于或等于96,则哈希函数就是
以下(n是第n个UniChar字符,从0开始):
   
  hash(-1)= length
  hash(n)= hash(n-1)* 257 + unichar(n);
  Hash = hash(length-1)*((length&31)+ 1)

如果长度大于96,则上述算法适用于
字符0..31,(长度/ 2)-16 ..(长度/ 2)+15,长度-32..length-1,包括端点;
因此,第一个,中间和最后32个字符。

请注意,下面的循环已展开;并且:257 ^ 2 = 66049; 257 ^ 3 = 16974593; 257 ^ 4 = 4362470401; 67503105是257 ^ 4  -  256 ^ 4
如果哈希码从UInt32更改为其他内容,则需要重新调整最后一块。
!我们尚未更新LP64

注意:哈希算法曾经在CF和Foundation中重复;但现在它应该只在下面的四个函数中。

Hash功能在Panther和Tiger,Tiger和Leopard之间发生了变化。
* /

源码中的实现

#define HashEverythingLimit 96

#define HashNextFourUniChars(accessStart, accessEnd, pointer) \
    {result = result * 67503105 + (accessStart 0 accessEnd) * 16974593  + (accessStart 1 accessEnd) * 66049  + (accessStart 2 accessEnd) * 257 + (accessStart 3 accessEnd); pointer += 4;}

#define HashNextUniChar(accessStart, accessEnd, pointer) \
    {result = result * 257 + (accessStart 0 accessEnd); pointer++;}


/* In this function, actualLen is the length of the original string; but len is the number of characters in buffer. The buffer is expected to contain the parts of the string relevant to hashing.
*/
CF_INLINE CFHashCode __CFStrHashCharacters(const UniChar *uContents, CFIndex len, CFIndex actualLen) {
    CFHashCode result = actualLen;
    if (len <= HashEverythingLimit) {  //如果长度小于96
        const UniChar *end4 = uContents + (len & ~3);
        const UniChar *end = uContents + len;
        while (uContents < end4) HashNextFourUniChars(uContents[, ], uContents);    // First count in fours
        while (uContents < end) HashNextUniChar(uContents[, ], uContents);      // Then for the last <4 chars, count in ones...
    } else {  //字符串长度
        const UniChar *contents, *end;
    contents = uContents;
        end = contents + 32;
        while (contents < end) HashNextFourUniChars(contents[, ], contents);
    contents = uContents + (len >> 1) - 16;
        end = contents + 32;
        while (contents < end) HashNextFourUniChars(contents[, ], contents);
    end = uContents + len;
        contents = end - 32;
        while (contents < end) HashNextFourUniChars(contents[, ], contents);
    }
    return result + (result << (actualLen & 31));
}

如果讲宏的方法写入代码中

//96:0000 0000 0110 0000
//UInt16  UniChar  16位无符号整型
CFHashCode wj_CFStrHashCharacters(const UniChar *uContents, CFIndex len, CFIndex actualLen) {
    // 3:0000 0000 0000 0011
    //~3:1111 1111 1111 1100
    CFHashCode result = actualLen;
    if (len <= HashEverythingLimit) {
        // 3:0000 0000 0000 0011
        //~3:1111 1111 1111 1100
        const UniChar *end4 = uContents + (len & ~3);   //字符串是否大于4位
        const UniChar *end = uContents + len;
        while (uContents < end4) {
            //257 ^ 2 = 66049; 257 ^ 3 = 16974593; 257 ^ 4 = 4362470401; 67503105是257 ^ 4  -  256 ^ 4
            result = result * 67503105 + (uContents[ 0 ]) * 16974593  + (uContents[ 1 ]) * 66049  + (uContents[ 2 ]) * 257 + (uContents[ 3 ]);
            uContents += 4;
        }     // First count in fours
        while (uContents < end){
            //
            result = result * 257 + (uContents[ 0 ]);
            uContents++;
        }
    } else {
        const UniChar *contents, *end;
        contents = uContents;
        end = contents + 32;
        while (contents < end){
            result = result * 67503105 + (contents[ 0 ]) * 16974593  + (contents[ 1 ]) * 66049  + (contents[ 2 ]) * 257 + (contents[ 3 ]);
            contents += 4;
            
        }
        contents = uContents + (len >> 1) - 16;
        end = contents + 32;
        while (contents < end){
            result = result * 67503105 + (contents[ 0 ]) * 16974593  + (contents[ 1 ]) * 66049  + (contents[ 2 ]) * 257 + (contents[ 3 ]);
            uContents += 4;
        }
        end = uContents + len;
        contents = end - 32;
        while (contents < end) {
            result = result * 67503105 + (contents[ 0 ]) * 16974593  + (contents[ 1 ]) * 66049  + (contents[ 2 ]) * 257 + (contents[ 3 ]);
            uContents += 4;
        }
    }
    
    //31: 0001 1111
    //
    return result + (result << (actualLen & 31));
}

如果字符串小于4位,

会执行

while (uContents < end){
            //
            result = result * 257 + (uContents[ 0 ]);
            uContents++;
 }
return result + (result << (actualLen & 31));

我们以字符串“ abc”举例

NSString *string = @"abc";
UniChar buffer[string.length];
//         [string getCharacters:buffer range:NSMakeRange(0, string.length)];
CFStringGetCharacters((CFStringRef)string, CFRangeMake(0, string.length), buffer);
//buffer[0] = 97
CFHashCode code = __CFStrHashCharacters(buffer,string.length,string.length);
NSLog(@"hash:%lu,code:%lu", string.hash,code);

字符"a"的ACISS码是97,字符"b"的ACISS码是97,字符"c"的ACISS码是99,字符串的长度是3,会执行

len = 3
CFHashCode result = 3;
while (uContents < end){
            //
            result = result * 257 + (uContents[ 0 ]);
            uContents++;
 }
return result + (result << (actualLen & 31));

以上可以写成如下公式:

假设长度为3
len = 3;
(((len*257 + content[0])*257 + content[0])*257 +  content[0]

(((3 * 257 + 97) * 257 + 98) * 257 +99

3 * 257^3 + 97 * 257^2 + 98 * 257^1  + 99 * 257^0 = 57355817

公式为:result  = len * 257^len +  content[len-1] * 257^len-1……content[0] * 257^0

31 转换成二进制为 0001 1111
(actualLen & 31) 取actualLen二进制的后五位
result + (result << (actualLen & 31));  //左移(actualLen & 31)位,最多31

由此可见最终执行的结果是
result = 3 * 257^3 + 97 * 257^2 + 96 * 257^1 + 95 * 257^0 = 57355817
result + (result << (3 & 31))

31可以写成2^5-1,转换为二进制0001 1111
所以(1 & 31)的结果是 1

  0000 0011
& 0001 1111
-------------
  0000 0011

abc的hash简化为

57355817 + (57355817 <<3)
最终字符串a的hash结果是516202353

如果字符串长度大于等于4小于96

会执行

 const UniChar *end4 = uContents + (len & ~3);   //字符串是否大于4位
while (uContents < end4) {

        result = result * 67503105 + (uContents[ 0 ]) * 16974593  + (uContents[ 1 ]) * 66049  + (uContents[ 2 ]) * 257 + (uContents[ 3 ]);
        uContents += 4;
}   
return result + (result << (actualLen & 31));

根据源码的注释我们可以看到,字符串是以4个为一组进行运算的,取上一次的结果乘以67503105,源码注释中有写到
257 ^ 2 = 66049; 257 ^ 3 = 16974593; 257 ^ 4 = 4362470401; 67503105是257 ^ 4 - 256 ^ 4
所以上述代码可以总结为以下公式

字符串长度为len
第一次while循环
 result = len * (257 ^ 4  -  256 ^ 4) + (uContents[ 0 ]) * 257 ^ 3  + (uContents[ 1 ]) * 257 ^ 2  + (uContents[ 2 ]) * 257 + (uContents[ 3 ] *  257^0);
第n次
 result = result * (257 ^ 4  -  256 ^ 4) + (uContents[ 0 + n*4]) * 257 ^ 3  + (uContents[ 1 + n*4 ]) * 257 ^ 2  + (uContents[ 2 + n*4 ]) * 257 + (uContents[ 3  + n*4] *  257^0);

以字符串“ abcd”举例
字符"a"的ACISS码是97,字符"b"的ACISS码是98,字符"c"的ACISS码是99,字符"c"的ACISS码是100,字符串的长度是4,会执行

result = 4;

        result = result * 67503105 + (uContents[ 0 ]) * 16974593  + (uContents[ 1 ]) * 66049  + (uContents[ 2 ]) * 257 + (uContents[ 3 ]);
//        result = 4 * 67503105 + (97) * 16974593  + (98) * 66049  + (99) * 257 + (100) = 1923046286;

return result + (result << (actualLen & 31));  //32691786862

总结

  • 当字符串长度小于4时:result = len * 257^len + content[len-1] * 257^len-1……content[0] * 257^0
  • 当字符串长度大于等于4小于96时:
    第一次while循环
    result = len * (257 ^ 4 - 256 ^ 4) + (uContents[ 0 ]) * 257 ^ 3 + (uContents[ 1 ]) * 257 ^ 2 + (uContents[ 2 ]) * 257 + (uContents[ 3 ] * 257^0);
    第n次while循环
    result = result * (257 ^ 4 - 256 ^ 4) + (uContents[ 0 + n4]) * 257 ^ 3 + (uContents[ 1 + n4 ]) * 257 ^ 2 + (uContents[ 2 + n4 ]) * 257 + (uContents[ 3 + n4] * 257^0);
  • 如果长度大于96,则上述算法适用于
    字符0..31,(长度/ 2)-16 ..(长度/ 2)+15,长度-32..length-1,包括端点;

相关文章

网友评论

      本文标题:OC 中NSString的hash方法

      本文链接:https://www.haomeiwen.com/subject/cmlryctx.html