순서 유지 집합 - LinkedHashSet 마스터하기
Java API Reference
import java.util.*;
public class LinkedHashSetBasic {
public static void main(String[] args) {
System.out.println("=== LinkedHashSet 특징 ===\n");
System.out.println("1. 중복 불가");
System.out.println(" - HashSet과 동일\n");
System.out.println("2. 삽입 순서 유지");
System.out.println(" - 추가한 순서대로 저장");
System.out.println(" - 이중 연결 리스트 사용\n");
System.out.println("3. 성능");
System.out.println(" - add: O(1)");
System.out.println(" - contains: O(1)");
System.out.println(" - HashSet보다 약간 느림\n");
System.out.println("4. null 허용");
System.out.println(" - null 1개만 저장 가능");
}
}public class HashSetVsLinkedHashSet {
public static void main(String[] args) {
// HashSet (순서 없음)
Set<String> hashSet = new HashSet<>();
hashSet.add("C");
hashSet.add("A");
hashSet.add("B");
System.out.println("HashSet: " + hashSet);
// 순서 무작위: [A, B, C] 또는 다른 순서
// LinkedHashSet (순서 유지)
Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("C");
linkedHashSet.add("A");
linkedHashSet.add("B");
System.out.println("LinkedHashSet: " + linkedHashSet);
// 삽입 순서: [C, A, B]
System.out.println("\n=== 차이점 ===");
System.out.println("HashSet: 빠름, 순서X");
System.out.println("LinkedHashSet: 약간 느림, 순서O");
}
}public class LinkedHashSetCreation {
public static void main(String[] args) {
// 기본 생성
Set<String> set1 = new LinkedHashSet<>();
System.out.println("기본: " + set1);
// 초기 용량 지정
Set<String> set2 = new LinkedHashSet<>(100);
System.out.println("용량 100: " + set2);
// 다른 컬렉션으로
Set<String> set3 = new LinkedHashSet<>(
Arrays.asList("A", "B", "C")
);
System.out.println("복사: " + set3);
// Set 인터페이스로 (권장)
Set<String> set4 = new LinkedHashSet<>();
System.out.println("Set 타입: " + set4);
}
}public class LinkedHashSetInit {
public static void main(String[] args) {
// 방법 1: add (순서대로)
Set<String> set1 = new LinkedHashSet<>();
set1.add("Apple");
set1.add("Banana");
set1.add("Cherry");
System.out.println("add: " + set1);
// [Apple, Banana, Cherry]
// 방법 2: Arrays.asList
Set<String> set2 = new LinkedHashSet<>(
Arrays.asList("C", "A", "B")
);
System.out.println("Arrays.asList: " + set2);
// [C, A, B] (순서 유지)
// 방법 3: addAll
Set<String> set3 = new LinkedHashSet<>();
set3.addAll(Arrays.asList("3", "1", "2"));
System.out.println("addAll: " + set3);
// [3, 1, 2]
// 방법 4: Stream
Set<Integer> set4 = Stream.of(5, 3, 1, 4, 2)
.collect(Collectors.toCollection(
LinkedHashSet::new));
System.out.println("Stream: " + set4);
// [5, 3, 1, 4, 2]
}
}public class LinkedHashSetAdd {
public static void main(String[] args) {
Set<String> set = new LinkedHashSet<>();
// 순서대로 추가
set.add("First");
set.add("Second");
set.add("Third");
System.out.println("추가 후: " + set);
// [First, Second, Third]
// 중복 추가 (무시되고 순서 변화 없음)
set.add("First");
System.out.println("중복 후: " + set);
// [First, Second, Third] (변화 없음)
// 중간에 추가 (끝에 추가됨)
set.add("Fourth");
System.out.println("Fourth 추가: " + set);
// [First, Second, Third, Fourth]
// null 추가
set.add(null);
System.out.println("null 추가: " + set);
}
}public class LinkedHashSetRemove {
public static void main(String[] args) {
Set<String> set = new LinkedHashSet<>(
Arrays.asList("A", "B", "C", "D", "E")
);
System.out.println("원본: " + set);
// [A, B, C, D, E]
// 중간 요소 삭제
set.remove("C");
System.out.println("C 삭제: " + set);
// [A, B, D, E]
// 첫 요소 삭제
set.remove("A");
System.out.println("A 삭제: " + set);
// [B, D, E]
// 여러 개 삭제
set.removeAll(Arrays.asList("B", "D"));
System.out.println("여러 개 삭제: " + set);
// [E]
}
}public class InsertionOrder {
public static void main(String[] args) {
Set<Integer> set = new LinkedHashSet<>();
// 랜덤하게 추가
set.add(5);
set.add(2);
set.add(8);
set.add(1);
set.add(9);
System.out.println("삽입 순서: " + set);
// [5, 2, 8, 1, 9]
// 중복 추가 (순서 변화 없음)
set.add(2);
set.add(8);
System.out.println("중복 후: " + set);
// [5, 2, 8, 1, 9] (변화 없음)
// 삭제 후 재추가 (끝에 추가)
set.remove(2);
System.out.println("2 삭제: " + set);
// [5, 8, 1, 9]
set.add(2);
System.out.println("2 재추가: " + set);
// [5, 8, 1, 9, 2] (끝에 추가)
}
}public class IterationOrder {
public static void main(String[] args) {
Set<String> set = new LinkedHashSet<>();
set.add("Apple");
set.add("Banana");
set.add("Cherry");
set.add("Durian");
// 순회 = 삽입 순서
System.out.println("=== 순회 ===");
for (String fruit : set) {
System.out.println(fruit);
}
// Apple, Banana, Cherry, Durian
// Iterator도 순서 유지
System.out.println("\n=== Iterator ===");
Iterator<String> it = set.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}public class LinkedHashSetUnion {
public static void main(String[] args) {
Set<Integer> set1 = new LinkedHashSet<>(
Arrays.asList(1, 2, 3)
);
Set<Integer> set2 = new LinkedHashSet<>(
Arrays.asList(3, 4, 5)
);
// 합집합 (set1 순서 + set2의 새 요소)
Set<Integer> union = new LinkedHashSet<>(set1);
union.addAll(set2);
System.out.println("Set1: " + set1);
System.out.println("Set2: " + set2);
System.out.println("Union: " + union);
// [1, 2, 3, 4, 5]
}
}public class LinkedHashSetIntersection {
public static void main(String[] args) {
Set<Integer> set1 = new LinkedHashSet<>(
Arrays.asList(1, 2, 3, 4, 5)
);
Set<Integer> set2 = new LinkedHashSet<>(
Arrays.asList(4, 5, 6, 7, 8)
);
// 교집합 (set1의 순서 유지)
Set<Integer> intersection = new LinkedHashSet<>(set1);
intersection.retainAll(set2);
System.out.println("Set1: " + set1);
System.out.println("Set2: " + set2);
System.out.println("Intersection: " + intersection);
// [4, 5]
}
}public class LinkedHashSetIteration {
public static void main(String[] args) {
Set<String> set = new LinkedHashSet<>(
Arrays.asList("A", "B", "C", "D", "E")
);
// 향상된 for (순서 보장)
System.out.println("=== 향상된 for ===");
for (String item : set) {
System.out.print(item + " ");
}
System.out.println();
// A B C D E
}
}public class LinkedHashSetIterator {
public static void main(String[] args) {
Set<String> set = new LinkedHashSet<>(
Arrays.asList("A", "B", "C", "D", "E")
);
// Iterator (순서 보장)
System.out.println("=== Iterator ===");
Iterator<String> it = set.iterator();
while (it.hasNext()) {
System.out.print(it.next() + " ");
}
System.out.println();
// A B C D E
}
}public class LinkedHashSetForEach {
public static void main(String[] args) {
Set<Integer> set = new LinkedHashSet<>(
Arrays.asList(5, 3, 8, 1, 9)
);
// forEach (순서 보장)
System.out.println("=== forEach ===");
set.forEach(n -> System.out.print(n + " "));
System.out.println();
// 5 3 8 1 9
// Stream (순서 보장)
System.out.println("\n=== Stream ===");
set.stream()
.forEach(n -> System.out.print(n + " "));
System.out.println();
// 5 3 8 1 9
}
}public class LinkedHashSetStructure {
public static void main(String[] args) {
System.out.println("=== LinkedHashSet 구조 ===\n");
System.out.println("내부: LinkedHashMap 사용");
System.out.println("구조: HashTable + 이중 연결 리스트");
System.out.println("노드: Entry (prev, next 포함)\n");
System.out.println("=== 메모리 ===");
System.out.println("HashSet보다 많음");
System.out.println("이유: prev/next 참조 추가\n");
System.out.println("=== 특징 ===");
System.out.println("해시: 빠른 검색");
System.out.println("연결: 순서 유지");
}
}public class LinkedHashSetComplexity {
public static void main(String[] args) {
System.out.println("=== 시간 복잡도 ===\n");
System.out.println("add: O(1)");
System.out.println(" - 해시 + 연결 리스트\n");
System.out.println("contains: O(1)");
System.out.println(" - 해시 탐색\n");
System.out.println("remove: O(1)");
System.out.println(" - 해시 + 연결 제거\n");
System.out.println("순회: O(n)");
System.out.println(" - 연결 리스트 순회\n");
System.out.println("HashSet보다 약간 느림");
System.out.println("하지만 여전히 O(1)");
}
}public class PerformanceComparison {
public static void main(String[] args) {
int n = 100000;
// HashSet
Set<Integer> hashSet = new HashSet<>();
long start = System.currentTimeMillis();
for (int i = 0; i < n; i++) {
hashSet.add(i);
}
long time1 = System.currentTimeMillis() - start;
// LinkedHashSet
Set<Integer> linkedHashSet = new LinkedHashSet<>();
start = System.currentTimeMillis();
for (int i = 0; i < n; i++) {
linkedHashSet.add(i);
}
long time2 = System.currentTimeMillis() - start;
System.out.println("HashSet: " + time1 + "ms");
System.out.println("LinkedHashSet: " + time2 + "ms");
System.out.println("\nLinkedHashSet이 약간 느림");
System.out.println("하지만 큰 차이 없음");
}
}public class RemoveDuplicatesKeepOrder {
public static void main(String[] args) {
List<String> list = Arrays.asList(
"Apple", "Banana", "Apple", "Cherry", "Banana"
);
System.out.println("원본: " + list);
// LinkedHashSet으로 중복 제거
Set<String> set = new LinkedHashSet<>(list);
System.out.println("중복 제거: " + set);
// [Apple, Banana, Cherry] (순서 유지)
// 다시 List로
List<String> result = new ArrayList<>(set);
System.out.println("List: " + result);
}
}public class VisitTracker {
private Set<String> visited = new LinkedHashSet<>();
public void visit(String page) {
visited.add(page);
}
public void showHistory() {
System.out.println("방문 순서: " + visited);
}
public static void main(String[] args) {
VisitTracker tracker = new VisitTracker();
tracker.visit("Home");
tracker.visit("About");
tracker.visit("Products");
tracker.visit("Home"); // 중복
tracker.visit("Contact");
tracker.showHistory();
// [Home, About, Products, Contact]
}
}public class OrderedUniqueData {
public static void main(String[] args) {
// 설문조사 응답 (순서 중요, 중복 제거)
Set<String> responses = new LinkedHashSet<>();
responses.add("Option A");
responses.add("Option B");
responses.add("Option A"); // 중복
responses.add("Option C");
responses.add("Option B"); // 중복
System.out.println("응답 순서: " + responses);
// [Option A, Option B, Option C]
// 첫 응답부터 처리
for (String response : responses) {
System.out.println("처리: " + response);
}
}
}// 로그에서 고유 IP를 추출하되 첫 등장 순서 유지
public class Problem1 {
public static List<String> extractUniqueIPs(List<String> logs) {
// 로그 형식: "192.168.1.1 - GET /home"
// 코드 작성
return null;
}
public static void main(String[] args) {
List<String> logs = Arrays.asList(
"192.168.1.1 - GET /home",
"192.168.1.2 - GET /about",
"192.168.1.1 - POST /login",
"192.168.1.3 - GET /products",
"192.168.1.2 - GET /contact"
);
List<String> ips = extractUniqueIPs(logs);
System.out.println("고유 IP (순서): " + ips);
// [192.168.1.1, 192.168.1.2, 192.168.1.3]
}
}정답:
정답 보기
public class Problem1 {
public static List<String> extractUniqueIPs(List<String> logs) {
Set<String> uniqueIPs = new LinkedHashSet<>();
for (String log : logs) {
String ip = log.split(" ")[0];
uniqueIPs.add(ip);
}
return new ArrayList<>(uniqueIPs);
}
}// 문장에서 단어가 첫 등장한 순서대로 출력
public class Problem2 {
public static List<String> firstAppearanceOrder(String text) {
// 코드 작성
return null;
}
public static void main(String[] args) {
String text = "the quick brown fox jumps over the lazy dog the fox";
List<String> words = firstAppearanceOrder(text);
System.out.println("첫 등장 순서: " + words);
// [the, quick, brown, fox, jumps, over, lazy, dog]
}
}정답:
정답 보기
public class Problem2 {
public static List<String> firstAppearanceOrder(String text) {
String[] words = text.split("\\s+");
Set<String> uniqueWords = new LinkedHashSet<>(
Arrays.asList(words)
);
return new ArrayList<>(uniqueWords);
}
// 소문자 변환 버전
public static List<String> firstAppearanceOrderLower(String text) {
String[] words = text.toLowerCase().split("\\s+");
Set<String> uniqueWords = new LinkedHashSet<>(
Arrays.asList(words)
);
return new ArrayList<>(uniqueWords);
}
}// 재생 목록: 중복 곡 자동 제거, 추가 순서 유지
public class Problem3 {
static class Playlist {
private Set<String> songs;
public Playlist() {
// 코드 작성
}
public void addSong(String song) {
// 코드 작성
}
public void removeSong(String song) {
// 코드 작성
}
public void play() {
// 순서대로 재생
}
}
public static void main(String[] args) {
Playlist playlist = new Playlist();
playlist.addSong("Song A");
playlist.addSong("Song B");
playlist.addSong("Song A"); // 중복, 무시
playlist.addSong("Song C");
playlist.play();
// Song A, Song B, Song C
}
}정답:
정답 보기
public class Problem3 {
static class Playlist {
private Set<String> songs;
public Playlist() {
this.songs = new LinkedHashSet<>();
}
public void addSong(String song) {
songs.add(song);
}
public void removeSong(String song) {
songs.remove(song);
}
public void play() {
System.out.println(String.join(", ", songs));
}
}
}// 중복 불가 + 순서 유지
Set<String> set = new LinkedHashSet<>();
set.add("C");
set.add("A");
set.add("B");
System.out.println(set); // [C, A, B]// 추가/삭제 (HashSet과 동일)
set.add("A"); // O(1)
set.remove("A"); // O(1)
set.clear();
// 검색
set.contains("A"); // O(1)
set.isEmpty();
set.size();
// 순회 (순서 보장!)
for (String s : set) {
System.out.println(s);
}add: O(1)
contains: O(1)
remove: O(1)
순회: O(n)
HashSet:
- 더 빠름
- 순서 없음
- 메모리 적음
LinkedHashSet:
- 약간 느림
- 순서 유지 ✅
- 메모리 많음
✅ 중복 제거 + 순서 유지
✅ 삽입 순서가 중요
✅ 예측 가능한 순회
❌ 순서 불필요 (HashSet)
❌ 정렬 필요 (TreeSet)