因为跨部门协作的原因,我们需要为主要使用Nodejs部门的同事提供一个SDK,我们此前已经提供了 .NET、.NET Core、Java、Golang 等多种语言的封装,其中有一个获取字符串HashCode的代码,为了保证各个平台计算结果统一,我们通过反编译方式获取到了 .NET Framework 的实现,并翻译成了其他语言。
.NET Framework 的代码大致如下:
// HashUtils.cs
public sealed class HashUtils
{
private HashUtils() { }
/// <summary>
/// 计算哈希值
/// </summary>
/// <param name="text">对象字符串</param>
/// <returns>哈希结果</returns>
public static int CalcHashCode(string text)
{
var chars = Encoding.Unicode.GetBytes(text);
var half_pre = 0x15051505;
var half_post = half_pre;
var index = 0;
for (int i = text.Length; i > 0; i -= 4)
{
var data = new int[]
{
calc(chars, index + 3, 24) + calc(chars, index + 2, 16) + calc(chars, index + 1, 8) + calc(chars, index + 0, 0),
calc(chars, index + 7, 24) + calc(chars, index + 6, 16) + calc(chars, index + 5, 8) + calc(chars, index + 4, 0),
};
half_pre = (((half_pre << 5) + half_pre) + (half_pre >> 0x1b)) ^ data[0];
if (i <= 2)
{
break;
}
half_post = (((half_post << 5) + half_post) + (half_post >> 0x1b)) ^ data[1];
index += 8;
}
return half_pre + (half_post * 0x5d588b65);
}
private static int calc(byte[] bytes, int index, int offset)
{
if (index >= bytes.Length)
{
return 0;
}
return bytes[index] << offset;
}
}
类似的代码我们也成功的翻译为其他语言,但是在Nodejs遇到这个需求时,我们遇到了麻烦——Nodejs是没有“类型”的概念的,更是没有Int32
来将有符号的整型数字控制在4个字节以内,这就导致了在Nodejs下,如果将一个数字乘以0x5d588b65
的话,其结果有很大可能会超过32位,于是我们只好另辟蹊径,使用位移的方式模拟乘法运算。其代码类似如下(尚未做优化,仅做了功能实现)。
// hash-utils.ts
export function getHashCode(text: string): number {
if (!text) {
return 0
}
let buffer = Buffer.from(text, 'utf16le')
let ints = new Array<number>(buffer.byteLength)
for (let i = 0; i < ints.length; i++) {
ints[i] = buffer[i] & 0xff
}
let halfPre = 0x15051505
let halfPost = halfPre
let index = 0
for (let i = text.length; i > 0; i -= 4) {
let data = [
calc(ints, index + 3, 24) +
calc(ints, index + 2, 16) +
calc(ints, index + 1, 8) +
calc(ints, index + 0, 0),
calc(ints, index + 7, 24) +
calc(ints, index + 6, 16) +
calc(ints, index + 5, 8) +
calc(ints, index + 4, 0)
]
halfPre = ((halfPre << 5) + halfPre + (halfPre >> 0x1b)) ^ data[0]
if (i <= 2) {
break
}
halfPost = ((halfPost << 5) + halfPost + (halfPost >> 0x1b)) ^ data[1]
index += 8
}
return int32Add(halfPre, int32Multi0x5d588b65(halfPost))
}
function calc(ints: Array<number>, index: number, offset: number): number {
if (index >= ints.length) {
return 0
}
return ints[index] << offset
}
function int32Multi0x5d588b65(value: number): number {
const powers = [1, 3, 6, 7, 9, 10, 12, 16, 20, 21, 23, 25, 27, 28, 29, 31]
const length = powers.length
let result = 0
for (let i = 0; i < length; i++) {
result = int32Add(result, int32Move(value, powers[i]))
}
return result
}
function int32Add(left: number, right: number): number {
return (left + right) & 0xffffffff
}
function int32Move(value: number, power: number): number {
if (power === 1) {
return value
} else {
return (value << (power - 1)) & 0xffffffff
}
}