2012년 11월 12일 월요일

자바의 해쉬테이블(Hashtable)


기본적으로 JDK 1.0 버젼에서부터 쓰여왔고 자바 2에서부터 컬렉션의 멤버로서 등재되기 시작한 해쉬테이블(Hashtable)이 다른 컬렉션들과 다른점은 동기화되어 있다는 점이다. 그럼 이제 맵(Map) 인터페이스를 구현한 해시테이블에 대해서 알아보도록 하겠다. 자바 컬렉션 프레임워크를 공부한 여러분은 이미 알고 있겠지만 Map은 컬렉션 즉, 컬렉션(Collection) 인터페이스를 계승하고 있지 않으므로 컬렉션에 관계된 모든 기능을 구현하고 있지는 않다. 또한 그동안 손쉽게 써오던 반복자(iterator)도 여기엔 없다. 대신에 Enumeration을 손쉽게 쓸수 있도록 구비되어 있다. 물론 반복자도 우회해서 쓸수있는 방법도 있다. 둘다 어떻게 쓸수 있는지 예제에서 다룰것이므로 깊은 체념이나 상심(?)은 자제하기 바란다.ㅎㅎ 그동안 배워온 리스트(List)나 셋(Set)이 요소들의 집합이나 순서 개념이 강하다면 오늘 배울 맵(Map)은 검색의 개념이 본격적으로 가미된 인터페이스라고 말할수 있겠다. 전화번호부처럼 이름을 찾아 전화번호를 찾는 과정을 떠올려보면 될것이다. 맵을 구현한 클래스를 다루는 첫번째 시간인만큼 해쉬테이블에 대해서 비교적 자세히 살펴보기로 하겠다. 오늘 강의를 잘 따라오면 앞으로 공부할 다른 종류의 맵 클래스들에 대해서도 공부하기가 한결 수월해질 것이다.^^


그림은 해쉬테이블에서 키를 활용해 값이 어떻게 저장되는지를 보여주고 있는데 들어가기전에 equals( ) 메소드와 hashCode( ) 메소드의 기본원리는 일단 알고 있어야한다. 두 객체가 equals( )의 결과가 같다면 해쉬코드는 반드시 같아야하며 두 객체가 equals( )의 결과가 다르다면 해쉬코드는 같을수도 있고 다를수도 있다. 압축설명하자면 해쉬테이블의 데이터구조에 대한 알고리즘은 key를 이용해서 내부적으로 equals( ) 메소드와 hashCode( ) 메소드가 연산하여 어느 주소를 가지는 메모리에 저장할지를 결정한다. 만일 해쉬코드가 같은 값을 가지게 된다면 같은 장소에 저장이 될것이다. 그럴경우 그림처럼 키값과 밸류값들을 연결리스트를 통해서 저장한다. 키가 달라도 해쉬코드는 같은 값을 가질수 있는데 해쉬테이블은 생성시 기본값으로 11개의 배열공간이 할당되며 이 배열공간의 길이로 방금 얘기한 해쉬코드 연산(키에 대한 해쉬코드값 % 11)을 통해 해당값들을 정해진 공간에 저장한다. 그림의 buckets는 값들을 저장할수 있는 메모리에 할당된 공간을 말하는데 배열구조랑 비교하자면 키(key)가 배열의 인덱스(index)라고 생각하면 이해가 쉬울 것이다. 그리고 그 키(key)와 관련 쌍을 이루어 저장되는 것이 값(value)이다.ㅎㅎ

해쉬테이블의 원리는 여러분의 건강(?)을 감안하여 이 정도로 하고 이제 예제를 보기로 하겠다.^^ 아직까지 뭐가뭔지 잘 모르겠다하는 이들은 끝까지 따라오기 바란다. 예제를 보면 모든 걱정이 기우(杞憂)에 지나지 않았다는 것을 깨닫게 될것이다. 아래의 예제가 좀 길어보일지 모르나 같은 내용을 다른 방법을 써가면서 같은 결과를 도출해내는 것이므로 실제는 예제의 반 정도만 여러분이 집중하면 될것이다. 혹시나 길다고 여기서 중도포기하는 이들(?)을 막기위해 한마디 남기면서 예제풀이로 들어가겠다. 그럼 아래의 해쉬테이블 예제를 보자.^^



자 시작해보겠다.ㅎㅎ 조금 설명이 길어질것 같은 불길한 예감(?)이 스쳐가고 있지만 최대한 간략하면서도 머리에 쏙쏙 들어오게 설명하겠으니 피곤하더라도 잠깐만 집중하기 바란다.^^ 먼저 프로그램에서 필요한 인터페이스나 클래스를 불러들이고 메인메소드에서 해쉬테이블의 인자 ht를 생성하고 키(key)와 값(value)을 put( ) 메소드를 이용해 집어넣었다. put 메소드안의 첫번째가 키이고 두번째가 값이다. 메소드의 형식을 빌어 다시 표현하자면 아래와 같다.

V put(K key, V value)

이 메소드 형식을 보면 참 재미있는 사실을 한가지 알수 있는데 앞에 V가 눈에 들어오지 않는가? (승리를 뜻하는게 아님.-) 당연히 value를 뜻하는 것이겠징? ㅎㅎ 키와 값을 넣어주면서 지금 넣어주는 키에 관련된 그전의 값이 있다면 리턴해 준다는 부분이다. 물론 없으면 null을 돌려준다. 조금 있다가 다시 나올텐데 미리 예고편(?) 때리고 있는거다.ㅎㅎ

다시 돌아가서 키와 값을 넣어주고 keys( ) 메소드와 elements( ) 메소드를 이용해 Enumeration과 함께 해쉬테이블을 돌려주니 키와 값이 자알~ 출력된다.^^ 순서는 해쉬테이블 마음가는데로 찍힌다.ㅎㅎ 열거에 대해선 안해본것도 아니니 별 어려움은 없을 것이라 보고 다음으로 가보겠다.

각종 메소드들을 통해 키와 값들을 함께도 찍고 따로도 찍어보고 하는 예제이다. 보기 좋으라고 제목 자체를 메소드 이름으로 달아놨으니 거침이 없을 것이다.ㅎㅎ 자 그럼 이제 아까 예고했던 본방송편(?)을 시작하겠다.

String s = (String)ht.put(3,"OK");

코드에 필자가 이렇게 만들어 놓았다. 키값으로 3을 주고 그 해당하는 키값으로 가서 OK를 value값으로 넣으라는거다. 그리고 그곳에서 쓰던 value값이 있었다면 넘겨주고 찍어보라는 예제인데 해보니까 C가 찍힌다. 키 3에 해당하는 값의 value값으로 C가 있었으니까 말이다. String은 Object 값을 넘겨받으므로 캐스팅 처리해준것 뿐이다. ht를 찍어보니 키 3의 위치에 방금 넣었던 OK가 제대로 출력되는 것을 알수 있고 또한 예전에 있던 키 대신에 같은 키를 넣으면 나중값을 받아들인다는 것을 여기서 캐치할수 있어야한다. value값은 상관없지만 key값은 중복을 허용하지 않으니까 말이다. 너무 당연한가? 혹시나 해서~ㅎㅎ

다음은 contains( ) 메소드를 이용해서 value값인 OK가 해쉬테이블에 들어있냐고 체크하라고 하니 있으니까 있다고 true 돌려준거고 containsKey( ) 메소드는 키값으로 0이 있냐고 물어보니까 없어서 false라고 돌려준 것인데 어려울건 없다고 본다.^^

이번엔 putAll( ) 이라는 메소드가 나온다. 한마디로 괄호안에 있는 ht 해쉬테이블을 ht2로 카피하라는거다. 출력해보니까 잘 옮겨졌다. get( ) 메소드를 이용해서 키값으로 3에 해당하는 value값을 물어보니 OK라고 대답하고 너무 혹사시키는 것 같아 미안한 마음(?)에 편히 보낼려고 remove( ) 메소드를 이용해 키값인 3을 넣고 찍어보았더니 이제 해쉬테이블의 쌍(pair)이 예상대로 하나가 줄었다.ㅎㅎ 욕하는거 아니다.-

오케이 드디어 마지막 구문이다. 조금만 참아라~ Iterator를 이용해서 기존의 ht 해쉬테이블을 찍어보도록 만든 것인데 하나는 keySet( ) 메소드를 이용해서 키값과 밸류값을 출력한 것이고 다른 하나는 entrySet( )을 이용해서 키값과 밸류값을 한번에 처리한 것이다. 이 부분은 Iterator도 사용할수 있다는 것을 보여줄겸 한번 적어보았으나 어떤 것을 사용할지는 상황을 고려해야한다.

덧붙여 잠시 얘기해 보자면 컬렉션 클래스에서 저장된 요소들을 차례대로 접근하기 위한 방법을 컬렉션 뷰 메소드(collection view methods)라고 부른다. 그리고 fail-fast라는 것이 있는데 Enumeration(not fail-fast 방식)이나 Iterator(fail-fast 방식)를 사용하여 각 요소들에 대한 순차적 접근이 끝나기도 전에 해당 컬렉션에 대한 객체들에 대해서 요소가 추가되거나 삭제되거나 하는 어떤 변경이 가해질 경우 요소들에 대한 접근에 실패하게 되는데 이때 Enumeration과 Iterator가 대처하는 방식이 다르다. 멀티쓰레드 환경에서 이런 상황이 일어날수 있는데 이런 경우에 Enumeration은 그와 상관없이 요소들에 대한 순차적 접근을 끝까지 수행하는 not fail-fast 방식인 반면에 Iterator는 그럴 경우 예외(ConcurrentModificationException)를 발생시키며 안전할지 모르는 사태를 대비해 작동을 중지하는 fail-fast 방식을 취한다.

해쉬테이블에 대해서 공부해 봤는데 그리 쉽지는 않았을 것이다. key와 value를 세트로 거기다가 해슁까지 소화(?)하기가 부담스러울걸 고려해 가급적이면 무난하게 읽을수 있도록 풍부한 해설과 다양한 예제를 담으려고 필자가 각고한 노력(?)을 기울인만큼 여러분도 그에 필적하는 노력(?)으로 응답해 주기를 기대하면서 오늘 강의는 여기서 마치겠다. 수고많았다.^^

댓글 1개: