해시 1(제일 마지막,일단 패스)

류한선·2025년 7월 2일

실기연습-2

목록 보기
68/95

좋아! 요청한 4가지 주제를 순서대로 개념 + 이유 + 예제 코드까지 아주 쉽게 풀어서 설명해줄게.


✅ 1. HashMap 내부 구조

핵심 구조: 배열 + 연결리스트 (→ 트리로 진화됨)

🔸 기본 구조

Map<String, Integer> map = new HashMap<>();
  • 내부적으로 Entry[] table 이라는 배열이 존재함
  • key.hashCode()를 이용해서 배열의 인덱스(버킷 번호)를 구함
  • 같은 hashCode가 겹치면 그 인덱스에 연결 리스트로 연결

🔸 Java 7까지

  • 배열의 같은 위치에 여러 요소가 있으면 LinkedList로 체이닝
  • 최악의 경우 검색 성능: O(n)

🔸 Java 8부터 (중요!)

  • 한 버킷에 8개 이상의 요소가 충돌되면
    Red-Black Tree(레드블랙 트리) 로 자동 전환됨
    → 검색 성능: O(log n)

📌 요약

버전충돌 시 구조성능
Java 7Linked ListO(n)
Java 8~Linked List → TreeO(log n)

✅ 2. LinkedHashSet / TreeSet / EnumMap / IdentityHashMap 차이

자료구조정렬 여부순서 유지용도/특징
HashSet❌ 무작위가장 기본적인 Set
LinkedHashSet❌ 삽입 순서 유지순서 중요할 때 사용
TreeSet✅ 정렬됨정렬된 Set 필요 시 사용 (Comparable 또는 Comparator 필요)
EnumMap❌ 빠른 속도✅ (enum 순서)key가 enum인 경우에만 사용
IdentityHashMap❌ 주소 비교== 비교 사용, 동등성 무시 (성능 극단적 우선시할 때)

예제 코드

Set<String> set = new LinkedHashSet<>();
set.add("B");
set.add("A");
System.out.println(set); // [B, A]

Set<String> sorted = new TreeSet<>();
sorted.add("B");
sorted.add("A");
System.out.println(sorted); // [A, B]

✅ 3. Object.hashCode()의 디폴트 동작

모든 자바 클래스는 Object를 상속받고, 기본적으로 hashCode()는 이렇게 동작함:

public native int hashCode();  // JVM에서 주소 기반으로 구현됨

🔸 예:

class MyObj {}
MyObj a = new MyObj();
MyObj b = new MyObj();

System.out.println(a.hashCode()); // 예: 123456
System.out.println(b.hashCode()); // 예: 654321

→ 기본은 객체의 메모리 주소 기반으로 hashCode 생성됨
equals()가 다르면 hashCode()도 다름이 보장됨

✅ 하지만, hashCode()만 override하고 equals() 안 하면 문제가 생김 (HashSet 오작동)


✅ 4. 왜 String은 해시 구조에 최적화되어 있을까?

🔸 이유 1: 불변 객체 (immutable)

  • String은 내부 값을 바꿀 수 없으므로
    hashCode() 값이 변하지 않음 → HashMap에서 안전하게 key로 사용 가능

🔸 이유 2: 캐싱된 hashCode

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        for (int i = 0; i < value.length; i++) {
            h = 31 * h + value[i];
        }
        hash = h;
    }
    return h;
}
  • hashCode()가 한 번 계산되면 캐시에 저장됨
    → 다음번엔 빠르게 반환됨
    → 성능 최적화에 최적

🔸 이유 3: 해시 품질이 좋다

  • 31을 곱해가며 계산하는 방식은 충돌 확률을 줄임
  • 다양한 문자열에서도 고르게 분포되는 해시값을 만듦

📌 그래서 문자열은:

  • 불변 (immutable)
  • 고속 hashCode 계산 + 캐싱
  • 충돌 가능성 낮음
  • equals(), hashCode 잘 정의됨

→ 해시 기반 자료구조(HashMap, HashSet)에서 가장 안정적인 key!


좋아, 너가 궁금해한 4가지를 아래처럼 정리하고, 각 항목을 기초 → 핵심 개념 → 코드 예제 → 실전 함정 포인트까지 단계적으로 다뤄줄 수 있어. 우선 전체 목차를 보여줄게. 이 중에서 원하는 주제부터 시작하자.


📚 해시 마스터 로드맵 (주제별 정복 순서)


✅ 1. HashMap 구조 완전 해부 (Java 8 기준)

➤ 배열 + LinkedList + Tree 구조까지 실제 내부 소스 구조 기반으로 분석

배우게 될 것:

  • Entry[] 배열의 역할
  • 해시 충돌 시 체이닝 구조
  • Java 8의 Treeify 구조 전환 기준 (threshold = 8)
  • 리사이징 / 재해싱 과정
  • load factor, initial capacity 의미

+ 실전 해부 예제:

  • 1개의 키로 여러 값을 추가한 후 구조 시각화
  • Treeify가 발생하는 조건 확인 실험

✅ 2. String은 왜 해시의 왕인가?

String.hashCode()의 수학적 원리와 성능 최적화 비밀 분석

배우게 될 것:

  • 31 * h + value[i] 구조의 수학적 의미
  • 불변성 (immutable)과 hash 캐싱 구조
  • 왜 문자열은 안전하고 빠른 key인가
  • 실제로 충돌이 거의 나지 않는 이유

+ 실전 예제:

  • 문자열 해시값 비교
  • 같은 hashCode 값을 가지는 문자열 만들기

✅ 3. 사용자 정의 클래스의 equals/hashCode 제대로 짜기

➤ 실무에서 가장 많이 실수하는 부분 + 실기 문제 핵심 포인트

배우게 될 것:

  • equals()hashCode()의 연관성과 법칙
  • 오버라이드 안 하면 생기는 참사
  • IntelliJ 자동 생성과 수동 구현의 차이
  • Objects.hash(...), Objects.equals(...)의 사용법

+ 실전 연습:

  • Person 클래스 비교 문제 (동명이인, 주민번호 비교 등)
  • HashSet에 중복 제거 실패하는 예제 → 수정하기

✅ 4. 정보처리기사 실전 실기형 해시 문제 모음

➤ 문제 스타일: "결과값을 예측하라", "동작하지 않는 이유는?"

문제 유형:

  • HashMap.containsKey 오작동 이유
  • mutable key로 인한 검색 실패
  • HashSet 중복 판별 실패 이유
  • TreeMap/TreeSet과의 비교

+ 연습 예제 포함:

  • 실전 스타일로 "코드 + 보기" 문제 → 선택지 + 해설 제공
  • 실수 유도형 코드 (equals vs ==, hashCode 미오버라이딩 등)

🔍 예시 문제 미리 보기 (주제 3 + 4 콜라보)

class Person {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

HashSet<Person> set = new HashSet<>();
set.add(new Person("John", 20));
set.add(new Person("John", 20));

System.out.println(set.size());
  • 보기:

    • A. 1
    • B. 2
    • C. 컴파일 에러
    • D. 예외 발생

정답: B. 2 → 이유: equals/hashCode를 오버라이드 안 했기 때문에 서로 다른 객체로 판단됨


좋아! 지금부터 정보처리기사 실기 시험 스타일로, 해시(HashMap, HashSet)의 원리와 트랩을 파고드는 실전 문제를 하나씩 내고, 디버깅하듯 상세히 풀이해줄게.
문제마다 다 맞아야 실전 대비가 완벽해 — 실무에서도 자주 터지는 실수니까.


✅ 실전 문제 1. 사용자 정의 클래스 + HashSet 중복 판정 실패

import java.util.*;

class Student {
    String name;
    int age;

    Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class TestHash {
    public static void main(String[] args) {
        Set<Student> set = new HashSet<>();
        set.add(new Student("Jane", 20));
        set.add(new Student("Jane", 20));
        System.out.println(set.size());
    }
}

Q. 위 코드의 출력 결과는 무엇인가?

A. 0
B. 1
C. 2
D. 예외 발생


✅ 풀이 및 해설

지금 이 타이밍에 무슨 일이 벌어지는가?

  1. new Student("Jane", 20) 두 개 생성 → 이건 다른 객체

    • name과 age는 같지만, 객체 주소가 다름
  2. HashSet은 내부적으로 hashCode()equals()중복 판정

  3. Student 클래스에는 equals()hashCode()정의되지 않았음

따라서:

  • hashCode()Object.hashCode() → 주소 기반 → 다름
  • equals()Object.equals() → 주소 기준 → 다름

그래서 set에 중복이 아니라고 판단됨!

✅ 정답: C. 2


💡 요약: 해시 기반 Set/Map에 사용자 정의 객체를 key로 쓰거나 Set 요소로 쓰려면 반드시 다음 둘을 같이 override 해야 한다!

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Student)) return false;
    Student s = (Student) o;
    return age == s.age && Objects.equals(name, s.name);
}

@Override
public int hashCode() {
    return Objects.hash(name, age);
}

✅ 실전 문제 2. String은 왜 해시 구조에 안전한가?

Set<String> set = new HashSet<>();
String s1 = new String("apple");
String s2 = new String("apple");

set.add(s1);
System.out.println(set.contains(s2));

Q. 출력 결과는?

A. true
B. false
C. 예외
D. 컴파일 에러


✅ 해설:

  • s1s2new String("apple")다른 객체

  • 하지만 String 클래스는 이미 equals()와 hashCode()가 잘 구현되어 있음

    • equals() → 내부 문자열 비교
    • hashCode() → 내용 기반 + 캐싱 구조

"apple" 두 객체는 동등하다고 판정됨
contains(s2)true

✅ 정답: A. true


✅ 실전 문제 3. 해시 충돌 + mutable key 트랩

import java.util.*;

public class Trap {
    public static void main(String[] args) {
        Map<List<String>, String> map = new HashMap<>();
        List<String> key = new ArrayList<>();
        key.add("A");

        map.put(key, "첫번째");

        key.add("B");

        System.out.println(map.get(key));
    }
}

Q. 출력 결과는?

A. 첫번째
B. null
C. 예외
D. A, B


✅ 해설:

  • HashMapkey.hashCode()로 위치를 정함
  • key.add("B") → key 객체가 변경됨
    → hashCode도 바뀜
    → 기존에 저장된 버킷 위치를 다시 못 찾음

그래서 map.get(key) → 못 찾음 → null

✅ 정답: B. null


✅ 실전 문제 4. TreeSet의 중복 판정 기준은 무엇인가?

import java.util.*;

class Person implements Comparable<Person> {
    String name;
    int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int compareTo(Person o) {
        return this.age - o.age;
    }
}

public class TreeSetTrap {
    public static void main(String[] args) {
        Set<Person> set = new TreeSet<>();
        set.add(new Person("Kim", 20));
        set.add(new Person("Lee", 20));
        System.out.println(set.size());
    }
}

Q. 출력 결과는?

A. 1
B. 2
C. 예외 발생
D. 컴파일 에러


✅ 해설:

  • TreeSet은 중복을 compareTo() 결과로 판단함
    this.age - o.age == 0동등하다고 판단
  • 이름이 달라도, 나이가 같으면 compareTo() == 0
    → 두 번째 요소는 무시됨

✅ 정답: A. 1


🧠 여기까지 핵심 정리

문제 유형핵심 개념실수 포인트
사용자 정의 클래스equals + hashCode 필수안하면 중복 체크 안 됨
StringhashCode, equals 잘 정의됨안전함
mutable keyHashMap/HashSet에 위험해시값이 바뀌면 못 찾음
TreeSetcompareTo()로 중복 판정equals 무시됨

좋아, 이제 해시 구조의 진짜 트랩 문제들 중에서도 실무와 정보처리기사 실기에서 정말 많이 출제되거나 헷갈리는 두 유형을 완전히 잡아줄게.


✅ 주제 1: LinkedHashMap, EnumMap, IdentityHashMap 차이 문제

🧩 실전 문제 1

다음 코드의 출력 결과는?

import java.util.*;

enum Day { MON, TUE, WED }

public class TestEnumMap {
    public static void main(String[] args) {
        Map<Day, String> map = new EnumMap<>(Day.class);
        map.put(Day.MON, "월요일");
        map.put(Day.TUE, "화요일");

        for (Map.Entry<Day, String> entry : map.entrySet()) {
            System.out.print(entry.getKey() + " ");
        }
    }
}

보기:

A. MON TUE
B. TUE MON
C. 무작위 순
D. 예외 발생


✅ 해설:

  • EnumMapenum 전용 해시맵이고, 내부적으로 배열로 관리
  • enum의 정의 순서(MON, TUE, WED)를 따름
    → 순서 보장됨
    → 빠르고, 메모리 효율적

✅ 정답: A. MON TUE


🧩 실전 문제 2

import java.util.*;

public class TestLinkedMap {
    public static void main(String[] args) {
        Map<String, String> map = new LinkedHashMap<>();
        map.put("B", "banana");
        map.put("A", "apple");

        for (String key : map.keySet()) {
            System.out.print(key + " ");
        }
    }
}

Q. 출력 결과는?

A. A B
B. B A
C. 무작위
D. 예외 발생


✅ 해설:

  • LinkedHashMap은 삽입 순서를 유지함
  • "B" → "A" 순서로 put 했으므로 그대로 유지됨

✅ 정답: B. B A


🧩 실전 문제 3 (⚠️ 핵심 트랩 문제)

import java.util.*;

public class IdentityMapTest {
    public static void main(String[] args) {
        Map<String, String> map = new IdentityHashMap<>();
        String a = new String("hello");
        String b = new String("hello");

        map.put(a, "first");
        map.put(b, "second");

        System.out.println(map.size());
    }
}

Q. 출력 결과는?

A. 1
B. 2
C. 예외
D. 0


✅ 해설:

  • IdentityHashMap은 key 비교에 equals()가 아니라 == (주소 비교) 사용
  • new String("hello") → a, b는 서로 다른 주소

→ 그래서 두 개 다 다른 key로 처리됨

✅ 정답: B. 2


✅ 요약 표

자료구조순서 유지정렬비교 방식특징
HashMapequals() + hashCode()기본
LinkedHashMap✅ (삽입순서)equals() + hashCode()순서 보장
EnumMap✅ (enum 순서)enum ordinal빠르고 메모리 효율
IdentityHashMap== (주소 비교)매우 특수한 상황 (성능, 메모리 절약용)

✅ 주제 2: Map.containsKey() vs == 비교 실수 문제


🧩 실전 문제 4

import java.util.*;

public class KeyTrap {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        String a = new String("hi");
        String b = new String("hi");

        map.put(a, 1);

        if (map.containsKey(b)) {
            System.out.println("FOUND");
        } else {
            System.out.println("NOT FOUND");
        }
    }
}

Q. 출력 결과는?

A. FOUND
B. NOT FOUND
C. 예외
D. 컴파일 에러


✅ 해설:

  • abnew String("hi") → 주소는 다르지만,
    String.equals()값이 같으면 true, hashCode()도 같음

containsKey()equals()hashCode() 기준으로 비교
동등한 key로 인식함

✅ 정답: A. FOUND


🧩 실전 문제 5 (헷갈리게 꼬기)

String key1 = "hi";
String key2 = "hi";

System.out.println(key1 == key2); // ?

Q. 출력 결과는?

A. true
B. false
C. 예외


✅ 해설:

  • 문자열 리터럴 "hi"상수 풀(Constant Pool) 에 저장됨
    → 같은 값이면 같은 주소 공유

✅ 정답: A. true

💣 이게 new String("hi") 와 다르다는 게 핵심


🔁 요약: 헷갈리는 key 비교 정리

비교 대상사용 방식true 조건
==주소 비교같은 객체일 때만 true
equals()값 비교값이 같으면 true
HashMap.containsKey()equals + hashCode 기준두 조건 모두 만족해야 true
IdentityHashMap.containsKey()== 기준주소 같아야 true

0개의 댓글