자바 스크립트 해시 맵


353

이 답변의 업데이트 3에서 분명한 것처럼 이 표기법은 다음과 같습니다.

var hash = {};
hash[X]

실제로 객체를 해시하지 않습니다 X. 실제로 X는 문자열 인 .toString()경우 ( 객체 인 경우 또는 다양한 기본 유형에 대한 다른 내장 변환을 통해) " hash" 에서 해시하지 않고 해당 문자열을 찾습니다 . 두 개의 서로 다른 객체가 동일한 문자열 변환을 갖는 경우 객체 동등성이 검사되지 않습니다.

이것을 감안할 때-자바 스크립트에서 효율적인 해시 맵 구현이 있습니까? (예를 들어, 구글의 두 번째 결과는 javascript hashmap모든 연산에 대해 O (n) 구현을 생성합니다. 다른 여러 결과는 동일한 문자열 표현을 가진 서로 다른 객체가 서로 덮어 쓴다는 사실을 무시합니다.


1
@Claudiu : 편집을해서 죄송하지만 제목의 "Map"은 실제로 오도되었습니다. 당신이 동의하지 않으면 롤백, 나는 장사 할 생각하지 않았습니다. :)
Tomalak

6
@Claudiu : 당신은 자바 스크립트에 대해 많은 질문을합니다. 좋은 질문입니다. 나는 그것을 좋아한다.

2
@Claudiu : 또한 참조하는 Google 결과에 연결할 수 있습니까? Google의 다른 로컬 버전은 다른 결과를 반환하므로 참조 한 구현이 나에게 나타나지 않는 것 같습니다.
Tomalak

@ Tomalak : 나는 정확히 똑같은 것을 쓰려고했습니다!

3
@Claudiu 아니요, Google에 연결하지 마십시오. 말한 페이지 (Google을 통해 찾은 페이지)에 연결하십시오. Google에 연결하면 검색 대상을 설명하는 것과 동일한 문제가 있습니다. 위치 또는 검색 기록에 따라 Google 맞춤 결과, 시간이 지남에 따라 Google 검색 결과가 변경됨 (현재이 검색의 최상위 결과) 및 기타 검색 가능한 항목 다른 결과를 보여줍니다.
재스퍼

답변:


369

왜 객체를 수동으로 해시하고 결과 문자열을 일반 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로 수행되는 색인 생성을 제어 할 수 있습니다.

물론 "산업 수준의 솔루션"을 원한다면 주요 기능과 컨테이너의 모든 필요한 API를 사용하여 매개 변수가 지정된 클래스를 작성할 수 있지만, JavaScript를 사용하여 단순하고 가벼워 지려고 노력합니다. 이 기능적 솔루션은 간단하고 빠릅니다.

키 기능은 객체의 올바른 속성, 예를 들어 이미 고유 한 키 또는 키 세트, 함께 고유 한 키 조합 또는 같은 암호 해시를 사용하는 것과 같은 복잡한 키를 선택하는 것처럼 간단 할 수 있습니다. 에서 DojoX는 인코딩 , 또는 DojoX는 UUID . 후자의 솔루션은 고유 한 키를 생성 할 수 있지만 개인적으로, 특히 객체를 고유하게 만드는 것이 무엇인지 아는 경우 모든 비용으로 키를 피하려고합니다.

2014 년 업데이트 : 2008 년에 답한이 간단한 솔루션에는 여전히 더 많은 설명이 필요합니다. Q & A 양식으로 아이디어를 명확히하겠습니다.

귀하의 솔루션에는 실제 해시가 없습니다. 어디입니까 ???

JavaScript는 고급 언어입니다. 기본 프리미티브 ( Object )에는 속성을 유지하기위한 해시 테이블이 포함되어 있습니다. 이 해시 테이블은 일반적으로 효율성을 위해 저수준 언어로 작성됩니다. 문자열 키와 함께 간단한 객체를 사용하여 우리는 노력하지 않고 효율적으로 구현 된 해시 테이블을 사용합니다.

그들이 해시를 사용한다는 것을 어떻게 알 수 있습니까?

키로 객체 컬렉션을 처리 할 수있는 주요 방법에는 세 가지가 있습니다.

  • 순서가 없습니다. 이 경우 키로 객체를 검색하려면 찾을 때 멈추는 모든 키를 거쳐야합니다. 평균적으로 n / 2 비교가 필요합니다.
  • 주문했다.
    • 예제 # 1 : 정렬 된 배열-이진 검색을 수행하면 평균 ~ log2 (n) 비교 후 키를 찾을 수 있습니다. 훨씬 낫다.
    • 예 # 2 : 나무. 다시 ~ log (n) 시도가됩니다.
  • 해시 테이블. 평균적으로 일정한 시간이 필요합니다. 비교 : O (n) vs. O (log n) vs. O (1). 팔.

분명히 JavaScript 객체는 일반적인 경우를 처리하기 위해 어떤 형태의 해시 테이블을 사용합니다.

브라우저 공급 업체는 실제로 해시 테이블을 사용합니까 ???

정말.

충돌을 처리합니까?

예. 위 참조. 동일하지 않은 문자열에서 충돌이 발견되면 주저하지 말고 공급 업체에 버그를 신고하십시오.

그래서 당신의 생각은 무엇입니까?

객체를 해시하려면 객체를 고유하게 만들고 키로 사용하십시오. 실제 해시를 계산하거나 해시 테이블을 에뮬레이션하지 마십시오. 기본 JavaScript 객체에서 이미 효율적으로 처리됩니다.

Object기본 속성과의 충돌 가능성을 피하면서 내장 된 해시 테이블을 활용 하려면 JavaScript와 함께이 키를 사용하십시오 .

시작하는 예 :

  • 객체에 고유 한 사용자 이름이 포함되어 있으면 키로 사용하십시오.
  • 고유 한 고객 번호가 포함 된 경우이를 키로 사용하십시오.
    • SSN 또는 여권 번호와 같은 정부에서 발행 한 고유 번호가 포함되어 있고 시스템에서 중복을 허용하지 않는 경우 키로 사용하십시오.
  • 필드 조합이 고유 한 경우이를 키로 사용하십시오.
    • 국가 약어 + 운전 면허 번호는 훌륭한 열쇠입니다.
    • 국가 약어 + 여권 번호도 훌륭한 열쇠입니다.
  • 필드 또는 전체 객체의 일부 함수는 고유 한 값을 반환 할 수 있습니다. 키로 사용하십시오.

귀하의 제안을 사용하고 사용자 이름을 사용하여 모든 객체를 캐시했습니다. 그러나 일부 현명한 사람은 이름이 "toString"이며 내장 속성입니다! 지금 어떻게해야합니까?

결과 키가 독점적으로 라틴 문자로만 구성되는 것이 원격으로 가능하다면, 그것에 대해 무언가를해야합니다. 예를 들어, 시작 또는 끝에 원하는 비 라틴 유니 코드 문자를 추가하여 기본 특성 "#toString", "#MarySmith"와 충돌하지 않도록하십시오. 복합 키를 사용하는 경우, 라틴어가 아닌 분리 문자를 사용하여 키 구성 요소를 분리하십시오 : "name, city, state".

일반적으로 이곳은 창의력을 발휘해야하는 곳으로 주어진 제한 사항 (고유성, 기본 속성과의 잠재적 충돌)이있는 가장 쉬운 키를 선택합니다.

참고 : 고유 키는 정의에 따라 충돌하지 않지만 잠재적 해시 충돌은 기본에 의해 처리됩니다 Object.

산업용 솔루션이 마음에 들지 않는 이유는 무엇입니까?

최고의 코드 인 IMHO는 코드가 전혀 없습니다. 오류가없고 유지 보수가 필요 없으며 이해하기 쉽고 즉각적으로 실행됩니다. 내가 본 "JavaScript의 해시 테이블"은 100 줄 이상의 코드였으며 여러 객체를 포함했습니다. 다음과 비교하십시오 : dict[key] = value.

또 다른 요점 : JavaScript와 동일한 기본 객체를 사용하여 이미 구현 된 것을 구현하여 저수준 언어로 작성된 기본 객체의 성능을 능가 할 수 있습니까?

여전히 키없이 객체를 해시하고 싶습니다!

운이 좋았습니다 : ECMAScript 6 (2015 년 중반 릴리스 예정이며, 그 후 널리 보급되기까지 1 ~ 2 년이 소요됨) mapset을 정의합니다 .

정의에 따라 판단하면 객체 주소를 키로 사용할 수 있으므로 인공 키없이 객체를 즉시 구별 할 수 있습니다. 두 개의 서로 다르지만 동일한 객체 인 OTOH는 별개의 것으로 매핑됩니다.

MDN 과의 비교 분석 :

객체는지도와 유사하여 키를 값으로 설정하고, 해당 값을 검색하고, 키를 삭제하고, 키에 무언가가 저장되어 있는지 감지 할 수 있습니다. 이 때문에 (및 내장 된 대안이 없기 때문에) 객체는 역사적으로지도로 사용되었습니다. 그러나 어떤 경우에는 맵 사용을 선호하는 중요한 차이점이 있습니다.

  • 객체의 키는 문자열과 심볼이지만 함수, 객체 및 기본 요소를 포함하여지도의 모든 값이 될 수 있습니다.
  • 맵에있는 키는 객체에 추가 된 키가 아닌 순서로 정렬됩니다. 따라서 반복 할 때 Map 객체는 삽입 순서대로 키를 반환합니다.
  • size 속성을 사용하여 Map의 크기를 쉽게 얻을 수 있지만 Object의 속성 수는 수동으로 결정해야합니다.
  • 맵은 반복 가능하므로 직접 반복 할 수 있지만 객체를 반복하려면 키를 가져 와서 반복해야합니다.
  • 객체에는 프로토 타입이 있으므로, 조심하지 않으면 키와 충돌 할 수있는 기본 키가 맵에 있습니다. ES5부터는 map = Object.create (null)을 사용하여 무시할 수 있지만 거의 수행되지 않습니다.
  • 키 쌍을 자주 추가 및 제거하는 시나리오에서 맵 성능이 향상 될 수 있습니다.

13
충돌을 처리하지 않기 때문에 적절한지도처럼 보이지 않습니다. 이것이 사실이라면 hash (obj1) == hash (obj2), 데이터가 손실됩니다.
beefeather

32
"PAUL AINLEY"와 "PAULA INLEY"가 모두 시스템에 등록 될 때 하늘은 당신을 도와줍니다.
Matt R

34
@MattR 실제로 모의 해시 함수를 사용하더라도 하늘의 도움없이 예제가 올바르게 작동합니다. 다른 독자들이 지나치게 단순화 된 비현실적인 해시 함수가 다른 기술을 시연하기위한 자리 표시 자로 사용되었다는 것을 알게 되길 바랍니다. 코드 주석과 대답 자체는 실제가 아니라고 강조합니다. 적절한 키 선택은 답변의 마지막 단락에서 설명합니다.
Eugene Lazutkin

6
@EugeneLazutkin-당신은 여전히 ​​착각합니다. 귀하의 예는 여전히 해시 충돌이 발생하기 쉽습니다. 성을 먼저 넣으면 어떻게 든 도움이 될 것이라고 생각하지 마십시오!
매트 R

3
@EugeneLazutkin 대부분의 사람들은 ES6가 나타나기 전에 이것에 대한 답을 읽지 못합니다 ... 깊은 JS 지식을 축하합니다.
가브리엘 안드레스 브랑 콜리 니

171

문제 설명

JavaScript에는 임의의 키로 임의의 값에 액세스 할 수있는 내장 된 일반 유형 ( 연관 배열 또는 사전 이라고도 함 )이 없습니다. JavaScript의 기본 데이터 구조는 객체로 , 문자열을 키로 만 받아들이고 프로토 타입 상속, getter 및 setter 및 추가 부두와 같은 특수 의미를 갖는 특수 유형의 맵입니다.

객체를 맵으로 사용하는 경우 키가를 통해 문자열 값으로 변환 toString()되어 매핑 5'5'동일한 값으로 변환되고 toString()색인이 생성 된 값으로 메서드를 덮어 쓰지 않는 모든 객체가 생성됩니다 '[object Object]'. 확인하지 않으면 상속 된 속성에 무의식적으로 액세스 할 수도 있습니다 hasOwnProperty().

JavaScript의 내장 배열 유형은 1 비트에 도움이되지 않습니다. JavaScript 배열은 연관 배열이 아니라 몇 가지 특별한 속성을 가진 객체 일뿐입니다. 지도로 사용할 수없는 이유를 알고 싶으면 여기를 참조하십시오 .

유진의 솔루션

Eugene Lazutkin은 이미 커스텀 해시 함수를 사용하여 딕셔너리 객체의 속성으로 연관된 값을 찾는 데 사용할 수있는 고유 한 문자열을 생성하는 기본 아이디어를 설명했습니다. 객체는 내부적으로 해시 테이블 로 구현되므로 가장 빠른 솔루션 일 것 입니다.

  • 참고 : 해시 테이블 (때로는 해시 맵 이라고도 함 )은 백업 배열 및 숫자 해시 값을 통한 조회를 사용하여 맵 개념의 특정 구현입니다. 런타임 환경은 다른 구조 (예 : 검색 트리 또는 건너 뛰기 목록 )를 사용하여 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클래스를 추가로 포장합니다 .

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 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는 toString()아마도 해시 함수로 메소드 를 덮어 쓸 것을 제안했습니다 . 이것은 프리미티브 값에는 작동하지 않기 때문에 실현 가능하지 않습니다 ( toString()프리미티브를 변경 하는 것은 매우 나쁜 생각입니다). 우리가 원하는 경우 toString()임의의 객체에 대한 의미있는 값을 반환, 우리는 수정해야 할 것입니다 Object.prototype어떤 사람들은 (자신 포함되지 않음)을 고려하는 금지 사항 .


편집 :Map구현 의 현재 버전 과 다른 JavaScript 기능 은 here에서 얻을 수 있습니다 .


ES5는 수신자 ( goo.gl/EeStE ) 의 사용을 중단합니다 . 대신, 나는 제안 Map._counter = 0하고 Map 생성자에서는을 제안한다 this._hash = 'object ' + Map._counter++. 그러면 hash ()는 다음과 같이됩니다.return (value && value._hash) || (typeof(value) + ' ' + String(value));
broofa


안녕하세요 @Christoph,지도 구현을 찾을 수있는 링크를 업데이트 할 수 있습니까?
NumenorForLife

2
@ jsc123 : 이것에 대해 살펴 보겠습니다. 지금은 pikacode.com/mercurial.intuxication.org/js-hacks.tar.gz
Christoph

58

나는이 질문이 꽤 오래되었다는 것을 알고 있지만 요즘에는 외부 라이브러리를 갖춘 훌륭한 솔루션이 있습니다.

JavaScript에는 언어도 제공 Map됩니다.


2
이것이 21 세기로 나아가는 길입니다. 못생긴 집에서 만든지도로 코드를 완성한 후 게시물을 발견 한 것이 너무 나쁩니다. WEEE는 귀하의 답변에 대해 더 많은 투표가 필요합니다
Phung D. An

1
Collections.js에는 몇 가지 구현이 있지만 underscore.js 또는 lodash에서 찾을 수 없습니다 ... 밑줄에서 무엇을 언급 했습니까?
Codebling

@CodeBling은 모른다. 지도 기능과 혼동되었다고 생각합니다. 나는 대답에서 그것을 제거 할 것입니다.
Jamel Toms

3
공정 해. Collections.js를 고려하는 사람은 전역 배열, 함수, 객체 및 Regexp 프로토 타입을 문제가있는 방식으로 수정한다는 것을 알고 있어야합니다 ( 여기서 발생한 문제 참조 ). 처음에 collections.js (그리고이 답변)에 매우 만족했지만 사용과 관련된 위험이 너무 높아서 삭제했습니다. kriskowal의 v2 collections.js 분기 (특히 v2.0.2 +) 만 전체 프로토 타입 수정을 제거하고 사용하기에 안전합니다.
Codebling

28

다음은 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'] );    // fives the value of map_value_1

......  etc  .....

2
이것은 문자열 키에서만 작동합니다. OP가 모든 유형의 키를 사용하는 데 관심이 있다고 생각합니다.
fractor

26

ECMAScript 2015 (ES6)에 따르면 표준 자바 스크립트에는 Map 구현이 있습니다. 여기 에 대한 자세한 내용

기본 사용법 :

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"

21

ES6 WeakMap또는 Map다음을 사용할 수 있습니다 .

  • WeakMaps는 키가 객체 인 키 / 값 맵입니다.

  • Map객체는 간단한 키 / 값 맵입니다. 임의의 값 (객체 및 프리미티브 값 모두)은 키 또는 값으로 사용될 수 있습니다.

두 가지 모두 광범위하게 지원되지는 않지만 ES6 Shim (기본 ES5 또는 ES5 Shim 필요 )을 사용하여 지원할 수 Map는 있지만 지원 하지는 않습니다 WeakMap( 이유 참조 ).


2019 년에는 매우 잘 지원되며 놀라운 방법이 있습니다! developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Juanma Menendez

13

객체 / 값 쌍의 내부 상태 쌍에 저장해야합니다.

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의 답변에 따른 또 다른 접근 방식은 어떻게 든 모든 객체에 고유 ID를 첨부하는 것입니다. 내가 가장 좋아하는 방법 중 하나는 Object 수퍼 클래스에서 상속 된 내장 메소드 중 하나를 사용하여 사용자 정의 함수 passthrough로 바꾸고 해당 함수 오브젝트에 특성을 첨부하는 것입니다. 이 작업을 수행하기 위해 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- 튜플 배열은 HashMap이 아닌 Map입니다. HashMap은 더 나은 성능을 위해 해시를 사용하는 맵입니다.
Erik Kaplun

사실, 왜 주제에서 머리카락을 나누는가? 객체 메모리 주소를 얻을 수 없으므로 JavaScript에서 실제 해시 맵을 만들 수있는 방법이 없습니다. 그리고 JavaScript의 내장 객체 키 / 값 쌍 (두 번째 예제에서 사용)은 HashMaps 역할을 할 수 있지만 조회가 구현되는 방식에 대해 브라우저에서 사용되는 런타임에 달려 있기 때문에 반드시 그런 것은 아닙니다.
pottedmeat

11

불행히도 위의 답변 중 어느 것도 내 경우에 좋지 않았습니다. 다른 키 객체에는 동일한 해시 코드가있을 수 있습니다. 따라서 간단한 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 () 메소드를 "구현"해야합니다.


7
new Array()over를 선호하는 []것은 코드의 완벽한 Java 유사성을 보장하는 것입니까? :)
Erik Kaplun

6

코드를 얻을 수있는 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 assocciation
   */
  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 assocciation 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")
}

2
동일한 객체를 여러 개 넣는 데 코드가 작동하지 않는 것 같습니다 HashMap.
Erik Kaplun

5

ECMA6에서 당신은 사용할 수 있습니다 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

그러나:

Because of references being weak, WeakMap keys are not enumerable (i.e. there is no method giving you a list of the keys). 

오, 예수를 칭찬합니다. 마침내 자바 스크립트에 대한 약한 참조를 추가하고 있습니다. 그것은 시간에 관한 것입니다 ... +1, 그러나 이것은 참조가 약하기 때문에 실제로 사용하는 것이 끔찍할 것입니다
Claudiu

2

Javascript는 Map / hashmap을 내장하지 않습니다. 연관 배열 이라고 합니다.

hash["X"]은 (와 hash.X) 같지만 문자열 변수로 "X"를 허용합니다. 즉, hash[x]기능적으로eval("hash."+x.toString())

키-값 매핑보다는 object.properties와 더 유사합니다. Javascript에서 더 나은 키 / 값 매핑을 찾고 있다면 웹에서 찾을 수있는 Map 객체를 사용하십시오.



2

이것은 https://github.com/flesler/hashmap 과 같은 매우 강력한 솔루션처럼 보입니다 . 기능과 객체가 동일하게 보이는 경우에도 잘 작동합니다. 그것이 사용하는 유일한 해킹은 모호한 멤버를 객체에 추가하여 식별하는 것입니다. 프로그램이 모호한 변수를 덮어 쓰지 않으면 ( hashid 와 같은 ) 황금색입니다.


2

성능 (예를 들면 키의 양이 상대적으로 작다) 중요하지 않은 당신이 당신의 (또는 아마 당신은) 같은 추가 필드 개체를 오염하지 않으려면 _hash, _id등, 당신은 사실을 사용할 수 있습니다 Array.prototype.indexOf사용을 엄격한 평등. 간단한 구현은 다음과 같습니다.

var Dict = (function(){
    // IE 8 and earlier has no 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.

ES6 WeakMap에 비교는 두 가지 문제가 있습니다 O (n)의 시간과 비 약점을 검색을 (사용하지 않는 경우 즉, 그것은 메모리 누수의 원인이됩니다 delete또는 clear키를 해제).


2

Christoph의 예에서 파생 된 내지도 구현 :

사용법 예 :

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 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;
};

1

또 다른 솔루션을 추가하는 것은 HashMapJava에서 Javascript로 이식 된 첫 번째 클래스와 거의 같습니다 . 많은 오버 헤드가 있다고 말할 수 있지만 구현은 Java 구현과 거의 100 % 동일하며 모든 인터페이스와 하위 클래스를 포함합니다.

프로젝트는 여기에서 찾을 수 있습니다 : 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 컴파일러를 실행합니까?
Claudiu

아니 :) 이것은 나에게 재미있는 프로젝트이며 코드를 단순히 "복사"할 수 없었던 몇 가지가있었습니다. Java-to-Javascript 컴파일러는 알지 못하지만 존재한다고 생각합니다. 그들이 이것을 얼마나 잘 번역할지 모르겠습니다. 그래도 어떤 경우에도 양질의 코드를 생성하지 않을 것이라고 확신합니다.
Ingo Bürk

아. Google 웹 툴킷의 컴파일러 에 대해 생각하고 있었지만 핵심 라이브러리에 대해 여기서 수행하고있는 작업으로 끝났습니다. "GWT 컴파일러는 대부분의 Java 언어 자체를 지원합니다. GWT 런타임 라이브러리는 자바 런타임 라이브러리. ". 다른 사람들이 어떻게 같은 문제를 해결했는지 살펴볼만한 내용이있을 수도 있습니다!
Claudiu

네. Google의 솔루션이 내 것보다 훨씬 더 확실하지만 다시 한 번 재미있게 놀고 있습니다. 불행히도 소스 코드가 취소 된 것 같습니다 (?), 적어도 그것을 찾을 수 없으며 흥미로운 링크가 죽은 것 같습니다. 너무 나쁘다.보고 싶었다.
Ingo Bürk

즐거운 시간을 보내는 것이 =)를 배우는 가장 좋은 방법입니다. 공유 주셔서 감사합니다
Claudiu

0

나에 의한 또 다른지도 구현. randomizer를 사용하면 'generics'와 'iterator'=)

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
*/
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.