<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>개발자가 끄적끄적 블로그</title>
    <link>https://fkqlaus.tistory.com/</link>
    <description>안녕하세요 Java, Spring boot 공부하는 주니어 개발자입니다</description>
    <language>ko</language>
    <pubDate>Mon, 22 Jun 2026 01:09:27 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>fkqlaus</managingEditor>
    <image>
      <title>개발자가 끄적끄적 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/7681869/attach/00fa634bbf424a4ca2ce2daf9568f76d</url>
      <link>https://fkqlaus.tistory.com</link>
    </image>
    <item>
      <title>[Java] 제네릭 (Generic) 완전 정복 T와 와일드 카드</title>
      <link>https://fkqlaus.tistory.com/25</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션 쓸 때 항상 보이는 List&amp;lt;String&amp;gt;, Map&amp;lt;String, Integer&amp;gt;. 그냥 외워서 쓰다 보면 어느 순간 &amp;lt;T&amp;gt;, &amp;lt;?&amp;gt;, &amp;lt;? extends Number&amp;gt; 같은 게 나왔을 때 알아도 멈칫하는 순간이 오더라. 그래서 이번 기회에 그냥 글로 적어보고 싶어서 주제삼아 작성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭은 클래스나 메서드를 만들 때 다룰 데이터 타입을 미리 정해두지 않고, 실제로 쓰는 시점에 타입을 끼워 넣을 수 있게 해주는 기능이다. List&amp;lt;T&amp;gt; 에서 T 가 바로 그 자리다. 사용할 때 String이 들어가면 String을 다루는 리스트가 되고, Integer가 들어가면 Integer를 다루는 리스트가 된다. 같은 코드인데 타입만 바꿔서 여러 군데 쓸 수 있는 셈이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;제네릭을 왜 쓰는걸까&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭 없이 코드를 짜면 두 가지 문제가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, 캐스팅(타입 변환, 예를 들어 Object 로 저장해둔 값을 다시 String 으로 바꾸는 작업)을 매번 해줘야 한다. 어떤 타입을 저장하는 자료구조를 만들 때 타입을 특정하지 않으면 Object 로 다루게 되는데, 꺼내서 쓸 때마다 원래 타입으로 직접 바꿔줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘째, 타입을 잘못 넣어도 컴파일할 때는 에러가 안 난다. 그러다가 프로그램이 실제로 돌아가는 시점(런타임)에 ClassCastException 이라는 에러가 터진다. 이게 문제다. 코드를 빌드할 때는 멀쩡해 보이는데, 막상 실행해보니 터지는 거다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();
list.add(&quot;hello&quot;);
list.add(123);  // 컴파일 에러. 타입이 안 맞으니 미리 잡아준다.

String value = list.get(0);  // 캐스팅 없이 바로 String으로 받음
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;List&amp;lt;String&amp;gt; 이라고 선언하면 컴파일러가 &quot;이 리스트에는 String만 들어간다&quot;고 인식한다. 그래서 123 같은 다른 타입을 넣으려고 하면 컴파일 단계에서 바로 막아준다. 실행해보고 나서야 알게 되는 에러를, 코드 짜는 단계에서 미리 알려주는 것.&amp;nbsp; 문제가 생기면 간단하게 수정하여 재배포 할 수 있겠다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;T, E, K, V - 타입 파라미터 네이밍 컨벤션&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;List&amp;lt;T&amp;gt; 에서 T 같은 걸 타입 파라미터라고 부른다. 실제 타입이 들어갈 자리를 임시로 표시해둔 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알파벳은 아무거나 써도 동작은 똑같다. 근데 회사나 문서 등 여러 자바 코드를 보다보면 보통 이런식으로 많이 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기호 의미 사용 예&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;T&lt;/td&gt;
&lt;td&gt;Type. 그냥 일반적인 타입을 가리킬 때&lt;/td&gt;
&lt;td&gt;class Box&amp;lt;T&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E&lt;/td&gt;
&lt;td&gt;Element. 컬렉션에 담기는 원소를 가리킬 때&lt;/td&gt;
&lt;td&gt;List&amp;lt;E&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;K&lt;/td&gt;
&lt;td&gt;Key. Map의 키&lt;/td&gt;
&lt;td&gt;Map&amp;lt;K, V&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;V&lt;/td&gt;
&lt;td&gt;Value. Map의 값&lt;/td&gt;
&lt;td&gt;Map&amp;lt;K, V&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;N&lt;/td&gt;
&lt;td&gt;Number. 숫자류 타입&lt;/td&gt;
&lt;td&gt;&amp;lt;N extends Number&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;T 자리에 A 라고 써도 컴파일러는 신경 안 쓴다. 근데 같이 일하는 사람이 코드 읽을 때 &quot;이게 뭐지&quot; 하고 멈추게 되니까, 관례를 따르는 게 좋다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;제네릭 클래스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 만들어보면 감이 빨리 온다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;// 제네릭 클래스 선언
public class Box&amp;lt;T&amp;gt; {
    private T value;

    public Box(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Box&amp;lt;T&amp;gt; 라고 선언해두면, 이 클래스를 실제로 사용하는 시점에 T 자리에 원하는 타입을 넣을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;Box&amp;lt;String&amp;gt; stringBox = new Box&amp;lt;&amp;gt;(&quot;hello&quot;);
Box&amp;lt;Integer&amp;gt; intBox = new Box&amp;lt;&amp;gt;(42);

String s = stringBox.getValue();  // &quot;hello&quot;, 캐스팅 불필요
Integer i = intBox.getValue();    // 42, 캐스팅 불필요
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Box&amp;lt;String&amp;gt; 으로 쓰면 컴파일러가 코드 안의 T 를 전부 String 으로 바꿔서 이해하고, Box&amp;lt;Integer&amp;gt; 로 쓰면 T 를 전부 Integer 로 바꿔서 이해한다. 클래스 코드는 하나만 작성했는데, 쓰는 곳에 따라 다른 타입을 다루는 클래스처럼 동작하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서 공통으로 쓰는 클래스를 만들 때 T 를 자주 쓰게 된다. 응답 형식이나 페이징 처리처럼 &quot;데이터 타입만 다르고 구조는 똑같은&quot; 클래스를 매번 새로 만들기 귀찮으니까, T 하나로 묶어서 여러 군데서 재사용하는 식이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;제네릭 메서드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 전체가 아니라 메서드 하나에만 타입 파라미터를 선언할 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class Util {
    // 반환 타입 앞에 &amp;lt;T&amp;gt; 선언
    public static &amp;lt;T&amp;gt; T getFirst(List&amp;lt;T&amp;gt; list) {
        if (list.isEmpty()) {
            return null;
        }
        return list.get(0);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; names = List.of(&quot;광혁&quot;, &quot;철수&quot;, &quot;영희&quot;);
String first = Util.getFirst(names);  // &quot;광혁&quot;

List&amp;lt;Integer&amp;gt; nums = List.of(1, 2, 3);
Integer firstNum = Util.getFirst(nums);  // 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드 단위 제네릭은 static 유틸리티 메서드에서 자주 보인다. 클래스 전체에 타입을 고정시키지 않고, 메서드를 호출할 때마다 타입을 다르게 받고 싶을 때 쓴다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;바운디드 타입 파라미터&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;T&amp;gt; 는 그 자체로는 아무 타입이나 받는다. 근데 &quot;Number 계열만 받겠다&quot; 처럼 특정 타입의 하위 타입으로 제한하고 싶을 때가 있다. 이때 extends 를 쓴다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;// Number 또는 Number의 하위 타입만 받겠다
public static &amp;lt;T extends Number&amp;gt; double sum(List&amp;lt;T&amp;gt; list) {
    double total = 0;
    for (T t : list) {
        total += t.doubleValue();  // Number의 메서드 사용 가능
    }
    return total;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;T extends Number&amp;gt; 라고 쓰면 Integer, Double, Long 처럼 Number 를 상속한 타입만 받을 수 있다. 그리고 T 가 Number 라는 게 보장되니까, 메서드 안에서 Number 가 가진 doubleValue() 같은 메서드를 바로 쓸 수 있다.&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;sum(List.of(1, 2, 3));       // OK (Integer extends Number)
sum(List.of(1.5, 2.5));      // OK (Double extends Number)
sum(List.of(&quot;a&quot;, &quot;b&quot;));      // 컴파일 에러 (String은 Number 아님)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스를 제한할 때도 extends 를 쓴다. 클래스를 상속받을 때는 extends, 인터페이스를 구현할 때는 implements 를 쓰는 게 보통이지만, 제네릭의 바운디드 타입에서는 둘 다 extends 로 쓴다.&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;public &amp;lt;T extends Comparable&amp;lt;T&amp;gt;&amp;gt; T max(T a, T b) {
    return a.compareTo(b) &amp;gt;= 0 ? a : b;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;와일드카드 &amp;lt;?&amp;gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 처음에 제일 헷갈리는 부분이다. 처음 배울 때 T 랑 비슷한거 같은데 뭔 차이지? 하고 그냥 넘겼다가 나~~~중에서야 찾아가면서 다시 공부했던 기억이 있다. 근데 사실 그렇게 많이는 안 쓰는거 같다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;? 는 &quot;어떤 타입인지 모른다&quot;, 정확히는 &quot;타입이 뭐든 상관없다&quot;는 뜻이다. T 와 다른 점은, T 는 타입을 이름 붙여서 다른 곳(반환 타입이나 다른 매개변수)에 다시 활용할 수 있는 것이고, ? 는 그냥 &quot;여기 타입은 신경 안 쓴다&quot; 하고 끝내는 것이다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;// T 사용 - 타입을 이름 붙여서 반환 타입 등에 다시 활용
public &amp;lt;T&amp;gt; T getFirst(List&amp;lt;T&amp;gt; list) { ... }

// ? 사용 - 그냥 읽기만 할 때, 타입이 뭔지 중요하지 않을 때
public void printAll(List&amp;lt;?&amp;gt; list) {
    for (Object item : list) {
        System.out.println(item);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;printAll 은 리스트 안의 값을 출력만 하면 된다. 그 값이 String 이든 Integer 든 신경 쓸 필요가 없다. 이럴 때 ? 를 쓴다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;와일드카드에 경계 추가하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와일드카드도 범위를 제한할 수 있는데, 방향이 두 가지다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;upper bounded wildcard (? extends T, 상한 경계 - T와 T의 하위 타입까지만 허용)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;// Number 또는 Number의 하위 타입 리스트를 받겠다
public double sumAll(List&amp;lt;? extends Number&amp;gt; list) {
    double total = 0;
    for (Number n : list) {
        total += n.doubleValue();
    }
    return total;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;List&amp;lt;Integer&amp;gt;, List&amp;lt;Double&amp;gt;, List&amp;lt;Long&amp;gt; 을 모두 받을 수 있다. 다만 읽기는 되는데 새 요소를 추가하는 건 막힌다. 정확히 어떤 하위 타입의 리스트인지 컴파일러가 확신할 수 없어서, 잘못된 타입이 들어갈 위험을 막으려고 쓰기를 금지하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;lower bounded wildcard (? super T, 하한 경계 - T와 T의 상위 타입까지만 허용)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;// Integer 또는 Integer의 상위 타입 리스트를 받겠다
public void addNumbers(List&amp;lt;? super Integer&amp;gt; list) {
    list.add(1);
    list.add(2);
    list.add(3);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;List&amp;lt;Integer&amp;gt;, List&amp;lt;Number&amp;gt;, List&amp;lt;Object&amp;gt; 를 모두 받을 수 있다. Integer 보다 상위 타입이라는 게 보장되니까, Integer 값을 넣는 건 항상 안전하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 개를 외우는 트릭이 있다. &lt;b&gt;PECS (Producer Extends, Consumer Super)&lt;/b&gt; 라고 부른다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리스트에서 값을 &lt;b&gt;꺼내서 쓸 때(Producer)&lt;/b&gt;: ? extends T&lt;/li&gt;
&lt;li&gt;리스트에 값을 &lt;b&gt;넣을 때(Consumer)&lt;/b&gt;: ? super T&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 그냥 이 문구로 외워두고 쓰다 보면 자연스럽게 익혀진다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;제네릭을 주로 쓰는 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서 제네릭이 등장하는 패턴은 거의 정해져 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컬렉션 (List, Map, Set)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;List&amp;lt;User&amp;gt; users = new ArrayList&amp;lt;&amp;gt;();
Map&amp;lt;String, Order&amp;gt; orderMap = new HashMap&amp;lt;&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제일 흔하게 마주치는 형태이고, 우리가 그냥 아무 생각 없이 사용하는 제네릭 패턴(?) 이라고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션 안에 어떤 타입이 들어가는지 미리 명시해서, 꺼낼 때 캐스팅 없이 바로 쓰게 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 List&amp;lt;User&amp;gt; 이렇게 쓰니까 당연한 문법처럼 느껴지는데, List 나 Map 도 결국 누군가가 제네릭으로 만들어놓은 인터페이스다. 자바독이나 IDE에서 List 등을 타고타고 올라가다보면 선언부가 이렇게 되어있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface List&amp;lt;E&amp;gt; extends Collection&amp;lt;E&amp;gt; {
    boolean add(E e);
    E get(int index);
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기 E 가 우리가 위에서 본 타입 파라미터 네이밍 컨벤션 그대로다(Element). Map 도 마찬가지로 K, V 를 쓴다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface Map&amp;lt;K, V&amp;gt; {
    V get(Object key);
    V put(K key, V value);
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;List&amp;lt;User&amp;gt; 라고 쓰면 인터페이스 선언부의 E 자리에 User 가 들어가서, add(E e) 는 add(User e) 가 되고 get(int index) 의 반환 타입은 User 가 된다. 우리가 지금까지 만들어본 Box&amp;lt;T&amp;gt; 랑 원리가 완전히 같다. List, Map, Set 같은 표준 라이브러리도 똑같은 제네릭 문법으로 만들어진 인터페이스일 뿐이라는 거다. 헷갈리면 자바독 가서 직접 선언부 확인해보면 바로 감이 온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;공통 응답/래퍼 클래스&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class ApiResponse&amp;lt;T&amp;gt; {
    private int status;
    private T data;
    private String message;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 타입만 다르고 구조는 똑같은 클래스를 매번 새로 만들지 않고 하나로 처리할 때 쓴다. ApiResponse&amp;lt;UserDto&amp;gt;, ApiResponse&amp;lt;OrderDto&amp;gt; 처럼 안에 담기는 데이터 타입만 바꿔서 재사용한다. 회사에서 API 응답 형식을 통일할 때 이런 식으로 T 를 써봤는데, 한 번 만들어두니까 컨트롤러마다 비슷한 응답 클래스를 또 만들 일이 없어서 편했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;저장소/DAO 패턴&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface Repository&amp;lt;T, ID&amp;gt; {
    T findById(ID id);
    List&amp;lt;T&amp;gt; findAll();
    void save(T entity);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다루는 엔티티 타입과 ID 타입은 매번 다른데, CRUD(생성/조회/수정/삭제) 로직 자체는 거의 똑같다. 제네릭으로 만들어두면 엔티티마다 똑같은 코드를 또 짜지 않아도 된다. Spring Data JPA의 JpaRepository&amp;lt;T, ID&amp;gt; 도 이 패턴이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비교/정렬 로직&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;r&quot;&gt;&lt;code&gt;public &amp;lt;T extends Comparable&amp;lt;T&amp;gt;&amp;gt; T findMax(List&amp;lt;T&amp;gt; list) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입이 달라도 &quot;서로 비교가 가능하다&quot;는 조건만 만족하면 동작하는 로직을 만들 때 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;유틸리티 메서드&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;public static &amp;lt;T&amp;gt; void swap(T[] arr, int i, int j) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입과 무관하게 똑같은 동작을 하는 메서드. 배열 swap, null 체크, 캐스팅 헬퍼 같은 게 여기 속한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;제네릭의 장단점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 안전성이 가장 큰 장점이다. 잘못된 타입이 들어가는 걸 코드 작성 단계(컴파일 시점)에 바로 잡아준다. 실행 중에 ClassCastException 같은 에러가 터지는 걸 미리 막아주는 셈이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐스팅이 사라진다. Object 로 받아서 매번 형변환하던 코드가 없어지니까 가독성도 올라간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 재사용성이 올라간다. 타입별로 거의 똑같은 클래스를 여러 개 만들 필요 없이, 하나의 클래스나 메서드로 다양한 타입을 처리한다. Repository&amp;lt;User, Long&amp;gt;, Repository&amp;lt;Order, String&amp;gt; 처럼 구조는 그대로 두고 타입만 바꿔서 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 소거(컴파일 후 런타임에는 타입 파라미터 정보가 사라지는 것, 바로 다음 섹션에서 자세히 다룬다) 때문에 생기는 제약이 꽤 있다. new T() 같은 코드를 못 쓰고, instanceof T 같은 검사도 못 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드가 복잡해 보일 수 있다. Map&amp;lt;String, List&amp;lt;Map&amp;lt;String, Object&amp;gt;&amp;gt;&amp;gt; 처럼 제네릭이 여러 겹 겹치면 처음 보는 사람은 읽기 부담스럽다. 와일드카드(&amp;lt;? extends T&amp;gt;, &amp;lt;? super T&amp;gt;) 까지 섞이면 진입 장벽이 더 올라간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열과 같이 쓸 때 제약이 있다. new T[10] 같은 제네릭 배열 생성이 안 되기 때문에, 우회 방법(@SuppressWarnings(&quot;unchecked&quot;) 로 경고를 무시하는 캐스팅 등)을 써야 하는 경우가 생긴다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;제네릭 사용시 주의해야 할 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭에는 몇 가지 제약이 있는데, 이유를 모르고 보면 &quot;왜 이게 안 되지&quot; 하고 막힐 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;타입 파라미터로 인스턴스 생성 불가&lt;/h3&gt;
&lt;pre class=&quot;smali&quot;&gt;&lt;code&gt;public class Repo&amp;lt;T&amp;gt; {
    // 컴파일 에러
    T instance = new T();  // 안 됨
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 왜 안 되는지 이해하려면 자바 제네릭이 동작하는 방식을 알아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 컴파일러는 .java 코드를 .class 파일(바이트코드)로 바꾸는 과정에서 제네릭 타입 정보를 지워버린다. 이걸 &lt;b&gt;타입 소거(Type Erasure)&lt;/b&gt; 라고 부른다. 즉 우리가 코드 짤 때 보는 T, List&amp;lt;String&amp;gt; 같은 타입 정보는 컴파일 시점에만 존재하고, 컴파일이 끝나서 실제로 프로그램이 돌아가는 시점(런타임)에는 사라진다.&lt;/p&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;// 우리가 작성한 코드
Box&amp;lt;String&amp;gt; box1 = new Box&amp;lt;&amp;gt;(&quot;hello&quot;);
Box&amp;lt;Integer&amp;gt; box2 = new Box&amp;lt;&amp;gt;(42);

// 컴파일러가 바이트코드로 바꾸면 사실상 이런 모습 (타입 정보 사라짐)
Box box1 = new Box(&quot;hello&quot;);
Box box2 = new Box(42);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;List&amp;lt;String&amp;gt; 이든 List&amp;lt;Integer&amp;gt; 든 런타임에는 그냥 List 다. JVM(자바 프로그램을 실제로 실행하는 가상 머신)이 보기엔 둘이 똑같은 타입이라는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 new T() 가 왜 안 되는지 보자. new T() 는 &quot;T라는 타입의 인스턴스를 새로 만들어라&quot;는 명령이다. 그런데 런타임에는 T 가 뭐였는지 정보 자체가 없다. Box&amp;lt;String&amp;gt; 으로 썼는지 Box&amp;lt;Integer&amp;gt; 로 썼는지 이미 지워진 상태라서, JVM은 T 자리에 뭘 넣어서 객체를 만들어야 할지 알 방법이 없다. 그래서 컴파일러가 애초에 이 코드를 막아버리는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 이유로 T.class 같은 코드도 못 쓰고, instanceof T 같은 타입 검사도 못 한다. 전부 &quot;런타임에 T가 뭔지 모른다&quot;는 동일한 원인에서 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 우회하고 싶으면 타입 정보를 직접 넘겨주는 방법을 쓴다. 자주 보이는 패턴이 Class&amp;lt;T&amp;gt; 를 매개변수로 받는 것이다.&lt;/p&gt;
&lt;pre class=&quot;fsharp&quot;&gt;&lt;code&gt;public class Repo&amp;lt;T&amp;gt; {
    private Class&amp;lt;T&amp;gt; type;

    public Repo(Class&amp;lt;T&amp;gt; type) {
        this.type = type;
    }

    public T createInstance() throws Exception {
        return type.getDeclaredConstructor().newInstance();  // 리플렉션으로 생성
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Repo&amp;lt;User&amp;gt; repo = new Repo&amp;lt;&amp;gt;(User.class);
User user = repo.createInstance();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Class&amp;lt;T&amp;gt; 를 생성자로 직접 받아두면, 런타임에도 &quot;이 타입은 User다&quot; 라는 정보를 들고 있게 된다. 그래서 리플렉션(클래스 정보를 이용해서 객체를 동적으로 다루는 기능)을 통해 인스턴스를 만들 수 있다. 다만 이건 어디까지나 우회법이고, 일반적인 상황에서는 new T() 가 필요한 구조 자체를 다시 생각해보는 게 맞다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;static 멤버에 타입 파라미터 사용 불가&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class Box&amp;lt;T&amp;gt; {
    // 컴파일 에러
    static T defaultValue;  // 안 됨
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;static 멤버는 인스턴스 하나하나가 아니라 클래스 자체에 딱 하나만 존재한다. 그런데 T 는 인스턴스를 만들 때마다 달라질 수 있는 값이다. Box&amp;lt;String&amp;gt; 을 만들면 T 가 String 이고, Box&amp;lt;Integer&amp;gt; 를 만들면 T 가 Integer 다. 만약 static T defaultValue 가 허용된다면, Box&amp;lt;String&amp;gt; 과 Box&amp;lt;Integer&amp;gt; 가 같은 defaultValue 를 공유하게 되는데 타입이 서로 다르니 충돌이 난다. 그래서 애초에 막혀 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어? 그런데 아까 제네릭 메서드 설명할 때 static 이 붙은 예시가 있었던 것 같은데, 싶을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;public static &amp;lt;T&amp;gt; T getFirst(List&amp;lt;T&amp;gt; list) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 분명 static 인데 T 를 멀쩡하게 쓰고 있다. 방금 안 된다고 했는데 왜 되는 걸까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차이는 이 T 가 어디 소속이냐다. Box&amp;lt;T&amp;gt; 의 T 는 클래스에 묶여있는 타입이다. 누군가 Box&amp;lt;String&amp;gt; 으로 쓰고 누군가 Box&amp;lt;Integer&amp;gt; 로 쓰는데, static 멤버는 그 둘 사이에서 단 하나만 존재해야 하니까 &quot;어느 쪽 T냐&quot;를 정할 수가 없어서 막힌 거였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 getFirst 의 &amp;lt;T&amp;gt; 는 메서드 이름 앞에 따로 선언되어 있다. 이건 클래스의 T 를 빌려쓰는 게 아니라, 이 메서드만의 T 를 새로 만든 것이다. 메서드가 호출될 때마다 그 순간에 맞는 타입으로 새로 정해진다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;String s = Util.getFirst(List.of(&quot;a&quot;, &quot;b&quot;));   // 이 호출에서는 T가 String
Integer i = Util.getFirst(List.of(1, 2, 3));   // 이 호출에서는 T가 Integer
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;static 멤버는 &quot;클래스 전체에 고정된 하나의 값&quot;이 문제였는데, 이 T 는 고정된 값이 아니라 호출될 때마다 새로 정해지는 값이라서 충돌이 안 난다. static 메서드 안에서 지역 변수를 매번 새로 받는 것과 비슷한 느낌이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면, 안 되는 건 &quot;클래스의 T를 static 멤버가 그대로 가져다 쓰는 것&quot;이고, getFirst 는 &quot;메서드 스스로 독립적인 T를 선언한 것&quot;이라서 서로 다른 얘기였던 거다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 타입 사용 불가&lt;/h3&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;List&amp;lt;int&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();    // 안 됨
List&amp;lt;Integer&amp;gt; list = new ArrayList&amp;lt;&amp;gt;(); // OK
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭의 타입 파라미터에는 참조 타입(객체로 다뤄지는 타입)만 들어갈 수 있다. int, double, char 같은 기본 타입(primitive type)은 들어갈 수 없고, 대신 그 타입을 객체로 감싼 래퍼 클래스(Integer, Double, Character)를 써야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념 요약&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;제네릭 클래스 &amp;lt;T&amp;gt;&lt;/td&gt;
&lt;td&gt;타입을 파라미터로 받는 클래스. 재사용성 높아짐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;제네릭 메서드 &amp;lt;T&amp;gt;&lt;/td&gt;
&lt;td&gt;메서드 단위 타입 파라미터. static 유틸에서 자주 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;바운디드 &amp;lt;T extends X&amp;gt;&lt;/td&gt;
&lt;td&gt;특정 타입 이하로 제한. T의 메서드 사용 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;와일드카드 &amp;lt;?&amp;gt;&lt;/td&gt;
&lt;td&gt;타입 불특정. 읽기 전용 상황에서 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;? extends T&amp;gt;&lt;/td&gt;
&lt;td&gt;상한 경계. T 하위 타입만. 읽기에 유리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;? super T&amp;gt;&lt;/td&gt;
&lt;td&gt;하한 경계. T 상위 타입만. 쓰기에 유리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;주요 사용처&lt;/td&gt;
&lt;td&gt;컬렉션, 공통 응답 래퍼, Repository/DAO, 비교/정렬, 유틸 메서드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;장점&lt;/td&gt;
&lt;td&gt;타입 안전성, 캐스팅 제거, 코드 재사용성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;단점&lt;/td&gt;
&lt;td&gt;타입 소거로 인한 제약, 중첩 시 가독성 저하, 배열 생성 제약&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;타입 소거&lt;/td&gt;
&lt;td&gt;컴파일 후 런타임엔 타입 정보 없음. new T() 불가 이유&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <category>Generic</category>
      <category>Java</category>
      <category>t</category>
      <category>type</category>
      <category>와일드카드</category>
      <category>제네릭</category>
      <category>타입소거</category>
      <author>fkqlaus</author>
      <guid isPermaLink="true">https://fkqlaus.tistory.com/25</guid>
      <comments>https://fkqlaus.tistory.com/25#entry25comment</comments>
      <pubDate>Wed, 17 Jun 2026 15:42:17 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] HikariCP, Redis Lettuce Pool 운영 병목 분석</title>
      <link>https://fkqlaus.tistory.com/24</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 이번에 맡은 프로젝트 배포 중 운영서버 배포 후&amp;nbsp; 대시보드 화면을 반복해서 새로고침하면 페이지 전체가 잠깐 응답하지 않는 현상이 생겼다. DB가 죽은 것도 아니고, 애플리케이션이 다운된 것도 아니었는데 무슨 문제일까 천천히 찾아보다가 원인으로 추정되는 문제들을 하나씩 뜯어봤고 원인이 하나가 아닌걸 알아버렸다... 그리고 이런 저런 방법들을 사용해보고 결국 해결했는데 겸사겸사 느낀점이나 관련 정보들을 정리하려고 글을 작성한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;새로고침 한 번이 요청 하나가 아니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에서 새로고침을 누르면 HTML 파일 하나만 다시 받을 것 같지만, 실제로는 다르다.&lt;/p&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;새로고침
-&amp;gt; HTML
-&amp;gt; CSS 파일들
-&amp;gt; JS 파일들
-&amp;gt; 이미지/아이콘
-&amp;gt; 로그인 알림 API
-&amp;gt; 대시보드 데이터 API (여러 개)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번의 새로고침이 수십 개의 요청을 동시에 발생시킨다. 그리고 이 요청들이 전부 서버를 통과한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 새로고침을 여러 번 누를 때다. 브라우저 입장에서는 이전 요청이 취소된 것처럼 보이지만, &lt;b&gt;서버에서는 이미 시작된 DB 쿼리나 세션 조회가 계속 진행된다.&lt;/b&gt; 그래서 새로고침을 누를수록 서버 내부에 처리 중인 요청이 계속 쌓인다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 Redis Session을 쓰게 됐나&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음부터 Redis Session을 쓰려던 건 아니었다. 원래는 단일 서버에 HTTP Session만 쓰면 충분했는데, 프로젝트 구조상 로그인 서버를 별도로 분리해야 하는 상황이 됐다. 로그인 서버와 업무 서버가 나뉘면 둘 사이에 세션을 공유할 방법이 필요해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택지는 세 가지였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방식 내용 문제점&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JWT 전환&lt;/td&gt;
&lt;td&gt;토큰 자체에 인증 정보 포함, 서버 간 공유 불필요&lt;/td&gt;
&lt;td&gt;인증 구조 전면 재작성 필요, 영향 범위 큼&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spring Session JDBC&lt;/td&gt;
&lt;td&gt;DB에 세션 저장해서 서버 간 공유&lt;/td&gt;
&lt;td&gt;DB 부하 증가, Redis보다 느림&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spring Session Redis&lt;/td&gt;
&lt;td&gt;Redis에 세션 저장해서 서버 간 공유&lt;/td&gt;
&lt;td&gt;기존 세션 코드 거의 그대로 유지 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT로 전환하면 인증 구조를 전면 재작성해야 해서 리스크가 컸다. Spring Session JDBC는 세션 조회가 DB를 타기 때문에 부하 측면에서 Redis보다 불리했다. Spring Session Redis는 기존 세션 기반 코드를 거의 손대지 않고 Redis만 공유 저장소로 추가하면 됐다. 그래서 Redis Session을 선택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 이 선택이 나중에 병목의 원인 중 하나가 됐다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;모든 요청이 Redis를 타고 있었다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security는 기본적으로 모든 HTTP 요청에 대해 보안 필터 체인을 실행한다. 그리고 Spring Session Redis를 사용하면 인증이 필요한 요청마다 Redis에서 세션을 조회하고 SecurityContext를 복원한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;요청 들어옴
-&amp;gt; Spring Security Filter Chain 실행
-&amp;gt; Redis에서 세션 조회
-&amp;gt; SecurityContext 복원
-&amp;gt; 컨트롤러 진입
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 흐름 자체는 문제가 없다. 문제는 &lt;b&gt;CSS 파일, JS 파일, 이미지&lt;/b&gt;도 같은 필터 체인을 타고 있었다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적 리소스는 인증이 필요 없다. 누가 요청하든 같은 파일을 내려주면 된다. 근데 Security 설정에서 정적 리소스 경로를 명시적으로 제외하지 않으면, JS 파일 하나를 받는 데도 Redis 세션 조회가 붙는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로고침 한 번에 정적 파일 요청이 수십 개 발생하고, 그게 전부 Redis를 타고 있었다. Redis 서버가 처리해야 할 세션 조회 횟수가 API 요청과는 비교도 안 되게 많았던 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결은 단순했다. Security 설정에서 정적 리소스 경로를 필터 대상에서 아예 제외했다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
    return web -&amp;gt; web.ignoring()
        .requestMatchers(&quot;/js/**&quot;, &quot;/css/**&quot;, &quot;/images/**&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;permitAll()이랑 다르다. permitAll()은 필터 체인은 그대로 타면서 요청을 허용하는 거라 Redis Session 조회가 여전히 붙는다. web.ignoring()은 필터 체인 자체를 건너뛰어서 Redis Session 조회가 아예 발생하지 않는다. 정적 리소스는 인증이 필요 없으니까 필터를 탈 이유가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것만으로 Redis 세션 조회 횟수가 크게 줄었다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Redis가 느린 게 아니라 너무 자주 타고 있었다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 Redis가 병목이라고 의심했다. 세션 조회가 너무 많으니 Redis가 느려졌을 거라고 봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Redis 상태를 직접 확인했다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;connected_clients: 5
blocked_clients: 0
SLOWLOG: empty
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis 서버 자체는 멀쩡했다. 처리 중인 슬로우 커맨드도 없고, 블로킹된 클라이언트도 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 구분이 있다. &lt;b&gt;Redis 서버가 느린 것과 Redis를 너무 자주 타는 구조가 병목인 것은 다르다.&lt;/b&gt; Redis가 아무리 빠른 인메모리 저장소라도, 인증 요청 수가 폭증하면 앱 쪽 Lettuce 커넥션 풀이 먼저 바닥난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis 서버 증설이 아니라 앱 구조와 Lettuce pool 설정을 손봐야 하는 이유였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lettuce는 Spring에서 Redis에 접근할 때 쓰는 클라이언트인데, 여기도 커넥션 풀이 있다. Redis 서버가 아무리 여유 있어도 앱 쪽 Lettuce pool이 작으면 Redis 요청이 몰릴 때 앱 내부에서 pool 대기가 생긴다. 그래서 pool 크기를 늘리고 timeout을 줄였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 건 이게 HikariCP 문제와 별개가 아니라는 점이다. 요청 하나가 Redis Session 조회와 DB 쿼리를 모두 거친다. 둘 중 하나만 막혀도 그 요청을 처리하던 Tomcat 스레드가 묶인다. 그래서 어느 한쪽만 고쳐서는 완전히 해소가 안 됐던 것이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DB 커넥션 풀이 빠르게 고갈되는 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적 리소스 문제를 해결해도 대시보드 API들은 여전히 DB를 사용한다. 여기서 HikariCP 커넥션 풀 구조를 봐야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB에 쿼리를 날리려면 커넥션을 먼저 확보해야 한다. 매 요청마다 커넥션을 새로 만들면 TCP 핸드셰이크, DB 인증, 세션 초기화 과정이 반복되니까 커넥션 풀을 미리 만들어두고 빌려 쓰는 방식을 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;b&gt;풀 크기가 동시에 DB를 사용할 수 있는 요청 수의 상한을 결정한다&lt;/b&gt;는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시 커넥션 풀이 너무 작아서 새로고침이 반복되면서 대시보드 API 요청이 몰리면 풀이 금방 소진됐다. 나머지 요청들은 커넥션이 날 때까지 기다리며 스레드를 점유한 채로 쌓였다.&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;동시 요청 N개가 DB 커넥션 요청
-&amp;gt; 풀 크기만큼만 커넥션 확보 후 쿼리 실행
-&amp;gt; 나머지는 커넥션 풀 앞에서 대기

connection-timeout: 30초
-&amp;gt; 대기 중인 스레드들이 30초 동안 살아서 자리를 차지
-&amp;gt; 그 사이 새 요청이 들어와도 처리할 스레드가 줄어든 상태
-&amp;gt; 사용자 입장에서 서버가 멈춘 것처럼 보임
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;커넥션 풀 크기를 보수적으로 늘린 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이 작다는 건 확실했다. 근데 바로 크게 올리지 않고 서버 상황 보면서 테스트해가며 늘렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB가 애플리케이션 서버 외부에 별도 서버로 있었기 때문이다. 풀을 크게 올리면 앱 서버 내부 대기는 줄어들지만, 그만큼 더 많은 동시 쿼리가 외부 DB 서버로 밀려든다. &lt;b&gt;앱 서버의 병목이 DB 서버 장애로 이동하는 것이다.&lt;/b&gt; PostgreSQL의 pg_stat_activity로 active connection, lock wait 여부를 보면서 조금씩 올리는 게 맞는 접근이었다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;connection-timeout을 30초에서 10초로 줄인 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커넥션을 기다리는 최대 시간이 30초면, 풀이 고갈됐을 때 요청 스레드들이 30초 동안 살아서 Tomcat 스레드를 점유한다. 새 요청이 들어와도 처리할 스레드가 줄어드는 구조다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10초로 줄이면 병목이 생겼을 때 더 빨리 실패한다. &lt;b&gt;장애를 오래 숨기는 것보다 빨리 드러내는 것이 서버 전체를 보호하는 방향이다.&lt;/b&gt; 30초를 매달리는 것보다 10초 안에 실패하고 상위에서 빠르게 복구 흐름을 타는 편이 낫다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;커넥션 풀 크기를 공식으로 계산하면 안 되는 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커넥션 풀 크기를 정할 때 자주 인용되는 공식이 있다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;pool size = Tn &amp;times; (Cm - 1) + 1

Tn: 동시에 DB를 사용하는 최대 스레드 수
Cm: 스레드 하나가 동시에 잡을 수 있는 최대 커넥션 수
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 이 공식은 데드락을 피하기 위한 최솟값에 가깝다. 일반적인 Spring + MyBatis 구조에서는 요청 하나가 커넥션을 동시에 여러 개 잡는 경우가 거의 없으니 Cm = 1이고, 그러면 공식상 최솟값은 굉장히 작게 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 사례에서 공식보다 중요했던 건 요청 흐름이었다. 새로고침 한 번이 요청 하나가 아니었고, 각 요청이 Redis Session과 DB를 모두 사용했다. 공식에는 이런 맥락이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영에서 풀 크기를 정할 때 같이 봐야 하는 게 있다. 한 화면이 발생시키는 API 수, 각 API가 DB를 점유하는 시간, DB가 외부 서버인지 여부, Redis 같은 공통 앞단 병목까지. 공식은 참고값이고 실제로는 운영 요청 패턴을 보고 잡아야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인은 한 계층이 아니었다. 정적 리소스가 Security 필터를 타면서 Redis Session 조회가 폭증했고, 거기에 커넥션 풀 부족으로 DB 대기 요청까지 쌓이면서 증상이 겹쳤다. 어느 하나만 고쳐서는 완전히 해소가 안 됐던 이유가 여기 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring boot</category>
      <category>ConnectionPool</category>
      <category>db</category>
      <category>HIKARI</category>
      <category>Java</category>
      <category>redis</category>
      <category>Session</category>
      <category>spring</category>
      <category>데이터베이스</category>
      <author>fkqlaus</author>
      <guid isPermaLink="true">https://fkqlaus.tistory.com/24</guid>
      <comments>https://fkqlaus.tistory.com/24#entry24comment</comments>
      <pubDate>Fri, 29 May 2026 10:29:41 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] Spring IoC 와 DI 개념정리</title>
      <link>https://fkqlaus.tistory.com/23</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring을 쓰다 보면 IoC, DI라는 말을 한번쯤은 듣게 된다. 근데 막상 &quot;IoC가 뭐예요?&quot;라고 물어보면 &quot;제어의 역전이라던데요?&quot;라고 답하고 끝나는 경우가 많다. 그게 왜 필요한지, 어떤 문제를 해결하는지까지 이어지지 않으면 쓸 때마다 그냥 Spring이 해주는 거겠지 하고 넘어가게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다들 들어는 봤을테고 대충 알고도 있지만 조금 더 자세하게 정리하기 위해 글을 작성해본다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;객체는 혼자 동작하지 않는다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 짜다 보면 하나의 클래스가 다른 클래스의 기능을 쓰는 일이 생긴다. 주문 서비스가 결제 모듈을 써야 하거나, 알림 서비스가 이메일 발송 모듈을 써야 하는 식으로.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class OrderService {

    public void order(String item, int price) {
        // 결제를 해야 하는데... PaymentProcessor가 필요하다
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OrderService가 결제 기능을 쓰려면 PaymentProcessor라는 객체가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 관계를 &lt;b&gt;&quot;OrderService는 PaymentProcessor에 의존한다&quot;&lt;/b&gt; 고 표현한다. 없으면 동작을 못 하니까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 단순한 해결책은 이거다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class OrderService {

    private PaymentProcessor paymentProcessor = new PaymentProcessor();

    public void order(String item, int price) {
        paymentProcessor.pay(price);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하다. 근데 이게 나중에 문제가 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;new로 직접 만들면 무슨 문제가 생기나&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OrderService 안에서 PaymentProcessor를 직접 new로 만들면 둘은 강하게 묶인다.&lt;/p&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;public class OrderService {
    private PaymentProcessor paymentProcessor = new PaymentProcessor();
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PaymentProcessor 내부 구현이 바뀌거나, 다른 결제 방식으로 교체해야 할 때 OrderService 코드를 열어서 직접 고쳐야 한다. 같은 방식으로 new PaymentProcessor()를 쓰는 클래스가 여러 개라면 그 클래스들을 전부 찾아다녀야 한다. 결제 모듈이 바뀐 건데 왜 주문 로직, 구독 로직, 환불 로직을 다 열어야 하나.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 &lt;b&gt;강한 결합(Tight Coupling)&lt;/b&gt; 이다. 한 클래스가 다른 클래스의 구체적인 구현에 직접 묶여있어서, 하나를 바꾸면 연쇄적으로 수정이 생기는 것이다. (참고로 결합도는 낮을수록 좋다.)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;IoC &amp;mdash; 직접 생성하지 않고, 외부에서 받아오기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;IoC(Inversion of Control, 제어의 역전)&lt;/b&gt; 는 다른 객체를 직접 생성하거나 제어하는 것이 아니라, 외부에서 관리하는 객체를 가져와 사용하는 것을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말로 들으면 추상적이니까 코드로 보자. IoC를 적용하면 OrderService가 이렇게 바뀐다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// IoC 적용 전 &amp;mdash; 내가 직접 만든다
public class OrderService {
    private PaymentProcessor paymentProcessor = new PaymentProcessor();
    ...
}

// IoC 적용 후 &amp;mdash; 외부에서 받아온다
public class OrderService {
    private PaymentProcessor paymentProcessor;

    public OrderService(PaymentProcessor paymentProcessor) {
        this.paymentProcessor = paymentProcessor;
    }
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;new PaymentProcessor()가 사라졌다. OrderService 안에 직접 생성하는 코드가 없다. 어딘가에서 만들어진 객체를 받아와서 쓰고 있다는 걸 추측할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 제어가 역전된 것이다. 원래는 OrderService가 PaymentProcessor를 스스로 만들고 제어했는데, 이제는 외부에서 만들어서 건네준다. 제어권이 OrderService에서 외부로 넘어갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 &lt;b&gt;외부&lt;/b&gt;가 스프링에서는 &lt;b&gt;스프링 컨테이너(ApplicationContext)&lt;/b&gt; 다. 스프링 컨테이너가 PaymentProcessor 객체를 만들어서 들고 있다가, OrderService가 필요로 할 때 넣어준다. OrderService는 PaymentProcessor가 어떻게 만들어졌는지, 누가 만들었는지 모른다. 그냥 받아서 쓸 뿐이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;라이브러리 vs 프레임워크 &amp;mdash; IoC로 설명하면&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IoC를 이해하면 라이브러리와 프레임워크의 차이도 같은 맥락으로 설명된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리는 내가 필요할 때 꺼내서 쓴다. 제어권이 나한테 있다. 언제 어떻게 쓸지 내가 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레임워크는 반대다. 내가 만든 코드를 프레임워크가 필요한 시점에 호출한다. @Service를 붙여두면 Spring이 알아서 인스턴스를 만들고, HTTP 요청이 들어오면 Spring이 알아서 @Controller의 메서드를 호출한다. 내가 호출하는 게 아니라 Spring이 내 코드를 사용하는 구조다. 제어권이 프레임워크에 있다. 이게 IoC라고 생각하면 될 것 같다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DI &amp;mdash; IoC를 실현하는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DI(Dependency Injection, 의존성 주입)&lt;/b&gt; 는 IoC를 구현하는 구체적인 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 게 하나 있다. IoC와 DI는 같은 개념이 아니다. &lt;b&gt;DI &amp;sub; IoC&lt;/b&gt;다. IoC는 더 넓은 개념이고, DI는 IoC를 구현하는 방법 중 하나다. Spring은 IoC를 DI 방식으로 구현한 것이다. 면접에서 &quot;IoC랑 DI 같은 거 아니에요?&quot;라고 물으면 이 관계를 명확히 답해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;외부에서 결정해서 넣어줘&quot;를 실제로 어떻게 구현할 것인가? 이미 위에서 봤다. 생성자 파라미터로 받으면 된다. 이게 주입(Injection)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주입 방식은 세 가지가 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;필드 주입&lt;/h3&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;@Service
public class OrderService {

    @Autowired
    private PaymentProcessor paymentProcessor;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;세터 주입&lt;/h3&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class OrderService {

    private PaymentProcessor paymentProcessor;

    @Autowired
    public void setPaymentProcessor(PaymentProcessor paymentProcessor) {
        this.paymentProcessor = paymentProcessor;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;생성자 주입 (보통 이걸로..)&lt;/h3&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class OrderService {

    private final PaymentProcessor paymentProcessor;

    public OrderService(PaymentProcessor paymentProcessor) {
        this.paymentProcessor = paymentProcessor;
    }

    public void order(String item, int price) {
        paymentProcessor.pay(price);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring은 이 클래스를 Bean으로 만들 때 생성자 파라미터를 보고 PaymentProcessor 타입의 Bean을 찾아서 자동으로 넣어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통은 Lombok의 @RequiredArgsConstructor를 사용해 final 필드를 모아서 생성자를 자동으로 만들어준다. 직접 생성자 코드를 쓰지 않아도 된다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class OrderService {

    private final PaymentProcessor paymentProcessor;
    // Lombok이 위 필드로 생성자를 자동 생성해줌
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그래서 Spring이 하는 일이 뭐냐&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 생성자 주입 예시를 봤는데, 한 가지 빠진 게 있다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// Spring이 없다면, 결국 누군가 이 코드를 실행해야 한다
PaymentProcessor processor = new CardPaymentProcessor();
OrderService orderService = new OrderService(processor);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체를 생성자 주입 방식으로 설계하면 결합은 줄어들지만, 결국 &lt;b&gt;어딘가에서는 new를 해야 한다&lt;/b&gt;. 의존성이 깊어질수록 이게 복잡해진다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 실제 상황은 이 정도 되면 손으로 관리하기 힘들다
DataSource dataSource = new HikariDataSource();
UserRepository userRepository = new UserRepository(dataSource);
EmailSender emailSender = new EmailSender(&quot;smtp.gmail.com&quot;, 587);
OrderRepository orderRepository = new OrderRepository(dataSource);
PaymentProcessor paymentProcessor = new CardPaymentProcessor();
NotificationService notificationService = new NotificationService(emailSender);
OrderService orderService = new OrderService(userRepository, orderRepository, paymentProcessor, notificationService);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring의 IoC Container가 이 new 작업을 전부 대신 해준다. 개발자는 클래스에 어노테이션만 붙이면 된다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Service
public class OrderService {
    // Spring이 알아서 주입해줌
}

@Repository
public class OrderRepository {
    // Spring이 알아서 주입해줌
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring은 @Service, @Repository, @Controller 같은 어노테이션이 붙은 클래스를 스캔해서 직접 인스턴스를 만들고, 생성자를 분석해서 필요한 의존성을 순서에 맞게 주입한다. 이렇게 Spring이 생성하고 관리하는 객체를 &lt;b&gt;Bean&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ApplicationContext &amp;mdash; Container의 실체&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring이 Bean을 담아두는 컨테이너가 &lt;b&gt;ApplicationContext&lt;/b&gt;다. 애플리케이션이 시작할 때 Bean을 전부 만들어서 여기에 저장해두고, 필요한 곳에 꺼내서 주입한다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;ApplicationContext
├── &quot;orderService&quot;      &amp;rarr; OrderService 인스턴스 (딱 하나)
├── &quot;orderRepository&quot;   &amp;rarr; OrderRepository 인스턴스 (딱 하나)
├── &quot;paymentProcessor&quot;  &amp;rarr; CardPaymentProcessor 인스턴스 (딱 하나)
└── ...

각 Bean은 기본적으로 딱 하나만 존재한다 (싱글톤)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot를 쓰면 이 ApplicationContext를 직접 만들 필요가 없다. @SpringBootApplication이 붙은 메인 클래스에서 SpringApplication.run()을 호출하는 것만으로 ApplicationContext 생성, Bean 등록, 내장 Tomcat 시작까지 전부 자동으로 처리된다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot가 이걸 자동화할 수 있는 이유는 &lt;b&gt;Auto Configuration&lt;/b&gt; 덕분이다. classpath에 어떤 라이브러리가 있는지 감지해서 필요한 Bean을 알아서 등록해준다. JPA 라이브러리가 있으면 EntityManagerFactory를, Redis 라이브러리가 있으면 RedisTemplate을 자동으로 만들어준다. 우리는 application.yml에 접속 정보만 쓰면 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Bean 등록 방법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어노테이션으로 등록&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Controller  // HTTP 요청을 받는 클래스
@Service     // 비즈니스 로직을 담는 클래스
@Repository  // DB 접근을 담당하는 클래스
@Component   // 위 3개에 해당하지 않는 나머지
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 어노테이션들 내부에는 전부 @Component가 들어있다. Spring은 시작할 때 @ComponentScan으로 이 어노테이션이 붙은 클래스를 전부 찾아서 Bean으로 등록한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@Configuration + @Bean으로 등록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 라이브러리 클래스는 소스코드를 열어서 @Component를 붙일 수가 없다. 이 경우엔 설정 클래스에서 직접 Bean으로 등록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 예가 비밀번호 암호화에 쓰는 PasswordEncoder다. BCryptPasswordEncoder는 Spring Security가 제공하는 클래스라 우리가 @Component를 붙일 수 없다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Configuration
public class SecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 등록해두면 UserService에서 생성자 주입으로 그냥 받아다 쓰면 된다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@Service
public class UserService {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder; // Spring이 위에서 등록한 Bean을 주입해줌

    public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
    }

    public void join(String email, String password) {
        String encodedPassword = passwordEncoder.encode(password); // 암호화
        userRepository.save(email, encodedPassword);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PasswordEncoder를 직접 new BCryptPasswordEncoder()로 만들지 않는다. 스프링 컨테이너가 관리하는 Bean을 받아서 쓰는 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DI는 Bean끼리만 된다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 가지 중요한 포인트가 있다. &lt;b&gt;Spring의 DI는 Bean으로 등록된 객체들 사이에서만 작동한다.&lt;/b&gt; Bean이 아닌 일반 객체에는 Spring이 주입을 해주지 않는다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;// ❌ Bean이 아닌 일반 클래스 &amp;mdash; Spring이 주입 못 해줌
public class SomeHelper {

    @Autowired
    private UserRepository userRepository; // 주입 안 됨. null이다.
}

// ✅ Bean으로 등록된 클래스 &amp;mdash; Spring이 주입해줌
@Component
public class SomeHelper {

    private final UserRepository userRepository;

    public SomeHelper(UserRepository userRepository) {
        this.userRepository = userRepository; // 정상 주입
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 진행하다가 &quot;분명히 주입했는데 왜 null이지?&quot;라는 상황이 생기면(물론 그럴 일은 거의 없음) 십중팔구 둘 중 하나가 Bean이 아니라서 생기는 문제다. 어노테이션을 빠뜨렸거나, new로 직접 만든 객체에서 주입을 기대하고 있거나.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Bean으로 등록해야 하는 것 vs 아닌 것&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 코드를 짜다 보면 이걸 Bean으로 등록해야 하나 말아야 하나 헷갈릴 수도 있다. 판단 기준은 단순하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로직을 담은 도구인가, 데이터를 담는 그릇인가.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로직을 담은 도구는 Bean으로 등록한다. UserService, PasswordEncoder, FieldCryptoUtil 같은 것들이다. 이것들은 상태가 없고, 어디서든 같은 방식으로 동작한다. Spring이 하나만 만들어서 필요한 곳에 주입해줘도 아무 문제없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 담는 그릇은 Bean으로 등록하지 않는다. DTO, Entity, 그리고 아래 같은 경우다.&lt;/p&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;// Bean 등록 X
public class CustomUserDetails implements UserDetails {
    private String id;
    private String password;
    private Role role;
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CustomUserDetails는 로그인한 사용자마다 다른 id, password, role을 담아야 한다. 싱글톤으로 하나만 있으면 안 된다. 그래서 UserDetailsService에서 로그인 요청이 들어올 때마다 직접 new로 만든다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service // 이건 Bean &amp;mdash; 로직을 담은 도구
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository; // 주입받아야 쓸 수 있음

    @Override
    public UserDetails loadUserByUsername(String id) {
        User user = userRepository.findById(id);

        CustomUserDetails userDetails = new CustomUserDetails(); // 이건 new &amp;mdash; 데이터 그릇
        userDetails.setId(user.getId());
        userDetails.setRole(user.getRole());
        return userDetails;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 이렇다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bean 등록 new로 직접 생성&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;성격&lt;/td&gt;
&lt;td&gt;로직을 담은 도구&lt;/td&gt;
&lt;td&gt;데이터를 담는 그릇&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;상태&lt;/td&gt;
&lt;td&gt;없음 (재사용 가능)&lt;/td&gt;
&lt;td&gt;있음 (요청마다 다름)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;예시&lt;/td&gt;
&lt;td&gt;UserService, PasswordEncoder, FieldCryptoUtil&lt;/td&gt;
&lt;td&gt;CustomUserDetails, DTO, Entity&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;싱글톤이라는 것&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bean은 기본적으로 싱글톤이다. ApplicationContext에 딱 하나만 존재하고, 주입이 필요한 모든 곳에 같은 인스턴스가 들어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 싱글톤으로 만드냐면, 서버는 동시에 수백 개의 요청을 처리한다. 요청마다 OrderService, UserService 같은 객체를 새로 만들면 메모리 낭비가 심하고 GC에 부담이 생긴다. 그래서 하나만 만들어두고 모든 요청이 재사용하는 방식을 택한 것이다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;SubscriptionService ──&amp;rarr; 
                         PaymentProcessor 인스턴스 #1 (하나만 존재)
OrderService        ──&amp;rarr;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SubscriptionService와 OrderService 둘 다 PaymentProcessor를 주입받지만 같은 객체다. 매 요청마다 새로 만들지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 여기서 주의할 게 있다. 하나의 인스턴스를 수백 개의 요청(스레드)이 동시에 쓴다는 뜻이기도 하다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;// ❌ 위험한 코드
@Service
public class OrderService {

    private int totalOrderPrice; // 필드에 상태를 저장

    public void order(String item, int price) {
        this.totalOrderPrice = price;

        // Thread-1이 totalOrderPrice = 15000 설정
        // Thread-2가 totalOrderPrice = 25000 으로 덮어씀
        // Thread-1이 totalOrderPrice를 읽으면 25000 &amp;rarr; 다른 요청의 데이터!

        processOrder(this.totalOrderPrice);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;싱글톤 Bean에서 필드에 상태를 저장하면 여러 스레드가 동시에 그 값을 덮어쓰면서 데이터가 꼬인다. 그러니까 상태를 필드에 두지 말고 메서드 파라미터로 넘기면 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념 한 줄 요약&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;의존성&lt;/td&gt;
&lt;td&gt;A가 동작하려면 B가 반드시 필요한 관계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;강한 결합&lt;/td&gt;
&lt;td&gt;new로 직접 만들면 구현체에 묶임. 바꾸려면 코드를 열어야 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IoC&lt;/td&gt;
&lt;td&gt;객체를 만드는 결정권을 클래스 밖으로 넘기는 것&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DI&lt;/td&gt;
&lt;td&gt;IoC를 구현하는 방법. 의존성 주입&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;라이브러리 vs 프레임워크&lt;/td&gt;
&lt;td&gt;라이브러리는 내가 호출, 프레임워크는 내 코드를 호출 &amp;mdash; 이게 IoC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DI 방법&lt;/td&gt;
&lt;td&gt;생성자 주입&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bean&lt;/td&gt;
&lt;td&gt;Spring이 직접 만들고 관리하는 객체&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ApplicationContext&lt;/td&gt;
&lt;td&gt;Bean을 저장하고 주입해주는 컨테이너&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DI 조건&lt;/td&gt;
&lt;td&gt;Bean으로 등록된 객체끼리만 주입 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spring Bean 싱글톤 이유&lt;/td&gt;
&lt;td&gt;요청마다 객체 생성하면 메모리 낭비. 하나만 만들어 재사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;싱글톤 주의&lt;/td&gt;
&lt;td&gt;Bean은 하나만 존재. 필드에 상태 저장하면 스레드 간 데이터 꼬임&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring이 자동으로 해주는 게 많아서 내부에서 뭐가 일어나는지 모르고 쓰는 경우가 많은데, 너무 Spring 이라는 프레임워크에만&amp;nbsp; 의존하지말고 왜 그렇게 돌아가고 왜 이렇게 구현이 되어 있는지 알고 사용하는 것과 모르고 사용하는 건 큰 차이가 있다고 생각한다!!!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;혹시 잘못된 내용이 있다면 댓글 부탁드립니다!&lt;/p&gt;</description>
      <category>Spring boot</category>
      <category>di</category>
      <category>IOC</category>
      <category>Java</category>
      <category>spring</category>
      <category>spring bean</category>
      <category>Spring IoC</category>
      <category>SpringBoot</category>
      <category>스프링</category>
      <category>싱글톤</category>
      <category>의존성주입</category>
      <author>fkqlaus</author>
      <guid isPermaLink="true">https://fkqlaus.tistory.com/23</guid>
      <comments>https://fkqlaus.tistory.com/23#entry23comment</comments>
      <pubDate>Wed, 15 Apr 2026 13:29:26 +0900</pubDate>
    </item>
    <item>
      <title>[Java] HashMap 은 뭐고 어떻게 동작 하는 걸까?</title>
      <link>https://fkqlaus.tistory.com/22</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;HashMap은 Java를 쓰다 보면 자연스럽게 자주 마주치는 자료구조다. 회사 프로젝트 진행 중에 WebSocket 프록시 서버를 구현할 일이 있었는데, 브라우저 클라이언트 세션과 외부 CCTV 스트리밍 서버 쪽 세션을 1:1로 매핑해서 보관해야 하는 상황이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;처음에는 HashMap을 쓰려고 했는데, WebSocket 특성상 여러 클라이언트가 동시에 연결되고 끊기는 일이 발생하다 보니 멀티스레드 환경에서 HashMap을 그냥 쓰면 안 된다는 걸 알게 됐다. 결국 ConcurrentHashMap을 썼는데, 정작 HashMap 자체를 제대로 정리한 적이 없다는 생각이 들어서 이번 기회에 HashMap부터 차근차근&amp;nbsp;정리해보려&amp;nbsp;한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;HashMap을 쓰는 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순하다. &lt;b&gt;조회가 빠르다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트에서 원하는 값을 찾으려면 처음부터 하나씩 돌아야 한다. 데이터가 만 개면 최악의 경우 만 번 비교한다. O(n).&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;// 리스트 - O(n)
for (User user : users) {
    if (user.getId().equals(targetId)) { ... }
}

// HashMap - O(1)
User user = userMap.get(targetId);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HashMap은 Key만 알면 데이터 수에 상관없이 거의 즉시 꺼낼 수 있다. 그 이유가 내부 구조에 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;내부 구조: 배열 + 연결리스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HashMap 내부는 Node&amp;lt;K,V&amp;gt; 배열이다. 이 배열의 각 칸을 &lt;b&gt;버킷(bucket)&lt;/b&gt; 이라고 부른다.&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;// HashMap 내부 (JDK 소스)
transient Node&amp;lt;K,V&amp;gt;[] table;

static class Node&amp;lt;K,V&amp;gt; implements Map.Entry&amp;lt;K,V&amp;gt; {
    final int hash;
    final K key;
    V value;
    Node&amp;lt;K,V&amp;gt; next;  // 충돌 시 연결리스트로 이어짐
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;put(&quot;userId&quot;, &quot;kwanghyuk&quot;)을 호출하면 내부에서 이런 순서로 동작한다.&lt;/p&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;1. &quot;userId&quot;.hashCode() 계산
2. 내부 hash() 함수로 한 번 더 가공
3. 배열 인덱스 결정
4. 해당 버킷에 Node 저장
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;버킷 배열 (기본 크기 16)
┌─────┬─────┬─────┬─────┬─────┬─────┐
│  0  │  1  │  2  │  3  │ ... │ 15  │
└─────┴─────┴──┬──┴─────┴─────┴─────┘
               │
               └─ Node { hash, &quot;userId&quot;, &quot;kwanghyuk&quot;, next=null }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;get(&quot;userId&quot;)도 마찬가지다. &quot;userId&quot;의 해시를 계산해서 인덱스를 구하고, 그 버킷에 바로 접근한다. 배열 인덱스 접근이니까 O(1)이 나오는 것.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;hash() 함수가 하는 일&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java의 모든 객체는 hashCode()를 가진다. HashMap은 이걸 그대로 쓰지 않고 내부 hash() 함수로 한 번 가공한다.&lt;/p&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;// JDK 내부 hash() 함수
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h &amp;gt;&amp;gt;&amp;gt; 16);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hashCode()의 상위 16비트를 하위 16비트에 XOR 연산한다. hashCode 값이 배열 크기보다 훨씬 클 때 하위 비트만 쓰면 충돌이 많아지기 때문에 비트를 섞어서 충돌 확률을 낮추는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해시 충돌과 처리 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 키의 해시가 같은 인덱스로 떨어지면 &lt;b&gt;해시 충돌(Hash Collision)&lt;/b&gt; 이 발생한다. HashMap은 이걸 &lt;b&gt;Chaining&lt;/b&gt; 방식으로 처리한다. 같은 버킷에 연결리스트로 이어 붙이는 것.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;버킷[2]
  └─ Node { &quot;userId&quot;, &quot;kwanghyuk&quot; }
       └─ Node { &quot;nickname&quot;, &quot;fkqlaus&quot; }  &amp;larr; 충돌, 체이닝으로 연결
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조회할 때도 체이닝이 있으면 연결리스트를 순회해서 Key가 일치하는 노드를 찾는다. 충돌이 많아질수록 리스트가 길어지고 최악의 경우 O(n)까지 떨어진다. O(1)이라고 믿었던 HashMap이 O(n)이 되는 첫 번째 케이스다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Java 8부터 달라진 점이 있다? : 트리화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 8 이전까지는 충돌이 아무리 많아도 그냥 연결리스트였다고 한다. 알아보니까 Java 8부터 &lt;b&gt;한 버킷의 체이닝이 8개를 넘으면 자동으로 Red-Black Tree로 전환&lt;/b&gt;된다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;static final int TREEIFY_THRESHOLD = 8;   // 이 이상이면 트리로 전환
static final int UNTREEIFY_THRESHOLD = 6; // 이 이하면 다시 리스트로
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;충돌 적을 때 (&amp;lt; 8)      충돌 많을 때 (&amp;gt;= 8)
버킷[2]                  버킷[2]
  └─ A                     └─ (Red-Black Tree)
       └─ B                        A
            └─ C               B       C
O(n) 조회                  O(log n) 조회
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;resize와 load factor&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 capacity는 16이다. 데이터가 계속 들어오면 언젠가 꽉 차는데, 이걸 방지하려고 &lt;b&gt;load factor&lt;/b&gt;라는 임계치가 있다. 기본값은 0.75.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;load factor = 현재 데이터 수 / 배열 크기

capacity 16, load factor 0.75
&amp;rarr; 16 * 0.75 = 12
&amp;rarr; 데이터가 12개 들어오는 순간 resize 발생
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;resize가 일어나면 배열 크기를 2배로 늘리고, 기존 데이터를 전부 새 배열로 옮긴다. 이 과정을 &lt;b&gt;rehashing&lt;/b&gt;이라고 한다. 모든 데이터를 다시 해싱해서 새 인덱스를 계산하고 옮기는 거라 비용이 크다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기에 들어올 데이터 크기를 어느 정도 예측할 수 있다면 capacity를 미리 지정해두는 게 성능에 유리하다.&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;// 데이터가 1000개 들어올 예정이라면
// 1000 / 0.75 &amp;asymp; 1334 &amp;rarr; 올림해서 2의 거듭제곱인 2048로 지정
Map&amp;lt;String, String&amp;gt; map = new HashMap&amp;lt;&amp;gt;(2048);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;load factor를 낮추면 충돌은 줄지만 메모리를 더 쓰고 resize가 자주 일어난다. 높이면 메모리는 아끼지만 충돌이 늘어서 성능이 나빠진다. 0.75는 그 트레이드오프에서 나온 기본값이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주요 특성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 내부 동작을 보면서 이미 특성이 대부분 나왔는데, 한 번에 정리하면 이렇다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;순서를 보장하지 않는다.&lt;/b&gt; 버킷 인덱스 기준으로 저장되기 때문에 삽입 순서가 유지되지 않는다. 순서가 필요하면 LinkedHashMap을 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;null을 허용한다.&lt;/b&gt; Key는 null 1개, Value는 null 여러 개 허용한다. Key가 null이면 항상 버킷 0에 들어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;thread-safe하지 않다.&lt;/b&gt; 멀티스레드 환경에서 HashMap을 공유하면 데이터 유실이 생기거나 Java 8 이전에는 resize 중에 실제로 무한루프 버그가 있었다. 멀티스레드 환경에서는 ConcurrentHashMap을 써야 한다.&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;// 멀티스레드 환경
Map&amp;lt;String, String&amp;gt; map = new ConcurrentHashMap&amp;lt;&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Collections.synchronizedMap()도 있지만 ConcurrentHashMap이 성능이 낫다. synchronizedMap은 Map 전체를 잠그지만, ConcurrentHashMap은 버킷 단위로 잠그기 때문이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;O(1)이 보장되는 조건, O(n)이 되는 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;O(1)이 나오려면 &lt;b&gt;hashCode()가 데이터를 버킷에 골고루 분산시켜줘야 한다.&lt;/b&gt; 충돌이 적어야 한다는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;O(n)이 되는 경우는 이렇다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hashCode()를 잘못 구현해서 모든 키가 같은 해시값을 반환하면 HashMap이 사실상 연결리스트가 된다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Override
public int hashCode() {
    return 1;  // 모든 키가 같은 버킷 &amp;rarr; O(n)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 극단적인 경우는 없더라도, &lt;b&gt;hashCode()와 equals()를 잘못 구현한 객체를 Key로 쓰면&lt;/b&gt; 비슷한 상황이 생긴다. Key로 사용할 클래스는 hashCode()와 equals()를 반드시 함께 재정의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 하나는 &lt;b&gt;mutable 객체를 Key로 쓰는 경우&lt;/b&gt;다. HashMap에 저장한 후 Key 객체의 내부 값이 바뀌면 hashCode가 달라져서 꺼낼 수 없게 된다.&lt;/p&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; key = new ArrayList&amp;lt;&amp;gt;(Arrays.asList(&quot;a&quot;));
map.put(key, &quot;value&quot;);

key.add(&quot;b&quot;);  // Key 변경!
map.get(key);  // null 반환 - hashCode가 바뀌었기 때문
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Key는 String, Integer처럼 불변 객체를 쓰는 게 원칙이다!!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;어떤 걸 구현할 때 자주 쓰는가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 코드 짜다 보면 HashMap이 자연스럽게 손에 잡히는 상황들이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 흔한 건 &lt;b&gt;중복 체크&lt;/b&gt;다. 어떤 값이 이미 나왔는지 확인할 때 리스트로 하면 매번 순회해야 하는데, HashMap에 넣어두면 containsKey()로 O(1)에 끝난다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;color: #eaecf0;&quot; data-darkreader-inline-color=&quot;&quot;&gt;&lt;code&gt;Map&amp;lt;String, Boolean&amp;gt; visited = new HashMap&amp;lt;&amp;gt;();
visited.put(&quot;user1&quot;, true);

System.out.println(visited.containsKey(&quot;user1&quot;)); // true
System.out.println(visited.containsKey(&quot;user2&quot;)); // false&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;빈도 카운팅&lt;/b&gt;도 자주 쓴다. 문자열에서 각 문자가 몇 번 나왔는지, 투표 결과 집계처럼 특정 값이 몇 번 등장했는지 셀 때.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;color: #eaecf0;&quot; data-darkreader-inline-color=&quot;&quot;&gt;&lt;code&gt;String[] votes = {&quot;A&quot;, &quot;B&quot;, &quot;A&quot;, &quot;C&quot;, &quot;B&quot;, &quot;A&quot;};

Map&amp;lt;String, Integer&amp;gt; count = new HashMap&amp;lt;&amp;gt;();
for (String vote : votes) {
    count.put(vote, count.getOrDefault(vote, 0) + 1);
}

System.out.println(count); // {A=3, B=2, C=1}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Key로 빠르게 조회&lt;/b&gt;하는 것도 단골 패턴이다. 유저 ID로 유저 정보 꺼내기, 상품 코드로 상품 정보 꺼내기처럼 리스트를 순회하지 않고 바로 찾아야 할 때.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot; style=&quot;color: #eaecf0;&quot; data-darkreader-inline-color=&quot;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;Map&amp;lt;String, String&amp;gt; userMap = new HashMap&amp;lt;&amp;gt;();
userMap.put(&quot;user1&quot;, &quot;김철수&quot;);
userMap.put(&quot;user2&quot;, &quot;이영희&quot;);

System.out.println(userMap.get(&quot;user1&quot;)); // 김철수
System.out.println(userMap.get(&quot;user3&quot;)); // null&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그룹핑&lt;/b&gt;도 있다. 같은 조건을 가진 데이터들을 묶을 때 Map&amp;lt;Key, List&amp;lt;Value&amp;gt;&amp;gt; 형태로 쓴다. 같은 학년 학생 묶기, 같은 카테고리 상품 묶기 같은 것들.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; style=&quot;color: #eaecf0;&quot; data-darkreader-inline-color=&quot;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// computeIfAbsent: 해당 Key가 없으면 빈 리스트 만들고, 있으면 기존 리스트에 바로 add
Map&amp;lt;String, List&amp;lt;String&amp;gt;&amp;gt; groups = new HashMap&amp;lt;&amp;gt;();
groups.computeIfAbsent(&quot;1학년&quot;, k -&amp;gt; new ArrayList&amp;lt;&amp;gt;()).add(&quot;김철수&quot;);
groups.computeIfAbsent(&quot;2학년&quot;, k -&amp;gt; new ArrayList&amp;lt;&amp;gt;()).add(&quot;이영희&quot;);
groups.computeIfAbsent(&quot;1학년&quot;, k -&amp;gt; new ArrayList&amp;lt;&amp;gt;()).add(&quot;박민준&quot;);

System.out.println(groups);
// {1학년=[김철수, 박민준], 2학년=[이영희]}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중복 체크, 빈도 카운팅, Key 기반 조회, 그룹핑. 이 네 가지 중 하나에 해당하면 HashMap부터 떠올리면 될 것 같다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념 핵심&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 147px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;내부 구조&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;Node 배열(버킷) + 연결리스트/Red-Black Tree&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;해시 충돌 처리&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;Chaining, 버킷당 8개 초과 시 트리 전환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;resize 비용&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;전체 rehashing &amp;mdash; 초기 capacity 지정으로 줄일 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;O(1) 조건&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;hashCode()가 충돌 없이 잘 분산될 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;thread-safe&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;멀티스레드 환경에서는 사용 금지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;주사용환경&lt;/td&gt;
&lt;td&gt;중복체크, 빈도 카운팅, Key 기반 조회, 그룹핑 등&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글에서는 HashMap의 형제들을 비교 해볼까 한다. LinkedHashMap, TreeMap, ConcurrentHashMap이 HashMap 대비 뭘 추가했고 언제 어떤 걸 선택해야 하는지. HashMap 내부를 이해하고 나면 나머지는 &quot;HashMap에 이런 특성을 얹었다&quot; 정도로 파악하면 되려나.. 필요할 때 잘 써먹으려고 정리해본다!&lt;/p&gt;</description>
      <category>Java</category>
      <category>hash</category>
      <category>HashMap</category>
      <category>Java</category>
      <category>자료구조</category>
      <category>해시</category>
      <category>해시코드</category>
      <author>fkqlaus</author>
      <guid isPermaLink="true">https://fkqlaus.tistory.com/22</guid>
      <comments>https://fkqlaus.tistory.com/22#entry22comment</comments>
      <pubDate>Fri, 3 Apr 2026 16:13:53 +0900</pubDate>
    </item>
    <item>
      <title>[DB] 데이터베이스 - 인덱스(index) 정리</title>
      <link>https://fkqlaus.tistory.com/21</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리가 갑자기 느려지거나, 데이터가 쌓일수록 조회가 버벅인다는 느낌이 든다면 십중팔구 인덱스 얘기가 나온다. 인덱스를 그냥 &quot;빠르게 해주는 것&quot;으로만 알고 쓰면 나중에 반드시 막히는 순간이 온다. 왜 빠른지, 어떤 상황에서 빠른지, 잘못 쓰면 오히려 느려지는 이유까지 같이 정리해두려고 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인덱스가 없으면 어떻게 될까&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블에 데이터가 100만 건 있다고 해보자. 여기서 특정 사용자를 찾으려면 DB는 어떻게 할까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스가 없다면 첫 번째 행부터 마지막 행까지 전부 다 훑는다. 이걸 &lt;b&gt;Full Table Scan&lt;/b&gt;이라고 한다. 100만 건을 다 뒤지는 거다. 운 좋으면 앞에서 찾겠지만, 최악의 경우 100만 번째에 있을 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 적을 때는 체감이 안 되지만, 트래픽이 붙고 데이터가 쌓이기 시작하면 이게 병목이 된다. 실제로 로컬에서 잘 돌던 쿼리가 운영 환경에서 갑자기 수 초씩 걸리는 이유 중 상당수가 인덱스 문제다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인덱스의 개념&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 직관적인 비유는 책의 &lt;b&gt;색인(index)&lt;/b&gt; 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두꺼운 책에서 &quot;컨텍스트 스위칭&quot;이라는 단어를 찾아야 한다면, 처음부터 한 페이지씩 넘기는 사람은 없다. 뒤쪽 색인 페이지에서 &quot;컨텍스트 스위칭 &amp;rarr; 142p&quot; 를 찾고 바로 그 페이지로 간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 인덱스도 똑같다. 특정 컬럼에 인덱스를 걸면, DB는 그 컬럼의 값과 실제 데이터 위치(주소)를 별도의 자료구조로 관리한다. 조회할 때 테이블 전체를 뒤지는 게 아니라, 인덱스에서 위치를 먼저 찾고 그 위치로 바로 점프한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;인덱스 없을 때                     인덱스 있을 때

[row 1] user_id = 100001          인덱스
[row 2] user_id = 100002     &amp;rarr;   +----------------+
[row 3] user_id = 100003         | 42 &amp;rarr; row 주소  |  &amp;larr; 바로 찾음
...                              | 43 &amp;rarr; row 주소  |
[row N] user_id = 42       &amp;larr;    | ...             |
(전부 다 뒤지다가 마지막에 발견)  +----------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;내부적으로 어떻게 동작하는가 - B-Tree&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 DB(MySQL InnoDB, PostgreSQL 등)에서 기본 인덱스 자료구조는 &lt;b&gt;B-Tree(Balanced Tree)&lt;/b&gt; 다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B-Tree는 항상 정렬된 상태를 유지하고, 루트 &amp;rarr; 브랜치 &amp;rarr; 리프 노드 방향으로 탐색한다. 어떤 값을 찾아도 루트에서 리프까지의 거리가 동일하게 유지되기 때문에 최악의 경우에도 탐색 시간이 일정하다 (O(log N)).&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;B-Tree 구조 예시 (user_id 기준)

          [50]                   &amp;larr; Root Node
         /    \
      [25]    [75]               &amp;larr; Branch Nodes
      /  \    /  \
  [10] [30] [60] [90]           &amp;larr; Leaf Nodes
  (실제 데이터 주소를 가짐)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;user_id = 60을 찾는다면:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Root(50) &amp;rarr; 60 &amp;gt; 50이니까 오른쪽&lt;/li&gt;
&lt;li&gt;Branch(75) &amp;rarr; 60 &amp;lt; 75이니까 왼쪽&lt;/li&gt;
&lt;li&gt;Leaf(60) &amp;rarr; 찾음&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;100만 건이 있어도 탐색 횟수는 로그 스케일이라 수십 번 안에 끝난다. Full Table Scan과 차이가 꽤나 나는걸 알 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인덱스 생성&lt;/h2&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;-- 단일 컬럼 인덱스
CREATE INDEX idx_user_email ON users (email);

-- 복합 인덱스 (여러 컬럼)
CREATE INDEX idx_order_user_status ON orders (user_id, status);

-- 유니크 인덱스 (중복 값 허용 안 함)
CREATE UNIQUE INDEX idx_user_email ON users (email);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블 생성 시 같이 선언할 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;CREATE TABLE users (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(255) NOT NULL,
    name VARCHAR(100),
    status VARCHAR(20),
    INDEX idx_email (email),
    INDEX idx_name_status (name, status)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PK는 선언하면 자동으로 클러스터드 인덱스가 생성된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인덱스가 실제로 어떻게 생겼는가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 INDEX idx_email (email) 을 걸었을 때, DB 내부에서 인덱스가 어떤 형태로 저장되는지 보면 이렇다.&lt;/p&gt;
&lt;pre class=&quot;gherkin&quot;&gt;&lt;code&gt;실제 테이블 (users) - 삽입 순서대로 저장됨

+----+---------------------------+---------+----------+
| id | email                     | name    | status   |
+----+---------------------------+---------+----------+
|  1 | charlie@kakao.com         | 찰리    | ACTIVE   |
|  2 | alice@gmail.com           | 앨리스  | INACTIVE |
|  3 | bob@naver.com             | 밥      | ACTIVE   |
|  4 | admin@example.com         | 관리자  | ACTIVE   |
|  5 | david@daum.net            | 데이빗  | BANNED   |
+----+---------------------------+---------+----------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블 자체는 삽입 순서대로 쌓이고, email 기준으로 정렬되어 있지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 INDEX idx_email (email) 을 걸면 DB는 별도로 아래 구조를 만들어 관리한다.&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;idx_email 인덱스 - email 기준 알파벳 정렬 상태로 유지

+---------------------------+------------------+
| email (정렬됨)             | &amp;rarr; 실제 row 위치  |
+---------------------------+------------------+
| admin@example.com         | &amp;rarr; row #4 (id=4)  |
| alice@gmail.com           | &amp;rarr; row #2 (id=2)  |
| bob@naver.com             | &amp;rarr; row #3 (id=3)  |
| charlie@kakao.com         | &amp;rarr; row #1 (id=1)  |
| david@daum.net            | &amp;rarr; row #5 (id=5)  |
+---------------------------+------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WHERE email = 'bob@naver.com' 쿼리가 들어오면:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 테이블 전체를 뒤지지 않고 idx_email 인덱스를 먼저 탐색
         &amp;darr;
2. 알파벳 정렬된 상태니까 B-Tree로 'bob@naver.com' 빠르게 찾음
         &amp;darr;
3. 거기 저장된 주소(row #3)로 실제 테이블에 바로 접근
         &amp;darr;
4. id=3 행의 전체 데이터 반환
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 row가 INSERT되면 테이블에도 추가되지만, &lt;b&gt;인덱스도 정렬 상태를 유지하면서 함께 갱신&lt;/b&gt;된다. 이게 INSERT/UPDATE/DELETE 할 때 인덱스가 부담이 되는 이유다. 인덱스가 많을수록 쓸 때마다 갱신할 게 늘어난다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;복합 인덱스와 순서 문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(user_id, status) 로 복합 인덱스를 만들었다면, 이 인덱스는 user_id 기준으로 먼저 정렬하고, 같은 user_id 안에서 status로 정렬된 구조다.&lt;/p&gt;
&lt;pre class=&quot;gherkin&quot;&gt;&lt;code&gt;idx_user_status 복합 인덱스

+---------+----------+------------------+
| user_id | status   | &amp;rarr; 실제 row 위치  |
+---------+----------+------------------+
|       1 | DONE     | &amp;rarr; row #5         |
|       1 | PENDING  | &amp;rarr; row #2         |
|       2 | DONE     | &amp;rarr; row #8         |
|       2 | DONE     | &amp;rarr; row #11        |
|       3 | CANCEL   | &amp;rarr; row #1         |
+---------+----------+------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조 때문에 선두 컬럼(user_id) 없이 status만 조건으로 걸면 인덱스를 탈 수 없다. status 기준으로는 정렬이 안 되어 있기 때문이다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- 인덱스 탐 (user_id가 선두 컬럼)
SELECT * FROM orders WHERE user_id = 1;
SELECT * FROM orders WHERE user_id = 1 AND status = 'DONE';

-- 인덱스 안 탐 (선두 컬럼 없음)
SELECT * FROM orders WHERE status = 'DONE';
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 규칙을 &lt;b&gt;Leftmost Prefix Rule&lt;/b&gt; 이라고 한다. 복합 인덱스를 설계할 때 항상 염두에 두어야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;커버링 인덱스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 탐색은 보통 두 단계다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;인덱스에서 조건에 맞는 row 위치 찾기&lt;/li&gt;
&lt;li&gt;실제 테이블에서 그 위치로 가서 데이터 읽기&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 SELECT 하는 컬럼이 전부 인덱스 안에 있다면 2번 단계를 건너뛸 수 있다. 인덱스 자체에서 모든 데이터가 해결되기 때문이다. 이걸 &lt;b&gt;커버링 인덱스&lt;/b&gt;라고 한다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- (user_id, status, created_at) 복합 인덱스가 있을 때
SELECT status, created_at FROM orders WHERE user_id = 1;
-- &amp;rarr; 테이블 접근 없이 인덱스만으로 해결
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EXPLAIN 결과에서 Extra: Using index 라고 나오면 커버링 인덱스가 적용된 것이다. 대량 조회 쿼리에서 이걸 활용하면 체감 성능 차이가 크다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실제 테이블에서 인덱스를 어떻게 설계할까&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스를 어디에 걸어야 할지 막막할 때 접근하는 방식이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;WHERE 절을 먼저 본다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스에서 가장 자주 실행되는 쿼리의 WHERE 조건이 뭔지 파악하는 게 출발점이다. 거기 들어가는 컬럼이 인덱스 후보다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- 이런 쿼리가 자주 실행된다면
SELECT * FROM orders WHERE user_id = ? AND status = ?;

-- &amp;rarr; (user_id, status) 복합 인덱스 고려
CREATE INDEX idx_orders_user_status ON orders (user_id, status);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JOIN ON 조건도 인덱스 후보&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JOIN할 때 ON에 사용하는 컬럼에 인덱스가 없으면 매번 Full Scan이 발생한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;SELECT u.name, o.created_at
FROM users u
JOIN orders o ON o.user_id = u.id  -- o.user_id에 인덱스 없으면 느림
WHERE u.status = 'ACTIVE';
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA에서 @ManyToOne을 걸면 FK는 생기는데 인덱스는 자동으로 안 생긴다. 생각보다 자주 놓치는 포인트다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ORDER BY, GROUP BY도 인덱스 대상&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬이나 그룹핑도 인덱스를 타면 훨씬 빠르다. 인덱스가 이미 정렬된 상태이기 때문에 별도의 정렬 작업이 필요 없어진다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- created_at 기준으로 자주 정렬한다면
SELECT * FROM orders ORDER BY created_at DESC LIMIT 20;

-- &amp;rarr; created_at에 인덱스
CREATE INDEX idx_orders_created_at ON orders (created_at);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EXPLAIN 결과에 Using filesort 가 보이면 인덱스 없이 별도 정렬이 일어나고 있다는 뜻이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;카디널리티를 확인한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카디널리티는 해당 컬럼에 값의 종류가 얼마나 다양한지를 나타낸다. 인덱스 효과는 카디널리티가 높을수록 크다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;-- 카디널리티 확인 (1에 가까울수록 고유값이 많음)
SELECT COUNT(DISTINCT email) / COUNT(*) AS cardinality FROM users;  -- 거의 1.0
SELECT COUNT(DISTINCT status) / COUNT(*) AS cardinality FROM users; -- 0.001 수준
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;status처럼 'ACTIVE', 'INACTIVE' 두 가지밖에 없는 컬럼은 인덱스를 탔을 때 절반씩 나뉘는 수준이라 Full Scan이랑 큰 차이가 없다. 단독 인덱스보다는 카디널리티 높은 컬럼과 복합으로 묶는 게 낫다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- status 단독 인덱스 (비효율)
CREATE INDEX idx_status ON users (status);

-- user_id + status 복합 인덱스 (훨씬 효과적)
CREATE INDEX idx_user_status ON orders (user_id, status);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Slow Query Log로 문제 쿼리 찾기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어디서 병목이 생기는지 모를 때는 Slow Query Log를 켜두고 기준 시간 이상 걸리는 쿼리를 뽑아서 확인한다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- MySQL 기준
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;  -- 1초 이상 걸리는 쿼리 기록
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그에서 자주 등장하는 쿼리를 EXPLAIN으로 분석하면 어디에 인덱스가 빠져 있는지 파악된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인덱스가 무시되는 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스를 걸어놨는데 쿼리가 안 빠른 경우, 인덱스가 안 타고 있을 가능성이 높다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;WHERE 절에서 컬럼에 함수나 연산을 가하면&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;-- 인덱스 안 탐 (함수 적용)
SELECT * FROM users WHERE YEAR(created_at) = 2024;
SELECT * FROM users WHERE UPPER(email) = 'TEST@EXAMPLE.COM';

-- 인덱스 탐 (컬럼 그대로 사용)
SELECT * FROM users WHERE created_at &amp;gt;= '2024-01-01' AND created_at &amp;lt; '2025-01-01';
SELECT * FROM users WHERE email = 'test@example.com';
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B-Tree는 원래 값 기준으로 정렬되어 있는데, 함수를 적용하면 정렬 기준이 바뀌어서 탐색이 불가능해진다. 컬럼은 건드리지 말고 조건 값 쪽을 바꿔야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;LIKE 앞에 % 붙이면&lt;/h3&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;-- 인덱스 안 탐 (앞에 % 있음)
SELECT * FROM users WHERE name LIKE '%인덱스';

-- 인덱스 탐 (앞에 % 없음)
SELECT * FROM users WHERE name LIKE '인덱스%';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;%인덱스는 앞부분을 모르니까 B-Tree에서 시작점을 특정할 수 없다. 전체를 다 뒤져야 한다. 인덱스%는 &quot;인덱스로 시작하는 것&quot;을 B-Tree에서 범위 탐색으로 찾을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문 검색이 필요하다면 MySQL Full-Text Index나 Elasticsearch 같은 별도 도구를 고려해야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;타입이 안 맞으면&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;-- phone_number가 VARCHAR인데 숫자로 조건 걸면
SELECT * FROM users WHERE phone_number = 01012345678;     -- 인덱스 안 탐
SELECT * FROM users WHERE phone_number = '010-1234-5678'; -- 인덱스 탐
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB가 내부적으로 타입 변환을 하면서 컬럼에 함수가 적용된 것과 같은 효과가 생긴다. 타입은 항상 맞춰서 쿼리해야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인덱스를 남발하면 안 되는 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스도 별도의 자료구조를 디스크에 저장하는 거다. 공짜가 아니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;INSERT, UPDATE, DELETE 할 때마다 인덱스도 갱신&lt;/b&gt;해야 한다. 인덱스가 많을수록 쓰기 작업이 느려진다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인덱스 자체가 디스크 공간을 차지&lt;/b&gt;한다. 컬럼이 많거나 데이터가 많으면 인덱스 크기가 상당해진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황 인덱스 효과&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;카디널리티 높음 (user_id, email 등)&lt;/td&gt;
&lt;td&gt;효과 큼&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;카디널리티 낮음 (gender, status 등)&lt;/td&gt;
&lt;td&gt;효과 미미&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;조회가 거의 없고 쓰기가 많은 테이블&lt;/td&gt;
&lt;td&gt;오히려 성능 저하&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데이터가 소량인 테이블&lt;/td&gt;
&lt;td&gt;Full Scan이 더 빠를 수도&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;EXPLAIN으로 인덱스 탔는지 확인하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리 앞에 EXPLAIN을 붙이면 DB가 해당 쿼리를 어떻게 실행할지 실행 계획을 보여준다. 실제로 쿼리를 실행하는 게 아니라 계획만 보는 거라 부담 없이 날려볼 수 있다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;EXPLAIN SELECT * FROM orders WHERE user_id = 1 AND status = 'DONE';
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과에서 봐야 할 핵심 컬럼:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬럼 의미&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;type&lt;/td&gt;
&lt;td&gt;접근 방식. ALL이면 Full Scan, ref / range / const면 인덱스 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;possible_keys&lt;/td&gt;
&lt;td&gt;이 쿼리에 쓸 수 있는 인덱스 후보 목록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;key&lt;/td&gt;
&lt;td&gt;실제로 선택된 인덱스 이름. NULL이면 인덱스 안 탐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rows&lt;/td&gt;
&lt;td&gt;결과를 찾기 위해 읽을 것으로 예상하는 행 수. 적을수록 좋음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extra&lt;/td&gt;
&lt;td&gt;Using index면 커버링 인덱스. Using filesort / Using temporary면 성능 주의&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;type = ALL이고 key = NULL이면 인덱스가 안 타고 있는 거다. rows가 테이블 전체 건수에 가깝게 나온다면 Full Scan 수준으로 데이터를 뒤지고 있다는 뜻이고, 이 상태에서 데이터가 많으면 튜닝이 필요하다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;SIMPLE&lt;/td&gt;
&lt;td&gt;orders&lt;/td&gt;
&lt;td&gt;ref&lt;/td&gt;
&lt;td&gt;idx_user_status&lt;/td&gt;
&lt;td&gt;idx_user_status&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Using index&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념 핵심&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;인덱스&lt;/td&gt;
&lt;td&gt;특정 컬럼의 값과 위치를 별도로 관리해서 조회 속도를 높이는 자료구조&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B-Tree&lt;/td&gt;
&lt;td&gt;대부분 DB의 기본 인덱스 구조. 항상 정렬 유지, O(log N) 탐색&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;복합 인덱스&lt;/td&gt;
&lt;td&gt;컬럼 순서가 중요. 선두 컬럼부터 순서대로만 탐색 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;커버링 인덱스&lt;/td&gt;
&lt;td&gt;SELECT 컬럼이 전부 인덱스에 있으면 테이블 접근 없이 해결&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;인덱스 무시&lt;/td&gt;
&lt;td&gt;컬럼에 함수 적용, 앞에 % 붙은 LIKE, 타입 불일치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;인덱스 남발&lt;/td&gt;
&lt;td&gt;쓰기 성능 저하, 공간 낭비. 카디널리티 높은 컬럼에만 전략적으로&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EXPLAIN&lt;/td&gt;
&lt;td&gt;인덱스 탔는지 확인하는 도구&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>DB</category>
      <category>data</category>
      <category>database</category>
      <category>db</category>
      <category>Index</category>
      <category>MySQL</category>
      <category>PostgreSQL</category>
      <category>데이터</category>
      <category>디비</category>
      <category>백엔드</category>
      <category>인덱스</category>
      <author>fkqlaus</author>
      <guid isPermaLink="true">https://fkqlaus.tistory.com/21</guid>
      <comments>https://fkqlaus.tistory.com/21#entry21comment</comments>
      <pubDate>Thu, 2 Apr 2026 17:24:20 +0900</pubDate>
    </item>
    <item>
      <title>프로세스와 메모리 구조</title>
      <link>https://fkqlaus.tistory.com/20</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring으로 개발하다 보면 스레드 풀, 트랜잭션, @Async 같은 것들을 설정할 일이 생긴다. 근데 이걸 그냥 외워서 쓰면 언젠가 반드시 막히는 순간이 온다. 왜 스레드 풀 사이즈를 이렇게 잡는지, @Transactional이 왜 내부 호출에서 안 먹히는지, 이런 것들이 결국 OS 레벨의 동작 원리와 연결되어 있기 때문이다!!!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 멀티 스레드 쪽을 깊게 공부해보다가 그냥 스레드부터 냅다 글을 적을까 했지만 학부생일 때 공부했던 운영체제부터 정리해서 쓰면 어떨까 싶어서 이 주제로 정리해봤다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CPU는 한 번에 하나밖에 못 한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU 코어 1개는 한 번에 명령어 1개만 실행할 수 있다. 그런데 우리는 컴퓨터로 유튜브 보면서 카카오톡 하고 IDE도 켜놓는다. 어떻게 가능한 걸까? CPU 코어가 많아서인가.... 그것도 맞지만!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;답은 CPU가 엄청나게 빠른 속도로 여러 작업을 번갈아 실행하기 때문이다. 1초에 수천 번씩 프로세스를 바꿔가며 실행하니까 사람 눈에는 동시에 돌아가는 것처럼 보이는 것뿐이다. 이걸 가능하게 해주는 게 OS의 핵심 역할이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 OS는 어떻게 여러 프로세스를 관리하는 걸까. 이걸 이해하려면 프로세스가 뭔지부터 알아야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로그램 vs 프로세스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램과 프로세스는 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로그램&lt;/b&gt;은 디스크에 저장된 실행 파일이다. 그냥 파일이다. 더블클릭 하기 전의 상태. IntelliJ를 아직 실행 안 했다면 그건 그냥 파일이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로세스&lt;/b&gt;는 프로그램을 실행한 것이다. OS가 프로그램을 메모리에 올리고 CPU를 할당해서 실제로 동작하는 상태를 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 프로그램을 두 번 실행하면 프로세스가 두 개 생긴다. 프로그램은 하나지만 프로세스는 별개로 동작한다. 각 프로세스는 독립적인 메모리 공간을 가지기 때문에 하나가 죽어도 다른 하나에 영향을 주지 않는다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로세스의 메모리 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스가 메모리에 올라갈 때 4개의 영역으로 나뉜다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;높은 주소
+------------------+
|                  |
|      Stack       |  &amp;larr; 함수 호출, 지역 변수 (&amp;darr; 방향으로 증가)
|        &amp;darr;         |
+------------------+
|                  |     Stack과 Heap은 같은 공간을 공유하며
|     빈 공간        |     서로 반대 방향으로 자란다
|                  |
+------------------+
|        &amp;uarr;         |
|       Heap       |  &amp;larr; 동적 할당 데이터 (&amp;uarr; 방향으로 증가)
|                  |
+------------------+
|      Data        |  &amp;larr; 전역 변수, static 변수
+------------------+
|      Code        |  &amp;larr; 실행할 코드 (기계어), 읽기 전용
+------------------+
낮은 주소&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Code 영역&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 작성한 소스 코드가 컴파일되어 기계어(0과 1)로 변환된 후 저장되는 공간이다. CPU는 이 영역에서 명령어를 하나씩 가져와서 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽기 전용이라 실행 중에 내용이 바뀌지 않는다. 레시피를 보고 요리하는 것처럼, 코드는 읽기만 하고 수정하지 않는다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Data 영역&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전역 변수, static 변수가 저장되는 공간이다. 프로그램 시작 시 할당되고 종료될 때 해제된다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;쉽게 언어를 예로 들면...&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;static int count = 0;    // Data 영역
int globalValue = 100;   // Data 영역
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램이 살아있는 동안 계속 메모리에 올라가 있다. 그래서 static 변수를 남발하면 메모리를 계속 점유하게 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Heap 영역&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;런타임에 동적으로 할당되는 공간이다. 크기가 실행 전에 정해지지 않고, 실행 중에 필요할 때 할당하고 필요 없으면 해제한다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// new 키워드로 객체를 만들면 Heap에 올라간다
String name = new String(&quot;안녕&quot;);
List&amp;lt;Integer&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C언어에서는 개발자가 직접 할당하고 해제해야 하지만, Java에서는 GC(Garbage Collector)가 이걸 자동으로 관리해 준다. GC가 Heap을 주기적으로 검사해서 더 이상 참조되지 않는 객체를 제거한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap은 낮은 주소에서 높은 주소 방향으로 채워진다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Stack 영역&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 호출과 지역 변수가 저장되는 공간이다. LIFO(Last In First Out) 구조로 동작한다. 함수를 호출하면 스택에 쌓이고(push), 함수가 끝나면 제거된다(pop).&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;void methodA() {
    int x = 10;    // Stack에 저장
    methodB();     // methodB 호출 시 Stack에 쌓임
}                  // methodA 끝나면 x도 Stack에서 제거

void methodB() {
    int y = 20;    // Stack에 저장
}                  // 끝나면 y도 제거
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;methodB() 호출 시 Stack 상태

+----------+
|  y = 20  |  &amp;larr; methodB의 지역변수
+----------+
|  x = 10  |  &amp;larr; methodA의 지역변수
+----------+
|  main()  |
+----------+

methodB() 종료 후

+----------+
|  x = 10  |  &amp;larr; methodB 프레임 제거됨
+----------+
|  main()  |
+----------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack은 높은 주소에서 낮은 주소 방향으로 채워진다. Heap과 반대 방향으로 자라기 때문에, 둘이 서로 침범하면 overflow가 발생한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Stack이 Heap을 침범하면 &amp;rarr; &lt;b&gt;Stack Overflow&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Heap이 Stack을 침범하면 &amp;rarr; &lt;b&gt;Heap Overflow&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀 함수가 끝없이 호출되면 스택이 계속 쌓이다가 Heap 영역을 침범한다. 이게 StackOverflowError가 발생하는 이유다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;PCB가 필요한 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 프로세스가 동시에 돌아가는 것처럼 보이려면, CPU가 프로세스 A를 실행하다가 B로 넘어갔다가 다시 A로 돌아올 때 &lt;b&gt;A가 어디까지 실행했는지 정확히 기억&lt;/b&gt;해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 저장하는 자료구조가 &lt;b&gt;PCB(Process Control Block)&lt;/b&gt; 이다. OS는 프로세스를 생성할 때 PCB도 함께 만들고, 프로세스가 종료되면 PCB도 제거한다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;PCB (Process Control Block)
+------------------------------+
| PID: 1234                    |  &amp;larr; 프로세스 고유 번호
+------------------------------+
| 상태: RUNNING                 |  &amp;larr; 현재 상태 (실행/대기/준비)
+------------------------------+
| PC: 0x004A21F0               |  &amp;larr; 다음에 실행할 명령어 주소
+------------------------------+
| 레지스터: R1=5, R2=99 ...      |  &amp;larr; CPU 레지스터 값들
+------------------------------+
| 메모리의 주소: 0x7FFF0000       |  &amp;larr; 메모리 공간 위치
+------------------------------+
| 열린 파일 목록: [fd1, fd2]      |  &amp;larr; 사용 중인 파일들
+------------------------------+
| 우선순위: 10                   |  &amp;larr; 스케줄링 우선순위
+------------------------------+&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스가 100개 실행 중이면 OS는 PCB를 100개 가지고 있다. PCB는 보안상 일반 사용자가 접근하지 못하는 보호된 커널 메모리 영역에 저장된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PCB가 실제로 쓰이는 순간 - 컨텍스트 스위칭&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU가 프로세스 A를 실행하다가 B로 전환할 때 이런 일이 일어난다:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 현재 실행 중인 프로세스 A의 상태를 PCB_A에 저장
   (PC 값, 레지스터 값 등 전부)
         &amp;darr;
2. 다음 실행할 프로세스 B의 PCB_B를 불러옴
         &amp;darr;
3. PCB_B에 저장된 값들을 CPU 레지스터에 복원
         &amp;darr;
4. 프로세스 B가 이어서 실행됨
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 전환 과정을 &lt;b&gt;컨텍스트 스위칭(Context Switching)&lt;/b&gt; 이라고 한다. 이 과정 자체에도 시간이 걸리기 때문에 컨텍스트 스위칭이 너무 자주 일어나면 오버헤드가 발생한다. 이게 나중에 스레드 풀 사이즈를 무한정 늘리면 안 되는 이유와 연결된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로세스 상태&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스는 실행되는 동안 상태가 계속 바뀐다. OS는 이 상태를 PCB에 기록하면서 관리한다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;           생성 (New)
               &amp;darr; OS가 메모리 할당, PCB 생성
           준비 (Ready) ◀──────────────────┐
               &amp;darr; CPU 할당 (Dispatch)        │ 타임 슬라이스 만료
           실행 (Running)                   │ (타이머 인터럽트)
               &amp;darr;                           │
       ┌───────┴───────┐                   │
       &amp;darr;               &amp;darr;                   │
   대기 (Waiting)  종료 (Terminated)         │
       &amp;darr;                                   │
   I/O 완료 후 ──────────────────────────── ┘&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;준비 (Ready)&lt;/b&gt;: CPU를 받을 준비는 됐는데 아직 차례가 안 된 상태. Ready Queue에서 대기&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실행 (Running)&lt;/b&gt;: 현재 CPU를 점유하고 실행 중&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대기 (Waiting)&lt;/b&gt;: I/O 작업이나 이벤트를 기다리는 상태. CPU를 안 쓰기 때문에 Ready Queue에서 빠져나와 Wait Queue로 이동&lt;/li&gt;
&lt;li&gt;&lt;b&gt;종료 (Terminated)&lt;/b&gt;: 실행 완료. PCB 제거&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 &amp;rarr; 준비 전환&lt;/b&gt;이 일어나는 이유가 중요하다. CPU는 한 프로세스를 무한정 실행하지 않는다. OS가 타이머 인터럽트를 발생시켜 일정 시간(타임 슬라이스, 보통 수 밀리초)이 지나면 강제로 CPU를 뺏는다. 이렇게 해야 모든 프로세스가 공평하게 CPU를 받을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 &amp;rarr; 대기 전환&lt;/b&gt;은 파일 읽기, 네트워크 요청같은 I/O 작업을 할 때 일어난다. I/O는 CPU 연산보다 훨씬 느리기 때문에 기다리는 동안 CPU를 다른 프로세스에게 넘겨주는 것이다. I/O가 끝나면 다시 준비 상태로 돌아가서 CPU를 받을 차례를 기다린다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념 핵심&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;프로그램&lt;/td&gt;
&lt;td&gt;디스크에 저장된 실행 파일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;프로세스&lt;/td&gt;
&lt;td&gt;프로그램을 실행해서 메모리에 올린 것&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code 영역&lt;/td&gt;
&lt;td&gt;기계어로 된 실행 코드. 읽기 전용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data 영역&lt;/td&gt;
&lt;td&gt;전역변수, static 변수. 프로그램 종료까지 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heap 영역&lt;/td&gt;
&lt;td&gt;동적 할당 데이터. Java는 GC가 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stack 영역&lt;/td&gt;
&lt;td&gt;함수 호출, 지역 변수. LIFO 구조&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PCB&lt;/td&gt;
&lt;td&gt;OS가 프로세스 상태를 저장하는 자료구조&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;컨텍스트 스위칭&lt;/td&gt;
&lt;td&gt;CPU가 프로세스 바꿀 때 PCB에 상태 저장/복원하는 과정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CS</category>
      <category>cpu</category>
      <category>cs</category>
      <category>heap</category>
      <category>OS</category>
      <category>stack</category>
      <category>변수</category>
      <category>운영체제</category>
      <category>컴퓨터</category>
      <category>컴퓨터공학과</category>
      <category>프로세스</category>
      <author>fkqlaus</author>
      <guid isPermaLink="true">https://fkqlaus.tistory.com/20</guid>
      <comments>https://fkqlaus.tistory.com/20#entry20comment</comments>
      <pubDate>Mon, 30 Mar 2026 22:05:31 +0900</pubDate>
    </item>
    <item>
      <title>Docker 기본 개념과 명령어 정리</title>
      <link>https://fkqlaus.tistory.com/19</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개발하다 보면 &quot;내 PC에서는 되는데 서버에서 안 된다&quot;는 상황을 한 번쯤 겪는다. Java 버전이 다르거나, OS가 달라서 라이브러리가 없거나. Docker는 이 문제를 해결하기 위해 나온 도구이기도 하지만, 실제로 쓰다 보면 다른 이유들이 더 와닿는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버 관리가 단순해진다.&lt;/b&gt; 앱을 올리고 내리는 게 명령어 한 줄로 끝난다. 예전엔 프로세스 찾아서 kill하고, 다시 실행하고, 잘 떴는지 확인하고 이걸 반복했는데 docker stop, docker start 로 끝난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;지금 뭐가 떠있는지 한눈에 보인다.&lt;/b&gt; docker ps 하면 현재 실행 중인 서비스가 전부 나온다. 언제 시작했는지, 포트는 뭔지, 상태는 어떤지 한 번에 확인 가능하다. 뭐 이거 이외에도 여러개의 이유들이 있겠지만 난 일단 그렇다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Docker가 뭔지&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker는 애플리케이션을 &lt;b&gt;컨테이너&lt;/b&gt;라는 단위로 묶어서 실행하는 플랫폼이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너가 뭔지 쉽게 비유하면, 배에 실리는 &lt;b&gt;화물 컨테이너&lt;/b&gt;랑 똑같다. 어떤 항구에 내려도 컨테이너 안에 뭐가 들었는지, 어떻게 쌓는지 규격이 정해져 있어서 어디서든 동일하게 처리된다. Docker 컨테이너도 마찬가지다. 안에 뭐가 들었든 어느 서버에 올려도 동일하게 동작한다. 라고 클로드가 설명 해줬는데 대강 맞는 말인 것 같다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 조금 더 기술적(?)으로 말하면, 코드, 런타임, 라이브러리, 설정까지 전부 하나로 패키징한 실행 단위다. 이걸 그대로 서버에 올리면 환경이 달라도 똑같이 돌아간다고 보면 되겠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;VM이랑 뭐가 다른지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VM(가상머신)도 비슷한 목적으로 쓰이는데, 둘의 차이가 크다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항목 VM Docker 컨테이너&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;구조&lt;/td&gt;
&lt;td&gt;OS 전체를 가상화&lt;/td&gt;
&lt;td&gt;호스트 OS 커널 공유&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;용량&lt;/td&gt;
&lt;td&gt;수 GB&lt;/td&gt;
&lt;td&gt;수십 ~ 수백 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;시작 시간&lt;/td&gt;
&lt;td&gt;수십 초 ~ 수 분&lt;/td&gt;
&lt;td&gt;1~2초&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;격리 수준&lt;/td&gt;
&lt;td&gt;강함&lt;/td&gt;
&lt;td&gt;약간 낮지만 실용적&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VM은 OS 자체를 통째로 올리는 방식이라 무겁다. Docker는 호스트 OS의 커널을 공유하면서 프로세스만 격리하는 방식이라 훨씬 가볍고 빠르다.&lt;/p&gt;
&lt;pre class=&quot;gherkin&quot;&gt;&lt;code&gt;VM 구조                          Docker 구조
+---------------------+          +---------------------+
|  App A   |  App B   |          |  App A   |  App B   |
+----------+----------+          +----------+----------+
|  Guest   |  Guest   |          |Container |Container |
|  OS      |  OS      |          +----------+----------+
+----------+----------+          |    Docker Engine    |
|     Hypervisor      |          +---------------------+
+---------------------+          |      Host OS        |
|      Host OS        |          +---------------------+
+---------------------+          |   Infrastructure    |
|   Infrastructure    |          +---------------------+
+---------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 개념 4가지&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이미지 (Image)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너를 만들기 위한 &lt;b&gt;설계도&lt;/b&gt;다. 어떤 OS 기반인지, 어떤 소프트웨어가 설치돼 있는지, 실행 명령어가 뭔지 등이 담겨있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;읽기 전용이라 직접 수정 불가&lt;/li&gt;
&lt;li&gt;Docker Hub에 공개 이미지들이 올라와 있음 (nginx, mysql, redis 등)&lt;/li&gt;
&lt;li&gt;Dockerfile로 직접 만들 수도 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;# Docker Hub에서 이미지 받기
docker pull nginx:latest

# 로컬에 있는 이미지 목록 확인
docker images
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
nginx        latest    a72860cb95fd   2 weeks ago    192MB
mysql        8.0       a3b6608898d6   3 weeks ago    596MB
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컨테이너 (Container)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지를 &lt;b&gt;실제로 실행한 것&lt;/b&gt;이다. 이미지 하나로 컨테이너를 여러 개 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;집에 비유하면 이미지는 건축 설계도, 컨테이너는 그 설계도로 지은 집이다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;# nginx 이미지로 컨테이너 실행
docker run -d --name my-nginx -p 80:80 nginx:latest

# 실행 중인 컨테이너 목록
docker ps

# 중지된 것 포함 전체 목록
docker ps -a
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;볼륨 (Volume)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너는 삭제하면 내부 데이터가 전부 날아간다. &lt;b&gt;볼륨은 데이터를 호스트에 영구적으로 보관&lt;/b&gt;하는 방법이다.&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;# 호스트 경로를 컨테이너에 마운트
docker run -d \
  -v /home/nginx/nginx.conf:/etc/nginx/nginx.conf:ro \
  nginx:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;호스트 파일 경로 : 컨테이너 내부 경로 : 옵션(ro=읽기전용)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너를 삭제하고 다시 만들어도 호스트에 파일이 남아있으니까 설정이 유지된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;네트워크 (Network)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너끼리 통신하려면 같은 네트워크에 있어야 한다. 기본적으로 컨테이너는 격리된 네트워크를 가지고 있어서 명시적으로 연결해줘야 한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# 네트워크 생성
docker network create my-network

# 컨테이너를 특정 네트워크에 연결해서 실행
docker run -d --name app --network my-network my-app:latest
docker run -d --name db --network my-network mysql:8.0

# 이제 app 컨테이너 내부에서 db라는 이름으로 mysql에 접근 가능
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 네트워크 안에서는 &lt;b&gt;컨테이너 이름으로 통신&lt;/b&gt;이 된다. IP 대신 이름을 쓰면 되니까 관리가 편하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자주 쓰는 명령어 정리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이미지 관련&lt;/h3&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;# 이미지 받기
docker pull nginx:latest

# 로컬 이미지 목록
docker images

# 이미지 삭제
docker rmi nginx:latest

# 이미지를 tar 파일로 추출 (폐쇄망 등에서 활용)
docker save -o nginx.tar nginx:latest

# tar 파일에서 이미지 로드
docker load -i nginx.tar
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컨테이너 실행&lt;/h3&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;docker run \
  -d \                           # 백그라운드 실행
  --name my-nginx \              # 컨테이너 이름 지정
  --restart always \             # 재부팅 시 자동 시작
  -p 80:80 \                     # 포트 매핑 (호스트:컨테이너)
  -v /host/path:/container/path \ # 볼륨 마운트
  nginx:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-p 8080:80 이면 호스트의 8080포트로 들어오면 컨테이너의 80포트로 연결된다는 뜻이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컨테이너 관리&lt;/h3&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;# 실행 중인 컨테이너 목록
docker ps

# 전체 목록 (중지된 것 포함)
docker ps -a

# 컨테이너 중지
docker stop my-nginx

# 컨테이너 시작
docker start my-nginx

# 컨테이너 재시작
docker restart my-nginx

# 컨테이너 삭제 (중지 후 삭제)
docker stop my-nginx &amp;amp;&amp;amp; docker rm my-nginx

# 실행 중인 컨테이너 강제 삭제
docker rm -f my-nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;로그 및 디버깅&lt;/h3&gt;
&lt;pre class=&quot;perl&quot;&gt;&lt;code&gt;# 로그 확인
docker logs my-nginx

# 실시간 로그 스트리밍
docker logs -f my-nginx

# 컨테이너 내부 접속
docker exec -it my-nginx bash

# 컨테이너 내부에서 명령어 실행
docker exec my-nginx cat /etc/nginx/nginx.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker exec -it 는 실행 중인 컨테이너 안으로 들어가는 명령어다. 컨테이너 안에서 뭔가 확인하거나 디버깅할 때 자주 쓴다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리 명령어&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# 중지된 컨테이너 전체 삭제
docker container prune

# 사용하지 않는 이미지 전체 삭제
docker image prune

# 컨테이너, 이미지, 네트워크 전부 정리
docker system prune
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실전 예시&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;nginx 컨테이너 띄우기&lt;/h3&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;# 1. 이미지 받기
docker pull nginx:latest

# 2. 컨테이너 실행
docker run -d --name nginx --restart always -p 80:80 nginx:latest

# 3. 확인
docker ps
curl http://localhost:80
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;mysql 컨테이너 띄우기&lt;/h3&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;docker run -d \
  --name mysql \
  --restart always \
  -e MYSQL_ROOT_PASSWORD=root1234 \
  -e MYSQL_DATABASE=mydb \
  -p 3306:3306 \
  -v /home/mysql/data:/var/lib/mysql \
  mysql:8.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-e 옵션으로 환경변수를 넘길 수 있다. mysql 이미지는 이 환경변수로 초기 설정을 잡는다. -v /home/mysql/data:/var/lib/mysql 로 데이터를 호스트에 마운트해두면 컨테이너 삭제해도 DB 데이터가 날아가지 않는다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컨테이너 안에서 확인하기&lt;/h3&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;# mysql 컨테이너 안으로 접속
docker exec -it mysql bash

# mysql 접속
mysql -u root -p

# DB 확인
show databases;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;명령어 한눈에 보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령어 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;docker pull [이미지]&lt;/td&gt;
&lt;td&gt;이미지 받기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker images&lt;/td&gt;
&lt;td&gt;이미지 목록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker run [옵션] [이미지]&lt;/td&gt;
&lt;td&gt;컨테이너 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker ps&lt;/td&gt;
&lt;td&gt;실행 중인 컨테이너 목록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker stop [컨테이너]&lt;/td&gt;
&lt;td&gt;컨테이너 중지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker rm [컨테이너]&lt;/td&gt;
&lt;td&gt;컨테이너 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker logs [컨테이너]&lt;/td&gt;
&lt;td&gt;로그 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker exec -it [컨테이너] bash&lt;/td&gt;
&lt;td&gt;컨테이너 내부 접속&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker save -o [파일] [이미지]&lt;/td&gt;
&lt;td&gt;이미지 tar 추출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker load -i [파일]&lt;/td&gt;
&lt;td&gt;tar에서 이미지 로드&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;막연하게 docker를 쓰기만 했는데 기본부터 정리해보자 해서 갑자기 쓴 글이다! 나중에 까먹으면 다시 찾아봐야지!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에는 docker-compose를 정리해봐야겠다~~~&lt;/p&gt;</description>
      <category>DevOps</category>
      <category>DevOps</category>
      <category>docker</category>
      <category>vm</category>
      <category>개발</category>
      <category>배포</category>
      <category>서버</category>
      <category>컴퓨터</category>
      <author>fkqlaus</author>
      <guid isPermaLink="true">https://fkqlaus.tistory.com/19</guid>
      <comments>https://fkqlaus.tistory.com/19#entry19comment</comments>
      <pubDate>Wed, 25 Mar 2026 21:50:52 +0900</pubDate>
    </item>
    <item>
      <title>사내 서버에서 Jenkins CI/CD 구축하기 (Synology NAS + Spring Boot + Windows)</title>
      <link>https://fkqlaus.tistory.com/18</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;시작하게 된 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 배포 방식이 너무 불편했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀원 3명이 각자 로컬에서 개발하고, 합쳐서 개발 서버에 올려서 확인하는 방식이었는데 배포할 때마다 이 과정을 반복해야 했다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;로컬에서 빌드&lt;/li&gt;
&lt;li&gt;jar 파일 복사&lt;/li&gt;
&lt;li&gt;개발 서버에 원격 접속&lt;/li&gt;
&lt;li&gt;jar 파일 직접 붙여넣기&lt;/li&gt;
&lt;li&gt;수동으로 실행&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이 직접 하다 보니 실수도 생기고, 누가 언제 배포했는지 추적도 안 되고, 무엇보다 매번 번거로웠다. 그래서 내가 Jenkins CI/CD 구축을 제안했고 직접 셋업했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;환경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항목 내용&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;프로젝트&lt;/td&gt;
&lt;td&gt;프로젝트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jenkins 버전&lt;/td&gt;
&lt;td&gt;2.541.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jenkins 서버 포트&lt;/td&gt;
&lt;td&gt;8081&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jenkins 설치 경로&lt;/td&gt;
&lt;td&gt;C:\Program Files\Jenkins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;배포 서버 OS&lt;/td&gt;
&lt;td&gt;Windows Server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;Eclipse Adoptium JDK 17.0.16.8-hotspot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;빌드 도구&lt;/td&gt;
&lt;td&gt;Gradle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Git 저장소&lt;/td&gt;
&lt;td&gt;Synology NAS Git Server (192.168.x.xxx)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;배포 경로&lt;/td&gt;
&lt;td&gt;D:\프로젝트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;앱 포트&lt;/td&gt;
&lt;td&gt;[앱포트]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;전체 네트워크 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분을 처음에 생각없이 해서&amp;nbsp; 삽질을 많이 했다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;개발 PC (외부)
  └─ git push &amp;rarr; [공인IP]:[포트포워딩포트] (공유기 포트포워딩)
                        &amp;darr;
              Synology NAS 192.168.x.xxx:22 (SSH 실제 포트)

Jenkins 서버 (내부망)
  └─ git pull &amp;rarr; 192.168.x.xxx:22 (내부니까 직접 SSH 포트로)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;b&gt;개발 PC는 외부에서 접근하니까 포트포워딩된 [포트포워딩포트] 포트&lt;/b&gt;를 쓰고, &lt;b&gt;Jenkins는 같은 내부망이니까 SSH 기본 포트인 22번&lt;/b&gt;을 직접 쓴다는 것. 이걸 처음에 헷갈려서 Jenkins에서도 [포트포워딩포트] 포트로 연결 시도했다가 Connection refused 가 떴었다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;전체 배포 흐름&lt;/h2&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;로컬에서 git push (master 브랜치)
        &amp;darr;
Synology NAS Git 저장소에 코드 저장
        &amp;darr;
Jenkins Webhook으로 변경 감지
        &amp;darr;
[Checkout] SSH Agent로 NAS에서 소스코드 pull
        &amp;darr;
[Build] Gradle bootJar로 jar 빌드
        &amp;darr;
[Stop] [앱포트] 포트 기존 앱 종료
        &amp;darr;
[Deploy] 새 jar 파일 D:\프로젝트 에 복사
        &amp;darr;
[Start] javaw로 앱 백그라운드 실행
        &amp;darr;
배포 완료 &amp;rarr; http://localhost:[앱포트]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Step 1. Synology NAS Git 서버 설정&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Git Server 패키지 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NAS 관리자 페이지 &amp;rarr; 패키지 센터 &amp;rarr; &lt;b&gt;Git Server&lt;/b&gt; 설치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git 사용자 계정은 git으로 생성했고, git-shell로 제한해서 SSH 접속은 되지만 일반 쉘 명령어는 못 쓰도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git 저장소 경로:&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;/volume1/homes/git/프로젝트.git
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SSH 키 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jenkins 서버(Windows)에서 키 생성:&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;# PEM 형식으로 생성 (-m PEM 필수)
ssh-keygen -t rsa -b 4096 -m PEM -C &quot;jenkins@project&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 -m PEM 옵션이 중요하다. 이게 없으면 기본적으로 OPENSSH 형식으로 생성되는데, Jenkins에서 읽을 때 문제가 생긴다. (아래 삽질 파트에서 자세히)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PEM 형식으로 생성하면 키 첫 줄이 이렇게 나온다:&lt;/p&gt;
&lt;pre class=&quot;brainfuck&quot;&gt;&lt;code&gt;-----BEGIN RSA PRIVATE KEY-----   &amp;larr; PEM 형식 (정상)
-----BEGIN OPENSSH PRIVATE KEY--- &amp;larr; OPENSSH 형식 (Jenkins에서 문제)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;NAS에 공개키 등록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NAS 터미널에 SSH로 직접 접속해서 등록한다:&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;# NAS에 관리자 계정으로 SSH 접속
ssh -p 22 [관리자계정]@192.168.x.xxx
sudo -i

# git 사용자 .ssh 폴더에 공개키 등록
nano /volume1/homes/git/.ssh/authorized_keys
# &amp;rarr; 공개키(id_rsa.pub) 내용 전체 붙여넣기

# 권한 설정 (이거 안 하면 SSH 인증 안 됨)
chown git:users /volume1/homes/git/.ssh/authorized_keys
chmod 600 /volume1/homes/git/.ssh/authorized_keys
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등록 후 테스트:&lt;/p&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;ssh -p 22 git@192.168.x.xxx
# 아래처럼 나오면 성공
# fatal: Interactive git shell is not enabled.
# (git 사용자는 shell이 제한되어 있어서 이 메시지가 정상)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Step 2. Jenkins 설치 및 설정&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 사이트에서 jenkins.msi 다운로드 후 설치.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치 옵션:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;포트: &lt;b&gt;8081&lt;/b&gt; (8080은 다른 서비스가 사용 중이었음)&lt;/li&gt;
&lt;li&gt;서비스 계정: &lt;b&gt;LocalSystem&lt;/b&gt; (권한 문제 최소화)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 비밀번호 위치:&lt;/p&gt;
&lt;pre class=&quot;taggerscript&quot;&gt;&lt;code&gt;C:\ProgramData\Jenkins\.jenkins\secrets\initialAdminPassword
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러그인은 Install suggested plugins로 기본 설치. 이후에 &lt;b&gt;SSH Agent Plugin&lt;/b&gt; 추가 설치 필요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Tools 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Jenkins 관리 &amp;rarr; Tools&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDK 설정:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Name: jdk-17
JAVA_HOME: C:\Program Files\Eclipse Adoptium\jdk-17.0.16.8-hotspot
Install automatically: 체크 해제 (이미 설치됨)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gradle 설정:&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;Name: Gradle-8
Install automatically: 체크
Version: Gradle 9.3.0
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Security 설정 (중요)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Jenkins 관리 &amp;rarr; Security &amp;rarr; Git Host Key Verification Configuration&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Host Key Verification Strategy: Accept first connection
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본값인 &quot;Known hosts file&quot;로 두면 NAS 서버 키가 등록 안 되어 있어서 아래 에러가 난다:&lt;/p&gt;
&lt;pre class=&quot;vbnet&quot;&gt;&lt;code&gt;No ED25519 host key is known for 192.168.x.xxx and you have requested strict checking.
Host key verification failed.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Accept first connection으로 바꾸면 첫 접속 시 자동으로 키를 수락한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Credential 등록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Jenkins 관리 &amp;rarr; Credentials &amp;rarr; System &amp;rarr; Global credentials &amp;rarr; Add&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;Kind: SSH Username with private key
ID: synology-git-ssh
Username: git
Private Key: Enter directly
  &amp;rarr; id_rsa (개인키) 전체 내용 붙여넣기
  &amp;rarr; -----BEGIN RSA PRIVATE KEY----- 부터 끝까지 전부
Passphrase: (비워둠)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SSH Agent Plugin 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Jenkins 관리 &amp;rarr; Plugins &amp;rarr; Available plugins &amp;rarr; &quot;SSH Agent&quot; 검색 후 설치&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 없으면 Pipeline에서 sshagent 사용 시 아래 에러가 난다:&lt;/p&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;No such DSL method 'sshagent' found among steps
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치 후 Jenkins 재시작:&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;Restart-Service Jenkins
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Step 3. Pipeline 구성&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Job 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jenkins 대시보드 &amp;rarr; 새로운 Item &amp;rarr; &lt;b&gt;Pipeline&lt;/b&gt; 선택&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름: 프로젝트-CICD&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Jenkinsfile (최종 버전)&lt;/h3&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;pipeline {
    agent any

    environment {
        APP_NAME = '프로젝트'
        JAR_PATH = 'build/libs'
        DEPLOY_DIR = 'D:\\프로젝트'
        APP_PORT = '[앱포트]'
    }

    tools {
        jdk 'jdk-17'
        gradle 'Gradle-8'
    }

    stages {
        stage('Checkout') {
            steps {
                echo '=== Git 코드 가져오기 ==='
                script {
                    sshagent(['synology-git-ssh']) {
                        bat &quot;&quot;&quot;
                            git clone -b master ssh://git@192.168.x.xxx:22/volume1/homes/git/프로젝트.git . || git pull
                        &quot;&quot;&quot;
                    }
                }
            }
        }

        stage('Build') {
            steps {
                echo '=== Gradle 빌드 시작 ==='
                bat 'gradle clean bootJar'
            }
        }

        stage('Stop Old Application') {
            steps {
                echo '=== 기존 애플리케이션 종료 ==='
                script {
                    bat &quot;&quot;&quot;
                        for /f &quot;tokens=5&quot; %%a in ('netstat -ano ^| findstr :${APP_PORT}') do (
                            taskkill /F /PID %%a 2&amp;gt;nul
                        )
                    &quot;&quot;&quot;
                }
            }
        }

        stage('Deploy') {
            steps {
                echo '=== 새 버전 배포 ==='
                bat &quot;&quot;&quot;
                    if not exist ${DEPLOY_DIR} mkdir ${DEPLOY_DIR}
                    copy /Y ${JAR_PATH}\\*.jar ${DEPLOY_DIR}\\${APP_NAME}.jar
                &quot;&quot;&quot;
            }
        }

        stage('Start Application') {
            steps {
                echo '=== 애플리케이션 시작 ==='
                bat &quot;&quot;&quot;
                    cd ${DEPLOY_DIR}
                    start /B javaw -jar ${APP_NAME}.jar
                &quot;&quot;&quot;
            }
        }
    }

    post {
        success {
            echo '✅ 배포 성공!'
        }
        failure {
            echo '❌ 배포 실패! 로그를 확인하세요.'
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;삽질 포인트 정리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 포트 혼동 (Connection refused)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 Jenkins Pipeline URL을 이렇게 설정했다:&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;ssh://git@192.168.x.xxx:[포트포워딩포트]/...  &amp;larr; 틀림
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[포트포워딩포트]는 외부에서 접근할 때 쓰는 포트포워딩된 포트고, 내부망에서는 SSH 기본 포트 22를 직접 쓰면 된다.&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;ssh://git@192.168.x.xxx:22/...  &amp;larr; 맞음
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. SSH 키 형식 문제 (error in libcrypto)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 키를 이렇게 생성했다:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;ssh-keygen -t rsa -b 4096 -C &quot;jenkins@project&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 OPENSSH 형식으로 생성되는데, Jenkins에서 이 키를 임시 파일로 저장하는 과정에서 아래 에러가 난다:&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;Load key &quot;...\jenkins-gitclient-ssh[random].key&quot;: error in libcrypto
Permission denied (publickey,password).
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Windows에서 직접 SSH 명령어 치면 잘 되는데 Jenkins에서만 안 되는 이유가 이것 때문이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결은 키를 PEM 형식으로 다시 생성하는 것:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;ssh-keygen -t rsa -b 4096 -m PEM -C &quot;jenkins@project&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PEM 형식은 Java SSH 라이브러리와 호환성이 좋아서 Jenkins에서 안정적으로 작동한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. SSH Agent Plugin 없이 git 스텝 사용 (여전히 libcrypto 에러)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PEM으로 바꿔도 기존 방식으로는 에러가 계속 났다:&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;// 이 방식은 Jenkins가 키를 임시 파일로 저장해서 libcrypto 에러 발생
git branch: 'master',
    credentialsId: 'synology-git-ssh',
    url: 'ssh://git@192.168.x.xxx:22/...'
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jenkins의 기본 git 스텝은 SSH 키를 임시 파일로 저장하는 방식인데, 이 과정에서 Windows libcrypto 이슈가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSH Agent Plugin을 쓰면 키를 메모리에 올려서 처리하기 때문에 임시 파일을 안 만든다:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;// SSH Agent로 메모리에서 처리 &amp;rarr; 에러 없음
sshagent(['synology-git-ssh']) {
    bat &quot;git clone -b master ssh://git@192.168.x.xxx:22/... . || git pull&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. Jenkins가 배포한 앱을 죽이는 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java -jar 로 앱을 실행하면 Jenkins 파이프라인이 끝날 때 같이 종료된다. Jenkins가 자신이 실행한 자식 프로세스를 정리하기 때문.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 start /B javaw 로 Jenkins 프로세스 트리와 완전히 분리해서 실행한다:&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;start /B javaw -jar 프로젝트.jar
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;javaw는 콘솔 창 없이 백그라운드에서 실행되고, start /B로 완전히 분리된 독립 프로세스로 뜬다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. NAS 자동 업데이트 후 SSH 타임아웃&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 날 갑자기 Jenkins 빌드가 실패하기 시작했다. 보니까 NAS에 SSH 연결이 안 되는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Synology NAS가 자동 업데이트되면서 방화벽 규칙이 초기화됐다. Jenkins 서버 IP에서 포트 22로 들어오는 접근이 막혀버린 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;NAS 제어판 &amp;rarr; 보안 &amp;rarr; 방화벽&lt;/b&gt;에서 규칙 추가:&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;출발지 IP: 192.168.x.xxx (Jenkins 서버)
포트: 22
동작: 허용
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 같은 일 반복 안 되도록 NAS 자동 업데이트를 &lt;b&gt;수동 / 중요 보안 업데이트만&lt;/b&gt; 받도록 변경했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;배포 방식&lt;/b&gt;: 원격 접속 후 jar 수동 복붙 &amp;rarr; git push 한 번으로 끝&lt;/li&gt;
&lt;li&gt;&lt;b&gt;배포 이력&lt;/b&gt;: Jenkins에서 누가 언제 어떤 커밋으로 배포했는지 확인 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실수 제거&lt;/b&gt;: 수동 작업 없어지니까 복붙 실수 같은 것도 없어짐&lt;/li&gt;
&lt;li&gt;&lt;b&gt;팀원 편의&lt;/b&gt;: 배포에 신경 안 써도 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;느낀 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NAS 사내 Git에 Jenkins 연결은&amp;nbsp; 일반적인 GitHub 기준 레퍼런스가 안 맞는 경우가 많았다. SSH 키 형식 이슈는 에러 메시지만 봐서는 원인을 바로 알기 어려웠고, Jenkins가 내부적으로 키를 어떻게 처리하는지까지 파악하고 나서야 해결됐다. 직접 삽질하면서 SSH 인증 흐름이나 네트워크 포트 개념을 훨씬 잘 이해하게 된 작업이었다.&lt;/p&gt;</description>
      <category>DevOps</category>
      <category>CICD</category>
      <category>DevOps</category>
      <category>Git</category>
      <category>Jenkins</category>
      <category>ssh</category>
      <category>synology</category>
      <category>자동배포</category>
      <author>fkqlaus</author>
      <guid isPermaLink="true">https://fkqlaus.tistory.com/18</guid>
      <comments>https://fkqlaus.tistory.com/18#entry18comment</comments>
      <pubDate>Tue, 24 Mar 2026 22:35:01 +0900</pubDate>
    </item>
    <item>
      <title>폐쇄망 서버에 Docker 설치하기 (Rocky Linux 8)</title>
      <link>https://fkqlaus.tistory.com/17</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 폐쇄망 클라우드 서버에 Docker 설치할 일이 생겼다. 인터넷이 아예 안 되는 환경이라 생각보다 삽질을 많이 했고, 클로드랑 이러쿵 저러쿵 북치기 박치기 하면서 해결은 했는데 새롭게 알게 된 것들이 있어서 정리해둠.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;환경&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버 OS: Rocky Linux 8, x86_64&lt;/li&gt;
&lt;li&gt;네트워크: 완전 폐쇄망 (외부 인터넷 차단)&lt;/li&gt;
&lt;li&gt;SSH 포트: 2222 (기본 22 아님)&lt;/li&gt;
&lt;li&gt;접속 유저: apuser&lt;/li&gt;
&lt;li&gt;내 PC: Windows&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;폐쇄망 여부 확인하는 법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에 SSH 접속 후 아래 명령어로 확인할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# 외부로 패킷 날려보기
ping -c 3 8.8.8.8

# HTTP 통신 되는지
curl -I https://google.com --connect-timeout 5

# DNS 해석 되는지
nslookup google.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전부 실패하면 완전 폐쇄망이다. ping은 되는데 curl이 안 되면 포트 제한이 있는 것.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기본 전략&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폐쇄망이라 서버에서 dnf install docker-ce 같은 명령어로 직접 설치가 안 된다. dnf가 인터넷에서 패키지를 받아오는 방식인데 인터넷이 없으니까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이렇게 한다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;인터넷 되는 내 PC에서 필요한 파일 전부 다운로드&lt;/li&gt;
&lt;li&gt;SCP로 서버에 파일 전송&lt;/li&gt;
&lt;li&gt;서버에서 로컬 파일로 설치&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SCP가 뭔지&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SCP(Secure Copy Protocol)는 SSH를 이용해서 파일을 전송하는 방식이다. SSH랑 같은 포트를 사용하기 때문에 SSH가 되면 SCP도 된다.&lt;/p&gt;
&lt;pre class=&quot;inform7&quot;&gt;&lt;code&gt;scp -P [포트번호] [보낼파일] [유저명]@[서버IP]:[받을경로]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSH 포트가 2222이면 SCP도 -P 2222 로 맞춰줘야 한다. (SSH는 소문자 -p, SCP는 대문자 -P 임)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SFTP랑 SCP 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SCP가 안 될 때 SFTP로 대신 시도해볼 수 있다. SFTP는 SSH 위에서 돌아가는 파일 전송 프로토콜인데, 서버 sshd_config에 아래 설정이 있어야 동작한다.&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;Subsystem sftp /usr/libexec/openssh/sftp-server
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 확인하는 법:&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;sudo grep -E &quot;Subsystem|PasswordAuthentication&quot; /etc/ssh/sshd_config
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 작업한 서버는 Subsystem 설정이 이미 있었는데도 SFTP가 안 됐다. 이유는 아직 정확히 모르겠다.... 결국 SCP로 해결했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. RPM 파일 받기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에서 아래 주소 접속 후 파일 직접 클릭해서 다운로드.&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;https://download.docker.com/linux/rhel/8/x86_64/stable/Packages/
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;받을 파일 5개:&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;containerd.io-1.7.27-3.1.el8.x86_64.rpm
docker-ce-cli-29.3.0-1.el8.x86_64.rpm
docker-ce-29.3.0-1.el8.x86_64.rpm
docker-buildx-plugin-0.31.1-1.el8.x86_64.rpm
docker-compose-plugin-5.1.1-1.el8.x86_64.rpm
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버전 고를 때 주의할 점:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;docker-ce랑 docker-ce-cli는 버전 번호가 반드시 같아야 함&lt;/li&gt;
&lt;li&gt;docker-ce가 요구하는 containerd.io 최소 버전이 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예: docker-ce 29.3.0은 containerd.io &amp;gt;= 1.7.27 필요&lt;/li&gt;
&lt;li&gt;나는 처음에 1.7.26 받았다가 의존성 오류남&lt;/li&gt;
&lt;li&gt;설치 시도하면 오류 메시지에 필요한 버전이 나오니까 그걸 보고 맞춰서 다시 받으면 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. SCP로 서버 전송&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Windows PowerShell에서 실행. 파일을 한 줄로 묶어서 보내면 비밀번호를 한 번만 입력하면 된다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;scp -P 2222 `
  C:\docker-files\containerd.io-1.7.27-3.1.el8.x86_64.rpm `
  C:\docker-files\docker-ce-cli-29.3.0-1.el8.x86_64.rpm `
  C:\docker-files\docker-ce-29.3.0-1.el8.x86_64.rpm `
  C:\docker-files\docker-buildx-plugin-0.31.1-1.el8.x86_64.rpm `
  C:\docker-files\docker-compose-plugin-5.1.1-1.el8.x86_64.rpm `
  apuser@서버IP:~/
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비밀번호 입력할 때 화면에 아무것도 안 보이는 게 정상이다. 다 치고 엔터 누르면 전송 시작됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 접속하는 서버면 아래처럼 물어보는데 yes 입력하면 된다.&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;Are you sure you want to continue connecting (yes/no/[fingerprint])?
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 서버에서 Docker 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSH 접속 후:&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;# 파일 읽기 권한 부여 (없으면 Can not load RPM file 오류남)
sudo chmod 644 ~/*.rpm

# 설치
sudo dnf install -y --disablerepo=&quot;*&quot; \
  ./containerd.io-1.7.27-3.1.el8.x86_64.rpm \
  ./docker-ce-cli-29.3.0-1.el8.x86_64.rpm \
  ./docker-ce-29.3.0-1.el8.x86_64.rpm \
  ./docker-buildx-plugin-0.31.1-1.el8.x86_64.rpm \
  ./docker-compose-plugin-5.1.1-1.el8.x86_64.rpm
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 --disablerepo=&quot;*&quot; 옵션이 핵심이다. 이게 없으면 폐쇄망인데도 dnf가 외부 repo에 접근을 시도하고 타임아웃이 남. 이 옵션을 붙이면 외부 repo는 전부 무시하고 지금 지정한 로컬 파일만 보고 설치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Complete! 뜨면 설치 완료.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Docker 서비스 시작&lt;/h2&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;# 서비스 시작 + 부팅 시 자동 시작 등록
sudo systemctl enable --now docker

# 상태 확인
sudo systemctl status docker

# 버전 확인
docker --version
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;삽질 포인트 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 원인 해결&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 93px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;containerd 의존성 오류&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;docker-ce가 요구하는 버전보다 낮은 containerd 다운로드&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;오류 메시지에서 필요한 버전 확인 후 다시 받기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Can not load RPM file&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;RPM 파일 읽기 권한 없음&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;sudo chmod 644 *.rpm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;dnf 설치 중 타임아웃&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;폐쇄망인데 외부 repo 접근 시도&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;--disablerepo=&quot;*&quot; 옵션 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;SFTP 접속 안 됨&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;원인 불명 (Subsystem 설정은 있었음)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;SCP로 대신 전송&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;tmp 폴더에 넣으려다가..&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;tmp 폴더에 rpm 파일을 넣고 돌리는게 잘 안되는거 같았다 아무래도 권한 문제도 조금 있는듯 함.&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;그냥 user 경로에 바로 넣어버리자&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>DevOps</category>
      <category>DevOps</category>
      <category>docker</category>
      <category>Linux</category>
      <category>서버</category>
      <category>폐쇄망</category>
      <author>fkqlaus</author>
      <guid isPermaLink="true">https://fkqlaus.tistory.com/17</guid>
      <comments>https://fkqlaus.tistory.com/17#entry17comment</comments>
      <pubDate>Tue, 24 Mar 2026 21:59:58 +0900</pubDate>
    </item>
    <item>
      <title>[완전탐색] 프로그래머스 - 최소 직사각형</title>
      <link>https://fkqlaus.tistory.com/16</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/86491&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/86491&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1773666792380&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/86491&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bGEzV5/dJMb83krICN/x7q2REsO4amC4KkWBz7njk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/bcAfQ0/dJMb9aKDVow/2GKfWGlHxgi96GoCKrolqK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/86491&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/86491&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bGEzV5/dJMb83krICN/x7q2REsO4amC4KkWBz7njk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/bcAfQ0/dJMb9aKDVow/2GKfWGlHxgi96GoCKrolqK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 설명&lt;/p&gt;
&lt;div style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot;&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;명함 지갑을 만드는 회사에서 지갑의 크기를 정하려고 합니다. 다양한 모양과 크기의 명함들을 모두 수납할 수 있으면서, 작아서 들고 다니기 편한 지갑을 만들어야 합니다. 이러한 요건을 만족하는 지갑을 만들기 위해 디자인팀은 모든 명함의 가로 길이와 세로 길이를 조사했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;아래 표는 4가지 명함의 가로 길이와 세로 길이를 나타냅니다.&lt;/p&gt;
명함 번호가로 길이세로 길이
&lt;table style=&quot;color: #000000; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody style=&quot;color: #000000;&quot;&gt;
&lt;tr style=&quot;color: #000000;&quot;&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;60&lt;/td&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;color: #000000;&quot;&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;30&lt;/td&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;color: #000000;&quot;&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;60&lt;/td&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;color: #000000;&quot;&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;80&lt;/td&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;가장 긴 가로 길이와 세로 길이가 각각 80, 70이기 때문에 80(가로) x 70(세로) 크기의 지갑을 만들면 모든 명함들을 수납할 수 있습니다. 하지만 2번 명함을 가로로 눕혀 수납한다면 80(가로) x 50(세로) 크기의 지갑으로 모든 명함들을 수납할 수 있습니다. 이때의 지갑 크기는 4000(=80 x 50)입니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;모든 명함의 가로 길이와 세로 길이를 나타내는 2차원 배열 sizes가 매개변수로 주어집니다. 모든 명함을 수납할 수 있는 가장 작은 지갑을 만들 때, 지갑의 크기를 return 하도록 solution 함수를 완성해주세요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;제한사항
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;sizes의 길이는 1 이상 10,000 이하입니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;sizes의 원소는 [w, h] 형식입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;w는 명함의 가로 길이를 나타냅니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;h는 명함의 세로 길이를 나타냅니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;w와 h는 1 이상 1,000 이하인 자연수입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;입출력 예sizesresult
&lt;table style=&quot;color: #000000; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody style=&quot;color: #000000;&quot;&gt;
&lt;tr style=&quot;color: #000000;&quot;&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;[[60, 50], [30, 70], [60, 30], [80, 40]]&lt;/td&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;4000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;color: #000000;&quot;&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;[[10, 7], [12, 3], [8, 15], [14, 7], [5, 15]]&lt;/td&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;120&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;color: #000000;&quot;&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;[[14, 4], [19, 6], [6, 16], [18, 7], [7, 11]]&lt;/td&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;133&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;입출력 예 설명
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;입출력 예 #1&lt;br /&gt;문제 예시와 같습니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;입출력 예 #2&lt;br /&gt;명함들을 적절히 회전시켜 겹쳤을 때, 3번째 명함(가로: 8, 세로: 15)이 다른 모든 명함보다 크기가 큽니다. 따라서 지갑의 크기는 3번째 명함의 크기와 같으며, 120(=8 x 15)을 return 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;입출력 예 #3&lt;br /&gt;명함들을 적절히 회전시켜 겹쳤을 때, 모든 명함을 포함하는 가장 작은 지갑의 크기는 133(=19 x 7)입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이&lt;/p&gt;
&lt;pre id=&quot;code_1773666831283&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Solution {
    public int solution(int[][] sizes) {
               int answer = 0;

        for(int i=0; i&amp;lt;sizes.length; i++) {
            if(sizes[i][0] &amp;lt; sizes[i][1]) {
                int temp = sizes[i][0];
                sizes[i][0] = sizes[i][1];
                sizes[i][1] = temp;
            }
        }

        int width = 0;
        int height = 0;

        for(int i=0; i&amp;lt;sizes.length; i++) {
            width = Math.max(width,sizes[i][0]);
            height = Math.max(height,sizes[i][1]);
        }
        
        answer = width * height;
        
        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제핵심&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;가로 세로의 방향 통일을 먼저 하자, 모든 카드의 가로 &amp;gt; 세로로 통일 하고 최대값을 구해얗ㅁ&lt;/li&gt;
&lt;li&gt;temp로 swap 하면서 가로 세로 구하기&lt;/li&gt;
&lt;li&gt;arr.length 는 행 개수, arr[i].length 는 열 개수, 헷갈리지 않기&lt;/li&gt;
&lt;li&gt;2차원 배열의 최대값 구하기 또 헷갈렸으니까 잘 기억하기.....&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1773667005249&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 2차원 배열의 최대값!!!!!!

int maxWidth = 0;
int maxHeight = 0;

for (int i = 0; i &amp;lt; sizes.length; i++) {
    maxWidth = Math.max(maxWidth, sizes[i][0]);
    maxHeight = Math.max(maxHeight, sizes[i][1]);
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알고리즘 문제풀이</category>
      <category>Java</category>
      <category>알고리즘</category>
      <category>완전탐색</category>
      <category>자바</category>
      <category>최소직사각형</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <category>프로그래밍</category>
      <author>fkqlaus</author>
      <guid isPermaLink="true">https://fkqlaus.tistory.com/16</guid>
      <comments>https://fkqlaus.tistory.com/16#entry16comment</comments>
      <pubDate>Mon, 16 Mar 2026 22:17:18 +0900</pubDate>
    </item>
    <item>
      <title>[완전탐색] 프로그래머스 - 두 개 뽑아서 더하기</title>
      <link>https://fkqlaus.tistory.com/15</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/68644&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/68644&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1773663463819&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/68644&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cVSxyj/dJMb9efcJB0/c73V2wtp5KipcLyM611pO0/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/yLDXQ/dJMb9iIFT9y/9391aJMsonAFttMDhFuxu0/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/68644&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/68644&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cVSxyj/dJMb9efcJB0/c73V2wtp5KipcLyM611pO0/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/yLDXQ/dJMb9iIFT9y/9391aJMsonAFttMDhFuxu0/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 설명&lt;/p&gt;
&lt;div style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot;&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;정수 배열 numbers가 주어집니다. numbers에서 서로 다른 인덱스에 있는 두 개의 수를 뽑아 더해서 만들 수 있는 모든 수를 배열에 오름차순으로 담아 return 하도록 solution 함수를 완성해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;제한사항&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;numbers의 길이는 2 이상 100 이하입니다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;numbers의 모든 수는 0 이상 100 이하입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;입출력 예numbersresult&lt;/span&gt;
&lt;table style=&quot;color: #000000; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody style=&quot;color: #000000;&quot;&gt;
&lt;tr style=&quot;color: #000000;&quot;&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;[2,1,3,4,1]&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;[2,3,4,5,6,7]&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;color: #000000;&quot;&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;[5,0,2,7]&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;[2,5,7,9,12]&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;입출력 예 설명&lt;/span&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;입출력 예 #1&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;2 = 1 + 1 입니다. (1이 numbers에 두 개 있습니다.)&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;3 = 2 + 1 입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;4 = 1 + 3 입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;5 = 1 + 4 = 2 + 3 입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;6 = 2 + 4 입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;7 = 3 + 4 입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;따라서&amp;nbsp;[2,3,4,5,6,7]&amp;nbsp;을 return 해야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;입출력 예 #2&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;2 = 0 + 2 입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;5 = 5 + 0 입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;7 = 0 + 7 = 5 + 2 입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;9 = 2 + 7 입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;12 = 5 + 7 입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;따라서&amp;nbsp;[2,5,7,9,12]&amp;nbsp;를 return 해야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;풀이&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773663508810&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*;

class Solution {
    public int[] solution(int[] numbers) {
                int[] answer = {};

        Set&amp;lt;Integer&amp;gt; set = new HashSet&amp;lt;&amp;gt;();
        for (int i=0;i&amp;lt;numbers.length;i++) {
            for (int j = i+1; j &amp;lt; numbers.length; j++) {
                set.add(numbers[i] + numbers[j]);
            }
        }

        List&amp;lt;Integer&amp;gt; list = new ArrayList&amp;lt;&amp;gt;(set);

        Collections.sort(list);


        return list.stream().mapToInt(Integer::intValue).toArray();

    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제핵심&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;모든 조합 순회는 2중 for문에&amp;nbsp; int j = i+1 을 기억하면 되겠다&lt;/li&gt;
&lt;li&gt;중복제거는 Set을 이용하자 Set은 중복을 없애줌. add 로 더해라&lt;/li&gt;
&lt;li&gt;정렬은 Collections.sort 사용하기&lt;/li&gt;
&lt;li&gt;Set -&amp;gt; List -&amp;gt; 배열 로 넘어간다. 또 나온 stream().mapToInt 기억 잘하기&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>알고리즘 문제풀이</category>
      <category>Java</category>
      <category>두 개 뽑아서 더하기</category>
      <category>알고리즘</category>
      <category>완전탐색</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <author>fkqlaus</author>
      <guid isPermaLink="true">https://fkqlaus.tistory.com/15</guid>
      <comments>https://fkqlaus.tistory.com/15#entry15comment</comments>
      <pubDate>Mon, 16 Mar 2026 21:27:02 +0900</pubDate>
    </item>
    <item>
      <title>[완전탐색] 프로그래머스 - 모의고사</title>
      <link>https://fkqlaus.tistory.com/14</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42840&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/42840&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 설명&lt;/p&gt;
&lt;div style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot;&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;수포자는 수학을 포기한 사람의 준말입니다. 수포자 삼인방은 모의고사에 수학 문제를 전부 찍으려 합니다. 수포자는 1번 문제부터 마지막 문제까지 다음과 같이 찍습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;1번 수포자가 찍는 방식: 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, ...&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;2번 수포자가 찍는 방식: 2, 1, 2, 3, 2, 4, 2, 5, 2, 1, 2, 3, 2, 4, 2, 5, ...&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;3번 수포자가 찍는 방식: 3, 3, 1, 1, 2, 2, 4, 4, 5, 5, 3, 3, 1, 1, 2, 2, 4, 4, 5, 5, ...&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;1번 문제부터 마지막 문제까지의 정답이 순서대로 들은 배열 answers가 주어졌을 때, 가장 많은 문제를 맞힌 사람이 누구인지 배열에 담아 return 하도록 solution 함수를 작성해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;span style=&quot;color: #ffffff;&quot;&gt;제한 조건&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;시험은 최대 10,000 문제로 구성되어있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;문제의 정답은 1, 2, 3, 4, 5중 하나입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;가장 높은 점수를 받은 사람이 여럿일 경우, return하는 값을 오름차순 정렬해주세요.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span style=&quot;color: #ffffff;&quot;&gt;입출력 예answersreturn&lt;/span&gt;
&lt;table style=&quot;color: #000000; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody style=&quot;color: #000000;&quot;&gt;
&lt;tr style=&quot;color: #000000;&quot;&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;[1,2,3,4,5]&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;[1]&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;color: #000000;&quot;&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;[1,3,2,4,2]&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #202b3d; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;[1,2,3]&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;span style=&quot;color: #ffffff;&quot;&gt;입출력 예 설명&lt;/span&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;입출력 예 #1&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;수포자 1은 모든 문제를 맞혔습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;수포자 2는 모든 문제를 틀렸습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;수포자 3은 모든 문제를 틀렸습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;따라서 가장 문제를 많이 맞힌 사람은 수포자 1입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;입출력 예 #2&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;모든 사람이 2문제씩을 맞췄습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;풀이&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773660325179&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*;

class Solution {
    public int[] solution(int[] answers) {
        int[][] patterns = {
        	{1, 2, 3, 4, 5},
            {2, 1, 2, 3, 2, 4, 2, 5},
            {3, 3, 1, 1, 2, 2, 4, 4, 5, 5}
        };

        int[] count = new int[3];

        for(int i=0; i&amp;lt;answers.length; i++) {
            for(int j=0; j&amp;lt;3; j++){
                if(answers[i] == patterns[j][i%patterns[j].length]){
                    count[j]++;
                }
            }
        }

        int max = 0;
        for (int c : count) {
            max = Math.max(max, c);
        }        
        List&amp;lt;Integer&amp;gt; result = new ArrayList&amp;lt;&amp;gt;();
        for (int i = 0; i &amp;lt; count.length; i++) {
            if(count[i] == max) {
                result.add(i+1);
            }
        }

        return result.stream().mapToInt(Integer::intValue).toArray();

    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;풀이&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 핵심&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&amp;nbsp;i % 배열.length 로 인덱스가 배열 길이를 넘지 않게 끝까지 반복 가능&lt;/li&gt;
&lt;li&gt;pattern을 1차원 배열 3개로 하려고 하지말고 2차원 배열로 묶어서 처리..(당연한 것)&lt;/li&gt;
&lt;li&gt;최댓값 구할때 그냥 Math 라이브러리 max 함수 쓰자&lt;/li&gt;
&lt;li&gt;크기가 가변적인 결과는 List 로 받아서 모으고 마지막에 stream().mapToInt 로 배열로 바꾸자(여러 함수를 잘 알아두자)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완전탐색 사고방식&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;모든 경우를 다 확인&lt;/li&gt;
&lt;li&gt;조건에 맞는 것만 추리기&lt;/li&gt;
&lt;li&gt;결과를 정리해서 리턴하기&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오랜만에 다시 하니까 너무 어렵다!!!!!! 꾸준히 좀 해놓을걸&lt;/p&gt;</description>
      <category>알고리즘 문제풀이</category>
      <category>Java</category>
      <category>모의고사</category>
      <category>알고리즘</category>
      <category>완전탐색</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <author>fkqlaus</author>
      <guid isPermaLink="true">https://fkqlaus.tistory.com/14</guid>
      <comments>https://fkqlaus.tistory.com/14#entry14comment</comments>
      <pubDate>Mon, 16 Mar 2026 20:34:25 +0900</pubDate>
    </item>
    <item>
      <title>[GIS]Geoserver  Tomcat으로 실행하기</title>
      <link>https://fkqlaus.tistory.com/13</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Geoserver 홈페이지에 접속한다&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://geoserver.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://geoserver.org/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1747396737804&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;GeoServer&quot; data-og-description=&quot;GeoServer is an open source server for sharing geospatial data. Designed for interoperability, it publishes data from any major spatial data source using open standards.&quot; data-og-host=&quot;geoserver.org&quot; data-og-source-url=&quot;https://geoserver.org/&quot; data-og-url=&quot;https://geoserver.org/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b2fRQk/hyYRxZQV62/Ks1Uw9Cgjr0Ti17ExoNvEk/img.png?width=640&amp;amp;height=278&amp;amp;face=0_0_640_278&quot;&gt;&lt;a href=&quot;https://geoserver.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://geoserver.org/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b2fRQk/hyYRxZQV62/Ks1Uw9Cgjr0Ti17ExoNvEk/img.png?width=640&amp;amp;height=278&amp;amp;face=0_0_640_278');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GeoServer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;GeoServer is an open source server for sharing geospatial data. Designed for interoperability, it publishes data from any major spatial data source using open standards.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;geoserver.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;홈페이지에서 원하는 버전을 다운로드하면 되는데&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;나는 2.26.3으로 정했고, war형식으로 받았다(Web Archive)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://tomcat.apache.org/download-90.cgi&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://tomcat.apache.org/download-90.cgi&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1747396799256&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Apache Tomcat&amp;reg; - Apache Tomcat 9 Software Downloads&quot; data-og-description=&quot;Welcome to the Apache Tomcat&amp;reg; 9.x software download page. This page provides download links for obtaining the latest version of Tomcat 9.0.x software, as well as links to the archives of older releases. Unsure which version you need? Specification version&quot; data-og-host=&quot;tomcat.apache.org&quot; data-og-source-url=&quot;https://tomcat.apache.org/download-90.cgi&quot; data-og-url=&quot;https://tomcat.apache.org/download-90.cgi&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://tomcat.apache.org/download-90.cgi&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tomcat.apache.org/download-90.cgi&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Apache Tomcat&amp;reg; - Apache Tomcat 9 Software Downloads&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Welcome to the Apache Tomcat&amp;reg; 9.x software download page. This page provides download links for obtaining the latest version of Tomcat 9.0.x software, as well as links to the archives of older releases. Unsure which version you need? Specification version&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;tomcat.apache.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;마찬가지로 Tomcat도 사이트에 들어가서 다운로드하면 된다!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;나는 9.0.104 를 다운로드하였다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그 후 다운로드한 Geoserver의 압축을 풀고 geoserver.war를&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Tomcat에 webapps에 넣어준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;641&quot; data-origin-height=&quot;330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bF5PEy/btsN0y91n5b/re3gpEfRsTTwlt14Vc3JT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bF5PEy/btsN0y91n5b/re3gpEfRsTTwlt14Vc3JT1/img.png&quot; data-alt=&quot;gerserver.war&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bF5PEy/btsN0y91n5b/re3gpEfRsTTwlt14Vc3JT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbF5PEy%2FbtsN0y91n5b%2Fre3gpEfRsTTwlt14Vc3JT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;641&quot; height=&quot;330&quot; data-origin-width=&quot;641&quot; data-origin-height=&quot;330&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;gerserver.war&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그 후 conf에 server.xml 파일에 들어가서&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;8080 포트와 8005 포트 앞에 3을 붙여줬다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;꼭 안 해도 되는데 포트가 꼬일 걸 염려해서 이렇게 설정했다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;739&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EtnxB/btsN19m7auT/GSsuePI2yay5VQ1sJOYOq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EtnxB/btsN19m7auT/GSsuePI2yay5VQ1sJOYOq0/img.png&quot; data-alt=&quot;8005 &amp;amp;rarr; 38005&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EtnxB/btsN19m7auT/GSsuePI2yay5VQ1sJOYOq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEtnxB%2FbtsN19m7auT%2FGSsuePI2yay5VQ1sJOYOq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;739&quot; height=&quot;132&quot; data-origin-width=&quot;739&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;8005 &amp;rarr; 38005&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;125&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oJt7z/btsN1ZEYYJf/MDcPCLSPWRio5HMiC3guu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oJt7z/btsN1ZEYYJf/MDcPCLSPWRio5HMiC3guu0/img.png&quot; data-alt=&quot;8080 &amp;amp;rarr; 38080&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oJt7z/btsN1ZEYYJf/MDcPCLSPWRio5HMiC3guu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoJt7z%2FbtsN1ZEYYJf%2FMDcPCLSPWRio5HMiC3guu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;638&quot; height=&quot;125&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;125&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;8080 &amp;rarr; 38080&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그 후 bin 파일에 있는&amp;nbsp; startup.bat 파일을 실행시켜 주면 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;623&quot; data-origin-height=&quot;606&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8Zjo7/btsN1ewaEUV/Te5EGym5NHdF8dLEWC29k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8Zjo7/btsN1ewaEUV/Te5EGym5NHdF8dLEWC29k0/img.png&quot; data-alt=&quot;startup.bat 파일 실행&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8Zjo7/btsN1ewaEUV/Te5EGym5NHdF8dLEWC29k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8Zjo7%2FbtsN1ewaEUV%2FTe5EGym5NHdF8dLEWC29k0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;623&quot; height=&quot;606&quot; data-origin-width=&quot;623&quot; data-origin-height=&quot;606&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;startup.bat 파일 실행&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그 후&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;주소창에 localhost:38080/geoserver로 접속해서 로그인을 하면 되는데&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;보통 초기 ID/PW는 admin/geoserver 다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;905&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QAuOi/btsN0vS02nw/qc2g5XMb7Iu7a21R8jqSVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QAuOi/btsN0vS02nw/qc2g5XMb7Iu7a21R8jqSVK/img.png&quot; data-alt=&quot;geoserver 접속완료&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QAuOi/btsN0vS02nw/qc2g5XMb7Iu7a21R8jqSVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQAuOi%2FbtsN0vS02nw%2Fqc2g5XMb7Iu7a21R8jqSVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;905&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;905&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;geoserver 접속완료&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 끝!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;각 사용한 버전&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;JAVA - 17.0.9&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Geoserver - 2.26.3&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Tomcat - 9.0.104&lt;/p&gt;</description>
      <category>GIS</category>
      <category>geoserver</category>
      <category>GIS</category>
      <category>Java</category>
      <category>개발</category>
      <category>공간정보</category>
      <author>fkqlaus</author>
      <guid isPermaLink="true">https://fkqlaus.tistory.com/13</guid>
      <comments>https://fkqlaus.tistory.com/13#entry13comment</comments>
      <pubDate>Fri, 16 May 2025 21:11:38 +0900</pubDate>
    </item>
    <item>
      <title>[SWEA]1926. 간단한 369게임</title>
      <link>https://fkqlaus.tistory.com/12</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://swexpertacademy.com/main/code/problem/problemDetail.do?problemLevel=2&amp;amp;contestProbId=AV5PTeo6AHUDFAUq&amp;amp;categoryId=AV5PTeo6AHUDFAUq&amp;amp;categoryType=CODE&amp;amp;problemTitle=&amp;amp;orderBy=FIRST_REG_DATETIME&amp;amp;selectCodeLang=JAVA&amp;amp;select-1=2&amp;amp;pageSize=10&amp;amp;pageIndex=1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;문제링크&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;369 게임을 만들면 되는 문제이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;보통 다른 369문제는 범위가 99까지였던 것 같은데 이건 1000까지여서 오잉? 했다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;풀이&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import java.util.Scanner;

public class JAVA1 {

    static Scanner sc = new Scanner(System.in);

    public static void solution(int n){

        for(int i=1;i&amp;lt;=n;i++){
            String values = String.valueOf(i);

            if(values.contains(&quot;3&quot;) || values.contains(&quot;6&quot;) || values.contains(&quot;9&quot;)){
                for(int j=0;j&amp;lt; values.length();j++){
                    if(values.charAt(j)=='3' ||values.charAt(j)=='6' ||values.charAt(j)=='9'){
                        System.out.print(&quot;-&quot;);
                    }
                }
                System.out.print(&quot; &quot;);
            } else {
                System.out.print(i + &quot; &quot;);
            }

        }
    }

    public static void main(String[] args) {
            int n = sc.nextInt();
            solution(n);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;숫자의 자릿수를 쉽게 검사하기 위해 주어진 숫자를 대신 문자열로 변환하는 방법을 생각했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주어진 숫자를 문자열로 변환한다.&lt;/li&gt;
&lt;li&gt;각 주어진 문자열에 3,6,9가 포함되어 있는지 1차 확인한다.&lt;/li&gt;
&lt;li&gt;그 후 포함되어 있다면 문자열의 각 자릿수에 3,6,9가 있는지 확인해 있다면 - 출력한다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다른 풀이&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745829557898&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.Scanner;

public class JAVA1 {

    static Scanner sc = new Scanner(System.in);

    public static void solution(int n) {
        for (int i = 1; i &amp;lt;= n; i++) {
            int number = i;
            int count = 0;

            while (number &amp;gt; 0) {
                int digit = number % 10;
                if (digit == 3 || digit == 6 || digit == 9) {
                    count++;
                }
                number /= 10;
            }

            if (count &amp;gt; 0) {
                for (int j = 0; j &amp;lt; count; j++) {
                    System.out.print(&quot;-&quot;);
                }
            } else {
                System.out.print(i);
            }
            System.out.print(&quot; &quot;);
        }
    }

    public static void main(String[] args) {
        int n = sc.nextInt();
        solution(n);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이건 숫자 연산을 통해서 문제를 해결한 코드이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;222&quot; data-start=&quot;122&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;151&quot; data-start=&quot;122&quot;&gt;숫자를 10으로 나누면서 각 자릿수를 뽑아낸다.&lt;/li&gt;
&lt;li data-end=&quot;186&quot; data-start=&quot;152&quot;&gt;뽑아낸 각 자릿수가 3, 6, 9이면 -를 출력한다.&lt;/li&gt;
&lt;li data-end=&quot;222&quot; data-start=&quot;187&quot;&gt;다 끝났을 때 아무 -도 없으면 원래 숫자를 출력한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들자면 39를 처리할 때&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;222&quot; data-start=&quot;122&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1439&quot; data-start=&quot;1409&quot;&gt;39 % 10 = 9 &amp;rarr; 9이므로 count++&lt;/li&gt;
&lt;li data-end=&quot;1483&quot; data-start=&quot;1440&quot;&gt;39 / 10 = 3 &amp;rarr; 3 % 10 = 3 &amp;rarr; 3이므로 count++&lt;/li&gt;
&lt;li data-end=&quot;1501&quot; data-start=&quot;1484&quot;&gt;3 / 10 = 0 &amp;rarr; 끝남&lt;/li&gt;
&lt;li data-end=&quot;1526&quot; data-start=&quot;1502&quot;&gt;count가 2이므로 - - 출력&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 문자열로 변환하는 접근을 했다가 숫자연산을 가지고도 풀이해 봤다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘 문제풀이</category>
      <category>369</category>
      <category>369게임</category>
      <category>D2</category>
      <category>Java</category>
      <category>SWEA</category>
      <category>문자열</category>
      <category>코딩테스트</category>
      <author>fkqlaus</author>
      <guid isPermaLink="true">https://fkqlaus.tistory.com/12</guid>
      <comments>https://fkqlaus.tistory.com/12#entry12comment</comments>
      <pubDate>Mon, 28 Apr 2025 17:50:27 +0900</pubDate>
    </item>
    <item>
      <title>[SWEA]21425. +=</title>
      <link>https://fkqlaus.tistory.com/11</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://swexpertacademy.com/main/code/problem/problemDetail.do?problemLevel=2&amp;amp;contestProbId=AZD8K_UayDoDFAVs&amp;amp;categoryId=AZD8K_UayDoDFAVs&amp;amp;categoryType=CODE&amp;amp;problemTitle=&amp;amp;orderBy=FIRST_REG_DATETIME&amp;amp;selectCodeLang=JAVA&amp;amp;select-1=2&amp;amp;pageSize=20&amp;amp;pageIndex=1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;문제링크&lt;/b&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 이거 어떻게 푸는 문제여? 싶었지만 그리디 알고리즘이라는 걸 파악했다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;문제를 요약하자면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;194&quot; data-start=&quot;90&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;109&quot; data-start=&quot;90&quot;&gt;현재 값이 (x, y)이고&lt;/li&gt;
&lt;li data-end=&quot;135&quot; data-start=&quot;110&quot;&gt;두 값 중 하나라도 N을 초과할 때까지&lt;/li&gt;
&lt;li data-end=&quot;169&quot; data-start=&quot;136&quot;&gt;&quot;x += y&quot; 또는 &quot;y += x&quot; 연산을 원하는 대로&lt;/li&gt;
&lt;li data-end=&quot;194&quot; data-start=&quot;170&quot;&gt;&lt;b&gt;연산 횟수를 최소로&lt;/b&gt; 만들고 싶다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;251&quot; data-start=&quot;196&quot; data-ke-size=&quot;size16&quot;&gt;그런데 이때 &lt;b&gt;매번 둘 중 더 작은 값에 더 큰 값을 더하는 게&lt;/b&gt; 최적이라는 걸 알 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;379&quot; data-start=&quot;252&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;330&quot; data-start=&quot;252&quot;&gt;예를 들어, x가 작고 y가 크다면, x에 y를 더하는 x += y를 하면 &lt;b&gt;x가 크게 증가&lt;/b&gt;하니까 빠르게 N을 넘을 수 있다.&lt;/li&gt;
&lt;li data-end=&quot;379&quot; data-start=&quot;331&quot;&gt;반대로, y가 작고 x가 크다면, y += x를 해서 y를 크게 키워야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;풀이&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import java.util.Scanner;

public class JAVA1 {

    static Scanner sc = new Scanner(System.in);

    public static void solution(int n){

        int [] answer = new int[3];

        for(int i=0;i&amp;lt;n;i++){
            int count=0;
            for(int j=0;j&amp;lt;3;j++){
                answer[j] = sc.nextInt();
            }
            while(answer[0] &amp;lt;= answer[2] &amp;amp;&amp;amp; answer[1] &amp;lt;= answer[2]){
                if(answer[0]&amp;lt;answer[1]){
                    answer[0] += answer[1];
                    count++;
                }
                else{
                    answer[1] += answer[0];
                    count++;
                }
            }
            System.out.println(count);
        }
    }

    public static void main(String[] args) {
        int n = sc.nextInt();
        solution(n);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘 문제풀이</category>
      <category>D2</category>
      <category>Java</category>
      <category>SWEA</category>
      <category>그리디알고리즘</category>
      <category>알고리즘</category>
      <category>코딩테스트</category>
      <author>fkqlaus</author>
      <guid isPermaLink="true">https://fkqlaus.tistory.com/11</guid>
      <comments>https://fkqlaus.tistory.com/11#entry11comment</comments>
      <pubDate>Mon, 28 Apr 2025 11:50:43 +0900</pubDate>
    </item>
    <item>
      <title>04. 단어 뒤집기</title>
      <link>https://fkqlaus.tistory.com/10</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;설명&lt;/span&gt;&lt;br /&gt;N개의 단어가 주어지면 각 단어를 뒤집어 출력하는 프로그램을 작성하세요.&lt;br /&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;입력&lt;/span&gt;&lt;br /&gt;첫 줄에 자연수 N(3 &amp;lt;=N &amp;lt;=20)이 주어집니다.&lt;br /&gt;두 번째 줄부터 N개의 단어가 각 줄에 하나씩 주어집니다. 단어는 영어 알파벳으로만 구성되어 있습니다.&lt;br /&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;출력&lt;/span&gt;&lt;br /&gt;N개의&amp;nbsp;단어를&amp;nbsp;입력된&amp;nbsp;순서대로&amp;nbsp;한&amp;nbsp;줄에&amp;nbsp;하나씩&amp;nbsp;뒤집어서&amp;nbsp;출력합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;예시&amp;nbsp;입력&amp;nbsp;1&amp;nbsp;&lt;/span&gt; &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745544237686&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;3
good
Time
Big&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;예시&amp;nbsp;출력&amp;nbsp;1&lt;/span&gt; &lt;/p&gt;
&lt;pre id=&quot;code_1745544254832&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;doog
emiT
giB&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;풀이&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import java.util.ArrayList;
import java.util.Scanner;

public class JAVA1 {

    public static ArrayList&amp;lt;String&amp;gt; solution(String[] s){

        ArrayList&amp;lt;String&amp;gt; answer = new ArrayList&amp;lt;&amp;gt;();

        for(String i:s){
            String temp = new StringBuilder(i).reverse().toString();
            answer.add(temp);
        }
        return answer;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);


        int n = sc.nextInt();
        sc.nextLine();
        String[] s = new String[n];

        for(int i=0;i&amp;lt;n;i++){
            s[i] = sc.nextLine();
        }

        for(String i:solution(s)){
            System.out.println(i);
        }

    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;StringBuilder를 활용한 풀이이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;풀다 보니 StringBuilder에 대해서 많이 까먹은 거 같아서 이것도 따로 포스팅해야겠다&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;두 번째 풀이&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.util.ArrayList;
import java.util.Scanner;

public class JAVA1 {

    public static ArrayList&amp;lt;String&amp;gt; solution(String[] s){

        ArrayList&amp;lt;String&amp;gt; answer = new ArrayList&amp;lt;&amp;gt;();
        
        for(String i:s){
            char[] c = i.toCharArray();
            int lt = 0;
            int rt = c.length-1;

            while(lt&amp;lt;rt){
                char temp = c[lt];
                c[lt] = c[rt];
                c[rt] = temp;
                lt++;
                rt--;

            }
            answer.add(new String(c));
        }
        return answer;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);


        int n = sc.nextInt();
        sc.nextLine();
        String[] s = new String[n];

        for(int i=0;i&amp;lt;n;i++){
            s[i] = sc.nextLine();
        }

        for(String i:solution(s)){
            System.out.println(i);
        }

    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;StringBuilder를 사용하지 않고 toCharArray로 String을 char 배열로 받아 각각의 index를 활용한 풀이이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;첫 번째 문자에 lt, 마지막 문자에 rt를 넣고 각각 +1, -1을 하며 교환하는 방식이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;lt가 rt보다 커진다면 배열의 중간을 넘어갔으므로 while문을 종료한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;reverse() 함수를 직접 구현했다고 보면 되겠다!&lt;/p&gt;</description>
      <category>알고리즘 문제풀이</category>
      <category>Java</category>
      <category>reverse</category>
      <category>String</category>
      <category>StringBuilder</category>
      <category>단어 뒤집기</category>
      <category>알고리즘</category>
      <category>코딩테스트</category>
      <author>fkqlaus</author>
      <guid isPermaLink="true">https://fkqlaus.tistory.com/10</guid>
      <comments>https://fkqlaus.tistory.com/10#entry10comment</comments>
      <pubDate>Fri, 25 Apr 2025 10:51:29 +0900</pubDate>
    </item>
    <item>
      <title>Java - Collection Framework란?</title>
      <link>https://fkqlaus.tistory.com/9</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이전에 포스팅했던&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://fkqlaus.tistory.com/entry/%EC%9E%90%EB%B0%94-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D1-for-each%EB%AC%B8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.04.24 - [프로그래밍언어/Java] - 자바 프로그래밍(1) - for each문&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1745486196698&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;자바 프로그래밍(1) - for each문&quot; data-og-description=&quot;알고리즘 공부를 하다가 for each문이 나와서 글을 작성해본다.학부에서 Java를 공부할 때 for each문을 접해본 적이 없었는데NHN 아카데미 자바 백엔드 과정을 수료하면서 처음 배웠던 기억이 있다...&quot; data-og-host=&quot;fkqlaus.tistory.com&quot; data-og-source-url=&quot;https://fkqlaus.tistory.com/entry/%EC%9E%90%EB%B0%94-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D1-for-each%EB%AC%B8&quot; data-og-url=&quot;https://fkqlaus.tistory.com/entry/%EC%9E%90%EB%B0%94-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D1-for-each%EB%AC%B8&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/booGL7/hyYFA9HjN1/r7WtLO8aebUp56pOMyA3q1/img.png?width=347&amp;amp;height=165&amp;amp;face=0_0_347_165,https://scrap.kakaocdn.net/dn/ligmm/hyYIfRj8kX/dHDM5Ouk8WE5YdKIKM6lKk/img.png?width=347&amp;amp;height=165&amp;amp;face=0_0_347_165,https://scrap.kakaocdn.net/dn/0PuEV/hyYH76QW9u/kAmmje9jK1ykfEXokNBmik/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://fkqlaus.tistory.com/entry/%EC%9E%90%EB%B0%94-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D1-for-each%EB%AC%B8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://fkqlaus.tistory.com/entry/%EC%9E%90%EB%B0%94-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D1-for-each%EB%AC%B8&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/booGL7/hyYFA9HjN1/r7WtLO8aebUp56pOMyA3q1/img.png?width=347&amp;amp;height=165&amp;amp;face=0_0_347_165,https://scrap.kakaocdn.net/dn/ligmm/hyYIfRj8kX/dHDM5Ouk8WE5YdKIKM6lKk/img.png?width=347&amp;amp;height=165&amp;amp;face=0_0_347_165,https://scrap.kakaocdn.net/dn/0PuEV/hyYH76QW9u/kAmmje9jK1ykfEXokNBmik/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;자바 프로그래밍(1) - for each문&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;알고리즘 공부를 하다가 for each문이 나와서 글을 작성해본다.학부에서 Java를 공부할 때 for each문을 접해본 적이 없었는데NHN 아카데미 자바 백엔드 과정을 수료하면서 처음 배웠던 기억이 있다...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;fkqlaus.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;에서 Iterable에 관해 이야기할 때 Collection에 대한 내용이 나왔었다(아주 쪼끔..)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;면접을 보러 갔을 때도 Collection에 관한 질문을 받은 적이 있는 만큼 잘 이해한다면 실무와 면접에서 도움이 될 거라고 생각한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Collection Framework가 뭔가요?&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Java의 Collection Framework는 데이터를 효율적으로 저장하고 조작하기 위한 구조이다. 배열보다 유연하고, 다양한 상황에 맞게 데이터를 저장하고 처리할 수 있게 도와준다...라고 하는데 도대체 무슨 말이야?&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;쉽게 말하면 여러 데이터를 담는 도구 상자라고 생각하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;즉 다양한 데이터를 저장하는 그릇들(List, Set, Map 등)을 모아둔 &lt;b&gt;도구 상자&lt;/b&gt; 또는 &lt;b&gt;정리함&lt;/b&gt;이다. 각각의 그릇은 성격이 달라서 용도에 따라 골라 쓰면 되고, 자바에서는 그걸 효율적이고 일관된 방식으로 다룰 수 있도록 Framework 형태로 제공하는 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;그럼 이걸 왜 쓰는 건가? 그냥 배열쓰면 되는 거 아니에요?라고 질문한다면 음..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;배열은 크기 조절이 어렵고 유연하지 않다.&lt;br /&gt;그래서 자바는 다양한 형태로 데이터를 쉽게 추가/삭제/검색할 수 있는 &lt;b&gt;컬렉션 도구들&lt;/b&gt;을 제공한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-pm-slice=&quot;1 3 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;Collection Framework의 구조&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;                          Iterable (반복할 수 있는 객체)
                             |
                         Collection (데이터를 모아 놓은 집합)
                        /    |    \
                     List   Set   Queue
                             |
                            Map (별도 인터페이스 - Collection의 하위 X)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;위 구조를 보면 Java Collection의 전체 큰 틀을 알 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;아래에서 각 인터페이스와 구현체들이 어떤 특징을 가지고 있는지 간단히 정리하자면&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;List&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 데이터의 순서를 기억하고, 중복된 값도 허용한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Set&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 중복된 값은 허용하지 않고, 순서도 기억하지 않는다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Queue&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 데이터를 줄 세우듯이 처리한다. (먼저 들어온 게 먼저 나감 FIFO!)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Map&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 키(key)와 값(value)을 짝지어 저장한다. (Key값은 중복이 불가능)&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;아마 면접을 보러 가서 Collection이 뭐냐는 질문을 받으면&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;Java의 Collection은 Iterable 인터페이스를 구현해 여러 데이터를 그룹으로 묶어 효율적으로 저장하고 관리할 수 있도록 도와주는 프레임워크입니다. 다양한 자료구조(List, Set, Map 등)를 일관된 방식으로 사용할 수 있게 해주는 도구 상자 역할을 합니다.&amp;rdquo;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;라고 답변하면 될 것 같다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 글에서는 Collection에 대해서만 알아보고 List, Set, Queue 등도 따로 정리해야겠다!&lt;/p&gt;</description>
      <category>Java</category>
      <category>collection</category>
      <category>cs</category>
      <category>Framework</category>
      <category>Java</category>
      <category>구조</category>
      <category>면접</category>
      <category>컬렉션</category>
      <author>fkqlaus</author>
      <guid isPermaLink="true">https://fkqlaus.tistory.com/9</guid>
      <comments>https://fkqlaus.tistory.com/9#entry9comment</comments>
      <pubDate>Thu, 24 Apr 2025 18:52:37 +0900</pubDate>
    </item>
    <item>
      <title>Java - 면접 질문으로 다시 보는 Java의 List와 구현체(ArrayList 등)와 관계</title>
      <link>https://fkqlaus.tistory.com/8</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;얼마 전에 면접을 보러 갔다가 이런 질문을 받았다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;면접관 : ArrayList는 List interface를 구현한 클래스인데 3가지 정도가 같이 넘어오는데 그게 뭔지 아나요?&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;라는 말을 듣고 아주 그냥 멘붕이 왔었다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 이 글에는 그 내용을 정리해보려고 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 질문에는 두가지의 의미를 가지고 있다고 생각했다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;첫 번째로는 객체지향프로그래밍(OOP)과 설계 관념에서의 추상적 개념&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;두 번째로는 실제 기능 관점&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;아마 첫번째가 맞을 것 같지만 두 가지 모두 끄적여보려고 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 객체지향프로그래밍(OOP)와 설계 관념에서의 추상적 개념&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-end=&quot;341&quot; data-start=&quot;310&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1. &lt;b&gt;다형성 (Polymorphism)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;511&quot; data-start=&quot;342&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;429&quot; data-start=&quot;342&quot;&gt;List list = new ArrayList(); 이렇게 선언하면 &lt;b&gt;List 타입&lt;/b&gt;의 참조 변수로 ArrayList 객체를 다룰 수 있다.&lt;/li&gt;
&lt;li data-end=&quot;474&quot; data-start=&quot;430&quot;&gt;이는 인터페이스를 구현한 클래스가 다형성을 지원하기 때문에 가능&lt;/li&gt;
&lt;li data-end=&quot;511&quot; data-start=&quot;475&quot;&gt;인터페이스를 통해 코드의 유연성과 확장성을 확보할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-end=&quot;547&quot; data-start=&quot;518&quot; data-ke-size=&quot;size16&quot;&gt;2. &lt;b&gt;업캐스팅 (Upcasting)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;647&quot; data-start=&quot;548&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;599&quot; data-start=&quot;548&quot;&gt;ArrayList 객체를 List 타입으로 &lt;b&gt;형 변환 없이&lt;/b&gt; 담을 수 있다.&lt;/li&gt;
&lt;li data-end=&quot;647&quot; data-start=&quot;600&quot;&gt;ArrayList는 List의 자식이기 때문에 자동으로 업캐스팅이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1745453503164&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; list = new ArrayList&amp;lt;&amp;gt;(); // 예를 들자면 이런식으로&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-end=&quot;762&quot; data-start=&quot;705&quot; data-ke-size=&quot;size16&quot;&gt;3. &lt;b&gt;인터페이스 기반 프로그래밍 (Interface-based programming)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;856&quot; data-start=&quot;763&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;851&quot; data-start=&quot;763&quot;&gt;List 인터페이스를 기준으로 코딩하면, 이후에 ArrayList, LinkedList, Vector 등으로 &lt;b&gt;쉽게 교체&lt;/b&gt;할 수 있다.&lt;/li&gt;
&lt;li data-end=&quot;1002&quot; data-start=&quot;949&quot;&gt;즉, &lt;b&gt;구현체를 바꾸더라도 코드 수정이 최소화됨&lt;/b&gt; &amp;rarr; 객체지향 설계의 중요한 원칙&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1745453557313&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();
// 나중에 바꾸기 쉬움
list = new LinkedList&amp;lt;&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이걸 정리해본다면&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다형성&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;업캐스팅 (유연한 객체 참조)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인터페이스 기반 설계 (유지보수성과 확장성 확보)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 세 가지가 있는 것..&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실제 기능 관점&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;사실 기능 관점에서는 3가지가 넘어가는데 면접관님이 굳이 3가지라고 말한 거 보면 이건 아닌 거 같긴 하다!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그래도 까먹을 수도 있고 알아두면 좋으니까 간략하게 작성하겠다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-end=&quot;294&quot; data-start=&quot;268&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 요소 추가 (add())&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;391&quot; data-start=&quot;295&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;339&quot; data-start=&quot;295&quot;&gt;add(E e) / add(int index, E element)&lt;/li&gt;
&lt;li data-end=&quot;363&quot; data-start=&quot;340&quot;&gt;List의 가장 기본적인 기능 중 하나&lt;/li&gt;
&lt;li data-end=&quot;391&quot; data-start=&quot;364&quot;&gt;순서를 유지하면서 요소를 삽입할 수 있는 특징&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-end=&quot;424&quot; data-start=&quot;398&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 요소 접근 (get())&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;537&quot; data-start=&quot;425&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;445&quot; data-start=&quot;425&quot;&gt;get(int index)&lt;/li&gt;
&lt;li data-end=&quot;487&quot; data-start=&quot;446&quot;&gt;인덱스를 통한 &lt;b&gt;순차적 접근&lt;/b&gt;이 가능하다는 게 List의 차별점&lt;/li&gt;
&lt;li data-end=&quot;537&quot; data-start=&quot;488&quot;&gt;특히 ArrayList는 이 기능이 상수 시간(O(1))이기 때문에 매우 빠름&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-end=&quot;574&quot; data-start=&quot;544&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 요소 개수 확인 (size())&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;648&quot; data-start=&quot;575&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;587&quot; data-start=&quot;575&quot;&gt;size()&lt;/li&gt;
&lt;li data-end=&quot;619&quot; data-start=&quot;588&quot;&gt;리스트에 얼마나 많은 요소가 들어있는지 확인하는 기능&lt;/li&gt;
&lt;li data-end=&quot;648&quot; data-start=&quot;620&quot;&gt;컬렉션의 상태를 판단하는 데 기본이 되는 메서드&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;물론 cotain()이나 remove()도 중요하지만 기초적이고 본질적인 3가지를 뽑으라면 이 세 개인 것 같다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;다른 List 구현체에서도 공통적으로 쓰이니까..?&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;앞으로는 이런 거 안 까먹어야지 ㅋㅅㅋ&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;혹시 잘못된 내용이 있거나 저 질문에 대한 다른 의견이 있으면 댓글로 꼭 알려주세요~!~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <category>cs</category>
      <category>Java</category>
      <category>list</category>
      <category>OOP</category>
      <category>개발자</category>
      <category>개발자 면접</category>
      <category>면접</category>
      <category>면접질문</category>
      <category>인터페이스</category>
      <author>fkqlaus</author>
      <guid isPermaLink="true">https://fkqlaus.tistory.com/8</guid>
      <comments>https://fkqlaus.tistory.com/8#entry8comment</comments>
      <pubDate>Thu, 24 Apr 2025 09:23:19 +0900</pubDate>
    </item>
    <item>
      <title>03. 문장 속 단어</title>
      <link>https://fkqlaus.tistory.com/7</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;설명&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 개의 문장이 주어지면 그 문장 속에서 가장 긴 단어를 출력하는 프로그램을 작성하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문장속의 각 단어는 공백으로 구분됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;입력&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 줄에 길이가 100을 넘지 않는 한 개의 문장이 주어집니다. 문장은 영어 알파벳으로만 구성되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;출력&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 줄에 가장 긴 단어를 출력한다. 가장 길이가 긴 단어가 여러개일 경우 문장속에서 가장 앞쪽에 위치한&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단어를 답으로 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;예시 입력 1&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745492529501&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;It is study time&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;예시 출력 1&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745492493741&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;study&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;풀이&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.util.Scanner;

public class JAVA1 {

    public static String solution(String s){

        String[] answer = s.split(&quot; &quot;);
        int n = 0;
        int maxlen = answer[0].length();
        for(int i=1;i&amp;lt;answer.length;i++){
            if(answer[i].length() &amp;gt; maxlen){
                maxlen = answer[i].length();
                n = i;
            }
        }
        return answer[n];
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        String s = sc.nextLine();

        System.out.println(solution(s));

    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 입력받은 String을 split을 통해 빈칸으로 나눴다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그 후 String 배열에서 index 0번째 값을 기준으로 삼은 후!!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여기가 나름 포인트인데 for문의 i를 1부터 시작해주는 것이다..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그다음 if문의 answer[i]. legnth() &amp;gt; maxlen 조건식을 통해&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;길이가 더 길면 maxlen을 그 문자의 길이로 교체, 그 문자의 index값을 나타내는 n값도 교체해 최종적으로 answer[n]을 리턴했다.&lt;/p&gt;</description>
      <category>알고리즘 문제풀이</category>
      <category>Java</category>
      <category>개발자</category>
      <category>문장 속 단어</category>
      <category>알고리즘</category>
      <category>컴퓨터</category>
      <category>코딩테스트</category>
      <author>fkqlaus</author>
      <guid isPermaLink="true">https://fkqlaus.tistory.com/7</guid>
      <comments>https://fkqlaus.tistory.com/7#entry7comment</comments>
      <pubDate>Thu, 24 Apr 2025 08:53:06 +0900</pubDate>
    </item>
    <item>
      <title>02. 대소문자 변환</title>
      <link>https://fkqlaus.tistory.com/6</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;문제&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;color: #495060; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;div id=&quot;problem-content&quot; data-v-4b3ae1dc=&quot;&quot;&gt;
&lt;p style=&quot;color: #3091f2;&quot; data-v-4b3ae1dc=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;설명&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대문자와 소문자가 같이 존재하는 문자열을 입력받아 대문자는 소문자로 소문자는 대문자로 변환하여 출력하는 프로그램을 작성하세요.&lt;/p&gt;
&lt;p style=&quot;color: #3091f2;&quot; data-v-4b3ae1dc=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;입력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 줄에 문자열이 입력된다. 문자열의 길이는 100을 넘지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열은 영어 알파벳으로만 구성되어 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #3091f2;&quot; data-v-4b3ae1dc=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;출력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 줄에 대문자는 소문자로, 소문자는 대문자로 변환된 문자열을 출력합니다.&lt;/p&gt;
&lt;div data-v-4b3ae1dc=&quot;&quot;&gt;
&lt;div data-v-4b3ae1dc=&quot;&quot;&gt;
&lt;div data-v-4b3ae1dc=&quot;&quot;&gt;
&lt;p style=&quot;color: #3091f2;&quot; data-v-4b3ae1dc=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;예시 입력 1&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot; data-v-4b3ae1dc=&quot;&quot;&gt;&lt;code&gt;StuDY
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div data-v-4b3ae1dc=&quot;&quot;&gt;
&lt;p style=&quot;color: #3091f2;&quot; data-v-4b3ae1dc=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;예시 출력 1&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot; data-v-4b3ae1dc=&quot;&quot;&gt;&lt;code&gt;sTUdy&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;풀이&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import java.util.Scanner;

public class JAVA1 {

    public static Character[] solution(String s){
        Character[] answer = new Character[s.length()];
        int i=0;

        for(char c : s.toCharArray()){
            if(c &amp;gt;= 'A' &amp;amp;&amp;amp; c &amp;lt;= 'Z'){
                answer[i++] = Character.toLowerCase(c);
            } else if(c &amp;gt;= 'a' &amp;amp;&amp;amp; c &amp;lt;= 'z'){
                answer[i++] = Character.toUpperCase(c);
            }
        }
        return answer;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        String s = sc.next();
        for(Character c : solution(s)){
            System.out.print(c);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;처음에는 그냥 생각나는 대로 코딩을 해봤는데 뭔가 더 간결한 다른 방법이 있을 거 같아서&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;고민하고 더 찾아봤다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import java.util.Scanner;

public class JAVA1 {

    public static String solution(String s){
        String answer = &quot;&quot;;
 
        for(char c: s.toCharArray()){
            if(Character.isUpperCase(c))
                answer += (char)(c+32);
            else answer += (char)(c-32);
        }
        return answer;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        String s = sc.next();

        System.out.println(solution(s));

    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;그래서 이런 방식으로도 코딩을 해봤는데 달라진 점은&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;solution 함수에서 Character대신 String을 사용한 점&lt;/li&gt;
&lt;li&gt;isUpperCase(c) 함수를 통해 대문자인지 확인하기&lt;/li&gt;
&lt;li&gt;toUpperCase 등의 함수가 아닌 아스키 코드를 이용해 대문자 소문자 변환하기&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;정도가 있는 것 같다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;73&quot; data-origin-height=&quot;48&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wZuoP/btsNwqYNjdv/TKTh4IabWqZ3ep7xL66ug1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wZuoP/btsNwqYNjdv/TKTh4IabWqZ3ep7xL66ug1/img.png&quot; data-alt=&quot;답&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wZuoP/btsNwqYNjdv/TKTh4IabWqZ3ep7xL66ug1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwZuoP%2FbtsNwqYNjdv%2FTKTh4IabWqZ3ep7xL66ug1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;148&quot; height=&quot;97&quot; data-origin-width=&quot;73&quot; data-origin-height=&quot;48&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;답&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;이런식으로 잘 나온다!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #222222; text-align: center;&quot;&gt;알아두어야 할 것들&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Character.isUpperCase(c)&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;대문자인지 확인하는 함수이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;true false로 반환한다&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&amp;nbsp;아스키코드&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;A는 65로 시작하고 Z는 90으로 끝난다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;a는 97로 시작하고 z는 122로 끝난다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;대문자와 소문자는 32가 차이 난다.&lt;/p&gt;</description>
      <category>알고리즘 문제풀이</category>
      <category>C</category>
      <category>Java</category>
      <category>대소문자 변환</category>
      <category>아스키코드</category>
      <category>알고리즘</category>
      <author>fkqlaus</author>
      <guid isPermaLink="true">https://fkqlaus.tistory.com/6</guid>
      <comments>https://fkqlaus.tistory.com/6#entry6comment</comments>
      <pubDate>Thu, 24 Apr 2025 05:28:22 +0900</pubDate>
    </item>
    <item>
      <title>Java - for each문 직접 구현해보기</title>
      <link>https://fkqlaus.tistory.com/5</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;for each문 설명글에서 이어지는 내용이다&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://fkqlaus.tistory.com/4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.04.24 - [프로그래밍언어/Java] - 자바 프로그래밍(1) - for each문&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1745434384353&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;자바 프로그래밍(1) - for each문&quot; data-og-description=&quot;알고리즘 공부를 하다가 for each문이 나와서 글을 작성해본다.학부에서 Java를 공부할 때 for each문을 접해본 적이 없었는데NHN 아카데미 자바 백엔드 과정을 수료하면서 처음 배웠던 기억이 있다...&quot; data-og-host=&quot;fkqlaus.tistory.com&quot; data-og-source-url=&quot;https://fkqlaus.tistory.com/4&quot; data-og-url=&quot;https://fkqlaus.tistory.com/4&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cr28uS/hyYFBUWKAp/zurU1gSRs25Lc3kGNrmpCK/img.png?width=347&amp;amp;height=165&amp;amp;face=0_0_347_165,https://scrap.kakaocdn.net/dn/bb4f5E/hyYJrdJEbY/Ix2Ffjd2P6msfn0oc6gWL0/img.png?width=347&amp;amp;height=165&amp;amp;face=0_0_347_165,https://scrap.kakaocdn.net/dn/95i9h/hyYH76IMCn/0tdwTFebhnI7jTxXmbiEIk/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://fkqlaus.tistory.com/4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://fkqlaus.tistory.com/4&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cr28uS/hyYFBUWKAp/zurU1gSRs25Lc3kGNrmpCK/img.png?width=347&amp;amp;height=165&amp;amp;face=0_0_347_165,https://scrap.kakaocdn.net/dn/bb4f5E/hyYJrdJEbY/Ix2Ffjd2P6msfn0oc6gWL0/img.png?width=347&amp;amp;height=165&amp;amp;face=0_0_347_165,https://scrap.kakaocdn.net/dn/95i9h/hyYH76IMCn/0tdwTFebhnI7jTxXmbiEIk/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;자바 프로그래밍(1) - for each문&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;알고리즘 공부를 하다가 for each문이 나와서 글을 작성해본다.학부에서 Java를 공부할 때 for each문을 접해본 적이 없었는데NHN 아카데미 자바 백엔드 과정을 수료하면서 처음 배웠던 기억이 있다...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;fkqlaus.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;for each문 직접 구현해 보기&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Iterable은 직접 구현할 수 있는데&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;347&quot; data-origin-height=&quot;165&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4x92D/btsNwKipott/HgRtVuBH3lFOlESbdVxcHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4x92D/btsNwKipott/HgRtVuBH3lFOlESbdVxcHK/img.png&quot; data-alt=&quot;Iterable 인터페이스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4x92D/btsNwKipott/HgRtVuBH3lFOlESbdVxcHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4x92D%2FbtsNwKipott%2FHgRtVuBH3lFOlESbdVxcHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;347&quot; height=&quot;165&quot; data-origin-width=&quot;347&quot; data-origin-height=&quot;165&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Iterable 인터페이스&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Iterable 인터페이스를 보면 iterator라는 함수를 정의해줘야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1tjwb/btsNwcGrVXx/P7QmMeCMYW3ySRByjd2d71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1tjwb/btsNwcGrVXx/P7QmMeCMYW3ySRByjd2d71/img.png&quot; data-alt=&quot;Iterator 인터페이스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1tjwb/btsNwcGrVXx/P7QmMeCMYW3ySRByjd2d71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1tjwb%2FbtsNwcGrVXx%2FP7QmMeCMYW3ySRByjd2d71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;649&quot; height=&quot;358&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Iterator 인터페이스&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그렇다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Iterator에서 hasNext()와 next()를 구현하면 Iterable을 사용할 수 있다!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그렇다면 hasNext()와 next()는 각각 어떤 역할을 하는 걸까&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;1709&quot; data-start=&quot;1563&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;1709&quot; data-start=&quot;1612&quot;&gt;
&lt;tr data-end=&quot;1654&quot; data-start=&quot;1612&quot;&gt;
&lt;td data-end=&quot;1626&quot; data-start=&quot;1612&quot;&gt;hasNext()&lt;/td&gt;
&lt;td data-end=&quot;1642&quot; data-start=&quot;1626&quot;&gt;다음 요소가 있는지 검사&lt;/td&gt;
&lt;td data-end=&quot;1654&quot; data-start=&quot;1642&quot;&gt;반복 종료 조건&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1709&quot; data-start=&quot;1655&quot;&gt;
&lt;td data-end=&quot;1666&quot; data-start=&quot;1655&quot;&gt;next()&lt;/td&gt;
&lt;td data-end=&quot;1682&quot; data-start=&quot;1666&quot;&gt;다음 요소 반환 후 이동&lt;/td&gt;
&lt;td data-end=&quot;1709&quot; data-start=&quot;1682&quot;&gt;호출 전에 hasNext()로 꼭 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;즉 hasNext()는 다음에 꺼낼 요소가 있는가?를 확인하고&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;있다면 true 없다면 false를 반환하는 함수&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;next()는 현재 위치의 요소를 반환하고 다음 위치로 이동하는 함수!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;class MyCollection implements Iterable&amp;lt;String&amp;gt; {
    private String[] data;

    public MyCollection(String[] data) {
        this.data = data;
    }

    @Override
    public Iterator&amp;lt;String&amp;gt; iterator() {

        return new Iterator&amp;lt;String&amp;gt;() {
            private int index = 0; // 현재 위치

            @Override
            public boolean hasNext() {
                return index &amp;lt; data.length;
            }
            @Override
            public String next() {
                return data[index++];
            }
        };
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;String에 대해 Iterable을 사용할 수 있는 클래스를 직접 구현해 봤다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;생성자를 통해서 String 배열 data를 받아오고&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;현재 위치를 알 수 있는 index 설정&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;hasnext()를 통해 다음 요소가 있는지 검사&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;next를 통해 값 반환정도 구현을 하면&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;479&quot; data-origin-height=&quot;113&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PYDZ6/btsNuKiM3TG/q64BXGYvBZ8O48klPHJEG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PYDZ6/btsNuKiM3TG/q64BXGYvBZ8O48klPHJEG0/img.png&quot; data-alt=&quot;iterator 인터페이스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PYDZ6/btsNuKiM3TG/q64BXGYvBZ8O48klPHJEG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPYDZ6%2FbtsNuKiM3TG%2Fq64BXGYvBZ8O48klPHJEG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;479&quot; height=&quot;113&quot; data-origin-width=&quot;479&quot; data-origin-height=&quot;113&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;iterator 인터페이스&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;iterator 인터페이스에 있는 forEachRemaining 함수를 통해&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;for each문이 실행된다고 보면 될 것 같다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;아래는 전체코드!&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import java.util.Iterator;

public class Main {


    public static void main(String[] args){


        String[] fruits = {&quot;Apple&quot;, &quot;Banana&quot;, &quot;Cherry&quot;};

        // 외부에서 데이터 주입
        MyCollection myCollection = new MyCollection(fruits);

        for (String fruit : myCollection) {
            System.out.println(fruit);
        }
        
    }


}

class MyCollection implements Iterable&amp;lt;String&amp;gt; {
    private String[] data;

    public MyCollection(String[] data) {
        this.data = data;
    }

    @Override
    public Iterator&amp;lt;String&amp;gt; iterator() {

        return new Iterator&amp;lt;String&amp;gt;() {
            private int index = 0; // 현재 위치

            @Override
            public boolean hasNext() {
                return index &amp;lt; data.length;
            }

            @Override
            public String next() {
                return data[index++];
            }
        };
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Java</category>
      <category>For Each</category>
      <category>Iterable</category>
      <category>iterator</category>
      <category>Java</category>
      <category>구현</category>
      <author>fkqlaus</author>
      <guid isPermaLink="true">https://fkqlaus.tistory.com/5</guid>
      <comments>https://fkqlaus.tistory.com/5#entry5comment</comments>
      <pubDate>Thu, 24 Apr 2025 04:12:12 +0900</pubDate>
    </item>
    <item>
      <title>Java - for each문</title>
      <link>https://fkqlaus.tistory.com/4</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;알고리즘 공부를 하다가 for each문이 나와서 글을 작성해 본다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;학부에서 Java를 공부할 때 for each문을 접해본 적이 없었는데&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;NHN 아카데미 자바 백엔드 과정을 수료하면서 처음 배웠던 기억이 있다....&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;for each문이란?&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;for-each 문은 컬렉션(Collection)이나 배열(Array)에 저장된 요소들을 하나씩 순차적으로 반복(iterate) 할 때 사용하는&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;간편한 반복문&lt;/b&gt;이다...&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;라고 하는데 향상된 for문이라고 부르기도 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1745431556473&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for (자료형 변수 : 컬렉션 또는 배열) {
    // 반복할 코드
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;예시로 코드를 작성해 본다면 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이런 식으로 사용할 수 있겠다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745431816674&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; names = Arrays.asList(&quot;Alice&quot;, &quot;Bob&quot;, &quot;Charlie&quot;);

for (String name : names) {
    System.out.println(name);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;사실 for문으로 작성해도 아무 이상이 없는 코드인데&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;왜 for each문을 사용하는 걸까?라는 생각이 들 수 있는데...&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;그 이유는 아래와 같다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;431&quot; data-start=&quot;406&quot;&gt;&lt;b&gt;가독성&lt;/b&gt;: 코드가 간결하고 읽기 쉬움&lt;/li&gt;
&lt;li data-end=&quot;463&quot; data-start=&quot;432&quot;&gt;&lt;b&gt;코드량 감소&lt;/b&gt;: 인덱스를 따로 관리할 필요 없음&lt;/li&gt;
&lt;li data-end=&quot;528&quot; data-start=&quot;464&quot;&gt;&lt;b&gt;오류 감소&lt;/b&gt;: 반복 범위 실수나 인덱스 오류(IndexOutOfBoundsException) 가능성 줄어듦&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;그렇다면 사용할 때 주의할 점은 뭐가 있을까?&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;for each문은 Iterable 인터페이스를 상속하는 자료구조에만 사용할 수 있다!@&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;이게 무슨 말이냐면&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;489&quot; data-origin-height=&quot;83&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddDJPd/btsNw2P2Iwt/DN6Ujrae85lVzphZA0vVP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddDJPd/btsNw2P2Iwt/DN6Ujrae85lVzphZA0vVP1/img.png&quot; data-alt=&quot;List 인터페이스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddDJPd/btsNw2P2Iwt/DN6Ujrae85lVzphZA0vVP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddDJPd%2FbtsNw2P2Iwt%2FDN6Ujrae85lVzphZA0vVP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;489&quot; height=&quot;83&quot; data-origin-width=&quot;489&quot; data-origin-height=&quot;83&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;List 인터페이스&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;50&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sd8DK/btsNuIeaqhJ/LuAkJp39JZcz16WGgxobA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sd8DK/btsNuIeaqhJ/LuAkJp39JZcz16WGgxobA1/img.png&quot; data-alt=&quot;SequencedCollection 인터페이스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sd8DK/btsNuIeaqhJ/LuAkJp39JZcz16WGgxobA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fsd8DK%2FbtsNuIeaqhJ%2FLuAkJp39JZcz16WGgxobA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;533&quot; height=&quot;50&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;50&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;SequencedCollection 인터페이스&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;437&quot; data-origin-height=&quot;75&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zWsNb/btsNvE3rDyd/d2PcU6MOOQol1bCPFq2QN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zWsNb/btsNvE3rDyd/d2PcU6MOOQol1bCPFq2QN1/img.png&quot; data-alt=&quot;Collection 인터페이스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zWsNb/btsNvE3rDyd/d2PcU6MOOQol1bCPFq2QN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzWsNb%2FbtsNvE3rDyd%2Fd2PcU6MOOQol1bCPFq2QN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;437&quot; height=&quot;75&quot; data-origin-width=&quot;437&quot; data-origin-height=&quot;75&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Collection 인터페이스&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;이렇게 쭉쭉 타고 들어가다 보면 결국 Iterable을 상속하는 걸 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;아니 그럼 그건 또 뭐야... 라고 할 텐데!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;Iterable은 Java에서 iteration이라 하는 즉 반복을 할 수 있게 만들어주는 최상위 인터페이스다&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;이 인터페이스를 구현한 클래스가 for each문을 사용할 수 있는 것이다&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;그렇다면 어떤 상황에 사용해야 하나?&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-b81e89fb-d3b0-4a1c-8990-03bbb0c34ff5&quot; style=&quot;color: #555555; text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;​기존의 for문 방식은 list의 원소를 접근할 때 get()을 사용해 접근한다. 그렇다면 ArrayList와 LinkedList의 get() 메서드 시간 복잡도는 어떻게 될까?&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-9b72da82-b90d-47bb-8ad9-b75d959117a1&quot; style=&quot;color: #555555; text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;​ArrayList는 해당 인덱스로 바로 접근이 가능하기 때문에 O(1)의 시간 복잡도를 가지지만&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt; LinkedList는 매번 첫 번째의 Head에서 해당 인덱스까지 이동하기 때문에 O(N)의 시간 복잡도를 가진다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;​즉, 다음과 같은 상황에서 List가 LinkedList라면 O(N)이 아닌 O(N^2)의 시간 복잡도를 가지게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;하지만 for each문 같은 경우는 Iterator를 사용하기 때문에 cursor변수를 가지고 자신이 순환하는 위치를 알고 있으므로&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;매번 처음부터 순환할 필요가 없다&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;LikedList에서 for each문을 사용한다면 O(N)의 시간 복잡도 나오는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt; &lt;span style=&quot;text-align: start;&quot;&gt;​&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;하지만 반대로 ArrayList인 경우는 평균적으로 기존의 for 문 방식이 더 빠르다. for-each문 방식은&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;Iterator라는 객체를 만들기 때문에 get()보다 next() 비용이 더 크기 때문이다&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;. 하지만 사실 큰 차이는 없다고 한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;text-align: start; color: #ffffff;&quot;&gt;index를 사용하지 않는다면 for each문을 사용하는 걸 추천한다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;참고 : &lt;a href=&quot;https://sorjfkrh5078.tistory.com/98&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://sorjfkrh5078.tistory.com/98&lt;/a&gt;&lt;/p&gt;</description>
      <category>Java</category>
      <category>collection</category>
      <category>for-each</category>
      <category>iterator</category>
      <category>Java</category>
      <category>list</category>
      <category>반복문</category>
      <author>fkqlaus</author>
      <guid isPermaLink="true">https://fkqlaus.tistory.com/4</guid>
      <comments>https://fkqlaus.tistory.com/4#entry4comment</comments>
      <pubDate>Thu, 24 Apr 2025 03:50:37 +0900</pubDate>
    </item>
    <item>
      <title>01. 문자 찾기</title>
      <link>https://fkqlaus.tistory.com/3</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;문제&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;설명&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 개의 문자열을 입력받고, 특정 문자를 입력받아 해당 특정문자가 입력받은 문자열에 몇 개 존재하는지 알아내는 프로그램을 작성하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대소문자를 구분하지 않습니다. 문자열의 길이는 100을 넘지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0593d3; text-align: left;&quot;&gt;입력&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 줄에 문자열이 주어지고, 두 번째 줄에 문자가 주어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열은 영어 알파벳으로만 구성되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0593d3; text-align: left;&quot;&gt;출력&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 줄에 해당 문자의 개수를 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px; color: #0593d3;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;예시 입력1&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745492862592&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Computercooler
c&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;예시 출력1&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745492874541&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;2&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;풀이&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import java.util.Scanner;

public class JAVA1 {

    public static int solution(String s, char c) {
        int answer = 0;
        s = s.toUpperCase();
        c = Character.toUpperCase(c);
        for(int i=0;i&amp;lt;s.length();i++){
            if(s.charAt(i) == c)
                answer++;
            
        }
        return answer;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        String s = sc.next();
        char c = sc.next().charAt(0);

        System.out.println(solution(s, c));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 풀이는 가장 기본적인 풀이라고 생각한다&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public static int solution(String s, char c) {
    int answer = 0;
    s = s.toUpperCase();
    c = Character.toUpperCase(c);

    for(char k: s.toCharArray()){
        if(k == c) answer++;
    }
    return answer;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 이 풀이는 기본 for문이 아닌 for each문을 활용한 풀이이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;알아두어야 할것들&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;toUpperCase()&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 함수는 String의 모든 소문자를 대문자로 변환하는 함수이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;값을 반환만 하기 때문에 원본의 값은 변하지 않는다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Character.toUpperCase(c)&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;String 이 아닌 char에 대한 변환 함수인데&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;마찬가지로 소문자를 대문자로 변환한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;str.toCharArray()&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;문자열에 대해 문자배열을 생성하는 함수&lt;/p&gt;</description>
      <category>알고리즘 문제풀이</category>
      <category>Java</category>
      <category>문자찾기</category>
      <category>알고리즘</category>
      <category>인프런 자바</category>
      <author>fkqlaus</author>
      <guid isPermaLink="true">https://fkqlaus.tistory.com/3</guid>
      <comments>https://fkqlaus.tistory.com/3#entry3comment</comments>
      <pubDate>Thu, 24 Apr 2025 02:11:55 +0900</pubDate>
    </item>
    <item>
      <title>Spring boot와 Maria DB 연동</title>
      <link>https://fkqlaus.tistory.com/2</link>
      <description>&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Spring boot와 Maria DB를 연동을 정리해 보겠다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;1. Maria DB 설치&lt;/h2&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mariadb.org/download/?m=blendbyte&amp;amp;t=mariadb&amp;amp;p=mariadb&amp;amp;r=11.7.2&amp;amp;os=windows&amp;amp;cpu=x86_64&amp;amp;pkg=msi&amp;amp;mirror=blendbyte&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://mariadb.org/download/?m=blendbyte&amp;amp;t=mariadb&amp;amp;p=mariadb&amp;amp;r=11.7.2&amp;amp;os=windows&amp;amp;cpu=x86_64&amp;amp;pkg=msi&amp;amp;mirror=blendbyte&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1745365676597&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Download MariaDB Server - MariaDB.org&quot; data-og-description=&quot;REST API Reporting Bugs &amp;hellip; Continue reading &amp;quot;Download MariaDB Server&amp;quot;&quot; data-og-host=&quot;mariadb.org&quot; data-og-source-url=&quot;https://mariadb.org/download/?m=blendbyte&amp;amp;t=mariadb&amp;amp;p=mariadb&amp;amp;r=11.7.2&amp;amp;os=windows&amp;amp;cpu=x86_64&amp;amp;pkg=msi&amp;amp;mirror=blendbyte&quot; data-og-url=&quot;https://mariadb.org/download/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/iFeLq/hyYJoAZstW/37N5HbUU99nFoN8hxeSQd1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dQrs2V/hyYH9wAlGA/M6mULOJOjPYdCmkLqkDyM1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://mariadb.org/download/?m=blendbyte&amp;amp;t=mariadb&amp;amp;p=mariadb&amp;amp;r=11.7.2&amp;amp;os=windows&amp;amp;cpu=x86_64&amp;amp;pkg=msi&amp;amp;mirror=blendbyte&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://mariadb.org/download/?m=blendbyte&amp;amp;t=mariadb&amp;amp;p=mariadb&amp;amp;r=11.7.2&amp;amp;os=windows&amp;amp;cpu=x86_64&amp;amp;pkg=msi&amp;amp;mirror=blendbyte&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/iFeLq/hyYJoAZstW/37N5HbUU99nFoN8hxeSQd1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dQrs2V/hyYH9wAlGA/M6mULOJOjPYdCmkLqkDyM1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Download MariaDB Server - MariaDB.org&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;REST API Reporting Bugs &amp;hellip; Continue reading &quot;Download MariaDB Server&quot;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;mariadb.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;위 링크에 접속해서 원하는 버전과 OS를 설정한 후 다운로드를 누르면 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이후 다운되는 installer를 실행시켜 쭉~~ 실행시켜 주면 된다&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;2. Maria DB 데이터베이스 만들기&lt;/h2&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;설치를 완료한 후 윈도우 검색창에&lt;span style=&quot;text-align: start;&quot;&gt; Maria DB와 함께 설치된 HeidisSQL을 검색하고 &lt;/span&gt;실행시켜 준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;173&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/spoif/btsNu1x0ujo/6pDmkfKv6ILY5kuf6J1IQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/spoif/btsNu1x0ujo/6pDmkfKv6ILY5kuf6J1IQ0/img.png&quot; data-alt=&quot;HeidisSQL은 무료로 사용할 수 있는 데이터베이스 관리 도구..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/spoif/btsNu1x0ujo/6pDmkfKv6ILY5kuf6J1IQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fspoif%2FbtsNu1x0ujo%2F6pDmkfKv6ILY5kuf6J1IQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;653&quot; height=&quot;173&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;173&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;HeidisSQL은 무료로 사용할 수 있는 데이터베이스 관리 도구..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그러면 이렇게 창이 뿅! 하고 나오는데 왼쪽 하단의 신규 버튼을 누르고 세션 선택 후&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;암호에는 처음 Maria DB를 설치할 때 설정한 암호를 입력하고 열기를 누르면 된다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ts3FC/btsNwoLu1Ry/SlwtMkp2eYeAgCIJnp5xYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ts3FC/btsNwoLu1Ry/SlwtMkp2eYeAgCIJnp5xYK/img.png&quot; data-alt=&quot;1.신규 2.세션 선택 3.암호 입력 4.열기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ts3FC/btsNwoLu1Ry/SlwtMkp2eYeAgCIJnp5xYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTs3FC%2FbtsNwoLu1Ry%2FSlwtMkp2eYeAgCIJnp5xYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;679&quot; height=&quot;462&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;462&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;1.신규 2.세션 선택 3.암호 입력 4.열기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그다음은 들어간 세션에 마우스 우클릭 후 데이터 베이스를 새로 만들어준다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;551&quot; data-origin-height=&quot;207&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2M9YI/btsNvqKxIve/fqr8m6r5eomDyvu6skUH3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2M9YI/btsNvqKxIve/fqr8m6r5eomDyvu6skUH3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2M9YI/btsNvqKxIve/fqr8m6r5eomDyvu6skUH3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2M9YI%2FbtsNvqKxIve%2Ffqr8m6r5eomDyvu6skUH3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;551&quot; height=&quot;207&quot; data-origin-width=&quot;551&quot; data-origin-height=&quot;207&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;3. Spring boot 설정&lt;/h2&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.mariadb.jdbc&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;mariadb-java-client&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;


&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.mybatis&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;mybatis-spring&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;1.2.3&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;pom.xml 파일에 의존성을 추가해 주면 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;spring.jpa.hibernate.ddl-auto=update
spring.datasource.driverClassName=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://DB IP주소:3306/데이터베이스이름
spring.datasource.username=DB 사용자명
spring.datasource.password=비밀번호&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그 후 properties파일에 설정을 해주면 끝!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;학교를 졸업하고 취업 준비를 하면서 부족한 점이 많은 것 같아 자바와 스프링 부트 공부를 다시 하면서&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;가볍게 게시판을 만들어보려고 했는데 평소 사용했던 MySql이 아닌 Maria DB도 한 번 사용해보고 싶어서 연동해 봤다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;앞으로도 꾸준히 블로그에 배우고 사용했던 기술들을 정리해야겠다~&lt;/p&gt;</description>
      <category>Spring boot</category>
      <category>db</category>
      <category>Java</category>
      <category>maria</category>
      <category>spring boot</category>
      <category>개발자</category>
      <category>데이터베이스</category>
      <author>fkqlaus</author>
      <guid isPermaLink="true">https://fkqlaus.tistory.com/2</guid>
      <comments>https://fkqlaus.tistory.com/2#entry2comment</comments>
      <pubDate>Wed, 23 Apr 2025 09:15:07 +0900</pubDate>
    </item>
  </channel>
</rss>