如何在 JavaScript 中进行关联数组/散列

Posted

技术标签:

【中文标题】如何在 JavaScript 中进行关联数组/散列【英文标题】:How to do associative array/hashing in JavaScript 【发布时间】:2010-09-12 22:22:57 【问题描述】:

我需要像在 C# 中那样使用 javascript 存储一些统计数据:

Dictionary<string, int> statistics;

statistics["Foo"] = 10;
statistics["Goo"] = statistics["Goo"] + 1;
statistics.Add("Zoo", 1);

JavaScript 中有 Hashtable 或类似 Dictionary&lt;TKey, TValue&gt; 的东西吗? 我怎样才能以这种方式存储值?

【问题讨论】:

js 是松散类型的,所以没有办法只声明一个字符串或 int,你可以只声明一个 var 并为其分配一个字符串或 int。 :D 您可能想查看 xDict。 jsfiddle.net/very/MuVwd 这是一个字典 String=>任何用 Javascript 编写的东西。 这篇文章很好地解释了关联数组是如何在 Javascript 中实现的 jayconrod.com/posts/52/a-tour-of-v8-object-representation 接受的答案写于 2009 年 - 它只支持 string 键。对于非字符串键,use Map or WeakMap, as in Vitalii's answer. 【参考方案1】:

使用JavaScript objects as associative arrays。

关联数组:简单来说,关联数组使用字符串而不是整数作为索引。

创建一个对象

var dictionary = ;

JavaScript 允许您使用以下语法向对象添加属性:

Object.yourProperty = value;

同样的另一种语法是:

Object["yourProperty"] = value;

如果可以,还可以使用以下语法创建键值对象映射:

var point =  x:3, y:2 ;

point["x"] // returns 3
point.y // returns 2

您可以使用 for..in 循环结构遍历关联数组,如下所示

for(var key in Object.keys(dict))
  var value = dict[key];
  /* use key/value for intended purpose */

【讨论】:

请注意,作者使用new Array() 初始化“关联数组”的方法是不受欢迎的。文章最终提到了它的缺点,并建议将 new Object() 作为首选替代方案,但这已接近尾声,我担心大多数读者不会走得那么远。 失败。 JavaScript 不支持将对象引用作为键,而像 Flash/AS3 Dictionary 这样的东西则支持。在 JavaScript 中,var obj1 = ; var obj2 = ; var table= ; table[obj1] = "A"; table[obj2] = "B"; alert(table[obj1]); //displays B,因为它无法区分键 obj1 和 obj2;它们都被转换为字符串,只是变成了“对象”之类的东西。完全失败,并且在 JavaScript 中使引用和循环引用完整的类型安全序列化变得困难或无效。在 Flash/AS3 中很容易。 嗯,在 JS 中我们可以通过检查相等性或定义 equals 方法来验证的唯一方法是:Point.prototype.equals = function(obj) return (obj instanceof Point) &amp;&amp; (obj.x === this.x) &amp;&amp; (obj.y === this.y); ; @Leo console.log(A:'B',C:'D'[foo]) 应该给你 A B。 @Leo 这个例子似乎是错误的。字典的for... in 将遍历其键,因此Object.keys 似乎放在那里。 Object.keys 返回字典键的数组,for... in 用于数组循环 “键”,对于数组而言,键是它的索引,而不是它的值。【参考方案2】:
var associativeArray = ;
associativeArray["one"] = "First";
associativeArray["two"] = "Second";
associativeArray["three"] = "Third";

如果您来自面向对象的语言,您应该查看this article。

【讨论】:

你也可以用更少的行来做到这一点: var associativeArray = "one" : "First", "two" : "second", "three" : "Third";然后 associativeArray["one"] 返回 "First", assocativeArray["four"] 返回 null。【参考方案3】:

所有现代浏览器都支持 JavaScript Map 对象。使用 Map 比使用 Object 更好的原因有两个:

一个对象有一个原型,所以映射中有默认键。 Object 的键是字符串,它们可以是 Map 的任何值。 您可以轻松获取地图的大小,同时您必须跟踪对象的大小。

例子:

var myMap = new Map();

var keyObj = ,
    keyFunc = function () ,
    keyString = "a string";

myMap.set(keyString, "value associated with 'a string'");
myMap.set(keyObj, "value associated with keyObj");
myMap.set(keyFunc, "value associated with keyFunc");

myMap.size; // 3

myMap.get(keyString);    // "value associated with 'a string'"
myMap.get(keyObj);       // "value associated with keyObj"
myMap.get(keyFunc);      // "value associated with keyFunc"

如果您希望对未从其他对象引用的键进行垃圾回收,请考虑使用 WeakMap 而不是 Map。

【讨论】:

希望几年后这将是投票最多的答案。 @CameronLee 肯定会的 这个Map 在你的键是一个对象但应该按值而不是引用比较时几乎没有用。 在写完这个答案一年多之后,“所有现代浏览器都支持 Map”仍然不是真的。只有在桌面上,您才能指望至少基本的地图支持。不在移动设备上。例如,android 浏览器根本不支持地图。即使在桌面上,一些实现也不完整。例如,IE11 仍然不支持通过“for...of...”进行枚举,所以如果你想要与 IE 兼容,就必须使用令人作呕的 .forEach 组件。此外,JSON.stringify() 在我尝试过的任何浏览器中都不适用于 Map。初始化器在 IE 或 Safari 中也不起作用。 有优秀的浏览器支持。再检查一遍。无论如何,这很容易填充,因此本机浏览器支持不是问题。【参考方案4】:

除非您有特定的理由不这样做,否则请使用普通对象。 JavaScript 中的对象属性可以使用 hashtable 样式的语法来引用:

var hashtable = ;
hashtable.foo = "bar";
hashtable['bar'] = "foo";

foobar 元素现在都可以引用为:

hashtable['foo'];
hashtable['bar'];

// Or
hashtable.foo;
hashtable.bar;

当然,这确实意味着您的键必须是字符串。如果它们不是字符串,它们会在内部转换为字符串,所以它可能仍然有效。您的里程可能会有所不同。

【讨论】:

作为整数的键对我没有任何问题。 ***.com/questions/2380019/… Jonas:请记住,在设置属性时,您的整数会转换为字符串:var hash = ; hash[1] = "foo"; alert(hash["1"]); alerts "foo"。 如果您的键之一是“proto”或“parent”怎么办? 请注意,对象不能用作 JavaScript 中的键。好吧,它们可以,但是它们被转换为它们的字符串表示形式,因此任何对象最终都会作为完全相同的键。请参阅下面的@TimDown 的 jshashtable 建议。 此示例令人困惑,因为您在两个实例中同时使用 foo 和 bar 作为键和值。更清楚地表明var dict = ; dict.key1 = "val1"; dict["key2"] = "val2"; dict 的key1 元素可以被dict["key1"]dict.key1 等效引用。【参考方案5】:

由于 JavaScript 中的每个对象都表现得像 - 并且通常被实现为 - 一个哈希表,所以我只是使用它......

var hashSweetHashTable = ;

【讨论】:

投反对票,因为它没有显示如何实际访问“哈希表”中的值。 我迟到了 9 年(我当时对编程一无所知,更不用说这个网站了),但是......如果你想在地图上存储点怎么办,并且需要查看地图上的某个点是否已经存在某些东西?在这种情况下,您最好使用 HashTable,通过坐标查找(object,而不是 string)。 如果设置了foo@MikeWarren if (hashSweetHashTable.foo) 应该进入 if 块。【参考方案6】:

在 C# 中,代码如下所示:

Dictionary<string,int> dictionary = new Dictionary<string,int>();
dictionary.add("sample1", 1);
dictionary.add("sample2", 2);

var dictionary = new Dictionary<string, int> 
    "sample1", 1,
    "sample2", 2
;

在 JavaScript 中:

var dictionary = 
    "sample1": 1,
    "sample2": 2

C# 字典对象包含有用的方法,例如 dictionary.ContainsKey()

在 JavaScript 中,我们可以使用 hasOwnProperty 之类的:

if (dictionary.hasOwnProperty("sample1"))
    console.log("sample1 key found and its value is"+ dictionary["sample1"]);

【讨论】:

为我投票,我不必写关于hasOwnProperty的答案【参考方案7】:

如果您要求您的键是任何对象而不仅仅是字符串,那么您可以使用我的jshashtable。

【讨论】:

在我发现这个之前,我花了多少个小时来纠结对象不能真正用作 JS 样式对象作为关联数组的键这一事实?谢谢你,蒂姆。 Flash/AS3 字典以及大多数其他语言都支持将对象引用作为键。 JavaScript 还没有实现它,但我认为它在未来的规范中作为某种 Map 类。同时再次使用 polyfill;标准如此之多。哦,等等……终于在 2015 年,Map 似乎已经到来:***.com/a/30088129/88409,并且受到“现代”浏览器的支持,哈哈:kangax.github.io/compat-table/es6/#Map(并没有得到广泛的支持)。仅落后 AS3 十年。 Tim,也许您应该更新 jshashtable 以在可用的情况下使用 Map()。 @DaveBurton:好计划。我一有时间就会这样做。【参考方案8】:

注意:

几年前,我实现了以下哈希表,它具有Map 类所缺少的一些功能。然而,情况不再如此——现在,可以遍历 Map 的条目,获取其键或值或两者的数组(不过,这些操作是通过复制到新分配的数组来实现的——这是一种浪费内存和它的时间复杂度总是和O(n)一样慢),删除给定键的特定项目,并清除整个地图。 因此,我的 hashtable 实现仅用于兼容性目的,在这种情况下,基于此编写适当的 polyfill 是一种更明智的方法。


function Hashtable() 

    this._map = new Map();
    this._indexes = new Map();
    this._keys = [];
    this._values = [];

    this.put = function(key, value) 
        var newKey = !this.containsKey(key);
        this._map.set(key, value);
        if (newKey) 
            this._indexes.set(key, this.length);
            this._keys.push(key);
            this._values.push(value);
        
    ;

    this.remove = function(key) 
        if (!this.containsKey(key))
            return;
        this._map.delete(key);
        var index = this._indexes.get(key);
        this._indexes.delete(key);
        this._keys.splice(index, 1);
        this._values.splice(index, 1);
    ;

    this.indexOfKey = function(key) 
        return this._indexes.get(key);
    ;

    this.indexOfValue = function(value) 
        return this._values.indexOf(value) != -1;
    ;

    this.get = function(key) 
        return this._map.get(key);
    ;

    this.entryAt = function(index) 

        var item = ;

        Object.defineProperty(item, "key", 
            value: this.keys[index],
            writable: false
        );

        Object.defineProperty(item, "value", 
            value: this.values[index],
            writable: false
        );

        return item;
    ;

    this.clear = function() 

        var length = this.length;

        for (var i = 0; i < length; i++) 
            var key = this.keys[i];
            this._map.delete(key);
            this._indexes.delete(key);
        

        this._keys.splice(0, length);
    ;

    this.containsKey = function(key) 
        return this._map.has(key);
    ;

    this.containsValue = function(value) 
        return this._values.indexOf(value) != -1;
    ;

    this.forEach = function(iterator) 
        for (var i = 0; i < this.length; i++)
            iterator(this.keys[i], this.values[i], i);
    ;

    Object.defineProperty(this, "length", 
        get: function() 
            return this._keys.length;
        
    );

    Object.defineProperty(this, "keys", 
        get: function() 
            return this._keys;
        
    );

    Object.defineProperty(this, "values", 
        get: function() 
            return this._values;
        
    );

    Object.defineProperty(this, "entries", 
        get: function() 
            var entries = new Array(this.length);
            for (var i = 0; i < entries.length; i++)
                entries[i] = this.entryAt(i);
            return entries;
        
    );

Hashtable的文档

方法:

get(key)

返回与指定键关联的值。

参数:key:从中检索值的键。


put(key, value)

将指定的值与指定的键相关联。

参数:key:与​​值关联的键。value:与键关联的值。


remove(key)

删除指定的键以及与其关联的值。

参数:key:要删除的密钥。


clear()

通过删除所有条目来清除整个哈希表。


indexOfKey(key)

返回指定key的索引,根据已经添加的条目的顺序。

参数:key:获取索引的key。


indexOfValue(value)

返回指定值的索引,根据已添加条目的顺序。

参数:value:获取索引的值。

备注: 值按身份进行比较。


entryAt(index)

返回一个具有keyvalue 属性的对象,表示指定索引处的条目。

参数:index:要获取的条目的索引。


containsKey(key)

返回哈希表是否包含指定的键。

参数: key:要找的钥匙。


containsValue(value)

返回哈希表是否包含指定的值。

参数:value:要查找的值。


forEach(iterator)

遍历哈希表中的所有条目,调用指定的iterator

参数:iterator:具有keyvalueindex三个参数的方法,其中index表示根据订单已添加。

属性:

length只读

获取哈希表中条目的计数。

keys只读

获取哈希表中所有键的数组。

values只读

获取哈希表中所有值的数组。

entries只读

获取哈希表中所有条目的数组。它们的表示方式与entryAt() 方法相同。

【讨论】:

【参考方案9】:
function HashTable() 
    this.length = 0;
    this.items = new Array();
    for (var i = 0; i < arguments.length; i += 2) 
        if (typeof (arguments[i + 1]) != 'undefined') 
            this.items[arguments[i]] = arguments[i + 1];
            this.length++;
        
    

    this.removeItem = function (in_key) 
        var tmp_previous;
        if (typeof (this.items[in_key]) != 'undefined') 
            this.length--;
            var tmp_previous = this.items[in_key];
            delete this.items[in_key];
        

        return tmp_previous;
    

    this.getItem = function (in_key) 
        return this.items[in_key];
    

    this.setItem = function (in_key, in_value) 
        var tmp_previous;
        if (typeof (in_value) != 'undefined') 
            if (typeof (this.items[in_key]) == 'undefined') 
                this.length++;
             else 
                tmp_previous = this.items[in_key];
            

            this.items[in_key] = in_value;
        

        return tmp_previous;
    

    this.hasItem = function (in_key) 
        return typeof (this.items[in_key]) != 'undefined';
    

    this.clear = function () 
        for (var i in this.items) 
            delete this.items[i];
        

        this.length = 0;
    

【讨论】:

对于反对此投票的人,您能评论一下原因吗?此答案发布于 2011 年,而不是当前日期。 我没有投反对票,但是……您不应该将数组用作对象。不是 100% 确定这是否是您的意图。在未删除的数组上使用切片重新索引;删除是可以的,但会设置为未定义——最好是明确的;在对象上也使用 = undefined b/c 它更快(但内存更多)。简而言之:始终使用对象: 而不是数组:[]new Array() 如果您打算使用字符串键,否则 js 引擎会出现问题——它会看到 1 个变量的 2 种类型,这意味着没有优化,否则它将与数组一起运行并意识到它必须更改为对象(可能重新分配)。 就像 Alex Hawkins 的回答一样,请提供一些解释,为什么这个看起来相当复杂的代码实际上是有用的,并且比这里给出的其他较短的答案更好。【参考方案10】:

https://gist.github.com/alexhawkins/f6329420f40e5cafa0a4

var HashTable = function() 
  this._storage = [];
  this._count = 0;
  this._limit = 8;



HashTable.prototype.insert = function(key, value) 

  // Create an index for our storage location by passing
  // it through our hashing function
  var index = this.hashFunc(key, this._limit);

  // Retrieve the bucket at this particular index in
  // our storage, if one exists
  //[[ [k,v], [k,v], [k,v] ] , [ [k,v], [k,v] ]  [ [k,v] ] ]
  var bucket = this._storage[index]

  // Does a bucket exist or do we get undefined
  // when trying to retrieve said index?
  if (!bucket) 
    // Create the bucket
    var bucket = [];
    // Insert the bucket into our hashTable
    this._storage[index] = bucket;
  

  var override = false;

  // Now iterate through our bucket to see if there are any conflicting
  // key value pairs within our bucket. If there are any, override them.
  for (var i = 0; i < bucket.length; i++) 
    var tuple = bucket[i];
    if (tuple[0] === key) 

      // Override value stored at this key
      tuple[1] = value;
      override = true;
    
  

  if (!override) 
    // Create a new tuple in our bucket.
    // Note that this could either be the new empty bucket we created above
    // or a bucket with other tupules with keys that are different than
    // the key of the tuple we are inserting. These tupules are in the same
    // bucket because their keys all equate to the same numeric index when
    // passing through our hash function.
    bucket.push([key, value]);
    this._count++

    // Now that we've added our new key/val pair to our storage
    // let's check to see if we need to resize our storage
    if (this._count > this._limit * 0.75) 
      this.resize(this._limit * 2);
    
  
  return this;
;


HashTable.prototype.remove = function(key) 
  var index = this.hashFunc(key, this._limit);
  var bucket = this._storage[index];
  if (!bucket) 
    return null;
  

  // Iterate over the bucket
  for (var i = 0; i < bucket.length; i++) 
    var tuple = bucket[i];

    // Check to see if key is inside bucket
    if (tuple[0] === key) 

      // If it is, get rid of this tuple
      bucket.splice(i, 1);
      this._count--;
      if (this._count < this._limit * 0.25) 
        this._resize(this._limit / 2);
      
      return tuple[1];
    
  
;


HashTable.prototype.retrieve = function(key) 
  var index = this.hashFunc(key, this._limit);
  var bucket = this._storage[index];

  if (!bucket) 
    return null;
  

  for (var i = 0; i < bucket.length; i++) 
    var tuple = bucket[i];
    if (tuple[0] === key) 
      return tuple[1];
    
  

  return null;
;


HashTable.prototype.hashFunc = function(str, max) 
  var hash = 0;
  for (var i = 0; i < str.length; i++) 
    var letter = str[i];
    hash = (hash << 5) + letter.charCodeAt(0);
    hash = (hash & hash) % max;
  
  return hash;
;


HashTable.prototype.resize = function(newLimit) 
  var oldStorage = this._storage;

  this._limit = newLimit;
  this._count = 0;
  this._storage = [];

  oldStorage.forEach(function(bucket) 
    if (!bucket) 
      return;
    
    for (var i = 0; i < bucket.length; i++) 
      var tuple = bucket[i];
      this.insert(tuple[0], tuple[1]);
    
  .bind(this));
;


HashTable.prototype.retrieveAll = function() 
  console.log(this._storage);
  //console.log(this._limit);
;

/******************************TESTS*******************************/

var hashT = new HashTable();

hashT.insert('Alex Hawkins', '510-599-1930');
//hashT.retrieve();
//[ , , , [ [ 'Alex Hawkins', '510-599-1930' ] ] ]
hashT.insert('Boo Radley', '520-589-1970');
//hashT.retrieve();
//[ , [ [ 'Boo Radley', '520-589-1970' ] ], , [ [ 'Alex Hawkins', '510-599-1930' ] ] ]
hashT.insert('Vance Carter', '120-589-1970').insert('Rick Mires', '520-589-1970').insert('Tom Bradey', '520-589-1970').insert('Biff Tanin', '520-589-1970');
//hashT.retrieveAll();
/*
[ ,
  [ [ 'Boo Radley', '520-589-1970' ],
    [ 'Tom Bradey', '520-589-1970' ] ],
  ,
  [ [ 'Alex Hawkins', '510-599-1930' ],
    [ 'Rick Mires', '520-589-1970' ] ],
  ,
  ,
  [ [ 'Biff Tanin', '520-589-1970' ] ] ]
*/

// Override example (Phone Number Change)
//
hashT.insert('Rick Mires', '650-589-1970').insert('Tom Bradey', '818-589-1970').insert('Biff Tanin', '987-589-1970');
//hashT.retrieveAll();

/*
[ ,
  [ [ 'Boo Radley', '520-589-1970' ],
    [ 'Tom Bradey', '818-589-1970' ] ],
  ,
  [ [ 'Alex Hawkins', '510-599-1930' ],
    [ 'Rick Mires', '650-589-1970' ] ],
  ,
  ,
  [ [ 'Biff Tanin', '987-589-1970' ] ] ]

*/

hashT.remove('Rick Mires');
hashT.remove('Tom Bradey');
//hashT.retrieveAll();

/*
[ ,
  [ [ 'Boo Radley', '520-589-1970' ] ],
  ,
  [ [ 'Alex Hawkins', '510-599-1930' ] ],
  ,
  ,
  [ [ 'Biff Tanin', '987-589-1970' ] ] ]


*/

hashT.insert('Dick Mires', '650-589-1970').insert('Lam James', '818-589-1970').insert('Ricky Ticky Tavi', '987-589-1970');
hashT.retrieveAll();


/* NOTICE HOW THE HASH TABLE HAS NOW DOUBLED IN SIZE UPON REACHING 75% CAPACITY, i.e. 6/8. It is now size 16.
 [,
  ,
  [ [ 'Vance Carter', '120-589-1970' ] ],
  [ [ 'Alex Hawkins', '510-599-1930' ],
    [ 'Dick Mires', '650-589-1970' ],
    [ 'Lam James', '818-589-1970' ] ],
  ,
  ,
  ,
  ,
  ,
  [ [ 'Boo Radley', '520-589-1970' ],
    [ 'Ricky Ticky Tavi', '987-589-1970' ] ],
  ,
  ,
  ,
  ,
  [ [ 'Biff Tanin', '987-589-1970' ] ] ]

*/

console.log(hashT.retrieve('Lam James'));  // 818-589-1970
console.log(hashT.retrieve('Dick Mires')); // 650-589-1970
console.log(hashT.retrieve('Ricky Ticky Tavi')); //987-589-1970
console.log(hashT.retrieve('Alex Hawkins')); // 510-599-1930
console.log(hashT.retrieve('Lebron James')); // null

【讨论】:

看起来不错。现在,还请解释为什么这很有用,并且可能比这里的所有其他答案更适合。 不是针对哈希表的整个点将数据存储在数组中吗?【参考方案11】:

您可以使用如下方式创建一个:

var dictionary =  Name:"Some Programmer", Age:24, Job:"Writing Programs"  ;

// Iterate over using keys
for (var key in dictionary) 
  console.log("Key: " + key + " , " + "Value: "+ dictionary[key]);


// Access a key using object notation:
console.log("Her name is: " + dictionary.Name)

【讨论】:

以上是关于如何在 JavaScript 中进行关联数组/散列的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript Hashmap散列算法

JavaScript Hashmap散列算法

动态创建关联数组

如何从我的 Ruby on Rails API 解析这些散列转换为字符串,以便它可以作为 Javascript 中的对象进行访问?

如何在 JavaScript 文字符号中创建关联数组

在javascript / jQuery中对关联数组执行“Diff”操作?