본문 바로가기
Java

자바 중급 2편 - Hash

by KongJiHoon 2025. 8. 12.

1. List vs Set

 

구분 List Set
순서 요소 추가 순서 유지 순서 보장 X
중복 허용 여부 허용 불허
접근 방식 인덱스로 접근 가능 인덱스 없음, 값 존재 여부로 접근
대표 용도 순서가 중요한 데이터, 중복 허용 고유한 값만 저장, 빠른 검색
예시 장바구니 목록, 이력 기록 회원 ID, 고유 태그 목록

 

2. Set 직접 구현 (기본형)

package com.example.collection.set;

import java.util.Arrays;

public class MyHashSetV0 {

    private int[] elementData = new int[10];
    private int size = 0;

    //O(n)
    public boolean add(int value) {

        if (contains(value)) {
            return false;
        }

        elementData[size++] = value;

        return true;

    }

    public int size() {
        return size;
    }

    // O(n)
    public boolean contains(int value) {

        for (int data : elementData) {

            if (data == value) {
                return true;
            }

        }

        return false;

    }

    @Override
    public String toString() {
        return "MyHashSetV0{" +
                "elementData=" + Arrays.toString(Arrays.copyOf(elementData, size)) +
                ", size=" + size +
                '}';
    }
}

 

특징

  • 단순 배열 기반 저장
  • 추가/검색 모두 O(n) → 데이터 많아지면 성능 저하

 

3. 해시 알고리즘의 핵심 아이디어

 

💡 배열은 인덱스로 접근할 때 O(1)로 빠르다
→ 데이터를 인덱스와 매칭시켜 저장하면 검색 속도를 비약적으로 향상 가능

 

예시)

  • 값 8을 찾기 → array[8] 로 즉시 접근
  • 검색 성능: O(n) → O(1) 로 개선
package com.example.collection.set;

import java.util.Arrays;

public class HashStart2 {

    public static void main(String[] args) {

        Integer[] inputArray = new Integer[10];

        inputArray[1] = 1;

        inputArray[2] = 2;

        inputArray[5] = 5;

        inputArray[8] = 8;

        System.out.println("inputArray = " + Arrays.toString(inputArray));


        int searchValue = 8;

        System.out.println("searchValue = " + inputArray[searchValue]);

    }
}

 

 

  • 단점
    • 값의 범위가 클 경우(예: int 전체 범위) → 배열 크기 = 값의 최대치 필요
    • 메모리 소모와 초기화 비용이 엄청남
      → 값이 몇 개 안 들어올 때 심각한 낭비 발생

 

 

 

5. 나머지 연산(Hashing)으로 해결

 

package com.example.collection.set;

import java.util.Arrays;

public class HashStart4 {

    static final int CAPACITY = 10;

    public static void main(String[] args) {


        System.out.println("hashIndex(1) = " + hashIndex(1));
        System.out.println("hashIndex(2) = " + hashIndex(2));
        System.out.println("hashIndex(5) = " + hashIndex(5));
        System.out.println("hashIndex(8) = " + hashIndex(8));
        System.out.println("hashIndex(14) = " + hashIndex(14));
        System.out.println("hashIndex(99) = " + hashIndex(99));


        Integer[] inputArray = new Integer[CAPACITY];

        add(inputArray, 1);
        add(inputArray, 2);
        add(inputArray, 5);
        add(inputArray, 8);
        add(inputArray, 14);
        add(inputArray, 99);


        int searchValue = 99;

        int hashIndex = hashIndex(searchValue);

        System.out.println("hashIndex = " + hashIndex);

        System.out.println("searchValue = " + inputArray[hashIndex]);

        System.out.println(Arrays.toString(inputArray));
    }


    static void add(Integer[] inputArray, int value) {

        int hashIndex = hashIndex(value);

        inputArray[hashIndex] = value;


    }

    static int hashIndex(int value) {
        return value % CAPACITY;
    }
}

 

  • CAPACITY = 배열 크기
  • 나머지 연산 결과는 항상 0 ~ CAPACITY-1 범위
  • 값 범위가 커도 배열 크기를 제한 가능
  • 저장/검색 모두 O(1) 평균 성능

 

6. 해시 충돌(Hash Collision)

1 % 10 = 1
11 % 10 = 1  // 같은 인덱스 → 덮어쓰기 위험

 

 

Separate Chaining (분리 연결법)

 

  • 각 인덱스에 LinkedList 또는 다른 자료구조 저장
  • 같은 해시 인덱스를 가진 값들은 해당 리스트에 모두 저장
  • 검색 시 리스트 내부 순회

 

package com.example.collection.set;

import java.util.Arrays;
import java.util.LinkedList;

public class HashStart5 {

    static final int CAPACITY = 10;

    public static void main(String[] args) {
        // {1, 2, 5, 8, 14, 99}

        LinkedList<Integer>[] buckets = new LinkedList[CAPACITY];

        System.out.println("buckets = " + Arrays.toString(buckets));

        for (int i = 0; i < CAPACITY; i++) {

            buckets[i] = new LinkedList<>();


        }

        System.out.println("buckets = " + Arrays.toString(buckets));

        add(buckets, 1);
        add(buckets, 2);
        add(buckets, 5);
        add(buckets, 8);
        add(buckets, 14);
        add(buckets, 99);
        add(buckets, 9);
        System.out.println("buckets = " + Arrays.toString(buckets));

        // 검색
        int searchValue = 9;

        if (contains(buckets, searchValue)) {

            System.out.println("searchValue = " + searchValue);
        }

    }

    private static void add(LinkedList<Integer>[] buckets, Integer value) {

        int hashIndex = hashIndex(value);

        LinkedList<Integer> bucket = buckets[hashIndex];

        if (!bucket.contains(value)) {

            bucket.add(value);

        }

    }

    private static boolean contains(LinkedList<Integer>[] buckets, Integer value) {
        int hashIndex = hashIndex(value);

        LinkedList<Integer> bucket = buckets[hashIndex];


        return bucket.contains(value);


    }



    static int hashIndex(int value) {
        return value % CAPACITY;
    }




}

 

 

실행결과

buckets = [[], [], [], [], [], [], [], [], [], []]
buckets = [[], [1], [2], [], [14], [5], [], [], [8], [99, 9]]
searchValue = 9

 

📌 충돌 확률 줄이기

  • 배열 크기(CAPACITY) ↑ → 메모리 낭비 ↑
  • 보통 저장 데이터 수 ≤ 배열 크기 × 0.75 유지 권장

 

출처: 인프런 자바 중급 2편 : 김영한

'Java' 카테고리의 다른 글

자바 중급 2편 - HashSet(2)  (3) 2025.08.17
자바 중급 2편 - HashSet(1)  (2) 2025.08.15
자바 중급 2편 - List(3)  (3) 2025.08.10
자바 중급 2편 - List(2)  (1) 2025.08.10
자바 중급 2편 - List(1)  (1) 2025.08.10