๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Tech Stack/Java & Spring

[์ปฌ๋ ‰์…˜ ํ”„๋ ˆ์ž„์›Œํฌ] HashSet

by _silver 2025. 4. 9.
1. Set์ด๋ž€?

โ˜‘๏ธŽ ์ค‘๋ณต์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š๊ณ , ์ˆœ์„œ๋ฅผ ๋ณด์žฅํ•˜์ง€ ์•Š๋Š” ์ž๋ฃŒ๊ตฌ์กฐ

โ˜‘๏ธŽ ๋Œ€ํ‘œ์ ์ธ ๊ตฌํ˜„์ฒด: HashSet, LinkedHashSet, TreeSet


2. Set์˜ ๊ธฐ๋ณธ ๋™์ž‘

โ˜‘๏ธŽ add(value) : ์ค‘๋ณต์ด๋ฉด ์ €์žฅํ•˜์ง€ ์•Š์Œ

โ˜‘๏ธŽ contains(value) : ํฌํ•จ ์—ฌ๋ถ€ ํ™•์ธ

โ˜‘๏ธŽ remove(value) : ๋ฐ์ดํ„ฐ ์‚ญ์ œ

โ˜น๏ธŽ ํ•˜์ง€๋งŒ ์ผ๋ฐ˜์ ์ธ Set ๊ตฌํ˜„์ฒด๋Š” ๋‚ด๋ถ€ ๋ฐ์ดํ„ฐ๋ฅผ ์ผ์ผ์ด ๋น„๊ต → ์‹œ๊ฐ„ ๋ณต์žก๋„ O(n)


3. HashSet์ด๋ž€?

โ˜‘๏ธŽ ๋‚ด๋ถ€์ ์œผ๋กœ ํ•ด์‹œ ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•œ ๋ฐฐ์—ด ๊ธฐ๋ฐ˜์˜ ๊ตฌ์กฐ

โ˜‘๏ธŽ hashCode()์™€ equals()๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ค‘๋ณต ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จ

โ˜‘๏ธŽ ํƒ์ƒ‰ ์†๋„ ๋น ๋ฆ„ (ํ‰๊ท  ์‹œ๊ฐ„ ๋ณต์žก๋„ O(1))


4. ์ง์ ‘ ๊ตฌํ˜„ํ•˜๋Š” Set - MyHashSetV1 (์ •์ˆ˜ํ˜• ์ „์šฉ)
๋”๋ณด๊ธฐ
public class MyHashSetV1 {
    static final int DEFAULT_INITIAL_CAPACITY = 16;
    LinkedList<Integer>[] buckets; // ๋ฒ„ํ‚ท(ํ•ด์‹œ ์ธ๋ฑ์Šค๋ณ„ ์ €์žฅ ๊ณต๊ฐ„)
    private int size = 0;
    private int capacity = DEFAULT_INITIAL_CAPACITY;

    public MyHashSetV1() {
        initBuckets();
    }

    public MyHashSetV1(int capacity) {
        this.capacity = capacity;
        initBuckets();
    }

    // ๋ฒ„ํ‚ท ๋ฐฐ์—ด ์ดˆ๊ธฐํ™”
    private void initBuckets() {
        buckets = new LinkedList[capacity];
        for (int i = 0; i < capacity; i++) {
            buckets[i] = new LinkedList<>();
        }
    }

    // ํ•ด์‹œ ์ธ๋ฑ์Šค ๊ณ„์‚ฐ
    private int hashIndex(int value) {
        return value % capacity;
    }

    public boolean add(int value) {
        int hashIndex = hashIndex(value);
        LinkedList<Integer> bucket = buckets[hashIndex];

        if (bucket.contains(value)) return false;

        bucket.add(value);
        size++;
        return true;
    }

    public boolean contains(int searchValue) {
        int hashIndex = hashIndex(searchValue);
        return buckets[hashIndex].contains(searchValue);
    }

    public boolean remove(int value) {
        int hashIndex = hashIndex(value);
        boolean removed = buckets[hashIndex].remove(Integer.valueOf(value));
        if (removed) size--;
        return removed;
    }

    public int getSize() {
        return size;
    }

    @Override
    public String toString() {
        return "MyHashSetV1{" +
                "buckets=" + Arrays.toString(buckets) +
                ", size=" + size +
                '}';
    }
}

5. ๋ฌธ์ž์—ด ํ•ด์‹œ ํ•จ์ˆ˜ ๊ตฌํ˜„

โ˜น๏ธŽ ๋ฌธ์ œ์ : "BC"(66+67=133), "AD"(65+68=133) → ํ•ด์‹œ ์ถฉ๋Œ ๋ฐœ์ƒ!

๋”๋ณด๊ธฐ
static int hashCode(String str) {
    char[] chars = str.toCharArray();
    int sum = 0;
    for (char c : chars) {
        sum += c; // ๊ฐ ๋ฌธ์ž์˜ ์œ ๋‹ˆ์ฝ”๋“œ ํ•ฉ์‚ฐ
    }
    return sum;
}

static int hashIndex(int hashCode) {
    return hashCode % 10; // capacity = 10 ๊ธฐ์ค€
}

6. ์ž๋ฐ”์˜ String ํ•ด์‹œ์ฝ”๋“œ ๊ณ„์‚ฐ ๋ฐฉ์‹
  • 31์„ ๊ณฑํ•˜๋Š” ์ด์œ : ์ถฉ๋Œ ๋ฐฉ์ง€ + ๊ณ„์‚ฐ ์ตœ์ ํ™”(31 = 32-1)
  • equals()๊ฐ€ true์ธ ๊ฒฝ์šฐ์—๋Š” ๋ฐ˜๋“œ์‹œ hashCode()๋„ ๊ฐ™์•„์•ผ ํ•จ
String str = "AB";
int hash = 0;
for (char c : str.toCharArray()) {
    hash = 31 * hash + c;
}

7. Object์˜ hashCode()๋Š” ์ฐธ์กฐ๊ฐ’ ๊ธฐ๋ฐ˜

โ˜‘๏ธŽ Object์˜ ๊ธฐ๋ณธ hashCode()๋Š” ์ฃผ์†Œ ๊ธฐ๋ฐ˜์ด๋ฏ€๋กœ, ์„œ๋กœ ๋‹ค๋ฅธ ๊ฐ์ฒด๋Š” ๋‹ค๋ฅธ ๊ฐ’

Object obj1 = new Object();
Object obj2 = new Object();
System.out.println(obj1.hashCode()); // ex) 123456
System.out.println(obj2.hashCode()); // ์„œ๋กœ ๋‹ค๋ฆ„

8. Member ํด๋ž˜์Šค ์˜ˆ์ œ (hashCode + equals ์˜ค๋ฒ„๋ผ์ด๋”ฉ)
๋”๋ณด๊ธฐ
public class Member {
    private String id;

    public Member(String id) {
        this.id = id;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Member)) return false;
        Member other = (Member) o;
        return Objects.equals(this.id, other.id); // id ๊ธฐ์ค€ ๋น„๊ต
    }

    @Override
    public int hashCode() {
        return Objects.hash(id); // id์˜ ํ•ด์‹œ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉ
    }

    @Override
    public String toString() {
        return "Member{id='" + id + "'}";
    }
}

9. ์ง์ ‘ ๊ตฌํ˜„ํ•˜๋Š” Set - MyHashSetV2 (Object ํƒ€์ž…์œผ๋กœ ํ™•์žฅ)
๋”๋ณด๊ธฐ
public class MyHashSetV2 {
    static final int DEFAULT_INITIAL_CAPACITY = 16;
    private LinkedList<Object>[] buckets;
    private int size = 0;
    private int capacity = DEFAULT_INITIAL_CAPACITY;

    public MyHashSetV2(int capacity) {
        this.capacity = capacity;
        initBuckets();
    }

    private void initBuckets() {
        buckets = new LinkedList[capacity];
        for (int i = 0; i < capacity; i++) {
            buckets[i] = new LinkedList<>();
        }
    }

    private int hashIndex(Object value) {
        return Math.abs(value.hashCode()) % capacity;
    }

    public boolean add(Object value) {
        int hashIndex = hashIndex(value);
        LinkedList<Object> bucket = buckets[hashIndex];
        if (bucket.contains(value)) return false;
        bucket.add(value);
        size++;
        return true;
    }

    public boolean contains(Object value) {
        return buckets[hashIndex(value)].contains(value);
    }

    public boolean remove(Object value) {
        boolean removed = buckets[hashIndex(value)].remove(value);
        if (removed) size--;
        return removed;
    }

    public int getSize() {
        return size;
    }

    @Override
    public String toString() {
        return "MyHashSetV2{" +
                "buckets=" + Arrays.toString(buckets) +
                ", size=" + size +
                ", capacity=" + capacity +
                '}';
    }
}

10. equals/hashCode ๋ฏธ๊ตฌํ˜„ ์‹œ ๋ฐœ์ƒ ๋ฌธ์ œ

โ˜‘๏ธŽ Object ๊ธฐ๋ณธ equals()๋Š” ์ฐธ์กฐ ๋น„๊ต (==)

โ˜‘๏ธŽ hashCode()๋งŒ ์˜ค๋ฒ„๋ผ์ด๋”ฉํ•˜๋ฉด equals() ์‹คํŒจ → ์ค‘๋ณต ๊ฐ์ง€ ๋ชป ํ•จ

โ˜‘๏ธŽ equals()๋งŒ ์˜ค๋ฒ„๋ผ์ด๋”ฉํ•ด๋„ ํ•ด์‹œ ๋ฒ„ํ‚ท ์œ„์น˜๊ฐ€ ๋‹ค๋ฅด๋ฉด ํƒ์ƒ‰ ๋ถˆ๊ฐ€

โ˜‘๏ธŽ ๋ฐ˜๋“œ์‹œ ๋‘˜ ๋‹ค ๊ตฌํ˜„ํ•ด์•ผ Set์—์„œ ์ •์ƒ ์ž‘๋™


11. ์ œ๋„ค๋ฆญ ๊ธฐ๋ฐ˜์˜ Set ๊ตฌํ˜„ - MyHashSetV3
๋”๋ณด๊ธฐ
public class MyHashSetV3<E> implements MySet<E> {
    static final int DEFAULT_INITIAL_CAPACITY = 16;
    private LinkedList<E>[] buckets;
    private int size = 0;
    private int capacity = DEFAULT_INITIAL_CAPACITY;

    public MyHashSetV3(int capacity) {
        this.capacity = capacity;
        initBuckets();
    }

    private void initBuckets() {
        buckets = new LinkedList[capacity];
        for (int i = 0; i < capacity; i++) {
            buckets[i] = new LinkedList<>();
        }
    }

    private int hashIndex(Object value) {
        return Math.abs(value.hashCode()) % capacity;
    }

    @Override
    public boolean add(E value) {
        int hashIndex = hashIndex(value);
        LinkedList<E> bucket = buckets[hashIndex];
        if (bucket.contains(value)) return false;
        bucket.add(value);
        size++;
        return true;
    }

    @Override
    public boolean contains(E value) {
        return buckets[hashIndex(value)].contains(value);
    }

    @Override
    public boolean remove(E value) {
        boolean removed = buckets[hashIndex(value)].remove(value);
        if (removed) size--;
        return removed;
    }

    public int getSize() {
        return size;
    }

    @Override
    public String toString() {
        return "MyHashSetV3{" +
                "buckets=" + Arrays.toString(buckets) +
                ", size=" + size +
                ", capacity=" + capacity +
                '}';
    }
}

12. ์šฉ์–ด ์ •๋ฆฌ
์šฉ์–ด ์˜๋ฏธ
ํ•ด์‹œ ํ•จ์ˆ˜ ๊ฐ์ฒด → ์ •์ˆ˜๊ฐ’(ํ•ด์‹œ ์ฝ”๋“œ)์œผ๋กœ ๋ฐ”๊ฟ”์ฃผ๋Š” ํ•จ์ˆ˜
ํ•ด์‹œ ์ฝ”๋“œ ๊ฐ์ฒด๋ฅผ ๋Œ€ํ‘œํ•˜๋Š” ์ •์ˆ˜๊ฐ’
ํ•ด์‹œ ์ธ๋ฑ์Šค ํ•ด์‹œ ์ฝ”๋“œ % ๋ฐฐ์—ด ํฌ๊ธฐ
equals() ๋‘ ๊ฐ์ฒด์˜ ๋…ผ๋ฆฌ์  ๋™๋“ฑ์„ฑ ๋น„๊ต
hashCode() ๊ฐ์ฒด์˜ ํ•ด์‹œ๊ฐ’ ๋ฐ˜ํ™˜, equals์™€ ์ผ์น˜ํ•ด์•ผ ํ•จ
Set ์ž๋ฃŒ๊ตฌ์กฐ ์ค‘๋ณต ์—†์Œ, ์ˆœ์„œ์—†์Œ, equals/hashCode ๊ธฐ์ค€ ๋น„๊ต

 

<์ฐธ๊ณ > - ๊น€์˜ํ•œ์˜ ์‹ค์ „ ์ž๋ฐ” ์ค‘๊ธ‰2ํŽธ ๊ฐ•์˜ ์„น์…˜ 8. ์ปฌ๋ ‰์…˜ ํ”„๋ ˆ์ž„์›Œํฌ ํ•ด์‹œ(HashSet)