java에서 HashMap, HashSet, TreeMap, TreeSet 판단 요소가 같은 몇 가지 방법 비교

java에서 HashMap, HashSet, TreeMap, TreeSet 판단 요소가 같은 몇 가지 방법 비교
1.1     HashMap
우선 HashMap에 요소가 어떻게 저장되어 있는지 살펴보자.맵에 저장된 모든 요소는 키-value와 같은 키 값이 맞고put 방법을 통해 추가되며, 같은 키는 맵에 이와 관련된value만 존재합니다.put 방법은 맵에서 다음과 같이 정의됩니다.

  V put(K key, V value);
이것은 키-value와 같은 키 값을 저장하는 데 사용됩니다. 되돌아오는 값은 키가 맵에 저장된 오래된 값입니다. 이전에 존재하지 않았다면null을 되돌려줍니다.HashMap의 put 방법은 이렇게 실현되었다.

 public V put(K key, V value) {

    if (key == null)

      return putForNullKey(value);

    int hash = hash(key);

    int i = indexFor(hash, table.length);

    for (Entry<K,V> e = table[i]; e != null; e = e.next) {

      Object k;

      if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

        V oldValue = e.value;

        e.value = value;

        e.recordAccess(this);

        return oldValue;

      }

    }

 

    modCount++;

    addEntry(hash, key, value, i);

    return null;

  }

위에서 볼 수 있듯이 대응하는 키-value와 같은 조합을 추가할 때 대응하는 키가 존재하면 대응하는 값을 직접 바꾸고 낡은 값을 되돌려줍니다. 키의 존재 여부를 판단할 때 키의hashCode를 먼저 비교하고 같거나 equals를 비교합니다.여기에서 우리가 아직 알아볼 수 없을 수도 있다. 바로 위의 코드로 볼 때 비교적 대응하는 맵이다.Entry의hashCode와key의hashCode,실제로는뒤에지도를볼 수 있습니다.Entry의 해시코드는 사실 키를 저장하는 해시코드입니다.대응하는 키가 존재하지 않으면addEntry를 호출하여 대응하는 키-value를 맵에 추가합니다.addEntry가 전달하는 매개 변수hash는 키에 대응하는 hashCode입니다.다음은 addEntry의 방법 정의를 살펴보겠습니다.

 void addEntry(int hash, K key, V value, int bucketIndex) {

    if ((size >= threshold) && (null != table[bucketIndex])) {

      resize(2 * table.length);

      hash = (null != key) ? hash(key) : 0;

      bucketIndex = indexFor(hash, table.length);

    }

 

    createEntry(hash, key, value, bucketIndex);

  }

 

  void createEntry(int hash, K key, V value, int bucketIndex) {

    Entry<K,V> e = table[bucketIndex];

    table[bucketIndex] = new Entry<>(hash, key, value, e);

    size++;

  }

addEntry의 핵심은createEntry를 호출하여 키-value에 대응하는 맵을 만드는 것입니다.Entry 객체를 저장합니다. 위의 table는 맵입니다.Entry의 배열입니다.Map.Entry에서 하나의 속성hash로 키에 대응하는hashCode를 저장합니다.아니면 위에서 호출한 맵을 보십시오.엔트리의 구조법이죠.

   Entry(int h, K k, V v, Entry<K,V> n) {

      value = v;

      next = n;

      key = k;

      hash = h;

    }

그 내부에는 키,value,key에 대응하는hashCode가 저장되어 있는 것이 분명하다.
HashMap이 원소를 어떻게 저장하는지 알게 된 후에 HashMap이 원소를 어떻게 저장하는지 보면 비교적 간단하다.HashMap에서 원소가 같은지 아닌지를 판단하는 방법은 주로 두 가지가 있는데 하나는 키가 같은지 아닌지를 판단하는 것이고, 하나는value가 같은지 판단하는 것이다.사실 HashMap이 원소를 어떻게 저장하는지 소개할 때 우리는 HashMap이 원소의 키가 같은지 아닌지를 어떻게 판단하는지 소개했다. 그것은 우선hashCode가 같고 그 다음은key가 같거나 equals가 있어야 한다는 것이다.맵에서 키가 같은지 아닌지를 판단하는 것은containsKey () 방법을 통해 이루어진 것으로 HashMap에서 다음과 같이 이루어진다.

 public boolean containsKey(Object key) {

    return getEntry(key) != null;

  }

 

  final Entry<K,V> getEntry(Object key) {

    int hash = (key == null) ? 0 : hash(key);

    for (Entry<K,V> e = table[indexFor(hash, table.length)];

       e != null;

       e = e.next) {

      Object k;

      if (e.hash == hash &&

        ((k = e.key) == key || (key != null && key.equals(k))))

        return e;

    }

    return null;

  }

분명히 이것은 키의hashCode가 같은지 아닌지를 먼저 판단하고 키가 같은지 아니면 equals인지 판단하는 것이다.
맵에서value가 같은지 아닌지를 판단하는 데 사용되는 것은containsValue 방법을 통해 판단하는 것으로 HashMap에서의 실현은 다음과 같다.

  public boolean containsValue(Object value) {

    if (value == null)

      return containsNullValue();

 

    Entry[] tab = table;

    for (int i = 0; i < tab.length ; i++)

      for (Entry e = tab[i] ; e != null ; e = e.next)

        if (value.equals(e.value))

          return true;

    return false;

  }

분명히, 비null 형식의value는value의equals를 통해 판단하고, null 형식의value는 같기만 하면 된다. 즉, 저장된 요소 중value가null이면 된다.
1.2     HashSet
HashMap이 원소를 어떻게 저장하고 원소가 같은지 판단하는 방식을 알게 된 후에 HashSet이 원소가 같은지 판단하는 방법을 보면 비교적 간단하다.
HashSet의 요소는 사실 HashMap을 통해 저장된다. 모든 HashSet 대상에 대응하는 HashMap 대상의 인용을 가지고 있으며, HashSet에 대한 요소의 추가, 삭제 등 작업을 할 때 HashMap을 통해 진행된다.원소를 저장할 때 대응하는 원소를 가지고 있는 HashMap의 키로 저장하고 대응하는 value는 상량 Object이기 때문에 원소가 같은지 아닌지를 판단할 때 사용하는 것은 HashMap이 키가 같은지 아닌지를 판단하는 논리이다.어떤 요소가 포함되어 있는지 판단할 때도 가지고 있는 HashMap의containsKey 방법을 사용해서 판단합니다.

 public boolean contains(Object o) {

    returnmap.containsKey(o);

  }

관심 있는 친구는 HashSet의 원본을 보러 갈 수 있다. 
1.3     TreeMap
TreeMap에 저장된 요소는 모두 질서정연하고 키에 따라 정렬됩니다.TreeMap은 저장된 요소를 정렬할 때 두 가지 방식이 있는데 하나는 자신이 가지고 있는 Comparator를 통해 정렬하는 것이고, 다른 하나는 Comparable 인터페이스를 실현한 키를 통해 정렬하는 것이다. 첫 번째 방식을 우선적으로 사용하고, 가지고 있는 Comparator(기본값이null)가null일 때 두 번째 방식을 사용한다.TreeMap은 여러 가지 구조 방법으로 가지고 있는 Comparator를 초기화할 수 있습니다.트리맵이 요소를 어떻게 저장하는지 먼저 살펴보겠습니다. 이put 방법은 다음과 같습니다.

  public V put(K key, V value) {

    Entry<K,V> t = root;

    if (t == null) {

      compare(key, key); // type (and possibly null) check

 

      root = new Entry<>(key, value, null);

      size = 1;

      modCount++;

      return null;

    }

    int cmp;

    Entry<K,V> parent;

    // split comparator and comparable paths

    Comparator<? super K> cpr = comparator;

    if (cpr != null) {

      do {

        parent = t;

        cmp = cpr.compare(key, t.key);

        if (cmp < 0)

          t = t.left;

        elseif (cmp > 0)

          t = t.right;

        else

          return t.setValue(value);

      } while (t != null);

    }

    else {

      if (key == null)

        thrownew NullPointerException();

      Comparable<? super K> k = (Comparable<? super K>) key;

      do {

        parent = t;

        cmp = k.compareTo(t.key);

        if (cmp < 0)

          t = t.left;

        elseif (cmp > 0)

          t = t.right;

        else

          return t.setValue(value);

      } while (t != null);

    }

    Entry<K,V> e = new Entry<>(key, value, parent);

    if (cmp < 0)

      parent.left = e;

    else

      parent.right = e;

    fixAfterInsertion(e);

    size++;

    modCount++;

    return null;

  }

상술한 실현에서 우리는 첫 번째 원소가 직접 저장될 것을 볼 수 있다.이후의 원소는 두 가지 상황으로 나뉘어 진행되는데 하나는 소지한Comparator가 비어 있지 않은 경우이고, 하나는 소지한Comparator가 비어 있는 경우이다.Comparator가 비어 있지 않을 때Comparator를 통해 원소를 저장하는 위치를 정합니다. 그 중에서 중요한 것은 Comparator를 통해 기존 원소의 키와 현재 저장된 원소의 키를 비교한 결과가 0일 때 현재 저장된 원소 키가 원래의 맵에 이미 존재한다고 생각하고 기존의 키에 대응하는value를 새로운 value로 바꾸면 바로 낡은 value로 되돌아옵니다.보유한Comparator가 비어 있을 때Comparable 인터페이스를 실현한 키의compareTo 방법을 통해 원소의 저장 위치를 결정합니다. 한 가지는Comparator를 사용하는 것과 유사한 점은 기존의 키가Comparable로 새로 저장된 키와 비교한 결과가 0일 때 새로 저장된 키가 원래맵에 이미 존재한다고 생각하고 대응하는 원키의value를 직접 바꾸어 더 이상 키-value에 새로 저장하지 않는다는 것입니다.실제로 그 판단 요소가 존재하는containsKey 방법의 주요 실현 논리도 유사한데 구체적으로 다음과 같다.

 public boolean containsKey(Object key) {

    return getEntry(key) != null;

  }

 

  final Entry<K,V> getEntry(Object key) {

    // Offload comparator-based version for sake of performance

    if (comparator != null)

      return getEntryUsingComparator(key);

    if (key == null)

      thrownew NullPointerException();

    Comparable<? super K> k = (Comparable<? super K>) key;

    Entry<K,V> p = root;

    while (p != null) {

      int cmp = k.compareTo(p.key);

      if (cmp < 0)

        p = p.left;

      elseif (cmp > 0)

        p = p.right;

      else

        return p;

    }

    return null;

  }

TreeMap이 원소의 존재 여부를 판단하는 논리는 Comparator나 Comparable를 판단하여 비교한 결과가 0인지 아닌지를 판단하기 때문에 우리는 TreeMap을 사용하여 원소equals와 유사한 논리를 실현하고자 할 때 특히 조심해야 한다.
TreeMap의 containsValue의 논리는 역시 판단에 대응하는 value가 equals인지, HashMap과 유사한지, 관심 있는 친구는 TreeMap의 원본을 볼 수 있습니다.
1.4     TreeSet
TreeSet도 Set의 일종의 실현이다. 저장된 요소는 중복되지 않고 질서정연하다. 기본적으로 저장된 요소는 Comparable 인터페이스를 실현해야 한다. 왜냐하면 기본적으로 요소를 Comparable 대상으로 비교하기 때문이다.TreeSet도 Comparator를 통해 그 안에 저장된 요소를 비교할 수 있다. 이것은 TreeSet을 구성할 때 Comparator 대상이나 Comparator 대상을 가진 TreeMap을 전송하여 실현할 수 있다.TreeSet의 실현은 HashSet의 실현과 유사하고 그 내부에도 맵의 인용이 하나 있다. 단지 HashMap이 아니라 TreeMap을 인용할 뿐이다.TreeSet에서 요소의 추가, 삭제 등 조작은 모두 그가 가지고 있는 TreeMap에 의해 이루어진 것이기 때문에 HashSet과 유사하다. TreeSet에서 요소가 같은지 아닌지를 판단하는 방식은 TreeMap과 일치하고 Comparator나 Comparable를 통해 판정하는 것이지 전통적인 equals 방법이 아니다.관심 있는 친구는 트리셋의 원본을 확인해 볼 수 있다.
읽어주셔서 감사합니다. 여러분에게 도움이 되었으면 좋겠습니다. 본 사이트에 대한 지지에 감사드립니다!

좋은 웹페이지 즐겨찾기