如何在 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<TKey, TValue>
的东西吗?
我怎样才能以这种方式存储值?
【问题讨论】:
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) && (obj.x === this.x) && (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";
foo
和 bar
元素现在都可以引用为:
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)
返回一个具有key
和value
属性的对象,表示指定索引处的条目。
参数:index
:要获取的条目的索引。
containsKey(key)
返回哈希表是否包含指定的键。
参数:
key
:要找的钥匙。
containsValue(value)
返回哈希表是否包含指定的值。
参数:value
:要查找的值。
forEach(iterator)
遍历哈希表中的所有条目,调用指定的iterator
。
参数:iterator
:具有key
、value
和index
三个参数的方法,其中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 中进行关联数组/散列的主要内容,如果未能解决你的问题,请参考以下文章
如何从我的 Ruby on Rails API 解析这些散列转换为字符串,以便它可以作为 Javascript 中的对象进行访问?