[Java 활용] 3.10 HashSet 클래스

앞에서 Collection 인터페이스를 기반으로 구현 클래스에는 List와 Set이 있다고 했습니다. List 클래스는 선형 자료구조를 구현한 클래스이며 Set은 비선형 자료를 구현한 클래스입니다.

그리고 Set 클래스를 기반으로 파생한 StoredSet, HashSet 클래스가 있습니다. StoredSet은 이진 탐색 트리를 구현한 클래스이며 HashSet은 해쉬 테이블을 구현한 클래스입니다. 두 가지 모두 빠른 검색이 필요할 때 사용하는 클래스이며 같은 자료를 중복 보관할 수 없는 특징을 갖고 있습니다.

자료구조를 학습하면 선형 자료구조에서의 탐색 비용은 O(N)이고 이진 탐색 트리의 탐색 비용은 (logN), 해쉬 테이블의 탐색 비용은 O(1)이라는 것을 배웁니다. 이에 관한 사항은 자료구조에 관한 레퍼런스를 참조하시기 바랍니다.

여기에서는 Set을 기반으로 파생한 HashSet 클래스 사용법을 알아보기로 할게요. StoredSet 클래스도 Set을 기반으로 파생한 클래스여서 사용 방법은 HashSet 클래스와 비슷합니다. 여러분께서는 별도로 사용해 보세요.

HashSet 클래스에는 자료를 보관하는 add 메서드, 삭제하는 remove 메서드, 모든 요소를 삭제하는 clear 메서드, 자신을 복제한 개체를 반환하는 clone 메서드, 특정 요소를 보관하였는지 판별하는 contains 메서드, 비었는지 판별하는 isEmpty 메서드, 반복자를 반환하는 iterator 메서드, 보관한 요소 개수를 반환하는 size 메서드가 있습니다. 물론 여기서 소개하는 메서드는 자주 사용하는 메서드 중심으로 얘기하고 있는 것입니다.

public boolean add(Object obj); //보관(같은 자료는 보관하지 않음)
public void clear();//모든 요소 삭제
public Object clone(); //자신을 복제한 개체 반환
public boolean contains(Object obj);//보관한 자료인지 판별
public boolean isEmpty();//비어있는지 판별
public Iterator<Element> iterator();//반복자 반환
public boolean remove(Object obj);//보관한 자료를 제거
public int size();//보관한 자료 개수 반환

HashSet 클래스 역시 Collection 인터페이스를 구현한 Set을 기반으로 파생한 형식이어서 앞에서 다루었던 List 클래스를 기반으로 파생한  Vector, ArrayList, LinkedList를 사용하는 방법과 매우 비슷합니다.

차이가 있는 부분은 같은 자료를 보관할 수 없다는 점과 내부적으로 빠른 검색을 할 수 있다는 점을 들 수 있지만 사용하는 개발자 입장에서는 같은 자료를 보관할 수 없다는 점을 빼고는 큰 차이가 없습니다. 하지만 검색 속도는 List에서 파생한 클래스들과 비교할 수 없을 정도의 성능을 지녔기 때문에 충분히 많은 자료를 관리해야 한다면 HashSet 클래스를 사용할 수 있는지 확인해 보시기 바랍니다.

다음은 HashSet 클래스를 이용하여 회원 명단을 관리하는 프로그램 예제입니다.

▷ 소스 3.12 회원 명단 관리 프로그램 (HashSet 클래스 이용)

//HashSet 사용하는 예제 클래스
import java.util.HashSet;
import java.util.Scanner;

public class Tracer {
	Scanner scan = new Scanner(System.in);
	HashSet<String> hs = new HashSet<String>();
	
	public void Run(){
		int key = 0;
		while((key = selectMenu())!=0){
			switch(key){
			case 1: addMember(); break;
			case 2: removeMember(); break;
			case 3: searchMember(); break;
			case 4: listMember(); break;
			default: System.out.println("잘못 선택하였습니다."); break;
			}
		}
		System.out.println("종료합니다...");
	}
	int selectMenu(){
		System.out.println("1:추가 2:삭제 3:검색 4:목록 0:종료");
		int key = scan.nextInt();
		scan.nextLine();	
		return key;
	}
	void addMember(){		
		String name="";		
		System.out.print("추가할 회원 이름:");
		name = scan.nextLine();
		 
		if(hs.add(name)){
			System.out.println("추가하였습니다.");
		}
		else{
			System.out.println("이미 존재합니다.");
		}
	}
	void removeMember(){
		String name="";		
		System.out.print("삭제할 회원 이름:");
		name = scan.nextLine();
		 
		if(hs.remove(name)){
			System.out.println("삭제하였습니다.");
		}
		else{
			System.out.println("존재하지 않습니다.");
		}
	}
	void searchMember(){
		String name="";		
		System.out.print("검색할 회원 이름:");
		name = scan.nextLine();
		 
		if(hs.contains(name)){
			System.out.println("존재합니다.");
		}
		else{
			System.out.println("존재하지 않습니다.");
		}	
	}
	void listMember(){
		System.out.println("전체 목록");	
		int cnt = hs.size();
		System.out.println("회원 수:"+cnt);
		for(String name: hs){
			System.out.println(name);
		}
	}		
}
//HashSet 사용 예
public class Program {
	public static void main(String[] args){
		Tracer tracer = new Tracer();
		tracer.Run();
	}
}

▷ 소스 3.12 실행 결과

1:추가 2:삭제 3:검색 4:목록 0:종료
1
추가할 회원 이름:홍길동
추가하였습니다.
1:추가 2:삭제 3:검색 4:목록 0:종료
1
추가할 회원 이름:강감찬
추가하였습니다.
1:추가 2:삭제 3:검색 4:목록 0:종료
1
추가할 회원 이름:이순신
추가하였습니다.
1:추가 2:삭제 3:검색 4:목록 0:종료
4
전체 목록
회원 수:3
홍길동
강감찬
이순신
1:추가 2:삭제 3:검색 4:목록 0:종료
3
검색할 회원 이름:강감찬
존재합니다.
1:추가 2:삭제 3:검색 4:목록 0:종료
2
삭제할 회원 이름:강감찬
삭제하였습니다.
1:추가 2:삭제 3:검색 4:목록 0:종료
4
전체 목록
회원 수:2
홍길동
이순신
1:추가 2:삭제 3:검색 4:목록 0:종료
0
종료합니다...

참고로 HashSet으로 특정 자료가 있는지 판별하는 Contains 메서드의 수행 속도가 빠른 것이지 반복자를 이용한다면 선형 자료구조와 성능에 차이가 없습니다. 이에 관한 해결은 Map 인터페이스를 기반으로 구현한 클래스를 사용해야 합니다.