Java HashMap은 동일한 해시 코드로 다른 객체를 어떻게 처리합니까?


223

내 이해에 따라 나는 생각한다 :

  1. 두 객체가 동일한 해시 코드를 갖는 것은 완벽하게 합법적입니다.
  2. 두 객체가 같으면 (equals () 메소드 사용) 동일한 해시 코드를 갖습니다.
  3. 두 객체가 같지 않으면 동일한 해시 코드를 가질 수 없습니다

나 맞아?

이제 정확하다면 다음과 같은 질문이 있습니다. HashMap내부적으로 객체의 해시 코드를 사용합니다. 두 객체가 동일한 해시 코드를 가질 수 있다면 HashMap어떤 키를 사용 하는지 추적 할 수 있습니까?

누군가 HashMap내부적으로 객체의 해시 코드를 어떻게 사용 하는지 설명 할 수 있습니까 ?


29
레코드의 경우 : # 1과 # 2는 정확하고 # 3은 잘못되었습니다. 동일하지 않은 두 객체 는 동일한 해시 코드를 가질 있습니다.
Joachim Sauer

6
# 1과 # 3도 모순됨
Delfic

실제로 # 2를 따르지 않으면 equals () 구현 (또는 틀림없이 hashCode ())이 올바르지 않습니다.
Joachim Sauer

답변:


346

해시 맵은 다음과 같이 작동합니다 (조금 단순화되었지만 기본 메커니즘을 보여줍니다).

여기에는 키-값 쌍을 저장하는 데 사용되는 많은 "버킷"이 있습니다. 각 버킷에는 고유 한 번호가 있습니다. 이는 버킷을 식별하는 것입니다. 키-값 쌍을 맵에 넣으면 해시 맵이 키의 해시 코드를보고 식별자가 키의 해시 코드 인 버킷에 쌍을 저장합니다. 예를 들어, 키의 해시 코드는 235-> 쌍은 버킷 번호 235에 저장됩니다. 하나의 버킷은 하나 이상의 키-값 쌍을 저장할 수 있습니다.

해시 맵에서 값을 조회 할 때 키를 제공하면 먼저 사용자가 제공 한 키의 해시 코드를 살펴 봅니다. 그러면 해시 맵이 해당 버킷을 조사한 다음 버킷과 비교하여 버킷에있는 모든 쌍의 키와 제공 한 키를 비교합니다 equals().

이제 이것이 맵에서 키-값 쌍을 찾는 데 매우 효율적인 방법을 알 수 있습니다. 키의 해시 코드에 의해 해시 맵은 어떤 버킷을 찾아야하는지 즉시 알고 있으므로 해당 버킷의 내용에 대해서만 테스트하면됩니다.

위의 메커니즘을 살펴보면 키 hashCode()equals()방법에 필요한 요구 사항도 확인할 수 있습니다 .

  • 두 개의 키가 동일 하면 (비교할 때 equals()반환 true) hashCode()메서드는 동일한 숫자를 반환해야합니다. 키가이를 위반하면 동일한 키가 다른 버킷에 저장 될 수 있으며 해시 맵은 동일한 버킷에서 보이므로 키-값 쌍을 찾을 수 없습니다.

  • 두 개의 키가 다르면 해시 코드가 동일한 지 여부는 중요하지 않습니다. 해시 코드가 동일하면 동일한 버킷에 저장되며,이 경우 해시 맵을 사용 equals()하여 구분합니다.


4
"해시 맵은 키-값 쌍을 찾을 수 없을 것입니다 (왜냐하면 동일한 버킷에서 보이므로)." 동일한 버킷에서 두 개의 동일한 객체가 t1과 t2이고 동일하고 t1과 t2에 각각 해시 코드 h1과 h2가 있다고 설명 할 수 있습니까? t1.equals (t2) = true 및 h1! = h2 따라서 해시 맵이 t1을 찾으면 버킷 h1에서 버킷 t2에서 t2를 찾습니다.
Geek

19
두 키가 동일하지만 해당 hashCode()메소드가 다른 해시 코드를 리턴 하는 경우 키 클래스 의 equals()hashCode()메소드가 계약을 위반하므로에 해당 키를 사용할 때 이상한 결과가 발생합니다 HashMap.
Jesper

각 버킷에는 여러 개의 키 값 쌍이있을 수 있으며 내부적으로 링크 된 목록을 사용합니다. 그러나 내 혼란은-양동이는 무엇입니까? 내부적으로 어떤 데이터 구조를 사용합니까? 버킷 사이에 연결이 있습니까?
Ankit Sharma

1
@AnkitSharma 모든 세부 사항을 알고 싶다면 JDK 설치 디렉토리 HashMap의 파일 src.zip에서 찾을 수 있는 소스 코드를 찾아 보십시오.
Jesper

1
@ 1290 동일한 버킷에있는 키 간의 유일한 관계는 동일한 해시 코드를 가지고 있다는 것입니다.
Jesper

88

세 번째 주장이 잘못되었습니다.

두 개의 동일하지 않은 객체가 동일한 해시 코드를 갖는 것은 완벽하게 합법적입니다. 그것은에 의해 사용되는 HashMap맵이 신속하게 찾을 수 있도록하는 "첫 번째 패스 필터"로 가능한 지정된 키를 가진 항목을. 그런 다음 동일한 해시 코드를 가진 키가 지정된 키와 동일한 지 테스트합니다.

두 개의 동일하지 않은 객체가 동일한 해시 코드를 가질 수 없다는 요구 사항을 원하지 않을 것입니다. 그렇지 않으면 가능한 32 개의 객체로 제한 됩니다. (다른 클래스가 동일한 해시를 생성 할 수 있기 때문에 다른 유형이 객체의 필드를 사용하여 해시 코드를 생성 할 수도 없었 음을 의미합니다.)


6
2 ^ 32 가능한 물체에 어떻게 도착 했습니까?
Geek

5
나는 늦었 어,하지만 그 여전히 궁금 : 자바에서의 해시 코드는 int이며, 그리고 INT는 2 ^ 32 가능한 값이
Xerus

69

HashMap 구조 다이어그램

HashMapEntry객체 의 배열입니다 .

HashMap객체의 배열로 간주하십시오 .

이것이 무엇인지 살펴보십시오 Object.

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;
 
}

Entry객체는 키-값 쌍을 나타냅니다. 버킷이 둘 이상인 경우이 필드 next는 다른 Entry객체를 참조합니다 Entry.

때로는 두 개의 다른 객체에 대한 해시 코드가 동일 할 수 있습니다. 이 경우 두 개의 객체가 하나의 버킷에 저장되고 연결된 목록으로 표시됩니다. 진입 점이 가장 최근에 추가 된 개체입니다. 이 객체는 next필드 등 이있는 다른 객체를 나타냅니다 . 마지막 항목은 null입니다.

HashMap기본 생성자로을 만들 때

HashMap hashMap = new HashMap();

어레이는 크기 16 및 기본 0.75로드 밸런스로 생성됩니다.

새로운 키-값 쌍 추가

  1. 키의 해시 코드 계산
  2. hash % (arrayLength-1)요소를 배치 할 위치 계산 (버킷 번호)
  3. 에 이미 저장된 키로 값을 추가하려고하면 HashMap값이 덮어 쓰기됩니다.
  4. 그렇지 않으면 요소가 버킷에 추가됩니다.

버킷에 이미 하나 이상의 요소가있는 경우 새로운 요소가 추가되어 버킷의 첫 번째 위치에 배치됩니다. 해당 next필드는 이전 요소를 나타냅니다.

삭제

  1. 주어진 키에 대한 해시 코드를 계산
  2. 버킷 번호 계산 hash % (arrayLength-1)
  3. 버킷의 첫 번째 Entry 객체에 대한 참조를 가져오고 equals 메소드를 사용하여 지정된 버킷의 모든 항목을 반복합니다. 결국 우리는 올바른 것을 찾을 것 Entry입니다. 원하는 요소를 찾지 못하면null

3
이것은 잘못된 hash % (arrayLength-1)것입니다 hash % arrayLength. 그러나 실제로는 hash & (arrayLength-1) 입니다. 즉, 2^n배열 길이 에 2의 거듭 제곱을 사용하기 때문에 n최하위 비트를 사용합니다.
weston

나는 그것이 Entity 객체의 배열이 아니라 LinkedList / Tree의 배열이라고 생각합니다. 그리고 각 트리에는 내부적으로 Entity 객체가 있습니다.
Mudit bhaintwal

@shevchyk 왜 우리는 키와 해시를 저장합니까? 그들의 용도는 무엇입니까? 우리는 여기서 기억을 낭비하지 않습니까?
roottraveller

hashset은 내부적으로 hashmap을 사용합니다. 해시 맵의 추가 및 삭제 규칙이 해시 셋에 적합합니까?
overexchange

2
@weston뿐만 아니라 hashCode는 int물론 음수 일 수 있습니다. 음수에 대해 모듈로를 수행하면 음수가됩니다
Eugene

35

http://javarevisited.blogspot.com/2011/02/how-hashmap-works-in-java.html 에서 훌륭한 정보를 찾을 수 있습니다.

요약:

HashMap은 해싱 원칙에 따라 작동합니다

put (key, value) : HashMap은 키와 값 객체를 Map.Entry로 저장합니다. 해시 맵은 해시 코드 (키)를 적용하여 버킷을 가져옵니다. 충돌이있는 경우 HashMap은 LinkedList를 사용하여 객체를 저장합니다.

get (key) : HashMap은 Key Object의 해시 코드를 사용하여 버킷 위치를 찾은 다음 keys.equals () 메서드를 호출하여 LinkedList에서 올바른 노드를 식별하고 Java HashMap에서 해당 키의 관련 값 객체를 반환합니다.


3
나는 Jasper가 제공 한 답변을 더 잘 찾았습니다. 블로그가 개념을 이해하는 것보다 인터뷰를 다루는 데 더 도움이된다고 느꼈습니다.
Narendra N

@NarendraN 동의합니다.
Abhijit Gaikwad

22

다음은 버전 HashMap의 메커니즘에 대한 대략적인 설명입니다 (Java 6과 약간 다를 수 있음) .Java 8


데이터 구조

  • 해시 테이블
    해시 값은 hash()on 키 를 통해 계산되며 지정된 키에 사용할 해시 테이블의 버킷을 결정합니다.
  • 링크 된 목록 (단일)
    버킷의 요소 수가 적을 때는 단일 링크 된 목록이 사용됩니다.
  • 빨강 검정 트리
    버킷의 요소 수가 많으면 빨강 검정 트리가 사용됩니다.

수업 (내부)

  • Map.Entry
    키 / 값 엔터티 인 맵에서 단일 엔터티를 나타냅니다.
  • HashMap.Node
    노드의 링크 된 목록 버전.

    다음을 나타낼 수 있습니다.

    • 해시 버킷.
      해시 속성이 있기 때문입니다.
    • 단일 연결리스트의 노드는, (따라서도 LinkedList의 머리) .
  • HashMap.TreeNode
    노드의 트리 버전.

필드 (내부)

  • Node[] table
    버킷 테이블 (링크 된 목록의 헤드)
    버킷에 요소가 포함되어 있지 않으면 null이므로 참조 공간 만 차지합니다.
  • Set<Map.Entry> entrySet 엔터티 집합입니다.
  • int size
    엔터티 수
  • float loadFactor
    크기를 조정하기 전에 해시 테이블이 허용되는 정도를 나타냅니다.
  • int threshold
    크기를 조정할 다음 크기입니다.
    공식:threshold = capacity * loadFactor

방법 (내부)

  • int hash(key)
    키로 해시를 계산하십시오.
  • 해시를 버킷에 매핑하는 방법?
    다음 논리를 사용하십시오.

    static int hashToBucket(int tableSize, int hash) {
        return (tableSize - 1) & hash;
    }

용량에 대해

해시 테이블에서 capacity는 버킷 수를 의미합니다 table.length.
또한을 통해 계산되지 수 thresholdloadFactor, 따라서 더 클래스 필드로 정의 될 필요가있다.

다음을 통해 유효 용량을 얻을 수 있습니다. capacity()


운영

  • 키로 엔티티를 찾으십시오.
    먼저 해시 값으로 버킷을 찾은 다음 링크 된 목록 또는 검색 정렬 트리를 반복합니다.
  • 키로 엔티티를 추가하십시오.
    먼저 키의 해시 값에 따라 버킷을 찾으십시오.
    그런 다음 값을 찾으십시오.
    • 발견되면 값을 바꾸십시오.
    • 그렇지 않으면 링크 된 목록의 시작 부분에 새 노드를 추가하거나 정렬 된 트리에 삽입하십시오.
  • 크기 조정 도달
    하면 threshold해시 테이블의 용량을 두 배로 늘리고 table.length모든 요소에 대해 해시를 수행하여 테이블을 다시 작성합니다.
    이것은 비용이 많이 드는 작업 일 수 있습니다.

공연

  • get & put
    Time의 복잡성은 다음 O(1)과 같습니다.
    • 버킷은 배열 인덱스를 통해 액세스되므로 O(1).
    • 각 버킷의 링크 된 목록은 길이가 작으므로로 볼 수 있습니다 O(1).
    • 트리 수 또한 제한되어 있습니다. 요소 수가 증가하면 용량을 늘리고 다시 해시하므로로 볼 수는 O(1)없습니다 O(log N).

예를 들어 주시겠습니까? 시간 복잡성 O (1)
Jitendra

더 명확하게 복잡성을 설명 할 수이를 @jsroyal : en.wikipedia.org/wiki/Hash_table를 . 그러나 간단히 말해서 : 대상 버킷을 찾는 것은 O (1)입니다. 왜냐하면 배열에서 인덱스로 찾을 수 있기 때문입니다. 버킷 내에서 요소의 양은 작고 전체 해시 테이블의 전체 요소 수에도 불구하고 평균은 일정한 수이므로 버킷 내에서 대상 요소를 검색하는 것도 O (1)입니다. 따라서 O (1) + O (1) = O (1)입니다.
Eric Wang

14

해시 코드는 해시 맵에서 확인할 버킷을 결정합니다. 버킷에 둘 이상의 객체가있는 경우 버킷에서 어떤 항목이 원하는 항목 ( equals()) 메서드 와 같은지 찾기 위해 선형 검색이 수행됩니다 .

즉, 완벽한 해시 코드가 있고 해시 맵 액세스가 일정하면 버킷을 반복 할 필요가 없습니다 (기술적으로 MAX_INT 버킷이 있어야합니다 .Java 구현은 동일한 버킷에서 몇 개의 해시 코드를 공유 할 수 있음) 공간 요구 사항을 줄입니다). 최악의 해시 코드가있는 경우 (항상 같은 숫자를 반환) 맵에서 모든 항목을 검색해야하기 때문에 해시 맵 액세스가 선형이됩니다 (모두 동일한 버킷에 있음).

대부분의 경우 잘 작성된 해시 코드는 완벽하지는 않지만 일정하게 액세스 할 수있을 정도로 독특합니다.


11

당신은 포인트 3에 착각합니다. 두 항목은 동일한 해시 코드를 가질 수 있지만 동일하지는 않습니다. OpenJdk에서 HashMap.get 구현을 살펴보십시오 . 해시가 같고 키가 같은지 확인합니다. 세 점이 사실이라면 키가 같은지 확인할 필요가 없습니다. 전자가 더 효율적인 비교이므로 해시 코드가 키보다 먼저 비교됩니다.

이것에 대해 조금 더 배우고 싶다면 Open Addressing 충돌 해결 에 관한 Wikipedia 기사를 살펴보십시오. . OpenJdk 구현이 사용하는 메커니즘이라고 생각합니다. 이 메커니즘은 다른 답변 중 하나가 언급 한 "버킷"접근 방식과 미묘하게 다릅니다.


6
import java.util.HashMap;

public class Students  {
    String name;
    int age;

    Students(String name, int age ){
        this.name = name;
        this.age=age;
    }

    @Override
    public int hashCode() {
        System.out.println("__hash__");
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        System.out.println("__eq__");
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Students other = (Students) obj;
        if (age != other.age)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

    public static void main(String[] args) {

        Students S1 = new Students("taj",22);
        Students S2 = new Students("taj",21);

        System.out.println(S1.hashCode());
        System.out.println(S2.hashCode());

        HashMap<Students,String > HM = new HashMap<Students,String > (); 
        HM.put(S1, "tajinder");
        HM.put(S2, "tajinder");
        System.out.println(HM.size());
    }
}

Output:

__ hash __

116232

__ hash __

116201

__ hash __

__ hash __

2

따라서 우리는 객체 S1과 S2가 서로 다른 내용을 가지고 있다면 재정의 된 Hashcode 메소드가 두 객체에 대해 서로 다른 Hashcode (116232,11601)를 생성한다는 것을 확신합니다. 해시 코드가 다르기 때문에 이제는 EQUALS 메소드를 호출하는 것을 귀찮게하지 않습니다. 다른 Hashcode가 오브젝트의 다른 컨텐츠를 보장하기 때문입니다.

    public static void main(String[] args) {

        Students S1 = new Students("taj",21);
        Students S2 = new Students("taj",21);

        System.out.println(S1.hashCode());
        System.out.println(S2.hashCode());

        HashMap<Students,String > HM = new HashMap<Students,String > (); 
        HM.put(S1, "tajinder");
        HM.put(S2, "tajinder");
        System.out.println(HM.size());
    }
}

Now lets change out main method a little bit. Output after this change is 

__ hash __

116201

__ hash __

116201

__ hash __

__ hash __

__ eq __

1
We can clearly see that equal method is called. Here is print statement __eq__, since we have same hashcode, then content of objects MAY or MAY not be similar. So program internally  calls Equal method to verify this. 


Conclusion 
If hashcode is different , equal method will not get called. 
if hashcode is same, equal method will get called.

Thanks , hope it helps. 

3

두 객체가 같으면 해시 코드가 같지만 그 반대는 아닙니다.

2 개의 동일한 객체 ------> 동일한 해시 코드를 가짐

2 개의 객체는 동일한 해시 코드 ---- xxxxx->를 가지고 있습니다

HashMap의 Java 8 업데이트

코드 에서이 작업을 수행합니다.

myHashmap.put("old","old-value");
myHashMap.put("very-old","very-old-value");

따라서 두 키에 대해 해시 코드가 반환 "old"되고 "very-old"동일 하다고 가정하십시오 . 그러면 어떻게 될까요?

myHashMap는 HashMap이며 처음에 용량을 지정하지 않았다고 가정하십시오. 따라서 Java에 따른 기본 용량은 16입니다. 이제 새 키워드를 사용하여 해시 맵을 초기화하자마자 16 개의 버킷이 만들어졌습니다. 지금 당신이 첫번째 진술을 실행할 때

myHashmap.put("old","old-value");

그런 다음에 대한 해시 코드 "old"가 계산되며 해시 코드도 매우 큰 정수 일 수 있으므로 Java는 내부적 으로이 작업을 수행했습니다 (해시는 해시 코드이며 >>>는 올바른 이동입니다)

hash XOR hash >>> 16

더 큰 그림으로 나타내려면 0에서 15 사이의 인덱스를 반환합니다. 이제 키 값 쌍이 "old"되고 "old-value"Entry 객체의 키 및 값 인스턴스 변수로 변환됩니다. 이 엔트리 객체는 버킷에 저장되거나 특정 인덱스에서이 엔트리 객체가 저장된다고 말할 수 있습니다.

FYI- Entry는 이러한 서명 / 정의가있는 Map 인터페이스 Map.Entry의 클래스입니다.

class Entry{
          final Key k;
          value v;
          final int hash;
          Entry next;
}

이제 다음 문장을 실행할 때-

myHashmap.put("very-old","very-old-value");

"very-old"동일한 해시 코드를 제공하므로이 "old"새로운 키 값 쌍은 다시 동일한 인덱스 또는 동일한 버킷으로 전송됩니다. 그러나이 버킷이 비어 있지 않기 next때문에 Entry 객체 의 변수를 사용하여이 새 키 값 쌍을 저장합니다.

이것은 해시 코드가 동일한 모든 객체에 대해 링크 된 목록으로 저장되지만 TRIEFY_THRESHOLD는 값 6으로 지정됩니다.이 도달하면 링크 된 목록은 첫 번째 요소가 뿌리.


멋진 답변 (y)
Sudhanshu Gaur

2

각 Entry 객체는 키-값 쌍을 나타냅니다. 버킷 다음에 항목이 둘 이상인 경우 필드 다음은 다른 항목 객체를 나타냅니다.

때로는 두 개의 다른 객체에 대한 해시 코드가 동일 할 수 있습니다. 이 경우 2 개의 객체가 하나의 버킷에 저장되고 LinkedList로 표시됩니다. 진입 점이 더 최근에 추가 된 개체입니다. 이 객체는 다음 필드가있는 다른 객체를 나타냅니다. 마지막 항목은 null을 나타냅니다. 기본 생성자로 HashMap을 만들 때

어레이는 크기 16 및 기본 0.75로드 밸런스로 생성됩니다.

여기에 이미지 설명을 입력하십시오

(출처)


1

해시 맵은 해싱의 원리에 따라 작동합니다

HashMap get (Key k) 메소드는 키 객체에서 hashCode 메소드를 호출하고 리턴 된 hashValue를 자체 정적 해시 함수에 적용하여 키와 값이 Entry (Map)라는 중첩 클래스의 형식으로 저장되는 버킷 위치 (백업 배열)를 찾습니다. 참가). 따라서 이전 행에서 키와 값이 모두 Entry 객체의 형태로 버킷에 저장된다는 결론을 내 렸습니다. 따라서 버킷에만 가치가 저장된다고 생각하는 것은 정확하지 않으며 면접관에게 좋은 인상을주지 않을 것입니다.

  • HashMap 객체에서 get (Key k) 메소드를 호출 할 때마다 먼저 키가 null인지 여부를 확인합니다. HashMap에는 하나의 null 키만있을 수 있습니다.

key가 null의 경우는, null 키는 항상 해시 0에 매핑되어 인덱스 0입니다.

key가 null이 아닌 경우, 키 객체에 대해 hashfunction을 호출합니다. 위의 메소드의 4 행, 즉 key.hashCode ()를 참조 해주세요.

            int hash = hash(hashValue)

이제 반환 된 hashValue를 자체 해싱 함수에 적용합니다.

hash (hashValue)를 사용하여 해시 값을 다시 계산하는 이유가 궁금 할 수 있습니다. 대답은 품질이 낮은 해시 함수를 방지합니다.

이제 최종 해시 값이 사용되어 Entry 객체가 저장되는 버킷 위치를 찾습니다. 엔트리 객체는 이와 같이 버킷에 저장됩니다 (해시, 키, 값, 버킷 인덱스)


1

HashMap의 작동 방식에 대한 자세한 내용은 다루지 않지만 실제와 관련하여 HashMap의 작동 방식을 기억할 수 있도록 예를 들겠습니다.

우리는 키, 가치, 해시 코드 및 버킷이 있습니다.

언젠가 우리는 다음과 관련이 있습니다.

  • 버킷-> 사회
  • HashCode-> 사회 주소 (고유 한 항상)
  • 가치-> 사회의 집
  • 키-> 집 주소.

Map.get (key) 사용 :

스티비는 VIP 사회의 빌라에 사는 친구의 집 (Josse)에 가서 JavaLovers Society가 되길 원합니다. Josse의 주소는 SSN (모든 사람마다 다릅니다)입니다. SSN을 기반으로 협회의 이름을 찾는 색인이 유지됩니다. 이 인덱스는 HashCode를 찾기위한 알고리즘으로 간주 될 수 있습니다.

  • SSN 학회 이름
  • 92313 (Josse 's)-자바 연인
  • 13214-AngularJSLovers
  • 98080-JavaLovers
  • 53808-생물학 연인

  1. 이 SSN (key)은 먼저 Society의 이름에 불과한 HashCode (인덱스 테이블)를 제공합니다.
  2. 이제 여러 주택이 같은 사회에있을 수 있으므로 HashCode가 공통적입니다.
  3. 우리 사회가 두 집에 공통적이라고 가정 해 봅시다. 예, 집 주소 외에는 아무것도 아닌 (SSN) 키를 사용하여 어떤 집을 찾아야합니까?

Map.put (key, Value) 사용

HashCode를 찾아이 Value에 적합한 사회를 찾은 다음 값을 저장합니다.

이것이 도움이 되었기를 바랍니다.


0

그것은 긴 대답이 될 것입니다, 음료를 잡고 읽어 ...

해싱은보다 빠르게 읽고 쓸 수있는 키-값 쌍을 메모리에 저장하는 것입니다. 배열에 키를 저장하고 LinkedList에 값을 저장합니다.

4 개의 키-값 쌍을 저장하고 싶다고합시다.

{
girl => ahhan , 
misused => Manmohan Singh , 
horsemints => guess what”, 
no => way
}

따라서 키를 저장하려면 4 요소의 배열이 필요합니다. 이제이 4 개의 키 중 하나를 4 개의 배열 인덱스 (0,1,2,3)에 어떻게 매핑합니까?

따라서 java는 개별 키의 hashCode를 찾아 특정 배열 인덱스에 매핑합니다. 해시 코드 공식은-

1) reverse the string.

2) keep on multiplying ascii of each character with increasing power of 31 . then add the components .

3) So hashCode() of girl would be –(ascii values of  l,r,i,g are 108, 114, 105 and 103) . 

e.g. girl =  108 * 31^0  + 114 * 31^1  + 105 * 31^2 + 103 * 31^3  = 3173020

해시와 소녀 !! 나는 당신이 무엇을 생각하는지 안다. 그 야생 듀엣에 대한 당신의 매력은 당신이 중요한 것을 놓치게 만들 수 있습니다.

왜 자바에 31을 곱합니까?

31은 2 ^ 5 – 1 형식의 홀수입니다. 홀수 프라임은 해시 충돌 가능성을 줄입니다.

이 해시 코드가 어떻게 배열 인덱스에 매핑됩니까?

대답은 Hash Code % (Array length -1) 입니다. 따라서 우리의 경우에 “girl”매핑됩니다 (3173020 % 3) = 1. 이것은 배열의 두 번째 요소입니다.

그리고 "ahhan"값은 배열 인덱스 1과 연관된 LinkedList에 저장됩니다.

HashCollision - 찾을하려고하면 hasHCode키의 “misused”“horsemints”당신 위에서 설명한 공식을 사용하여 모두 같은 우리를주고 볼 수 있습니다 1069518484. 우와! 교훈-

2 개의 동일한 객체는 동일한 hashCode를 가져야하지만 hashCode가 일치하면 객체가 동일한 지 보장 할 수 없습니다. 따라서 "misused"및 "horsemints"에 해당하는 값을 버킷 1에 저장해야합니다 (1069518484 % 3).

이제 해시 맵은 다음과 같습니다.

Array Index 0 
Array Index 1 - LinkedIst (“ahhan , Manmohan Singh , guess what”)
Array Index 2  LinkedList (“way”)
Array Index 3  

이제 어떤 본문이 키의 값을 찾으려고 시도하면 “horsemints”java 는 해당 키 의 hashCode를 신속하게 찾아 모듈화하고 해당 값을 LinkedList에서 찾습니다 index 1. 따라서이 방법으로 4 개의 배열 인덱스를 모두 검색 할 필요가 없으므로 데이터 액세스 속도가 빨라집니다.

그러나 잠시만 기다려주십시오. linkedList 1에 해당하는 3 개의 값이 있는데, 어떤 값이 "horsemints"키의 값인지 어떻게 알 수 있습니까?

실제로 HashMap은 LinkedList에 값을 저장한다고 말했을 때 거짓말했습니다.

키 값 쌍을 맵 항목으로 저장합니다. 실제로지도는 다음과 같습니다.

Array Index 0 
Array Index 1 - LinkedIst (<”girl => ahhan”> , <” misused => Manmohan Singh”> , <”horsemints => guess what”>)
Array Index 2  LinkedList (<”no => way”>)
Array Index 3  

이제 ArrayIndex1에 해당하는 linkedList를 통과하는 동안 실제로는 각 항목의 키를 해당 LinkedList의 키와 "horsemints"를 비교하고 하나를 찾으면 그 값을 반환합니다.

당신이 그것을 읽는 동안 재미 있었기를 바랍니다 :)


나는 이것이 틀렸다고 생각한다 : "키를 배열에 저장하고 값을 LinkedList에 저장한다"
ACV

각 버킷에 대한 목록의 각 요소에는 다음 노드에 대한 참조뿐만 아니라 키와 값이 포함됩니다.
ACV

0

말했듯이 그림은 1000 단어의 가치가 있습니다. 나는 말한다 : 일부 코드는 1000 단어보다 낫다. HashMap의 소스 코드는 다음과 같습니다. 방법을 얻으십시오 :

/**
     * Implements Map.get and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @return the node, or null if none
     */
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

따라서 해시가 "버킷"을 찾는 데 사용되고 첫 번째 요소는 항상 해당 버킷에서 확인됩니다. 그렇지 않은 경우 equals키 중 하나를 사용하여 링크 된 목록에서 실제 요소를 찾습니다.

put()방법을 보자 .

  /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

약간 더 복잡하지만 새 요소가 해시를 기반으로 계산 된 위치에 탭에 배치되는 것이 분명합니다.

i = (n - 1) & hash다음 i은 새 요소를 넣을 인덱스입니다 (또는 "버킷"). 배열 n의 크기입니다 tab( "버킷"의 배열).

먼저, 그 "버킷"의 첫 번째 요소로 놓이려고합니다. 이미 요소가 있으면 새 노드를 목록에 추가하십시오.

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