ChatGPT解决这个技术问题 Extra ChatGPT

JavaScript hashmap 等价物

this answer 的更新 3 中所述,此表示法:

var hash = {};
hash[X]

实际上并没有散列对象 X;它实际上只是将 X 转换为一个字符串(如果它是一个对象,则通过 .toString(),或者针对各种原始类型的一些其他内置转换),然后在“hash”中查找该字符串,而不对其进行散列。也不会检查对象相等性 - 如果两个不同的对象具有相同的字符串转换,它们只会相互覆盖。

鉴于此 - JavaScript 中是否有任何有效的 hashmap 实现?

(例如,javascript hashmap 的第二个 Google 结果产生的实现对于任何操作都是 O(n)。各种其他结果忽略了具有等效字符串表示的不同对象相互覆盖的事实。

@Claudiu:很抱歉编辑,但标题中的“地图”确实具有误导性。不同意就回滚,我无意光顾。 :)
@Claudiu:你问了很多关于 javascript 的问题。好问题。我喜欢。
@Claudiu:另外,你能链接到你提到的谷歌结果吗?不同的本地版本的谷歌返回不同的结果,你提到的实现似乎甚至没有出现在我身上。
@Tomalak:我只是要写完全一样的东西!
@Claudiu 不,不要链接到谷歌。链接到您正在谈论的页面(您碰巧通过谷歌找到)。链接到 google 与解释要搜索的内容有同样的问题:google 根据位置或搜索历史自定义结果、google 的结果随时间变化(目前,这是该搜索的最高结果)以及其他任何可以使它的结果显示不同的结果。

P
Peter Mortensen

自己手动散列您的对象,并将生成的字符串用作常规 JavaScript 字典的键。毕竟,您最有能力知道是什么让您的对象与众不同。我就是做这个的。

例子:

var key = function(obj){
  // Some unique object-dependent key
  return obj.totallyUniqueEmployeeIdKey; // Just an example
};

var dict = {};

dict[key(obj1)] = obj1;
dict[key(obj2)] = obj2;

通过这种方式,您可以控制由 JavaScript 完成的索引,而无需繁重的内存分配和溢出处理。

当然,如果你真的想要“工业级的解决方案”,你可以构建一个通过key函数参数化的类,并拥有容器所需的所有API,但是……我们使用JavaScript,并尝试简单轻量,所以这个功能解决方案简单快捷。

密钥功能可以像选择对象的正确属性一样简单,例如,一个密钥或一组密钥,它们已经是唯一的,密钥的组合,它们一起是唯一的,或者像使用一些密码散列一样复杂,例如在 DojoX encodingDojoX UUID 中。虽然后一种解决方案可能会产生唯一的密钥,但我个人会不惜一切代价避免它们,特别是如果我知道是什么让我的对象独一无二。

2014 年更新:2008 年回答这个简单的解决方案仍然需要更多解释。让我以问答形式澄清这个想法。

您的解决方案没有真正的哈希。它在哪里???

JavaScript 是一种高级语言。它的基本原语 (Object) 包括一个用于保存属性的哈希表。为了提高效率,这个哈希表通常是用低级语言编写的。使用带有字符串键的简单对象,我们无需任何努力即可使用高效实现的哈希表。

你怎么知道他们使用哈希?

有三种主要的方法来保持对象集合可通过键寻址:

无序。在这种情况下,要通过其键检索对象,我们必须遍历所有在找到它时停止的键。平均而言,它将进行 n/2 次比较。

订购。示例#1:一个排序数组——进行二分搜索,我们将平均在 ~log2(n) 次比较之后找到我们的键。好多了。示例#2:一棵树。再次是 ~log(n) 尝试。

示例#1:一个排序数组——进行二分搜索,我们将平均在 ~log2(n) 次比较之后找到我们的键。好多了。

示例#2:一棵树。再次是 ~log(n) 尝试。

哈希表。平均而言,它需要一个恒定的时间。比较:O(n) vs. O(log n) vs. O(1)。繁荣。

显然,JavaScript 对象以某种形式使用哈希表来处理一般情况。

浏览器供应商真的使用哈希表吗???

真的。

Chrome/node.js/V8:JSObject。在 objects.cc 和 objects-inl.h 中查找具有相关详细信息的 NameDictionary 和 NameDictionaryShape。

Firefox/Gecko:JSObject、NativeObject 和 PlainObject,在 jsobj.cpp 和 vm/NativeObject.cpp 中有相关细节。

他们处理碰撞吗?

是的。看上面。如果您在不相等的字符串上发现冲突,请立即向供应商提交错误。

那么你的想法是什么?

如果你想散列一个对象,找出是什么使它独一无二并将其用作键。不要试图计算真正的哈希或模拟哈希表——它已经被底层的 JavaScript 对象有效地处理了。

将此密钥与 JavaScript 的 Object 一起使用,以利用其内置哈希表,同时避免与默认属性可能发生的冲突。

帮助您入门的示例:

如果您的对象包含唯一的用户名 - 将其用作键。

如果它包含一个唯一的客户编号 - 将其用作密钥。如果它包含唯一的政府颁发的号码,例如美国 SSN 或护照号码,并且您的系统不允许重复 - 将其用作密钥。

如果它包含唯一的政府颁发的号码,例如美国 SSN 或护照号码,并且您的系统不允许重复 - 将其用作密钥。

如果字段组合是唯一的 - 将其用作键。美国州缩写+驾驶执照号码是一个很好的关键。国家缩写+护照号码也是一个很好的关键。

美国州缩写+驾驶执照号码是一个很好的关键。

国家缩写+护照号码也是一个很好的关键。

字段或整个对象上的某些函数可以返回唯一值——将其用作键。

我使用了您的建议并使用用户名缓存了所有对象。但是一些聪明的人被命名为“toString”,这是一个内置属性!我现在该怎么办?

显然,如果生成的密钥完全由拉丁字符组成的可能性很小,那么您应该对此采取一些措施。例如,在开头或结尾添加您喜欢的任何非拉丁 Unicode 字符以与默认属性取消冲突:“#toString”、“#MarySmith”。如果使用复合键,则使用某种非拉丁分隔符分隔键组件:“name,city,state”。

通常,这是我们必须发挥创造力并选择具有给定限制(唯一性,与默认属性的潜在冲突)的最简单键的地方。

注意:根据定义,唯一键不会发生冲突,而潜在的哈希冲突将由底层 Object 处理。

你为什么不喜欢工业解决方案?

恕我直言,最好的代码根本就是没有代码:它没有错误,不需要维护,易于理解,并且可以立即执行。我看到的所有“JavaScript 中的哈希表”都超过 100 行代码,并且涉及多个对象。将其与:dict[key] = value 进行比较。

另一点:使用 JavaScript 和相同的原始对象来实现已经实现的东西,甚至有可能击败用低级语言编写的原始对象的性能吗?

我仍然想在没有任何键的情况下散列我的对象!

我们很幸运:ECMAScript 6(2015 年 6 月发布)定义了 mapset

从定义来看,他们可以使用对象的地址作为键,这使得对象在没有人工键的情况下立即区分。 OTOH,两个不同但相同的对象,将被映射为不同的。

MDN 的比较细分:

对象与 Maps 类似,都允许您将键设置为值、检索这些值、删除键以及检测是否在键中存储了某些内容。正因为如此(并且因为没有内置的替代品),对象在历史上一直被用作地图;然而,在某些情况下,使用 Map 有一些重要的区别:对象的键是字符串和符号,而它们可以是 Map 的任何值,包括函数、对象和任何原语。 Map 中的键是有序的,而添加到对象的键不是。因此,在对其进行迭代时, Map 对象会按插入顺序返回键。您可以使用 size 属性轻松获取 Map 的大小,而 Object 中的属性数量必须手动确定。 Map 是可迭代的,因此可以直接迭代,而对 Object 进行迭代则需要以某种方式获取其键并对其进行迭代。一个对象有一个原型,所以如果你不小心,地图中有默认键可能会与你的键发生冲突。从 ES5 开始,这可以通过使用 map = Object.create(null) 绕过,但很少这样做。在涉及频繁添加和删除密钥对的场景中,Map 可能会表现得更好。


这看起来不像是一张合适的地图,因为您不处理碰撞。如果发生这种情况:hash(obj1) == hash(obj2),您将丢失数据。
当“PAUL AINLEY”和“PAULA INLEY”都在您的系统中注册时,天堂会帮助您...
@MattR 实际上,即使使用模拟哈希函数,您的示例也可以在没有天堂帮助的情况下正常工作。我希望其他读者会意识到过度简化的非现实哈希函数被用作占位符来演示不同的技术。代码注释和答案本身都强调它不是真实的。答案的最后一段讨论了正确键的选择。
@EugeneLazutkin - 恐怕你还是误会了。您的示例仍然容易发生哈希冲突。不要以为只是把姓放在第一位会以某种方式帮助你!
@EugeneLazutkin 大多数人没有读过你在 ES6 出现之前就已经回答了这个问题……让我祝贺你对 JS 的深入了解。
2
21 revs, 2 users 96%

问题描述

JavaScript 没有内置的通用映射类型(有时称为关联数组或字典),它允许通过任意键访问任意值。 JavaScript 的基本数据结构是对象,这是一种特殊类型的映射,它只接受字符串作为键,并具有特殊的语义,如原型继承、getter 和 setter 以及一些更深的巫术。

将对象用作映射时,您必须记住,键将通过 toString() 转换为字符串值,这会导致将 5'5' 映射到相同的值,并且所有对象都不会覆盖 { 1} 方法到由 '[object Object]' 索引的值。如果您不选中 hasOwnProperty(),您也可能会不自觉地访问其继承的属性。

JavaScript 的内置 array 类型一点帮助都没有:JavaScript 数组不是关联数组,而只是具有一些特殊属性的对象。如果您想知道为什么它们不能用作地图,look here

尤金的解决方案

Eugene Lazutkin already described 使用自定义哈希函数生成唯一字符串的基本思想,该字符串可用于查找作为字典对象属性的关联值。这很可能是最快的解决方案,因为对象在内部实现为 哈希表

注意:哈希表(有时称为哈希映射)是映射概念的特定实现,它使用支持数组和通过数字哈希值查找。运行时环境可能会使用其他结构(例如搜索树或跳过列表)来实现 JavaScript 对象,但由于对象是基本的数据结构,因此应该对其进行充分优化。

为了获得任意对象的唯一哈希值,一种可能性是使用全局计数器并将哈希值缓存在对象本身中(例如,在名为 __hash 的属性中)。

执行此操作并适用于原始值和对象的哈希函数是:

function hash(value) {
    return (typeof value) + ' ' + (value instanceof Object ?
        (value.__hash || (value.__hash = ++arguments.callee.current)) :
        value.toString());
}

hash.current = 0;

这个函数可以按照 Eugene 的描述来使用。为方便起见,我们将进一步将其包装在 Map 类中。

我的地图实现

以下实现会将键值对另外存储在双向链表中,以允许对键和值进行快速迭代。要提供您自己的哈希函数,您可以在创建后覆盖实例的 hash() 方法。

// Linking the key-value-pairs is optional.
// If no argument is provided, linkItems === undefined, i.e. !== false
// --> linking will be enabled
function Map(linkItems) {
    this.current = undefined;
    this.size = 0;

    if(linkItems === false)
        this.disableLinking();
}

Map.noop = function() {
    return this;
};

Map.illegal = function() {
    throw new Error("illegal operation for maps without linking");
};

// Map initialisation from an existing object
// doesn't add inherited properties if not explicitly instructed to:
// omitting foreignKeys means foreignKeys === undefined, i.e. == false
// --> inherited properties won't be added
Map.from = function(obj, foreignKeys) {
    var map = new Map;

    for(var prop in obj) {
        if(foreignKeys || obj.hasOwnProperty(prop))
            map.put(prop, obj[prop]);
    }

    return map;
};

Map.prototype.disableLinking = function() {
    this.link = Map.noop;
    this.unlink = Map.noop;
    this.disableLinking = Map.noop;
    this.next = Map.illegal;
    this.key = Map.illegal;
    this.value = Map.illegal;
    this.removeAll = Map.illegal;

    return this;
};

// Overwrite in Map instance if necessary
Map.prototype.hash = function(value) {
    return (typeof value) + ' ' + (value instanceof Object ?
        (value.__hash || (value.__hash = ++arguments.callee.current)) :
        value.toString());
};

Map.prototype.hash.current = 0;

// --- Mapping functions

Map.prototype.get = function(key) {
    var item = this[this.hash(key)];
    return item === undefined ? undefined : item.value;
};

Map.prototype.put = function(key, value) {
    var hash = this.hash(key);

    if(this[hash] === undefined) {
        var item = { key : key, value : value };
        this[hash] = item;

        this.link(item);
        ++this.size;
    }
    else this[hash].value = value;

    return this;
};

Map.prototype.remove = function(key) {
    var hash = this.hash(key);
    var item = this[hash];

    if(item !== undefined) {
        --this.size;
        this.unlink(item);

        delete this[hash];
    }

    return this;
};

// Only works if linked
Map.prototype.removeAll = function() {
    while(this.size)
        this.remove(this.key());

    return this;
};

// --- Linked list helper functions

Map.prototype.link = function(item) {
    if(this.size == 0) {
        item.prev = item;
        item.next = item;
        this.current = item;
    }
    else {
        item.prev = this.current.prev;
        item.prev.next = item;
        item.next = this.current;
        this.current.prev = item;
    }
};

Map.prototype.unlink = function(item) {
    if(this.size == 0)
        this.current = undefined;
    else {
        item.prev.next = item.next;
        item.next.prev = item.prev;
        if(item === this.current)
            this.current = item.next;
    }
};

// --- Iterator functions - only work if map is linked

Map.prototype.next = function() {
    this.current = this.current.next;
};

Map.prototype.key = function() {
    return this.current.key;
};

Map.prototype.value = function() {
    return this.current.value;
};

例子

以下脚本,

var map = new Map;

map.put('spam', 'eggs').
    put('foo', 'bar').
    put('foo', 'baz').
    put({}, 'an object').
    put({}, 'another object').
    put(5, 'five').
    put(5, 'five again').
    put('5', 'another five');

for(var i = 0; i++ < map.size; map.next())
    document.writeln(map.hash(map.key()) + ' : ' + map.value());

生成此输出:

string spam : eggs
string foo : baz
object 1 : an object
object 2 : another object
number 5 : five again
string 5 : another five

进一步的考虑

PEZ suggested 覆盖 toString() 方法,大概是用我们的哈希函数。这是不可行的,因为它不适用于原始值(将 toString() 更改为原始值是一个非常 坏主意)。如果我们希望 toString() 为任意对象返回有意义的值,我们将不得不修改 Object.prototype,有些人(不包括我自己)认为 verboten

我的 Map 实现的当前版本以及其他 JavaScript 好东西can be obtained from here


ES5 不赞成使用被调用者 (goo.gl/EeStE)。相反,我建议使用 Map._counter = 0,并在 Map 构造函数中执行 this._hash = 'object ' + Map._counter++。然后 hash() 变成 return (value && value._hash) || (typeof(value) + ' ' + String(value));
嗨@Christoph,你能更新你的链接到哪里可以找到你的地图实现吗?
@jsc123:我会调查一下 - 现在您可以在 pikacode.com/mercurial.intuxication.org/js-hacks.tar.gz 获得存储库的转储
P
Peter Mortensen

现在有一些非常棒的外部库解决方案:

集合.js

不可变的.js

JavaScript 也有其语言提供的 Map

地图


这是迈向 21 世纪的道路。太糟糕了,我在用一些丑陋的自制地图完成我的代码后找到了你的帖子。 WEEE 需要更多投票给你的答案
Collections.js 有一些实现,但我在 underscore.js 或 lodash 中找不到任何实现......你在下划线中指的是什么有用的?
@CodeBling 不知道。我想我把它与地图功能混淆了。我将从答案中删除它。
这还算公平。任何考虑使用 Collections.js 的人都应该知道,它会以一种有问题的方式修改全局数组、函数、对象和正则表达式原型 (see the issues I encountered here)。虽然我最初对 collections.js(以及这个答案)非常满意,但使用它的风险太高,所以我放弃了它。只有 kriskowal 的 v2 branch of collections.js(特别是 v2.0.2+)消除了全局原型修改并且可以安全使用。
P
Peter Mortensen

根据 ECMAScript 2015 (ES6),标准 JavaScript 有一个 Map 实现。更多关于哪个可以找到here

基本用法:

var myMap = new Map();
var keyString = "a string",
    keyObj = {},
    keyFunc = function () {};

// Setting the values
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

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

但它是否使用基于哈希的实现?显然,出于性能原因,这很重要,在您使用其他语言的哈希图的情况下
它使用底层对象 ID。所以如果你说 o = {}map.set(o, 42) 并改变 o,那么 map.get(o) 仍然可以工作
P
Peter Mortensen

这是使用类似于 Java 映射的简单方便的方法:

var map = {
              'map_name_1': map_value_1,
              'map_name_2': map_value_2,
              'map_name_3': map_value_3,
              'map_name_4': map_value_4
          }

并获得价值:

alert(map['map_name_1']);    // Gives the value of map_value_1

... etc. ...

这仅适用于字符串键。我相信 OP 有兴趣使用任何类型的密钥。
P
Peter Mortensen

您可以使用 ECMAScript 6 WeakMapMap

WeakMaps 是键/值映射,其中键是对象。

Map 对象是简单的键/值映射。任何值(对象和原始值)都可以用作键或值。

请注意,两者都没有得到广泛支持,但您可以使用 ECMAScript 6 Shim(需要本机 ECMAScript 5 或 ECMAScript 5 Shim)来支持 Map,但不能使用 WeakMap (see why)。


在 2019 年,他们得到了很好的支持,并且有惊人的方法! developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
P
Peter Mortensen

您必须以某种内部状态存储对象/值对的对联:

HashMap = function(){
  this._dict = [];
}

HashMap.prototype._get = function(key){
  for(var i=0, couplet; couplet = this._dict[i]; i++){
    if(couplet[0] === key){
      return couplet;
    }
  }
}

HashMap.prototype.put = function(key, value){
  var couplet = this._get(key);
  if(couplet){
    couplet[1] = value;
  }else{
    this._dict.push([key, value]);
  }
  return this; // for chaining
}
HashMap.prototype.get = function(key){
  var couplet = this._get(key);
  if(couplet){
    return couplet[1];
  }
}

并像这样使用它:

var color = {}; // Unique object instance
var shape = {}; // Unique object instance
var map = new HashMap();
map.put(color, "blue");
map.put(shape, "round");
console.log("Item is", map.get(color), "and", map.get(shape));

当然,这个实现也是在 O(n) 的某个地方。 Eugene's examples 是获得以您期望从真实哈希中获得的任何速度运行的哈希的唯一方法。

与尤金的回答类似,另一种方法是以某种方式将唯一 ID 附加到所有对象。我最喜欢的方法之一是采用从 Object 超类继承的内置方法之一,将其替换为自定义函数传递并将属性附加到该函数对象。如果你要重写我的 HashMap 方法来做到这一点,它看起来像:

HashMap = function(){
  this._dict = {};
}

HashMap.prototype._shared = {id: 1};
HashMap.prototype.put = function put(key, value){
  if(typeof key == "object"){
    if(!key.hasOwnProperty._id){
      key.hasOwnProperty = function(key){
        return Object.prototype.hasOwnProperty.call(this, key);
      }
      key.hasOwnProperty._id = this._shared.id++;
    }
    this._dict[key.hasOwnProperty._id] = value;
  }else{
    this._dict[key] = value;
  }
  return this; // for chaining
}

HashMap.prototype.get = function get(key){
  if(typeof key == "object"){
    return this._dict[key.hasOwnProperty._id];
  }
  return this._dict[key];
}

这个版本似乎只是稍微快了一点,但理论上它对于大型数据集会快得多。


关联数组,即2元组数组,是Map,不是HashMap; HashMap 是一种使用哈希来获得更好性能的 Map。
是的,但为什么要在这个话题上分裂头发?由于无法获取对象内存地址,因此无法在 JavaScript 中创建真正的哈希映射。并且 JavaScript 的内置对象键/值对(在我的第二个示例中使用)可以充当 HashMap,但不一定,因为它取决于浏览器中使用的运行时如何实现查找。
P
Peter Mortensen

不幸的是,以前的答案都不适用于我的情况:不同的关键对象可能具有相同的哈希码。因此,我编写了一个简单的类似 Java 的 HashMap 版本:

function HashMap() {
    this.buckets = {};
}

HashMap.prototype.put = function(key, value) {
    var hashCode = key.hashCode();
    var bucket = this.buckets[hashCode];
    if (!bucket) {
        bucket = new Array();
        this.buckets[hashCode] = bucket;
    }
    for (var i = 0; i < bucket.length; ++i) {
        if (bucket[i].key.equals(key)) {
            bucket[i].value = value;
            return;
        }
    }
    bucket.push({ key: key, value: value });
}

HashMap.prototype.get = function(key) {
    var hashCode = key.hashCode();
    var bucket = this.buckets[hashCode];
    if (!bucket) {
        return null;
    }
    for (var i = 0; i < bucket.length; ++i) {
        if (bucket[i].key.equals(key)) {
            return bucket[i].value;
        }
    }
}

HashMap.prototype.keys = function() {
    var keys = new Array();
    for (var hashKey in this.buckets) {
        var bucket = this.buckets[hashKey];
        for (var i = 0; i < bucket.length; ++i) {
            keys.push(bucket[i].key);
        }
    }
    return keys;
}

HashMap.prototype.values = function() {
    var values = new Array();
    for (var hashKey in this.buckets) {
        var bucket = this.buckets[hashKey];
        for (var i = 0; i < bucket.length; ++i) {
            values.push(bucket[i].value);
        }
    }
    return values;
}

注意:关键对象必须“实现” hashCode() 和 equals() 方法。


new Array() 优于 [] 是为了确保您的代码绝对类似于 Java? :)
P
Peter Mortensen

我已经实现了一个 JavaScript HashMap,它的代码可以从 http://github.com/lambder/HashMapJS/tree/master 获得

这是代码:

/*
 =====================================================================
 @license MIT
 @author Lambder
 @copyright 2009 Lambder.
 @end
 =====================================================================
 */
var HashMap = function() {
  this.initialize();
}

HashMap.prototype = {
  hashkey_prefix: "<#HashMapHashkeyPerfix>",
  hashcode_field: "<#HashMapHashkeyPerfix>",

  initialize: function() {
    this.backing_hash = {};
    this.code = 0;
  },
  /*
   Maps value to key returning previous association
   */
  put: function(key, value) {
    var prev;
    if (key && value) {
      var hashCode = key[this.hashcode_field];
      if (hashCode) {
        prev = this.backing_hash[hashCode];
      } else {
        this.code += 1;
        hashCode = this.hashkey_prefix + this.code;
        key[this.hashcode_field] = hashCode;
      }
      this.backing_hash[hashCode] = value;
    }
    return prev;
  },
  /*
   Returns value associated with given key
   */
  get: function(key) {
    var value;
    if (key) {
      var hashCode = key[this.hashcode_field];
      if (hashCode) {
        value = this.backing_hash[hashCode];
      }
    }
    return value;
  },
  /*
   Deletes association by given key.
   Returns true if the association existed, false otherwise
   */
  del: function(key) {
    var success = false;
    if (key) {
      var hashCode = key[this.hashcode_field];
      if (hashCode) {
        var prev = this.backing_hash[hashCode];
        this.backing_hash[hashCode] = undefined;
        if(prev !== undefined)
          success = true;
      }
    }
    return success;
  }
}

//// Usage

// Creation

var my_map = new HashMap();

// Insertion

var a_key = {};
var a_value = {struct: "structA"};
var b_key = {};
var b_value = {struct: "structB"};
var c_key = {};
var c_value = {struct: "structC"};

my_map.put(a_key, a_value);
my_map.put(b_key, b_value);
var prev_b = my_map.put(b_key, c_value);

// Retrieval

if(my_map.get(a_key) !== a_value){
  throw("fail1")
}
if(my_map.get(b_key) !== c_value){
  throw("fail2")
}
if(prev_b !== b_value){
  throw("fail3")
}

// Deletion

var a_existed = my_map.del(a_key);
var c_existed = my_map.del(c_key);
var a2_existed = my_map.del(a_key);

if(a_existed !== true){
  throw("fail4")
}
if(c_existed !== false){
  throw("fail5")
}
if(a2_existed !== false){
  throw("fail6")
}

您的代码似乎不适用于将同一个对象放在多个 HashMap 中。
P
Peter Mortensen

在 ECMAScript 6 中,您可以使用 WeakMap

例子:

var wm1 = new WeakMap(),
    wm2 = new WeakMap(),
    wm3 = new WeakMap();
var o1 = {},
    o2 = function(){},
    o3 = window;

wm1.set(o1, 37);
wm1.set(o2, "azerty");
wm2.set(o1, o2); // A value can be anything, including an object or a function
wm2.set(o3, undefined);
wm2.set(wm1, wm2); // Keys and values can be any objects. Even WeakMaps!

wm1.get(o2); // "azerty"
wm2.get(o2); // Undefined, because there is no value for o2 on wm2
wm2.get(o3); // Undefined, because that is the set value

wm1.has(o2); // True
wm2.has(o2); // False
wm2.has(o3); // True (even if the value itself is 'undefined')

wm3.set(o1, 37);
wm3.get(o1); // 37
wm3.clear();
wm3.get(o1); // Undefined, because wm3 was cleared and there is no value for o1 anymore

wm1.has(o1);   // True
wm1.delete(o1);
wm1.has(o1);   // False

但:

由于引用很弱,WeakMap 键是不可枚举的(即没有方法可以为您提供键列表)。


哦赞美耶稣,他们终于添加了对 javascript 的弱引用。是时候了...为此+1,但这实际上使用起来很糟糕,因为引用很弱
T
Tim Down

试试我的 JavaScript 哈希表实现:http://www.timdown.co.uk/jshashtable

它查找关键对象的 hashCode() 方法,或者您可以在创建 Hashtable 对象时提供散列函数。


P
Peter Mortensen

JavaScript 没有内置的 map/hashmap。它应该被称为 associative array

hash["X"] 等于 hash.X,但它允许“X”作为字符串变量。换句话说,hash[x] 在功能上等于 eval("hash."+x.toString())

它更类似于 object.properties 而不是键值映射。如果您正在寻找更好的 JavaScript 键/值映射,请使用 the Map object


P
Peter Mortensen

我的“地图”实现,源自 Christoph's example

示例用法:

var map = new Map();  // Creates an "in-memory" map
var map = new Map("storageId");  // Creates a map that is loaded/persisted using html5 storage

function Map(storageId) {
    this.current = undefined;
    this.size = 0;
    this.storageId = storageId;
    if (this.storageId) {
        this.keys = new Array();
        this.disableLinking();
    }
}

Map.noop = function() {
    return this;
};

Map.illegal = function() {
    throw new Error("illegal operation for maps without linking");
};

// Map initialisation from an existing object
// doesn't add inherited properties if not explicitly instructed to:
// omitting foreignKeys means foreignKeys === undefined, i.e. == false
// --> inherited properties won't be added
Map.from = function(obj, foreignKeys) {
    var map = new Map;
    for(var prop in obj) {
        if(foreignKeys || obj.hasOwnProperty(prop))
            map.put(prop, obj[prop]);
    }
    return map;
};

Map.prototype.disableLinking = function() {
    this.link = Map.noop;
    this.unlink = Map.noop;
    this.disableLinking = Map.noop;

    this.next = Map.illegal;
    this.key = Map.illegal;
    this.value = Map.illegal;
//    this.removeAll = Map.illegal;


    return this;
};

// Overwrite in Map instance if necessary
Map.prototype.hash = function(value) {
    return (typeof value) + ' ' + (value instanceof Object ?
        (value.__hash || (value.__hash = ++arguments.callee.current)) :
        value.toString());
};

Map.prototype.hash.current = 0;

// --- Mapping functions

Map.prototype.get = function(key) {
    var item = this[this.hash(key)];
    if (item === undefined) {
        if (this.storageId) {
            try {
                var itemStr = localStorage.getItem(this.storageId + key);
                if (itemStr && itemStr !== 'undefined') {
                    item = JSON.parse(itemStr);
                    this[this.hash(key)] = item;
                    this.keys.push(key);
                    ++this.size;
                }
            } catch (e) {
                console.log(e);
            }
        }
    }
    return item === undefined ? undefined : item.value;
};

Map.prototype.put = function(key, value) {
    var hash = this.hash(key);

    if(this[hash] === undefined) {
        var item = { key : key, value : value };
        this[hash] = item;

        this.link(item);
        ++this.size;
    }
    else this[hash].value = value;
    if (this.storageId) {
        this.keys.push(key);
        try {
            localStorage.setItem(this.storageId + key, JSON.stringify(this[hash]));
        } catch (e) {
            console.log(e);
        }
    }
    return this;
};

Map.prototype.remove = function(key) {
    var hash = this.hash(key);
    var item = this[hash];
    if(item !== undefined) {
        --this.size;
        this.unlink(item);

        delete this[hash];
    }
    if (this.storageId) {
        try {
            localStorage.setItem(this.storageId + key, undefined);
        } catch (e) {
            console.log(e);
        }
    }
    return this;
};

// Only works if linked
Map.prototype.removeAll = function() {
    if (this.storageId) {
        for (var i=0; i<this.keys.length; i++) {
            this.remove(this.keys[i]);
        }
        this.keys.length = 0;
    } else {
        while(this.size)
            this.remove(this.key());
    }
    return this;
};

// --- Linked list helper functions

Map.prototype.link = function(item) {
    if (this.storageId) {
        return;
    }
    if(this.size == 0) {
        item.prev = item;
        item.next = item;
        this.current = item;
    }
    else {
        item.prev = this.current.prev;
        item.prev.next = item;
        item.next = this.current;
        this.current.prev = item;
    }
};

Map.prototype.unlink = function(item) {
    if (this.storageId) {
        return;
    }
    if(this.size == 0)
        this.current = undefined;
    else {
        item.prev.next = item.next;
        item.next.prev = item.prev;
        if(item === this.current)
            this.current = item.next;
    }
};

// --- Iterator functions - only work if map is linked

Map.prototype.next = function() {
    this.current = this.current.next;
};

Map.prototype.key = function() {
    if (this.storageId) {
        return undefined;
    } else {
        return this.current.key;
    }
};

Map.prototype.value = function() {
    if (this.storageId) {
        return undefined;
    }
    return this.current.value;
};

P
Peter Mortensen

如果性能不重要(例如,键的数量相对较少)并且您不想使用 _hash_id 等附加字段污染您的(或者可能不是您的)对象,那么您可以利用 Array.prototype.indexOf 采用严格相等的事实。这是一个简单的实现:

var Dict = (function(){
    // Internet Explorer 8 and earlier does not have any Array.prototype.indexOf
    function indexOfPolyfill(val) {
      for (var i = 0, l = this.length; i < l; ++i) {
        if (this[i] === val) {
          return i;
        }
      }
      return -1;
    }

    function Dict(){
      this.keys = [];
      this.values = [];
      if (!this.keys.indexOf) {
        this.keys.indexOf = indexOfPolyfill;
      }
    };

    Dict.prototype.has = function(key){
      return this.keys.indexOf(key) != -1;
    };

    Dict.prototype.get = function(key, defaultValue){
      var index = this.keys.indexOf(key);
      return index == -1 ? defaultValue : this.values[index];
    };

    Dict.prototype.set = function(key, value){
      var index = this.keys.indexOf(key);
      if (index == -1) {
        this.keys.push(key);
        this.values.push(value);
      } else {
        var prevValue = this.values[index];
        this.values[index] = value;
        return prevValue;
      }
    };

    Dict.prototype.delete = function(key){
      var index = this.keys.indexOf(key);
      if (index != -1) {
        this.keys.splice(index, 1);
        return this.values.splice(index, 1)[0];
      }
    };

    Dict.prototype.clear = function(){
      this.keys.splice(0, this.keys.length);
      this.values.splice(0, this.values.length);
    };

    return Dict;
})();

使用示例:

var a = {}, b = {},
    c = { toString: function(){ return '1'; } },
    d = 1, s = '1', u = undefined, n = null,
    dict = new Dict();

// Keys and values can be anything
dict.set(a, 'a');
dict.set(b, 'b');
dict.set(c, 'c');
dict.set(d, 'd');
dict.set(s, 's');
dict.set(u, 'u');
dict.set(n, 'n');

dict.get(a); // 'a'
dict.get(b); // 'b'
dict.get(s); // 's'
dict.get(u); // 'u'
dict.get(n); // 'n'
// etc.

与 ECMAScript 6 WeakMap 相比,它有两个问题:O(n) 搜索时间和非弱点(即不使用 deleteclear 释放键会导致内存泄漏)。


P
Peter Mortensen

添加另一个解决方案:HashMap 几乎是我从 Java 移植到 JavaScript 的第一个类。你可以说有很多开销,但实现几乎 100% 等于 Java 的实现,并且包括所有接口和子类。

该项目可以在这里找到:https://github.com/Airblader/jsava我还将附上 HashMap 类的(当前)源代码,但如上所述,它也依赖于超类等。使用的 OOP 框架是 qooxdoo。

请注意,此代码已经过时,当前工作请参阅 GitHub 项目。在撰写本文时,还有一个 ArrayList 实现。

qx.Class.define( 'jsava.util.HashMap', {
    extend: jsava.util.AbstractMap,
    implement: [jsava.util.Map, jsava.io.Serializable, jsava.lang.Cloneable],

    construct: function () {
        var args = Array.prototype.slice.call( arguments ),
            initialCapacity = this.self( arguments ).DEFAULT_INITIAL_CAPACITY,
            loadFactor = this.self( arguments ).DEFAULT_LOAD_FACTOR;

        switch( args.length ) {
            case 1:
                if( qx.Class.implementsInterface( args[0], jsava.util.Map ) ) {
                    initialCapacity = Math.max( ((args[0].size() / this.self( arguments ).DEFAULT_LOAD_FACTOR) | 0) + 1,
                        this.self( arguments ).DEFAULT_INITIAL_CAPACITY );
                    loadFactor = this.self( arguments ).DEFAULT_LOAD_FACTOR;
                } else {
                    initialCapacity = args[0];
                }
                break;
            case 2:
                initialCapacity = args[0];
                loadFactor = args[1];
                break;
        }

        if( initialCapacity < 0 ) {
            throw new jsava.lang.IllegalArgumentException( 'Illegal initial capacity: ' + initialCapacity );
        }
        if( initialCapacity > this.self( arguments ).MAXIMUM_CAPACITY ) {
            initialCapacity = this.self( arguments ).MAXIMUM_CAPACITY;
        }
        if( loadFactor <= 0 || isNaN( loadFactor ) ) {
            throw new jsava.lang.IllegalArgumentException( 'Illegal load factor: ' + loadFactor );
        }

        var capacity = 1;
        while( capacity < initialCapacity ) {
            capacity <<= 1;
        }

        this._loadFactor = loadFactor;
        this._threshold = (capacity * loadFactor) | 0;
        this._table = jsava.JsavaUtils.emptyArrayOfGivenSize( capacity, null );
        this._init();
    },

    statics: {
        serialVersionUID: 1,

        DEFAULT_INITIAL_CAPACITY: 16,
        MAXIMUM_CAPACITY: 1 << 30,
        DEFAULT_LOAD_FACTOR: 0.75,

        _hash: function (hash) {
            hash ^= (hash >>> 20) ^ (hash >>> 12);
            return hash ^ (hash >>> 7) ^ (hash >>> 4);
        },

        _indexFor: function (hashCode, length) {
            return hashCode & (length - 1);
        },

        Entry: qx.Class.define( 'jsava.util.HashMap.Entry', {
            extend: jsava.lang.Object,
            implement: [jsava.util.Map.Entry],

            construct: function (hash, key, value, nextEntry) {
                this._value = value;
                this._next = nextEntry;
                this._key = key;
                this._hash = hash;
            },

            members: {
                _key: null,
                _value: null,
                /** @type jsava.util.HashMap.Entry */
                _next: null,
                /** @type Number */
                _hash: 0,

                getKey: function () {
                    return this._key;
                },

                getValue: function () {
                    return this._value;
                },

                setValue: function (newValue) {
                    var oldValue = this._value;
                    this._value = newValue;
                    return oldValue;
                },

                equals: function (obj) {
                    if( obj === null || !qx.Class.implementsInterface( obj, jsava.util.HashMap.Entry ) ) {
                        return false;
                    }

                    /** @type jsava.util.HashMap.Entry */
                    var entry = obj,
                        key1 = this.getKey(),
                        key2 = entry.getKey();
                    if( key1 === key2 || (key1 !== null && key1.equals( key2 )) ) {
                        var value1 = this.getValue(),
                            value2 = entry.getValue();
                        if( value1 === value2 || (value1 !== null && value1.equals( value2 )) ) {
                            return true;
                        }
                    }

                    return false;
                },

                hashCode: function () {
                    return (this._key === null ? 0 : this._key.hashCode()) ^
                        (this._value === null ? 0 : this._value.hashCode());
                },

                toString: function () {
                    return this.getKey() + '=' + this.getValue();
                },

                /**
                 * This method is invoked whenever the value in an entry is
                 * overwritten by an invocation of put(k,v) for a key k that's already
                 * in the HashMap.
                 */
                _recordAccess: function (map) {
                },

                /**
                 * This method is invoked whenever the entry is
                 * removed from the table.
                 */
                _recordRemoval: function (map) {
                }
            }
        } )
    },

    members: {
        /** @type jsava.util.HashMap.Entry[] */
        _table: null,
        /** @type Number */
        _size: 0,
        /** @type Number */
        _threshold: 0,
        /** @type Number */
        _loadFactor: 0,
        /** @type Number */
        _modCount: 0,
        /** @implements jsava.util.Set */
        __entrySet: null,

        /**
         * Initialization hook for subclasses. This method is called
         * in all constructors and pseudo-constructors (clone, readObject)
         * after HashMap has been initialized but before any entries have
         * been inserted.  (In the absence of this method, readObject would
         * require explicit knowledge of subclasses.)
         */
        _init: function () {
        },

        size: function () {
            return this._size;
        },

        isEmpty: function () {
            return this._size === 0;
        },

        get: function (key) {
            if( key === null ) {
                return this.__getForNullKey();
            }

            var hash = this.self( arguments )._hash( key.hashCode() );
            for( var entry = this._table[this.self( arguments )._indexFor( hash, this._table.length )];
                 entry !== null; entry = entry._next ) {
                /** @type jsava.lang.Object */
                var k;
                if( entry._hash === hash && ((k = entry._key) === key || key.equals( k )) ) {
                    return entry._value;
                }
            }

            return null;
        },

        __getForNullKey: function () {
            for( var entry = this._table[0]; entry !== null; entry = entry._next ) {
                if( entry._key === null ) {
                    return entry._value;
                }
            }

            return null;
        },

        containsKey: function (key) {
            return this._getEntry( key ) !== null;
        },

        _getEntry: function (key) {
            var hash = (key === null) ? 0 : this.self( arguments )._hash( key.hashCode() );
            for( var entry = this._table[this.self( arguments )._indexFor( hash, this._table.length )];
                 entry !== null; entry = entry._next ) {
                /** @type jsava.lang.Object */
                var k;
                if( entry._hash === hash
                    && ( ( k = entry._key ) === key || ( key !== null && key.equals( k ) ) ) ) {
                    return entry;
                }
            }

            return null;
        },

        put: function (key, value) {
            if( key === null ) {
                return this.__putForNullKey( value );
            }

            var hash = this.self( arguments )._hash( key.hashCode() ),
                i = this.self( arguments )._indexFor( hash, this._table.length );
            for( var entry = this._table[i]; entry !== null; entry = entry._next ) {
                /** @type jsava.lang.Object */
                var k;
                if( entry._hash === hash && ( (k = entry._key) === key || key.equals( k ) ) ) {
                    var oldValue = entry._value;
                    entry._value = value;
                    entry._recordAccess( this );
                    return oldValue;
                }
            }

            this._modCount++;
            this._addEntry( hash, key, value, i );
            return null;
        },

        __putForNullKey: function (value) {
            for( var entry = this._table[0]; entry !== null; entry = entry._next ) {
                if( entry._key === null ) {
                    var oldValue = entry._value;
                    entry._value = value;
                    entry._recordAccess( this );
                    return oldValue;
                }
            }

            this._modCount++;
            this._addEntry( 0, null, value, 0 );
            return null;
        },

        __putForCreate: function (key, value) {
            var hash = (key === null) ? 0 : this.self( arguments )._hash( key.hashCode() ),
                i = this.self( arguments )._indexFor( hash, this._table.length );
            for( var entry = this._table[i]; entry !== null; entry = entry._next ) {
                /** @type jsava.lang.Object */
                var k;
                if( entry._hash === hash
                    && ( (k = entry._key) === key || ( key !== null && key.equals( k ) ) ) ) {
                    entry._value = value;
                    return;
                }
            }

            this._createEntry( hash, key, value, i );
        },

        __putAllForCreate: function (map) {
            var iterator = map.entrySet().iterator();
            while( iterator.hasNext() ) {
                var entry = iterator.next();
                this.__putForCreate( entry.getKey(), entry.getValue() );
            }
        },

        _resize: function (newCapacity) {
            var oldTable = this._table,
                oldCapacity = oldTable.length;
            if( oldCapacity === this.self( arguments ).MAXIMUM_CAPACITY ) {
                this._threshold = Number.MAX_VALUE;
                return;
            }

            var newTable = jsava.JsavaUtils.emptyArrayOfGivenSize( newCapacity, null );
            this._transfer( newTable );
            this._table = newTable;
            this._threshold = (newCapacity * this._loadFactor) | 0;
        },

        _transfer: function (newTable) {
            var src = this._table,
                newCapacity = newTable.length;
            for( var j = 0; j < src.length; j++ ) {
                var entry = src[j];
                if( entry !== null ) {
                    src[j] = null;
                    do {
                        var next = entry._next,
                            i = this.self( arguments )._indexFor( entry._hash, newCapacity );
                        entry._next = newTable[i];
                        newTable[i] = entry;
                        entry = next;
                    } while( entry !== null );
                }
            }
        },

        putAll: function (map) {
            var numKeyToBeAdded = map.size();
            if( numKeyToBeAdded === 0 ) {
                return;
            }

            if( numKeyToBeAdded > this._threshold ) {
                var targetCapacity = (numKeyToBeAdded / this._loadFactor + 1) | 0;
                if( targetCapacity > this.self( arguments ).MAXIMUM_CAPACITY ) {
                    targetCapacity = this.self( arguments ).MAXIMUM_CAPACITY;
                }

                var newCapacity = this._table.length;
                while( newCapacity < targetCapacity ) {
                    newCapacity <<= 1;
                }
                if( newCapacity > this._table.length ) {
                    this._resize( newCapacity );
                }
            }

            var iterator = map.entrySet().iterator();
            while( iterator.hasNext() ) {
                var entry = iterator.next();
                this.put( entry.getKey(), entry.getValue() );
            }
        },

        remove: function (key) {
            var entry = this._removeEntryForKey( key );
            return entry === null ? null : entry._value;
        },

        _removeEntryForKey: function (key) {
            var hash = (key === null) ? 0 : this.self( arguments )._hash( key.hashCode() ),
                i = this.self( arguments )._indexFor( hash, this._table.length ),
                prev = this._table[i],
                entry = prev;

            while( entry !== null ) {
                var next = entry._next,
                    /** @type jsava.lang.Object */
                        k;
                if( entry._hash === hash
                    && ( (k = entry._key) === key || ( key !== null && key.equals( k ) ) ) ) {
                    this._modCount++;
                    this._size--;
                    if( prev === entry ) {
                        this._table[i] = next;
                    } else {
                        prev._next = next;
                    }
                    entry._recordRemoval( this );
                    return entry;
                }
                prev = entry;
                entry = next;
            }

            return entry;
        },

        _removeMapping: function (obj) {
            if( obj === null || !qx.Class.implementsInterface( obj, jsava.util.Map.Entry ) ) {
                return null;
            }

            /** @implements jsava.util.Map.Entry */
            var entry = obj,
                key = entry.getKey(),
                hash = (key === null) ? 0 : this.self( arguments )._hash( key.hashCode() ),
                i = this.self( arguments )._indexFor( hash, this._table.length ),
                prev = this._table[i],
                e = prev;

            while( e !== null ) {
                var next = e._next;
                if( e._hash === hash && e.equals( entry ) ) {
                    this._modCount++;
                    this._size--;
                    if( prev === e ) {
                        this._table[i] = next;
                    } else {
                        prev._next = next;
                    }
                    e._recordRemoval( this );
                    return e;
                }
                prev = e;
                e = next;
            }

            return e;
        },

        clear: function () {
            this._modCount++;
            var table = this._table;
            for( var i = 0; i < table.length; i++ ) {
                table[i] = null;
            }
            this._size = 0;
        },

        containsValue: function (value) {
            if( value === null ) {
                return this.__containsNullValue();
            }

            var table = this._table;
            for( var i = 0; i < table.length; i++ ) {
                for( var entry = table[i]; entry !== null; entry = entry._next ) {
                    if( value.equals( entry._value ) ) {
                        return true;
                    }
                }
            }

            return false;
        },

        __containsNullValue: function () {
            var table = this._table;
            for( var i = 0; i < table.length; i++ ) {
                for( var entry = table[i]; entry !== null; entry = entry._next ) {
                    if( entry._value === null ) {
                        return true;
                    }
                }
            }

            return false;
        },

        clone: function () {
            /** @type jsava.util.HashMap */
            var result = null;
            try {
                result = this.base( arguments );
            } catch( e ) {
                if( !qx.Class.isSubClassOf( e.constructor, jsava.lang.CloneNotSupportedException ) ) {
                    throw e;
                }
            }

            result._table = jsava.JsavaUtils.emptyArrayOfGivenSize( this._table.length, null );
            result.__entrySet = null;
            result._modCount = 0;
            result._size = 0;
            result._init();
            result.__putAllForCreate( this );

            return result;
        },

        _addEntry: function (hash, key, value, bucketIndex) {
            var entry = this._table[bucketIndex];
            this._table[bucketIndex] = new (this.self( arguments ).Entry)( hash, key, value, entry );
            if( this._size++ >= this._threshold ) {
                this._resize( 2 * this._table.length );
            }
        },

        _createEntry: function (hash, key, value, bucketIndex) {
            var entry = this._table[bucketIndex];
            this._table[bucketIndex] = new (this.self( arguments ).Entry)( hash, key, value, entry );
            this._size++;
        },

        keySet: function () {
            var keySet = this._keySet;
            return keySet !== null ? keySet : ( this._keySet = new this.KeySet( this ) );
        },

        values: function () {
            var values = this._values;
            return values !== null ? values : ( this._values = new this.Values( this ) );
        },

        entrySet: function () {
            return this.__entrySet0();
        },

        __entrySet0: function () {
            var entrySet = this.__entrySet;
            return entrySet !== null ? entrySet : ( this.__entrySet = new this.EntrySet( this ) );
        },

        /** @private */
        HashIterator: qx.Class.define( 'jsava.util.HashMap.HashIterator', {
            extend: jsava.lang.Object,
            implement: [jsava.util.Iterator],

            type: 'abstract',

            /** @protected */
            construct: function (thisHashMap) {
                this.__thisHashMap = thisHashMap;
                this._expectedModCount = this.__thisHashMap._modCount;
                if( this.__thisHashMap._size > 0 ) {
                    var table = this.__thisHashMap._table;
                    while( this._index < table.length && ( this._next = table[this._index++] ) === null ) {
                        // do nothing
                    }
                }
            },

            members: {
                __thisHashMap: null,

                /** @type jsava.util.HashMap.Entry */
                _next: null,
                /** @type Number */
                _expectedModCount: 0,
                /** @type Number */
                _index: 0,
                /** @type jsava.util.HashMap.Entry */
                _current: null,

                hasNext: function () {
                    return this._next !== null;
                },

                _nextEntry: function () {
                    if( this.__thisHashMap._modCount !== this._expectedModCount ) {
                        throw new jsava.lang.ConcurrentModificationException();
                    }

                    var entry = this._next;
                    if( entry === null ) {
                        throw new jsava.lang.NoSuchElementException();
                    }

                    if( (this._next = entry._next) === null ) {
                        var table = this.__thisHashMap._table;
                        while( this._index < table.length && ( this._next = table[this._index++] ) === null ) {
                            // do nothing
                        }
                    }

                    this._current = entry;
                    return entry;
                },

                remove: function () {
                    if( this._current === null ) {
                        throw new jsava.lang.IllegalStateException();
                    }

                    if( this.__thisHashMap._modCount !== this._expectedModCount ) {
                        throw new jsava.lang.ConcurrentModificationException();
                    }

                    var key = this._current._key;
                    this._current = null;
                    this.__thisHashMap._removeEntryForKey( key );
                    this._expectedModCount = this.__thisHashMap._modCount;
                }
            }
        } ),

        _newKeyIterator: function () {
            return new this.KeyIterator( this );
        },

        _newValueIterator: function () {
            return new this.ValueIterator( this );
        },

        _newEntryIterator: function () {
            return new this.EntryIterator( this );
        },

        /** @private */
        ValueIterator: qx.Class.define( 'jsava.util.HashMap.ValueIterator', {
            extend: jsava.util.HashMap.HashIterator,

            construct: function (thisHashMap) {
                this.base( arguments, thisHashMap );
            },

            members: {
                next: function () {
                    return this._nextEntry()._value;
                }
            }
        } ),

        /** @private */
        KeyIterator: qx.Class.define( 'jsava.util.HashMap.KeyIterator', {
            extend: jsava.util.HashMap.HashIterator,

            construct: function (thisHashMap) {
                this.base( arguments, thisHashMap );
            },

            members: {
                next: function () {
                    return this._nextEntry().getKey();
                }
            }
        } ),

        /** @private */
        EntryIterator: qx.Class.define( 'jsava.util.HashMap.EntryIterator', {
            extend: jsava.util.HashMap.HashIterator,

            construct: function (thisHashMap) {
                this.base( arguments, thisHashMap );
            },

            members: {
                next: function () {
                    return this._nextEntry();
                }
            }
        } ),

        /** @private */
        KeySet: qx.Class.define( 'jsava.util.HashMap.KeySet', {
            extend: jsava.util.AbstractSet,

            construct: function (thisHashMap) {
                this.base( arguments );
                this.__thisHashMap = thisHashMap;
            },

            members: {
                __thisHashMap: null,

                iterator: function () {
                    return this.__thisHashMap._newKeyIterator();
                },

                size: function () {
                    return this.__thisHashMap._size;
                },

                contains: function (obj) {
                    return this.__thisHashMap.containsKey( obj );
                },

                remove: function (obj) {
                    return this.__thisHashMap._removeEntryForKey( obj ) !== null;
                },

                clear: function () {
                    this.__thisHashMap.clear();
                }
            }
        } ),

        /** @private */
        Values: qx.Class.define( 'jsava.util.HashMap.Values', {
            extend: jsava.util.AbstractCollection,

            construct: function (thisHashMap) {
                this.base( arguments );
                this.__thisHashMap = thisHashMap;
            },

            members: {
                __thisHashMap: null,

                iterator: function () {
                    return this.__thisHashMap._newValueIterator();
                },

                size: function () {
                    return this.__thisHashMap._size;
                },

                contains: function (obj) {
                    return this.__thisHashMap.containsValue( obj );
                },

                clear: function () {
                    this.__thisHashMap.clear();
                }
            }
        } ),

        /** @private */
        EntrySet: qx.Class.define( 'jsava.util.HashMap.EntrySet', {
            extend: jsava.util.AbstractSet,

            construct: function (thisHashMap) {
                this.base( arguments );
                this.__thisHashMap = thisHashMap;
            },

            members: {
                __thisHashMap: null,

                iterator: function () {
                    return this.__thisHashMap._newEntryIterator();
                },

                size: function () {
                    return this.__thisHashMap._size;
                },

                contains: function (obj) {
                    if( obj === null || !qx.Class.implementsInterface( obj, jsava.util.Map.Entry ) ) {
                        return false;
                    }

                    /** @implements jsava.util.Map.Entry */
                    var entry = obj,
                        candidate = this.__thisHashMap._getEntry( entry.getKey() );
                    return candidate !== null && candidate.equals( entry );
                },

                remove: function (obj) {
                    return this.__thisHashMap._removeMapping( obj ) !== null;
                },

                clear: function () {
                    this.__thisHashMap.clear();
                }
            }
        } )
    }
} );

嗯有趣的方法..您是否考虑过尝试自动化方法?也就是说,在当前 java 实现的源代码上运行 Java-to-javascript 编译器?
不 :) 这对我来说只是一个有趣的项目,有很多事情我不能简单地“复制”代码。我不知道 Java-to-Javascript 编译器,尽管我相信它们存在。我不确定他们翻译得有多好。不过,我相当肯定他们在任何情况下都不会产生高质量的代码。
啊,明白了。我在考虑 Google Web Toolkit's 编译器,但似乎他们最终做了您在此处为核心库所做的事情:“GWT 编译器支持绝大多数 Java 语言本身。GWT 运行时库模拟了Java 运行时库。”。也许可以看看其他人如何解决同样的问题!
是的。我确信 Google 的解决方案远远超出了我的范围,但话又说回来,我只是在玩玩而已。不幸的是,源代码似乎已被撤销(?),至少我无法浏览它并且有趣的链接似乎已死。太糟糕了,我很想看看它。
玩得开心是最好的学习方式=)。感谢分享
P
Peter Mortensen

这看起来是一个非常强大的解决方案:https://github.com/flesler/hashmap

它甚至适用于看起来相同的函数和对象。它使用的唯一技巧是向对象添加一个不起眼的成员来识别它。如果您的程序没有覆盖那个晦涩的变量(它类似于 hashid),那么您就是黄金。


o
ovnia

我的另一个地图实现。使用随机数,“泛型”和“迭代器”=)

var HashMap = function (TKey, TValue) {
    var db = [];
    var keyType, valueType;

    (function () {
        keyType = TKey;
        valueType = TValue;
    })();

    var getIndexOfKey = function (key) {
        if (typeof key !== keyType)
            throw new Error('Type of key should be ' + keyType);
        for (var i = 0; i < db.length; i++) {
            if (db[i][0] == key)
                return i;
        }
        return -1;
    }

    this.add = function (key, value) {
        if (typeof key !== keyType) {
            throw new Error('Type of key should be ' + keyType);
        } else if (typeof value !== valueType) {
            throw new Error('Type of value should be ' + valueType);
        }
        var index = getIndexOfKey(key);
        if (index === -1)
            db.push([key, value]);
        else
            db[index][1] = value;
        return this;
    }

    this.get = function (key) {
        if (typeof key !== keyType || db.length === 0)
            return null;
        for (var i = 0; i < db.length; i++) {
            if (db[i][0] == key)
                return db[i][1];
        }
        return null;
    }

    this.size = function () {
        return db.length;
    }

    this.keys = function () {
        if (db.length === 0)
            return [];
        var result = [];
        for (var i = 0; i < db.length; i++) {
            result.push(db[i][0]);
        }
        return result;
    }

    this.values = function () {
        if (db.length === 0)
            return [];
        var result = [];
        for (var i = 0; i < db.length; i++) {
            result.push(db[i][1]);
        }
        return result;
    }

    this.randomize = function () {
        if (db.length === 0)
            return this;
        var currentIndex = db.length, temporaryValue, randomIndex;
        while (0 !== currentIndex) {
            randomIndex = Math.floor(Math.random() * currentIndex);
            currentIndex--;
            temporaryValue = db[currentIndex];
            db[currentIndex] = db[randomIndex];
            db[randomIndex] = temporaryValue;
        }
        return this;
    }

    this.iterate = function (callback) {
        if (db.length === 0)
            return false;
        for (var i = 0; i < db.length; i++) {
            callback(db[i][0], db[i][1]);
        }
        return true;
    }
}

例子:

var a = new HashMap("string", "number");
a.add('test', 1132)
 .add('test14', 666)
 .add('1421test14', 12312666)
 .iterate(function (key, value) {console.log('a['+key+']='+value)});
/*
a[test]=1132
a[test14]=666
a[1421test14]=12312666 
*/
a.randomize();
/*
a[1421test14]=12312666
a[test]=1132
a[test14]=666
*/

想法是什么?就其工作方式而言,重要的区别是什么?结果是什么(性能、更好/更差的最坏情况性能、扩展等)?