기초시리즈입니다.
오늘은 Hashtable에 대해서 잠시 이야기해 보도록 하죠.

Hashtable은 key를 이용해서 value를 꺼낼 수 있도록 해 주는 자료구조죠.

                   Hashtable table = new Hashtable();                

                   table.put("영화, "유쥬얼서스펙트);

                   table.put("오락, "황금어장라디오스타);

                   table.put("음악, "윤종신노래짱 ㅎㅎ);

뭐 이런식으로, 우선 table에 key와 value를 넣어 주고

                   String key = "영화";
                   String value = (String) table.get(key);

"영화"를 key로 가지고 있는 value, 즉 "유쥬얼서스펙트"를 꺼낼 수 있도록 되어 있습니다.
보다시피 아주 간단한 방법으로 사용할 수 있습니다.

그래서 간단하게 코드를 한번 짜 보았습니다.

                   Hashtable table = new Hashtable();

                   table.put("영화, "유쥬얼서스펙트);
                   table.put("오락, "황금어장라디오스타);
                   table.put("음악, "윤종신노래짱 ㅎㅎ);                 

                   String key = "오락;

                   String value = null;

                   

                   // "오락"이라고 되어 있는 key에 값이 있는지 확인하다.

                   boolean isContains = table.containsKey(key);


                   if ( isContains ) {
                             // 값이 있으니깐 가져다 쓰도록 하자.

                             value = (String) table.get(key);

                   } else {

                             // 값이 없으니깐 안 정해 놨다고 하자.

                             value = "뭘좋아하는지안정해놨네";

                   }


                   System.err.println(key + " : " + value);

하는 일은,
1. Hashtable을 하나 만들고,
2. 각종 값을 넣고,
3. 가지고 오고자 하는 key가 있다면, value를 가지고 오고
4. 없으면 다른 값을 설정해 준다.

아주 자연스러운 과정으로 보인다.

하지만 위의 코드는 수정되어야 할 부분을 가지고 있다.
위의, 코드를 짤때 Hashtable의 특성을 생각하지 않고, 쉽게 짰기 때문에 수정할 부분이 발생된다.

재미삼아 한번 맞춰 보세요~ 일부러 공란을 조금 두겠습니다.
( 블로그에 오시는 분들은 ;; 고수분들이실꺼라 ;; 걍 공란 없습니다. ㅋㅋ )

그 부분은 바로 아래의 코드이다.

// "오락"이라고 되어 있는 key에 값이 있는지 확인하다.

boolean isContains = table.containsKey(key);


if ( isContains ) { ....

 


해당 key에 설정된 값이 있는지 확인한 뒤에 값을 가지고 오는게 뭐가 틀렸냐고 하겠지만,
위의 코드로 인해서 약간의 손해를 보게 된다.

왜 그런지 Hashtable의 API를 확인해 보자.
get(Object) 메소드를 살펴 보도록 하자.

------

http://java.sun.com/javase/6/docs/api/java/util/Hashtable.html#get(java.lang.Object)

get

public V get(Object key)
Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key.

.....

Returns:
the value to which the specified key is mapped, or null if this map contains no mapping for the key

......

------

get의 return부분은 아래와 같이 설명되어 있다.

인자로 들어온 key에 맵핑되어 있는 value를 반환하거나,
map에서 인자로 들어온 key로 맵핑된것이 없으면 null을 반환한다.

즉, get했을때 인자로 key를 주어서 null이 나오면, map(table)안에 해당 정보가 없다는 말이다.

그렇다면 put할때, key에 해당하는 value로 null을 줄 수 있지 않을까?
key에 해당하는 value를 null을 줄 수 있다면, get(key) 했을때 null이 반환될 가능성이 있기 때문이다.

하지만 그런 걱정은 안해도 된다.
Hashtable에 대해 설명해둔 API문서를 다시 확인해 보자.

------
http://java.sun.com/javase/6/docs/api/java/util/Hashtable.html

public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, Serializable

This class implements a hashtable, which maps keys to values. Any non-null object can be used as a key or as a value.

.....

------

Hashtable은 key혹은 value로 non-null인 어떠한 Object도 사용가능하다고 한다.
즉, key와 value는 절대로 null이 될 수 없다.


자,
Hashtable에 대한 대충의 정리가 끝났으니, 생각없이 짜 놓았던 소스를 다시 보도록 하자.

                   Hashtable table = new Hashtable();

                   table.put("영화, "유쥬얼서스펙트);
                   table.put("오락, "황금어장라디오스타);
                   table.put("음악, "윤종신노래짱 ㅎㅎ);          

                   

                   String key = "오락;

                   String value = null;

                   

                   // "오락"이라고 되어 있는 key에 값이 있는지 확인하다.

                   boolean isContains = table.containsKey(key);


                   if ( isContains ) {
                             // 값이 있으니깐 가져다 쓰도록 하자.

                             value = (String) table.get(key);

                   } else {

                             // 값이 없으니깐 안 정해 놨다고 하자.

                             value = "뭘좋아하는지안정해놨네";

                   }


                   System.err.println(key + " : " + value);



위의 소스에서 손해 보고 있는 코드는 아래와 같다.



boolean isContains = table.containsKey(key);

if ( isContains ) { ....




위의 소스대로라면

1. containsKey로 실제 key가 존재하는지 확인하고,

2. key가 존재하면 get을 이용해서 값을 가지고 온다.



하지만, 다음과 같이 고쳐 쓸 수도 있다.


                   String key = "오락;

                   String value = table.get(key);


                   if ( value == null ) {

                             // 값이 없으니깐 안 정해 놨다고 하자.

                             value = "뭘좋아하는지안정해놨네";

                   }




get(key)메소드만을 사용해서 null체크를 이용해 똑같은 일을 수행할 수 있다.

굳이 containsKey를 이용해서 확인할 필요가 없다는 말이다.


그렇다면 재미삼에 containsKey메소드와 get 메소드의 내용을 잠시 살펴 보자.



------

JDK 1.6.0_05 ( c:\program files\java\jdk1.6.0_05\src.zip 파일안에 있음 )


 

   public synchronized boolean containsKey(Object key) {

             Entry tab[] = table;

             int hash = key.hashCode();

             int index = (hash & 0x7FFFFFFF) % tab.length;

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

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

                           return true;

                 }

             }

             return false;

   }

 

   public synchronized V get(Object key) {

             Entry tab[] = table;

             int hash = key.hashCode();

             int index = (hash & 0x7FFFFFFF) % tab.length;

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

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

                           return e.value;

                 }

             }

             return null;

   }

------



보다시피, containsKey와 get의 메소드는 거의 동일하며,


마지막에 true, false를 반환하느냐 ( containsKey )

혹은 Object, null을 반환하느냐만 ( get )

다를 뿐이다.


그러므로 constainsKey대신에 get을 사용해도 똑같은 일을 할 수 있다.

( 사실은 get을쓰게 되면 Object의 레퍼런스가 하나 더 생성되겠지만.. 자세한 이야기는 집어 치우자. )




get을 하려고 containsKey를 사용해서 값이 있는지 확인하는것은

전혀 필요 없는짓이며 이로 인해서 많은 손해를 보게 된다.



사실 containsKey를 get하기 전에 사용한다고 해서,그렇게 많이 손해를 보겠느냐~ 라고 생각할 수도 있다.

기껏해야 0.00001초 정도 아닐까. 라고 생각할 수 있다.

그렇다고 치면 10000번씩 호출해도 0.001초 차이 날까 말까 하지 않을까?


그래 그 말도 맞을 수도 있다.


하지만, 위의 코드중에서 containsKey 메소드를 다시 한번 보도록 하자.


   public synchronized boolean containsKey(Object key) {

             Entry tab[] = table;

             int hash = key.hashCode();

             int index = (hash & 0x7FFFFFFF) % tab.length;

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

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

                           return true;

                 }

             }

             return false;

   }


위의 코드에서 눈여겨 볼만한 부분은 synchronized 키워드이다.


Hashtable은 put, remove, get등 모든 데이터 입출력에 대해서 "동기화" 된다.

( 왜 그러냐고 물으면 안된다. Java에서 그렇게 정의해 두었다. 정의라는것에 이유는 없다. )

( 동기화에 관련된 자세한 이야기는 다른 강좌등을 참고하도록 하자. )

------

As of the Java 2 platform v1.2, this class was retrofitted to implement the Map interface, making it a member of the Java Collections Framework. Unlike the new collection implementations, Hashtable is synchronized.

------


Hashtable 객체의 put, remove, get, containsKey 등의 메소드가 호출 될때마다

1. 다른 Thread에서 자신의 객체에 접근할 수 없게 무조건 Lock를 잡고

2. 해당 메소드를 수행하고 결과를 반환한 뒤에

3. 잡았던 Lock를 놓게 된다.


이때 위의 동작만 보더라도 결과만 반환하는것이 아니라, Lock를 잡고, 푸는 과정만해도 2번의 동작이 추가 된다.

그리고 일반적으로 Lock를 잡고, 푸는것은 많은 시간이 소모되는 작업으로 알려져 있다.




그러므로

Hashtable의 특성인

1. cotainsKey대신 get을 이용할 수도 있음.

2. 동기화

에 대해서 잘 알고, Hashtable을 사용하도록 하자.



--------

문제가 될만한 사항이 있으면 언제든지 알려 주세요.

신고
크리에이티브 커먼즈 라이선스
Creative Commons License
  1. 무적조로™ 2008.11.20 09:35 신고

    난 containsKey() 메서드를 안쓸 뿐이고....
    넣었으면 빼야된다는 생각만 할 뿐이고....
    내가 안넣었으니 null 올꺼라고 생각할 뿐이고..

    • Chan 2008.11.20 10:00 신고

      난....

      하고, 시간을 두는 센스~ 를 보여야징~ ㅎㅎ

  2. 버리 2008.11.20 10:34 신고

    아주 사소한 문제긴 한데...ㅋㅋ
    string을 감싸는 "가 왼쪽에만 있는건 새로 추가된..건 아니죠?ㅋㅋㅋ

    • Chan 2008.11.20 13:16 신고

      너무 예리하세요~ ㅎㅎ
      복사해서 붙여 넣다 보니~ ㅎㅎ

  3. 쇼니 2008.11.24 11:45 신고

    있는지만 확인할땐 containsKey() 값을 결국 써야할땐 get()...
    값을 사용하는 경우가 거의 다라고 보면 되겠지만... ^^
    단일쓰레드환경에서는 HashMap을 사용하면 되는거궁..
    HashMap은 value로 null을 넣을 수도 있고..
    ㅋ 뭐든지 상황에 맞게!! ㅎ

    • Chan 2008.11.24 13:45 신고

      빙고~ 옳소~ ㅎㅎ

      HashTable을 HashMap으로 바꾸려고 하면,
      1. 단일 Thread에서 동작하는가?
      2. null을 넣을 가능성이 있냐?
      를 살펴 보아야 겠고~ ㅎㅎ

      추가하자면 Multi Thread환경에서도
      1. static하게 put하고 이후에 get만 하는 경우에는
      HashTable을 HashMap으로 바꾸어도 상관없다는거..

      뭐든지 상황에 맞게~ ㅋㅋ
      ( 점점 ;; java 공부 블로그가 되어 가고 있나~ ㅎ. )

      모르는게 너무 많아.. ㅠ_ㅠ

  4. Richpapa 2009.08.27 12:51 신고

    왜 더 낫다는 거죠? 둘다 sync 이고, 내부 소스가 거의 동일하고... 성능에 지장이 없을 것 같은데... 또한 위의 예가 단편적일 수도 있지만 키든 vaule든 객체가 null인지 체크해야만 하는 null object 패턴까지 써야 되는 것처럼 보입니다. 그렇다면 아싸리 불린값이 더 깔끔해보이는데... 한 수 알려주세요.

    • Chan 2009.08.27 13:45 신고

      위 글에서 세번째에 있는 코드들을 확인해 보면
      위의 예제가 하는 일은,
      1. 해당 값이 있는지 확인하고,
      2. 값이 있으면 가지고 오는 코드입니다.

      즉,
      1. contains()로 확인을 한 뒤에
      2. get()을 수행한다.
      는 것입니다.

      하지만,
      contains와 get()은 같은일을 하고,
      결과의 형태만 다르게 반환하므로,

      contains()와 get()을 순차적으로 수행할때에는
      그냥 get()을 이용하는것이 더 효율적이다.
      라는 말을 하고 싶었습니다.

      당연히, 있는지 없는지 체크할때에는
      contains만 사용하는것이 의미상 더 바람직하겠지요.


      이하의 이야기는 Richpapa님에게 드리는 말은 아닙니다.
      오래된 글인데 여러분들이 글을 남겨주셔서 ^^; 혹시나 오해하지 않도록 설명을 달아 둡니다.

      참고로,
      위에 예제로 제시한 코드는 Multi Thread에서 안전한 코드가 아닙니다. Hashtable를 썼으니 안전한거 아니냐? 고 생각되시는 분들은, Thread 공부를 조금 더 하셔야 할 듯 합니다. ^^

  5. 초보 2013.10.11 20:41 신고

    궁금한게 있어서 질문을 드립니다.

    "위에 예제로 제시한 코드는 Multi Thread에서 안전한 코드가 아닙니다. Hashtable를 썼으니 안전한거 아니냐? 고 생각되시는 분들은 ..."라고 하셨는데 설명을 부탁드립니다.

    그리고 "추가하자면 Multi Thread환경에서도 1. static하게 put하고 이후에 get만 하는 경우에는 HashTable을 HashMap으로 바꾸어도 상관없다는거.." 이 부분에 대해서도 설명을 부탁드립니다.

    조금 고민을 해봤는데 잘 모르겠네요. ㅎㅎ

    • 2013.10.23 20:47 신고

      답변이 많이 늦었습니다.

      "Hashtable을 사용했다고 MultiThread에 안전하다고 생각해서는 안된다" 에 대한 답변 드립니다.

      Hashtable은 put, remove의 각각의 메소드에 대해서는 MultiThread에 안전하게 동작합니다. 하지만 이러한것이 섞여 있을때는 불안전해 질 수 있습니다.

      예를 들어...
      Hashtable table = new Hashtable();

      1: boolean contains = table.contains("AA")
      2:
      3: if ( !contains ) table.put("AA","aa");

      위와 같은 코드가 있다고 한다면

      Thead-1에서는 1번 Line 문장을 수행하여 contains값이 true라고 받았다고 가정합니다.

      이때 Thread-1이 3번 라인을 수정하기 전에
      Thread-2가 table.remove("AA") 를 수행했다고 합시다.

      실제 table에는 "AA"가 없는 상태인데도,
      Thread-1에서는 이미 있다고 판단하였기 때문에
      table에 "AA"를 추가해 주지 않습니다.

      이렇듯 단순 Hashtable만을 사용했다고 해서 Thread에 안전한 코드를 작성했다고 볼 수는 없다는 말입니다.

    • 2013.10.23 20:52 신고

      "추가하자면 Multi Thread환경에서도 1. static하게 put하고 이후에 get만 하는 경우에는 HashTable을 HashMap으로 바꾸어도 상관없다는거.." 에 대한 설명 드립니다.

      위에 설명한 Hashtable의 예에서도 보았다시피, Hashtable을 썼다고 해서 안전한 코드를 작성했다고 보기 어렵습니다.

      반대로 HashMap을 사용한다고해도 잘만 작성한다면, MultiThread에 안전한 코드를 작성할 수 있습니다.

      public synchronized void put(String key, String value ) {
      hashmap.put(key, value);
      }

      public synchronized void remove(String key) {
      hashmap.remove(key);
      }

      위와 같이 작성한다면 hashmap은, 동기화 되는 method내부에서만 사용됩니다. 즉 hashmap에 접근할 수 있는것은 한번에 1개의 thread만 접근할 수 있게 된 것입니다.

      그러므로 hashmap을 사용해도 코드를 작성하는 방법에 따라서 MultiThread에 안전하게 작성할 수 있게 되는것입니다.

  6. MJS 2016.08.17 13:50 신고

    와우.. 좋은정보 감사드립니다.

+ Recent posts