임의의 데이터를 고정된 길이의 값(숫자나 문자열)으로 바꿔주는 함수나 방법
👉 긴 데이터를 "짧고 고유한 번호"로 변환하는 것
예를 들어:
"사과"
→ 123
"바나나"
→ 456
"포도"
→ 789
이렇게 바꿔주는 게 해시 함수이다.
1. 빠른 검색 (O(1))
"사과"
를 넣으면 항상 123
이 나와야 함"사과"
→ 123
, "배"
→ 123
같은 경우 발생할 수 있음Set 인터페이스를 구현한 컬렉션 클래스
✔️ 순서, 중복이 없다.
❗️ 순서를 유지하려면, LinkedHashSet 클래스를 사용하면 된다.
[1, 2, 3, 2]
→ 저장하면 [1, 2, 3]
set.add(5); set.add(1); set.add(3);
→ 결과: [1, 3, 5]
같은 식으로 순서가 뒤죽박죽.1. 중복을 허용하지 않고, 유일한 값만 저장하고 싶을 때
HashSet<Integer> lotto = new HashSet<>();
while (lotto.size() < 6) {
int num = (int)(Math.random() * 45) + 1;
lotto.add(num); // 중복 자동 제거
}
System.out.println(lotto);
2. 순서가 중요하지 않을 때
ArrayList
는 넣은 순서를 지켜야 할 때 사용하지만,HashSet
은 순서 상관없이 "있다/없다"만 중요할 때 사용한다.3. 빠른 검색이 필요할 때
ArrayList
는 특정 값 검색 시 O(n) (하나씩 다 확인)HashSet
은 해시 기반이라 O(1) (거의 즉시 찾음)HashSet<String> blacklist = new HashSet<>();
blacklist.add("192.168.0.10");
blacklist.add("10.0.0.5");
if (blacklist.contains("192.168.0.10")) {
System.out.println("차단된 IP!");
}
import java.util.HashSet;
public class Main {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
// 값 추가
set.add("Apple");
set.add("Banana");
set.add("Orange");
set.add("Apple"); // 중복 ❌
System.out.println(set); // [Apple, Banana, Orange] (순서 일정하지 않음)
// 특정 값 포함 여부
System.out.println(set.contains("Banana")); // true
System.out.println(set.contains("Grape")); // false
// 값 제거
set.remove("Orange");
System.out.println(set); // [Apple, Banana]
// 전체 반복
for (String fruit : set) {
System.out.println(fruit);
}
}
}
- initialCapacity: 배열의 용량
- loadFactor: 언제 용량을 늘릴것인지
❗️Set은 집합이기 때문에 이러한 기능이 가능하다.
- addAll: 합집합
- removeAll: 교집합
- retainAll: 차집합
예: set.add("바나나")
"바나나".hashCode()
실행 → 해시값 얻음 (예: 94328
)94328 % 16 = 8
"바나나"
라는 key를 저장 (value는 DUMMY)"바나나"
가 있으면? equals()
검사 후 중복이므로 추가 안 함 ✅
- contains: 요소가 있는지 확인
객체를 저장하기 전에 기존에 같은 객체가 있는지 확인해야 한다.
같은 객체가 없으면 저장하고, 있으면 저장하지 않는다.
boolean add(Object o)
는 저장할 객체의 equals()
와 hashcode()
를 호출한다.
equals()
와 hashcode()
가 같이 오버라이딩 되어 있어야 한다.
import java.util.*;
public class HashSetObject {
public static void main(String[] args) {
HashSet set = new HashSet();
// 같은 String
set.add("abc");
set.add("abc");
// 같은 사람
set.add(new Person("Jennie" , 25) );
set.add(new Person("Jennie", 25));
System.out.println(set);
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
setAge(age);
setName(name);
}
@Override
public String toString() {
return name + ": " + age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
[abc, Jennie: 25, Jennie: 25]
HashSet
은 중복을 제거하지만 객체안에서 어떤것이 같은지 아닌지를 모르기 때문에 위 예시처럼 그대로 나온다. @Override
public boolean equals(Object o) {
if(o == null || !(o instanceof Person)) {
return false;
}
Person other = (Person) o;
return name.equals(other.getName()) && age == other.getAge();
}
@Override
public int hashCode() {
// int hash(Object ... values) 가변인자
return Objects.hash(name, age);
}
HashSet
이 중복을 소거해준다.import java.util.*;
public class HashSetObject {
public static void main(String[] args) {
HashSet set = new HashSet();
set.add("abc");
set.add("abc");
set.add(new Person("Jennie" , 25) );
set.add(new Person("Jennie", 25));
System.out.println(set);
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
setAge(age);
setName(name);
}
@Override
public String toString() {
return name + ": " + age;
}
@Override
public boolean equals(Object o) {
if(o == null || !(o instanceof Person)) {
return false;
}
Person other = (Person) o;
return name.equals(other.getName()) && age == other.getAge();
}
@Override
public int hashCode() {
// int hash(Object ... values) 가변인자
return Objects.hash(name, age);
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
[abc, Jennie: 25]
특징 | HashSet | ArrayList |
---|---|---|
중복 | 허용 ❌ | 허용 ⭕ |
순서 | 보장 ❌ | 추가한 순서 보장 ⭕ |
검색 속도 | 빠름 (O(1)) | 느림 (O(n)) |
사용 목적 | "고유한 값만 저장" | "순서대로 여러 개 저장" |
Set 인터페이스 구현체 중 하나: "정렬된 집합"
👉 즉, 저장하면서 오름차순 정렬까지 알아서 해주는 Set.
1. 정렬된 상태 유지
Integer
, String
같은 Comparable 객체면 기본 정렬(숫자 오름차순, 알파벳 순)4. 이진 탐색 트리(binary search tree)로 구현
class TreeNode {
TreeNode left; // 왼쪽 자식노드
Object elelment; // 저장할 객체
TreeNode right; // 오른쪽 자식노드
}
- 부모보다 작은 값은 왼쪽, 큰 값은 오른쪽에 저장
- 데이터가 많아질 수록 추가, 삭제에 시간이 더 걸림(비교 횟수 증가)
✔️ boolean add(Object o)
HashSet은
equals()
,hashCode()
로 비교
TreeSet은compare()
를 호출해서 비교
Collections.sort()
안 해도 됨.TreeSet<Integer> scores = new TreeSet<>();
scores.add(95);
scores.add(70);
scores.add(85);
System.out.println(scores); // [70, 85, 95]
TreeSet<Integer> lotto = new TreeSet<>();
while (lotto.size() < 6) {
lotto.add((int)(Math.random() * 45) + 1);
}
System.out.println(lotto); // 자동으로 오름차순
TreeSet<Integer> set = new TreeSet<>();
set.add(10); set.add(20); set.add(30); set.add(40);
System.out.println(set.headSet(25)); // [10, 20] (25보다 작은 값들)
System.out.println(set.tailSet(20)); // [20, 30, 40] (20 이상 값들)
import java.util.HashSet;
public class Main {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
// 값 추가
set.add("Apple");
set.add("Banana");
set.add("Orange");
set.add("Apple"); // 중복 ❌
System.out.println(set); // [Apple, Banana, Orange] (순서 일정하지 않음)
// 특정 값 포함 여부
System.out.println(set.contains("Banana")); // true
System.out.println(set.contains("Grape")); // false
// 값 제거
set.remove("Orange");
System.out.println(set); // [Apple, Banana]
// 전체 반복
for (String fruit : set) {
System.out.println(fruit);
}
}
}
Object first()
: 오름차순으로 제일 작은 값을 나타낸다. (last()
: 큰 값)Object ceiling(Object o)
: 올림 (floor()
: 버림)
import java.util.*;
public class TreeSetEx1 {
public static void main(String[] args) {
// 범위 검색, 정렬.
Set set = new TreeSet();
for(int i = 0; set.size() < 6; i++) {
int num = (int)(Math.random() * 45) + 1;
set.add(num); // set.add(new Integer(num));
// Integer클래스 안에 정렬기준을 이용했다.
}
System.out.println(set);
}
}
[13, 14, 16, 21, 33, 36]
TreeSet은 정렬이 된 상태로 결과값이 나온다는 것을 알 수 있다.
import java.util.*;
public class TreeSetEx1 {
public static void main(String[] args) {
Set set = new TreeSet();
for(int i = 0; set.size() < 6; i++) {
int num = (int)(Math.random() * 45) + 1;
set.add(new Test());
}
System.out.println(set);
}
}
class Test {} // 비교 기준이 없음
Exception in thread "main" java.lang.ClassCastException: class Test cannot be cast to class java.lang.Comparable (Test is in unnamed module of loader 'app'; java.lang.Comparable is in module java.base of loader 'bootstrap')
at java.base/java.util.TreeMap.compare(TreeMap.java:1291)
at java.base/java.util.TreeMap.put(TreeMap.java:536)
at java.base/java.util.TreeSet.add(TreeSet.java:255)
at TreeSetEx1.main(TreeSetEx1.java:9)
형변환 예외가 나온다. 그 이유는 비교기준이 없기 때문이다.
import java.util.*;
public class TreeSetEx1 {
public static void main(String[] args) {
Set set = new TreeSet(new TestComp()); // TreeSet에다 새로운 비교기준을 넣어준다.
set.add(new TestComp());
set.add(new TestComp());
set.add(new TestComp());
set.add(new TestComp());
System.out.println(set);
}
}
class TestComp implements Comparator {
@Override
public int compare(Object o1, Object o2) {
return 1;
}
}
[TestComp@1cd072a9, TestComp@7c75222b, TestComp@4c203ea1, TestComp@27f674d]
import java.util.*;
public class TreeSetEx1 {
public static void main(String[] args) {
Set set = new TreeSet();
set.add(new TestComp());
set.add(new TestComp());
set.add(new TestComp());
set.add(new TestComp());
System.out.println(set);
}
}
class TestComp implements Comparable {
@Override
public int compareTo(Object o) {
return 1;
}
}
[TestComp@4c203ea1, TestComp@27f674d, TestComp@1d251891, TestComp@48140564]
import java.util.*;
public class TreeSetEx1 {
public static void main(String[] args) {
TreeSet set = new TreeSet();
int[] score = {80, 95, 50, 35, 45, 65, 10, 100};
for(int i = 0; i < score.length; i++) {
set.add(new Integer(score[i]));
}
System.out.println("50보다 작은 값: " + set.headSet(50));
System.out.println("50보다 같거나 큰 값: " + set.tailSet(50));
System.out.println("40과 80사이의 값: " + set.subSet(40, 80));
}
}
50보다 작은 값: [10, 35, 45]
50보다 같거나 큰 값: [50, 65, 80, 95, 100]
40과 80사이의 값: [45, 50, 65]
표를 그려보면 이렇다.