具有64位整数的Hashtable

时间:2015-11-05 15:35:57

标签: javascript node.js

我希望为binary quadkeys

实现O(1)查找

我知道javascript没有真正的哈希表,而是使用对象和o(1)查找其属性,但问题是密钥总是转换为字符串。

我怀疑内存中有超过1000万个条目,如果我必须依赖键是字符串而且平均四键字符串是11.5个字符,相当于(1000万个条目* 11.5长度* 2个字节)= 230,000,000字节或230 MB。

与存储为int64(1000万条* 8字节)= 80,000,000字节或80 MB

相比

我知道javascript本身不支持int64,但是有些库可以帮助我完成我想要的按位操作。

现在,即使存在可以处理int64的库,它们最终也不是真正代表8个字节的对象,所以我相信我不能在哈希表中使用任何int64库,而是考虑使用2-deep哈希表有两个int32。第一个键是前4个字节,第二个键是最后4个字节。它不像1次操作查找那样理想找到一个值,但是2次操作仍然足够好。

但是,如果所有密钥都存储为字符串,或者所有数字都是双精度浮点格式数字(8字节),那么我认为这不值得,因此每个哈希条目将占用16个字节(两个“int32”数字)。

我的问题是:

   1.如果您存储一个号码作为物业的钥匙,它将占用8     内存的字节或将转换为字符串并占用     长* 2个字节?

  1. 有没有办法将二进制四元组编码为双精度 javascript采用的浮点格式数字标准, 即使这个数字毫无意义,基础位也是有用的 目的(构造的唯一标识)。
  2. PS:我用nodejs标记这个,因为可能存在可以帮助我最终目标的库

    编辑1:

    Map()和节点0.12.x +

    可能 1

    就数字 2 而言,我能够使用int64 lib(bytebuffer)并将64int转换为缓冲区。

    我想只使用缓冲区作为Map()的键,但它不会让我因为缓冲区内部是一个对象,每个实例都充当Map()的新键。

    所以我考虑将缓冲区恢复为原生类型,64位双倍。

    使用readDoubleBE我将缓冲区读作double,表示我的64int二进制文件,并成功让我在地图中使用它并允许O(1)查找。

    var ByteBuffer = require("bytebuffer");
    
    var lookup = new Map();
    
    
    var startStr = "123123738232777";
    for(var i = 1; i <= 100000; i++) {
        var longStr = startStr + i.toString();
        var longVal = new ByteBuffer.Long.fromValue(longStr);
    
        var buffer = new ByteBuffer().writeUint64(longVal).flip().toBuffer();
        var doubleRepresentation = buffer.readDoubleBE();
        lookup.set(doubleRepresentation, longStr);
    }
    
    console.log(exists("12312373823277744234")); // true
    console.log(exists("123123738232777442341232332322")); // false
    
    
    function exists(longStr) {
        var longVal = new ByteBuffer.Long.fromValue(longStr);
        var doubleRepresentation = new ByteBuffer().writeUint64(longVal).flip().toBuffer().readDoubleBE();
        return lookup.has(doubleRepresentation);
    }
    

    代码很草率,我可能缺少快捷方式,所以欢迎任何建议/提示。

    理想情况下,我想利用bytebuffer的varint,这样我可以节省更多的内存,但我不确定这是否可以在Map中使用,因为我无法使用缓冲区作为键。

    编辑2:

    使用memwatch-next我能够看到最大堆大小为497962856字节,此方法使用Set(),而在Set()中使用字符串为1111082864字节。这大概是500MB对1GB,这与80mb和230mb相差甚远,不确定额外的内存使用来自何处。我使用Set而不是Map的这些内存测试值得一提,因为它应该只在数据结构中存储唯一键。 (使用Set作为存在检查器,Map将用作查找)

2 个答案:

答案 0 :(得分:1)

因为你的键是整数(并且是唯一的),所以你可以将它们用作数组索引。但是,JS数组仅限于包含32或64位整数的最大条目,具体取决于您的平台。

要克服这个问题,您可以使用两步法,但不使用对象及其字符串哈希值。你可以存储这样的东西 store[0010][1000] = 'VALUE' - 具有二进制密钥00101000的项目存储在第一个数组中的索引0010下,并存储在子数组中的索引1000

在十进制中,您处理store[2][8] = 'VALUE',这相当于在64位空间中执行store[40] = 'VALUE'

你会得到一棵树,上面有你想要的所有属性:

  • 您可以通过键轻松查找(分两步)
  • 您的密钥是整数
  • 你正在处理32位整数(或更少,取决于你如何组合)

答案 1 :(得分:0)

您的节点版本中从MapSet的内存加倍来自错误的实现。好吧,不是“坏”本身不适合数百万条目。更简单的Set处理是通过内存购买的。一如既往,没有免费的午餐,抱歉。

为什么他们一般会使用这么多?他们应该处理任何对象,并且能够处理所有可能变种的方法真的昂贵。如果您拥有的只是一种,但是您必须检查它,并且在99,99%的情况下,这是不值得的麻烦,因为地图和集合和数组很短,有几千个条目可以优化最。要平淡无奇:开发人员的时间很昂贵,而且花在其他地方更好。我可以补充一下:它是开源的,自己动手吧!但我知道这说起来容易做起来难;;)

你需要自己滚动它。您可以使用Uint32Array并围绕它构建哈希表。

根据MSQuad-key description,Bing-Maps使用基数为4位(最多23位)的字符串进行编码。使用后者的编码(没有读过前者,所以在细节上可能是错的)我们可以把它放到两个32位整数中:

function quadToInts(quad, zoom){
  var high,low, qlen, i, c;
  high = 0>>>0;
  low = 0>>>0
  zoom = zoom>>>0;

  // checks & balances omitted!

  qlen = quad.length;

  for(i = 0; i < 16 && i < qlen ;i++){
    c = parseInt(quad.charAt(i));
    high |= c << (32-(i*2 + 2));
  }
  // max = 23 characters (says MS)
  for(i = 0; i < 8 && i < qlen - 16 ;i++){
    c = parseInt(quad.charAt(16 + i));
    low |= c << (32-(i*2 + 2));
  }

  low |= zoom;

  return [high,low];
}

然后回来

// obligatory https://graphics.stanford.edu/~seander/bithacks.html
function rev(v){
  var s = 32;
  var mask = (~0)>>>0;         
  while ((s >>>= 1) > 0) {
    mask ^= (mask << s)>>>0;
    v = ((v >>> s) & mask) | ((v << s) & (~mask)>>>0);
  }
  return v;
}

function intsToQuad(k1,k2){
  var r, high, low, zoom, c, mask;

  r = "";
  mask = 0x3; // 0b11

  high = k1>>>0;
  low = k2>>>0;
  zoom = low & (0x20 - 1);
  low ^= zoom;

  high = rev(high);
  for(var i = 0;i<16;i++){
    c =  high & mask;
    c = (c<<1 | c>>>1) & mask;
    r += c.toString(10);
    high >>>= 2;
  }
  low = rev(low);
  for(var i = 0;i<16;i++){
    c =  low & mask;
    c = (c<<1 | c>>>1) & mask;
    r += c.toString(10);
    low >>>= 2;
  }
  return [r,zoom];
}

(所有快速黑客,请在使用前检查!C&amp; P恶魔也可能已经在这里进行了测试)

基于以下哈希函数

的哈希表的草图
// shamelessly stolen from http://www.burtleburtle.net/bob/c/lookup3.c
function hashword(k1, // high word of 64 bit value
                  k2, // low word of 64 bit value
                  seed // the seed
                  ){

  var a,b,c;
  var rot = function(x,k){
     return (((x)<<(k)) | ((x)>>>(32-(k))));
  };

  /* Set up the internal state */
  a = b = c = 0xdeadbeef + ((2<<2)>>>0) + seed>>>0;

  if(arguments.length === 2){
    seed = k1^k2;
  }

  b+=k2;
  a+=k1;

  c ^= b; c -= rot(b,14)>>>0;
  a ^= c; a -= rot(c,11)>>>0;
  b ^= a; b -= rot(a,25)>>>0;
  c ^= b; c -= rot(b,16)>>>0;
  a ^= c; a -= rot(c,4)>>>0; 
  b ^= a; b -= rot(a,14)>>>0;
  c ^= b; c -= rot(b,24)>>>0;

  return c;
}
function hashsize(N){
    var highbit = function(n) {
        var r = 0 >>> 0;
        var m = n >>> 0;
        while (m >>>= 1) {
            r++;
        }
        return r;
    };

  return (1<<(highbit(N)+1))>>>0;
}
function hashmask(N){
  return (hashsize(N)-1)>>>0;
}

表处理的(相当不完整的)代码

/*
 Room for 8-byte (64-bit) entries
  Table pos.  Array pos.
   0             0         high, low
   1             2         high, low
   2             4         high, lowl
           ...
   n             n*2       high, low

*/
function HashTable(N){
  var buf;
  if(!N)
    return null;

  N = (N+1) * 2;

  buf = new ArrayBuffer(hashsize(N) * 8);
  this.table = new Uint32Array(buf);
  this.mask = hashmask(N);
  this.length = this.table.length;
}

HashTable.prototype.set = function(s,z){
  var hash, quad, entry, check, i;

  entry = null;
  quad = quadToInts(s,z);

  hash = hashword(quad[0],quad[1]);

  entry = hash & this.mask;

  check = entry * 2;
  if(this.table[check] != 0 || this.table[check + 1] != 0){
    //handle collisions here
    console.log("collision in SET found")
    return null;
  } else {
    this.table[check] = quad[0];
    this.table[check + 1] = quad[1];
  }
  return entry;
};

HashTable.prototype.exists = function(s,z){
  var hash, quad, entry, check, i;

  entry = null;
  quad = quadToInts(s,z);

  hash = hashword(quad[0],quad[1]);
  entry = hash & this.mask;

  check = entry * 2;
  if(this.table[check] != 0 || this.table[check + 1] != 0){

    return entry;
  }
  return -1;
};

HashTable.prototype.get = function(index){
  var entry = [0,0];

  if(index > this.length)
    return null;

  entry[0] = this.table[index * 2];
  entry[1] = this.table[index * 2 + 1];

  return entry;
};

// short test
var ht = new HashTable(100);
ht.set("01231031020112310",17);
ht.set("11231031020112311",12);
ht.set("21231033020112310",1);
ht.set("31231031220112311321312",23);

var s = "";
for(var i=0;i<ht.table.length;i+=2){
  if(ht.table[i] !== 0){
     var e = intsToQuad(ht.table[i],ht.table[i+1]);
     s += e[0] +", " +e[1] + "\n";
  }
}
console.log(s)

碰撞应该是罕见的,所以一些短标准阵列可以捕捉它们。要处理它,你需要为代表Quad的两个整数的8个字节添加另一个字节,或者更好的是,将第二个整数设置为全部(对于Quad不会发生),第一个到第四个位置。碰撞数组。

添加有效负载有点复杂,因为你只有一个固定的长度。

我已将表的大小设置为下一个更高的2的幂。这可能太多,甚至太多,你可能很想适应它,所以请注意,屏蔽不再按预期工作,你需要做一个模数。