<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>s0hlog</title>
    <link>https://s0h22.tistory.com/</link>
    <description>s0h22 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Thu, 23 Jul 2026 14:27:03 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>s0hlog</managingEditor>
    <item>
      <title>[로그인] Spring Security + JWT 로그인 구현하기</title>
      <link>https://s0h22.tistory.com/entry/%EB%A1%9C%EA%B7%B8%EC%9D%B8-Spring-Security-JWT-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security는 스프링의 보안 Java의 인증과 권한 부여에 중점을 둔 하위 프레임워크이다.&lt;span style=&quot;background-color: #f4f8fa; color: #000000; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;보안 용어&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;접근 주체 (Principal)&lt;/b&gt; : 보호된 시스템 리소스에 접근하는 사용자나 프로세스 등, 식별 가능한 모든 행위자.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인증 (Authentication)&lt;/b&gt; : 접근 주체가 제시한 자격 증명을 통해 그들의 신원이 진짜임을 검증하는 절차.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인가 (Authorization)&lt;/b&gt; : 인증된 주체가 특정 리소스에 대해 요청한 작업을 수행할 수 있는 권한을 가지고 있는지 여부를 결정하는 과정.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;권한 (Authority)&lt;/b&gt; : 인증된 주체에게 부여된, 특정 리소스에 접근하거나 특정 작업을 수행할 수 있도록 허가된 권리의 범위 또는 수준.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JWT&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT는 &lt;b&gt;헤더(Header), 페이로드(Payload), 서명(Signature)&lt;/b&gt; 세 부분으로 구성됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 세 부분은 각각 Base64Url로 인코딩되어 점(.)으로 구분 (예: xxxxx.yyyyy.zzzzz)&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;- 헤더 (Header)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰의 종류(typ)와 어떤 알고리즘(alg)으로 서명되었는지 등, 토큰에 대한 &lt;b&gt;기본 정보 &lt;/b&gt;담김.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;{
  &quot;alg&quot;: &quot;HS256&quot;,
  &quot;typ&quot;: &quot;JWT&quot;
}&lt;/code&gt;&lt;/pre&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;- 페이로드 (Payload)&lt;/b&gt;&lt;/h4&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;사용자의 아이디(sub), 토큰 만료 시간(exp) 등 '클레임(Claim)'이라 불리는 정보 조각들이 포함되는 곳.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;중요: 이 부분은 암호화된 것이 아니라서 누구나 디코딩해서 볼 수 있으므로, &lt;span style=&quot;color: #ee2323;&quot;&gt;비밀번호 같은 민감한 정보는 절대 넣으면 안 됨&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;sub&quot;: &quot;user123&quot;,
  &quot;name&quot;: &quot;홍길동&quot;,
  &quot;exp&quot;: 1672531199
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;서명 (Signature)&lt;/b&gt;&lt;/h4&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;b&gt;서버만 알고 있는 비밀 키(Secret Key)&lt;/b&gt;로 암호화하여 생성&lt;/p&gt;
&lt;p 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;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구현 과정&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;1. build.gradle 에 코드 추가&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1753619250364&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-security'
	// ... 기존 코드 ...
	implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
	implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
	implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security를 위한 의존성 하나, JWT를 위한 의존성 3개를 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;jjwt-api : 전체적인 구조&lt;/li&gt;
&lt;li&gt;jjwt-jackson : 헤더와 페이로드의 내용을 JSON으로 변환&lt;/li&gt;
&lt;li&gt;jjwt-impl : 서명 &amp;amp; 최종 JWT를 완성&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;2. JwtToken DTO 생성&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1753686149469&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Builder
@Data
@AllArgsConstructor
public class JwtToken {
    private String grantType;
    private String accessToken;
    private String refreshToken;
}&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;Lombok 을 사용하는 이유 : &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;JWT 처리 과정에 필요한 수많은 데이터 객체들을 생성하고 관리하는 코드를 획기적으로 줄여줌&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;@Data&lt;/b&gt;: @Getter, @Setter, @ToString, @EqualsAndHashCode 등 핵심적인 메서드들 전부 자동 생성&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Builder&lt;/b&gt;: 객체 생성을 위한 빌더(Builder) 패턴 코드를 자동 생성, 직관적인 객체 생성을 가능하게 함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@AllArgsConstructor&lt;/b&gt;: 모든 필드를 인자로 받는 생성자를 자동 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;grantType : 토큰 유형&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;이 토큰은 어떤 종류의 토큰입니다&quot;를 알려주는 필드.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;JWT에서는 거의 항상 &lt;b&gt;Bearer&lt;/b&gt; 라는 값을 사용 (Bearer : 표준 인증 방식)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;accessToken : 접근 토큰&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제로 API를 호출하거나 보호된 리소스에 접근할 때 사용하는 &lt;b&gt;단기 유효기간&lt;/b&gt;의 토큰&lt;/li&gt;
&lt;li&gt;클라이언트는 요청을 보낼 때마다 이 토큰을 HTTP 헤더에 담아 보내야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;refreshToken : 갱신 토큰&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;accessToken이 만료되었을 때, 새로 발급받기 위해 사용하는 &lt;b&gt;장기 유효기간&lt;/b&gt;의 토큰&lt;/li&gt;
&lt;li&gt;이 토큰 덕분에 사용자는 accessToken이 만료될 때마다 다시 로그인할 필요 없이, 자동으로 인증 연장 가능&lt;/li&gt;
&lt;/ul&gt;
&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;3.application.properties 에 코드&amp;nbsp; 추가&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1753688679768&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;jwt.secret=apU0yOW7PgV5L5qwE04kNjwms6SmuNbM4HBGUQGvCdpF0Xd1f3aI7QMk59h9&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT의 서명을 만들고 검증하는 데 사용되는 비밀 키로, 외부 노출 XXX&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;4. SecurityConfig 파일 생성&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;우리 서버의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;보안 총괄 설정&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;파일이다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;이 클래스 안에서 &lt;b&gt;로그인 방식, URL별 접근 권한, CORS, CSRF&lt;/b&gt; 등 애플리케이션의 모든 보안 규칙을 정의한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;CORS (Cross-Origin Resource Sharing) 정책&lt;/b&gt;: 외부(프론트엔드)와의 연결 규칙&amp;nbsp;정의&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CSRF (Cross-Site Request Forgery) 보호 기능&lt;/b&gt;: JWT 기반 인증을 사용할 때 일반적으로 따르는 보안 정책 중 하나를 선택&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1753718415053&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration // 스프링의 설정 클래스임을 나탄냄
@EnableWebSecurity // 스프링 시큐리티 활성화, 웹 보안 설정을 커스터마이징
@RequiredArgsConstructor // final 필드나 @NonNull 필드에 대한 생성자를 자동으로 생성해주는 Lombok 어노테이션
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .cors(Customizer.withDefaults())
                .csrf(AbstractHttpConfigurer::disable)

                // 세션 비활성화: JWT를 사용하므로 세션을 STATELESS 상태로 변경
                .sessionManagement(session -&amp;gt; session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                )

                // 기본 로그인 및 인증 비활성화: 기본으로 제공되는 formLogin, httpBasic 인증 사용 X
                .formLogin(AbstractHttpConfigurer::disable)
                .httpBasic(AbstractHttpConfigurer::disable)

                // URL별 접근 권한 설정
                .authorizeHttpRequests(authz -&amp;gt; authz
                        .requestMatchers(&quot;/api/v1/signup&quot;, &quot;/api/v1/login&quot;).permitAll() // 회원가입, 로그인 API는 인증 없이 누구나 접근 가능하도록 허용
                        .anyRequest().authenticated() // 그 외 모든 요청은 반드시 인증된 사용자만 접근 가능
                )
                .addFilterBefore(new JwtAuthenticationFilter(jwtProvider, userDetailsService), UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration(); // CORS 정책을 설정하기 위한 객체 생성

        config.setAllowCredentials(true);  // 요청에 쿠키나 인증 헤더 같은 자격 증명(credentials)을 포함하도록 허용
        config.setAllowedOrigins(List.of(&quot;http://localhost:3000&quot;)); // 요청을 허용할 출처(Origin)를 지정. 배포시에는 그 도메인주소로 설정
        config.setAllowedMethods(List.of(&quot;GET&quot;, &quot;POST&quot;, &quot;PUT&quot;, &quot;DELETE&quot;, &quot;PATCH&quot;, &quot;OPTIONS&quot;)); // 허용할 HTTP 메서드 지정
        config.setAllowedHeaders(List.of(&quot;*&quot;)); // 허용할 요청 헤더 지정. &quot;*&quot;는 모든 헤더 허용
        config.setExposedHeaders(List.of(&quot;*&quot;)); // 클라이언트(브라우저)에서 노출할 응답 헤더를 지정

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); // URL 기반으로 CORS 설정을 관리하는 소스 객체를 생성
        source.registerCorsConfiguration(&quot;/**&quot;, config); // &quot;/**&quot; 패턴, 즉 모든 URL 경로에 대해 위에서 정의한 CORS 정책(config) 적용
        return source; // 설정이 완료된 소스 객체를 반환합니다.
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;92&quot; data-start=&quot;48&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; filterChain(HttpSecurity http) : &lt;span style=&quot;font-size: 16px; letter-spacing: 0px;&quot;&gt;Spring Security의 핵심 보안 설정 담당&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;92&quot; data-start=&quot;48&quot; data-ke-size=&quot;size18&quot;&gt;  corsConfigurationSource() : &lt;span style=&quot;font-size: 16px; letter-spacing: 0px;&quot;&gt;CORS(Cross-Origin Resource Sharing) 정책 정의&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  passwordEncoder() : &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;비밀번호를 안전하게 암호화하기 위한 암호화 도구 생성&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;5. JwtProvider 파일 생성&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰을 본격적으로 사용하는 파일.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 JWT 생성, JWT 검증, 정보 추출의 역할을 한다.&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;@Slf4j
@Component
public class JwtProvider {

    private final Key key;

    // application.yml 또는 application.properties에서 시크릿 키를 주입받음
    public JwtProvider(@Value(&quot;${jwt.secret}&quot;) String secretKey) {
        byte[] keyBytes = Decoders.BASE64.decode(secretKey);
        this.key = Keys.hmacShaKeyFor(keyBytes);
    }

    /**
     * 사용자 정보를 기반으로 Access Token과 Refresh Token을 생성
     * @param userId - 토큰을 발급받을 사용자 ID
     * @return 생성된 토큰 정보 (TokenDto)
     */
    public JwtToken generateToken(String userId) {
        long now = (new Date()).getTime();

        // Access Token 생성 (유효 기간: 1시간)
        Date accessTokenExpiresIn = new Date(now + 3600000); // 1시간
        String accessToken = Jwts.builder()
                .setSubject(userId)
                .setExpiration(accessTokenExpiresIn)
                .signWith(key, SignatureAlgorithm.HS256)
                .compact();

        // Refresh Token 생성 (유효 기간: 7일)
        String refreshToken = Jwts.builder()
                .setExpiration(new Date(now + 604800000)) // 7일
                .signWith(key, SignatureAlgorithm.HS256)
                .compact();

        return JwtToken.builder()
                .grantType(&quot;Bearer&quot;)
                .accessToken(accessToken)
                .refreshToken(refreshToken)
                .build();
    }

    /**
     * JWT 토큰의 유효성을 검증
     * @param token - 검증할 토큰 문자열
     * @return 토큰이 유효하면 true, 아니면 false
     */
    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
            return true;
        } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
            log.info(&quot;잘못된 JWT 서명입니다.&quot;);
        } catch (ExpiredJwtException e) {
            log.info(&quot;만료된 JWT 토큰입니다.&quot;);
        } catch (UnsupportedJwtException e) {
            log.info(&quot;지원되지 않는 JWT 토큰입니다.&quot;);
        } catch (IllegalArgumentException e) {
            log.info(&quot;JWT 토큰이 잘못되었습니다.&quot;);
        }
        return false;
    }

    /**
     * 토큰에서 사용자 ID를 추출
     * @param accessToken - 사용자 ID를 추출할 토큰
     * @return 추출된 사용자 ID
     */
    public String getUserId(String accessToken) {
        Claims claims = parseClaims(accessToken);
        return claims.getSubject();
    }

    /**
     * 토큰을 파싱하여 클레임(정보)을 추출 (만료된 토큰도 정보 추출 가능)
     * @param accessToken - 파싱할 토큰
     * @return 추출된 클레임
     */
    private Claims parseClaims(String accessToken) {
        try {
            return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(accessToken).getBody();
        } catch (ExpiredJwtException e) {
            return e.getClaims();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&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;역할&lt;/b&gt;: JWT 관련 모든 기능을 제공하는 핵심 도구&lt;/li&gt;
&lt;li&gt;&lt;b&gt;생성자&lt;/b&gt;: 설정 파일의 비밀 키를 가져와 JWT 서명에 사용할 키를 생성&lt;/li&gt;
&lt;li&gt;&lt;b&gt;generateToken 메서드&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자 ID를 받아 단기 액세스 토큰과 장기 리프레시 토큰을 생성&lt;/li&gt;
&lt;li&gt;생성된 토큰들을 TokenDto에 담아 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;validateToken 메서드&lt;/b&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;유효하면 true, 아니면 false 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;getUserId 메서드&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유효한 토큰에서 사용자 ID 정보를 추출&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;parseClaims 메서드&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;토큰의 데이터 부분(Payload)을 꺼내는 내부 기능&lt;/li&gt;
&lt;/ul&gt;
&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;h4 id=&quot;5%EF%B8%8F%E2%83%A3%20JwtAuthenticationFilter%20%EA%B5%AC%ED%98%84%20-1&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;6. JwtAuthenticationFilter 구현&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트의 모든 요청을 가로채서, 요청 헤더에 담긴 JWT를 검증하고, 유효하다면 해당 사용자를 인증 상태로 만들어주는 역할&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;@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtTokenProvider jwtProvider;
    private final UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 1. 요청 헤더에서 토큰 추출
        String token = resolveToken(request);

        // 2. 토큰 유효성 검사
        if (token != null &amp;amp;&amp;amp; jwtProvider.validateToken(token)) {
            // 토큰이 유효할 경우 토큰에서 Authentication 객체를 가져와서 SecurityContext에 저장
            String username = jwtProvider.getUserId(token);
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);

            // 인증 객체 생성
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, &quot;&quot;, userDetails.getAuthorities());

            // SecurityContext에 인증 정보 저장
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }

        filterChain.doFilter(request, response);
    }

    // Request Header에서 토큰 정보 추출
    private String resolveToken(HttpServletRequest request) {
        String bearerToken = request.getHeader(&quot;Authorization&quot;);
        if (StringUtils.hasText(bearerToken) &amp;amp;&amp;amp; bearerToken.startsWith(&quot;Bearer &quot;)) {
            return bearerToken.substring(7);
        }
        return null;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;7-1. User 엔티티 생성&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;데이터베이스에 저장될 사용자 정보를 담는 &lt;/span&gt;User&lt;span&gt; 엔티티 생성&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Entity
@Getter
@NoArgsConstructor
@Table(name = &quot;users&quot;)
public class User implements UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String username; // 로그인 ID

    @Column(nullable = false)
    private String password; // 암호화된 비밀번호

    @ElementCollection(fetch = FetchType.EAGER)
    @Builder.Default
    private List&amp;lt;String&amp;gt; roles = new ArrayList&amp;lt;&amp;gt;(); // 사용자의 권한 목록

    @Builder
    public User(String username, String password, List&amp;lt;String&amp;gt; roles) {
        this.username = username;
        this.password = password;
        this.roles = roles;
    }

    @Override
    public Collection&amp;lt;? extends GrantedAuthority&amp;gt; getAuthorities() {
        // 'roles' 목록을 Spring Security가 이해할 수 있는 권한(GrantedAuthority) 객체로 변환
        return this.roles.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
    }

    @Override
    public String getPassword() {
        return this.password; // 암호화된 비밀번호 반환
    }

    @Override
    public String getUsername() {
        return this.username; // 로그인 ID 반환
    }

    // 아래 4개는 계정 상태에 대한 설정입니다. (지금은 모두 true로 설정)
    @Override
    public boolean isAccountNonExpired() {
        return true; // 계정 만료 여부
    }

    @Override
    public boolean isAccountNonLocked() {
        return true; // 계정 잠금 여부
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true; // 비밀번호 만료 여부
    }

    @Override
    public boolean isEnabled() {
        return true; // 계정 활성화 여부
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;8. UserRepository 생성&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;위에서 만든 &lt;/span&gt;User&lt;span&gt; 엔티티의 데이터베이스 작업을 처리할 &lt;/span&gt;UserRepository 생성&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public interface UserRepository extends JpaRepository&amp;lt;User, Long&amp;gt; {
    // username을 기반으로 사용자를 찾는 메서드
    Optional&amp;lt;User&amp;gt; findByUsername(String username);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;9. UserDetailsService 구현&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Spring Security가 사용자 정보를 조회할 때 사용할 서비스인 &lt;/span&gt;UserDetailsService&lt;span&gt;의 실제 구현체 생성&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 클래스가 &lt;/span&gt;JwtAuthenticationFilter&lt;span&gt;에서 호출됨&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // UserRepository를 통해 DB에서 사용자를 찾아 UserDetails 타입으로 반환합니다.
        // 사용자를 찾지 못하면 예외를 발생시킵니다.
        return userRepository.findByUsername(username)
                .orElseThrow(() -&amp;gt; new UsernameNotFoundException(&quot;해당하는 유저를 찾을 수 없습니다.&quot;));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;10. 로그인, 회원가입 로직 구현하기&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;10-1. UserService 생성&lt;/b&gt;&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final JwtProvider jwtProvider;
    private final AuthenticationManagerBuilder authenticationManagerBuilder;

    @Transactional
    public Long signUp(String username, String password) {
        if (userRepository.findByUsername(username).isPresent()) {
            throw new IllegalArgumentException(&quot;이미 사용 중인 아이디입니다.&quot;);
        }
        String encodedPassword = passwordEncoder.encode(password);
        User user = User.builder()
                .username(username)
                .password(encodedPassword)
                .roles(Collections.singletonList(&quot;USER&quot;)) // 기본 권한
                .build();
        return userRepository.save(user).getId();
    }

    @Transactional
    public JwtToken login(String username, String password) {
        // 1. ID/PW 기반으로 AuthenticationToken 생성
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);

        // 2. 실제 검증 (비밀번호 대조)
        // 이 과정에서 CustomUserDetailsService의 loadUserByUsername 메서드가 실행됨
        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);

        // 3. 인증 정보를 기반으로 JWT 생성
        return jwtProvider.generateToken(authentication.getName());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;10-2. UserController 생성&lt;/b&gt;&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@RestController
@RequiredArgsConstructor
@RequestMapping(&quot;/api/v1&quot;)
public class UserController {

    private final UserService userService;

    @PostMapping(&quot;/signup&quot;)
    public ResponseEntity&amp;lt;String&amp;gt; signUp(@RequestBody UserRequestDto request) {
        userService.signUp(request.getUsername(), request.getPassword());
        return ResponseEntity.ok(&quot;회원가입이 완료되었습니다.&quot;);
    }

    @PostMapping(&quot;/login&quot;)
    public ResponseEntity&amp;lt;JwtToken&amp;gt; login(@RequestBody UserRequestDto request) {
        JwtToken token = userService.login(request.getUsername(), request.getPassword());
        return ResponseEntity.ok(token);
    }
    
    // 요청 시 사용할 DTO
    @Data
    static class UserRequestDto {
        private String username;
        private String password;
    }
}&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것으로 Spring Security와 JWT를 이용한 로그인 시스템의 모든 구현이 끝났습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;전체 흐름&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;최초 요청&lt;/b&gt;: 클라이언트가 ID/PW로 /api/v1/login을 호출&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인증 및 토큰 발급&lt;/b&gt;: UserService가 ID/PW를 검증하고, 성공 시 JwtProvider를 통해 Access/Refresh 토큰을 발급하여 클라이언트에게 전달&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인증이 필요한 요청&lt;/b&gt;: 클라이언트는 발급받은 accessToken을 Authorization: Bearer &amp;lt;토큰&amp;gt; 형식으로 HTTP 헤더에 담아 다른 API 요청&lt;/li&gt;
&lt;li&gt;&lt;b&gt;필터 기반 검증&lt;/b&gt;: JwtAuthenticationFilter가 요청을 가로채 헤더의 토큰을 검증함. 토큰이 유효하면, CustomUserDetailsService를 통해 사용자 정보를 찾아 SecurityContext에 저장하여 &quot;인증된 사용자&quot;로 만듬.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;API 로직 수행&lt;/b&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;후기 : 항상 간단하게 패스워드 받고 로그인하는 과정만 만들어봐서 신기했다. 토큰이라해서 어렵게 생각했는데, 작은 단위에서 하나하나 만들다보니 유기적으로 연계된게 느껴져서 이해가 더 쉬웠다. JWT를 실제로 해보니 더 깊게 이해하게 된 것 같다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <author>s0hlog</author>
      <guid isPermaLink="true">https://s0h22.tistory.com/7</guid>
      <comments>https://s0h22.tistory.com/entry/%EB%A1%9C%EA%B7%B8%EC%9D%B8-Spring-Security-JWT-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0#entry7comment</comments>
      <pubDate>Tue, 29 Jul 2025 15:12:08 +0900</pubDate>
    </item>
    <item>
      <title>혼자 공부하는 컴퓨터구조론 + 운영체제 : 10단원 정리</title>
      <link>https://s0h22.tistory.com/entry/%ED%98%BC%EC%9E%90-%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-%EC%BB%B4%ED%93%A8%ED%84%B0%EA%B5%AC%EC%A1%B0%EB%A1%A0-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-10%EB%8B%A8%EC%9B%90-%EC%A0%95%EB%A6%AC</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;혼자 공부하는 컴퓨터구조론 + 운영체제 : 10단원 정리&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;I. 10-1: 프로세스 개요&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;프로세스&lt;/b&gt;&lt;/h4&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ex) &lt;span&gt;&amp;nbsp;&lt;/span&gt;hwp.exe 아이콘은 그 자체로는 아무 일도 하지 않는 프로그램&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;프로세스 : &lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;- 메모리 위에서 활발하게 실행되고 있는 프로그램&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;- 프로그램을 더블 클릭하여 실행하는 순간, 운영체제는 실행을 시작&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;[작업 관리자-&amp;gt;프로세스] 탭의 프로세스 직접 확인&lt;/span&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1598&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTbtGh/btsPuOQF5Ay/k7Hdlrhl0GOxCDQsEdNKT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTbtGh/btsPuOQF5Ay/k7Hdlrhl0GOxCDQsEdNKT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTbtGh/btsPuOQF5Ay/k7Hdlrhl0GOxCDQsEdNKT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTbtGh%2FbtsPuOQF5Ay%2Fk7Hdlrhl0GOxCDQsEdNKT1%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;1598&quot; height=&quot;870&quot; data-origin-width=&quot;1598&quot; data-origin-height=&quot;870&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 사용자가 직접 실행한 몇몇 응용 프로그램 외에도 수십, 수백 개의 알 수 없는 프로세스들이 실행되고 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;-&amp;gt; &lt;/span&gt;운영체제가 단순히 사용자가 실행한 프로그램을 띄워주는 수동적인 플랫폼이 아님&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;-&amp;gt; 자체적인 프로세스들을 끊임없이 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;프로세스의 분류&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;포그라운드 프로세스 (Foreground Process):&lt;/b&gt; 사용자가 눈으로 보며 직접 상호작용하는 프로세스
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹 브라우저, 텍스트 편집기, 게임 등&lt;span&gt;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;백그라운드 프로세스 (Background Process):&lt;/b&gt; 보이지 않는 곳에서 정해진 작업을 수행하는 프로세스&lt;/li&gt;
&lt;li&gt;유닉스 계열에서는 &lt;b&gt;데몬(daemon)&lt;/b&gt;,&lt;/li&gt;
&lt;li&gt;Windows에서는 &lt;b&gt;서비스(service) &lt;/b&gt;라고 부름.&lt;span&gt;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; 프로세스 제어 블록(Process Control Block, PCB)&lt;/b&gt;&lt;/h4&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;동시에 실행되는 수백 개의 프로세스를 구별하고 관리할 수 있는 이유&lt;/li&gt;
&lt;li&gt;운영체제가 프로세스를 관리하는 데 필요한 거의 모든 정보를 포함&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;PCB에 담기는 정보&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&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: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;항목&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;프로세스 ID (PID)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;프로세스를 식별하기 위해 부여하는 고유한 번호&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;레지스터 값&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;프로세스가 실행하며 사용했던 레지스터 값들이 저장됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;프로세스 상태&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;현재 프로세스의 상태가 저장됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;CPU 스케줄링 정보&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;프로세스가 언제 CPU를 할당받을지에 대한 정보가 저장됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;메모리 관리 정보&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;프로세스의 메모리 주소가 저장됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;사용한 파일과 입출력장치 목록&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;프로세스에 할당되었던 입출력장치나 파일에 대한 내용이 저장됨&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&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;b&gt;문맥 교환 (Context Switching)&lt;/b&gt;&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;기존 프로세스의 문맥을 PCB에 백업하고, 새로운 프로세스를 실행하기 위해 문맥을 PCB로부터 복구하여 새로운 프로세스를 실행하는 것&lt;/span&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;문맥(Context)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 하나의 프로세스를 다시 실행하기 위해 저장되는 정보&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: PCB에 기록되는 정보&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;프로세스의 메모리 영역&lt;/h4&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/br4hUK/btsPxk8cRNg/K06xJJ3LCoglhqR3FIAyD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/br4hUK/btsPxk8cRNg/K06xJJ3LCoglhqR3FIAyD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/br4hUK/btsPxk8cRNg/K06xJJ3LCoglhqR3FIAyD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbr4hUK%2FbtsPxk8cRNg%2FK06xJJ3LCoglhqR3FIAyD1%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;300&quot; height=&quot;230&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;230&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&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;div&gt;
&lt;table style=&quot;letter-spacing: 0px; border-collapse: collapse; width: 75%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.3953%;&quot;&gt;영역&lt;/td&gt;
&lt;td style=&quot;width: 40.3488%;&quot;&gt;설명&lt;/td&gt;
&lt;td style=&quot;width: 13.1395%;&quot;&gt;할당 방식&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.3953%;&quot;&gt;&lt;b&gt;코드 (Code)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 40.3488%;&quot;&gt;- 텍스트 영역&lt;br /&gt;- CPU가 실행할 명령어 저장되는 공간&lt;br /&gt;- 읽기 전용 공간&lt;/td&gt;
&lt;td style=&quot;width: 13.1395%;&quot;&gt;&lt;b&gt;정적 할당&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.3953%;&quot;&gt;&lt;b&gt;데이터 (Data)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 40.3488%;&quot;&gt;- 프로그램이 실행되는 동안 유지할 데이터 저장&lt;/td&gt;
&lt;td style=&quot;width: 13.1395%;&quot;&gt;&lt;b&gt;정적 할당&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.3953%;&quot;&gt;&lt;b&gt;힙 (Heap)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 40.3488%;&quot;&gt;- 프로그래머가 직접 할당할 수 있는 저장 공간&lt;br /&gt;- 메모리의 낮은 주소에서 높은 주소로 할당&lt;br /&gt;- 동적으로 할당하고 해제&lt;br /&gt;- 메모리 누수 주의&lt;/td&gt;
&lt;td style=&quot;width: 13.1395%;&quot;&gt;&lt;b&gt;동적 할당&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.3953%;&quot;&gt;&lt;b&gt;스택 (Stack)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 40.3488%;&quot;&gt;- 데이터를 임시로 저장하는 공간&lt;br /&gt;- 메모리의 높은 주소에서 낮은 주소로 할당&lt;/td&gt;
&lt;td style=&quot;width: 13.1395%;&quot;&gt;&lt;b&gt;동적 할당&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
정적 할당 영역 : 크기 고정&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;동적 할당 영역 : 크기 변화 가능&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;10-2. 프로세스 상태와 계층 구조&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqEp8E/btsPvDuMkyQ/zymKGfaKaxp4E9igcGIOs1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqEp8E/btsPvDuMkyQ/zymKGfaKaxp4E9igcGIOs1/img.jpg&quot; data-alt=&quot;프로세스 상태 다이어그램&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqEp8E/btsPvDuMkyQ/zymKGfaKaxp4E9igcGIOs1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqEp8E%2FbtsPvDuMkyQ%2FzymKGfaKaxp4E9igcGIOs1%2Fimg.jpg&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;600&quot; height=&quot;252&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;프로세스 상태 다이어그램&lt;/figcaption&gt;
&lt;/figure&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;생성 상태&amp;nbsp;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로세스를 생성 중인 상태로, 이제 막 메모리에 적재되어 PCB를 할당받은 상태.&lt;/li&gt;
&lt;li&gt;생성 상태를 거쳐 실행할 준비가 완료된 프로세스는 곧바로 실행되지 않고 준비 상태가 되어 CPU의 할당을 기다림&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;준비 상태 &lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;당장이라도 CPU를 할당받아 실행할 수 있지만, 아직 자신의 차례가 아니기에 기다리고 있는 상태&lt;/li&gt;
&lt;li&gt;차례가 되면 CPU를 할당받아 실행 상태로&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실행 상태&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU를 할당받아 실행 중인 상태&lt;/li&gt;
&lt;li&gt;할당된 일정 시간 동안만 CPU 사용 가능&lt;/li&gt;
&lt;li&gt;프로세스가 할당된 시간을 모두 사용한다면 다시 준비 상태,&lt;/li&gt;
&lt;li&gt;실행 도중 입출력장치를 사용하여 입출력 장치의 작업이 끝날 때까지 기다려야 한다면 대기 상태&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대기 상태 &lt;/b&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;좀 더 일반적으로는 특정 이벤트(주로 입출력)가 일어나길 기다리는 상태&lt;/li&gt;
&lt;li&gt;입출력 작업이 완료되면 해당 프로세스는 다시 준비 상태로 CPU 할당을 기다림&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;종료 상태 &lt;/b&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;프로세스가 종료되면 운영체제는 PCB와 프로세스가 사용한 메모리를 정리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;프로세스 계층 구조&lt;/b&gt;&lt;/h3&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; : 새 프로세스를 생성한 프로세스&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자식 프로세스 &lt;/b&gt;: 부모에 의해 생성된 프로세스&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로세스 생성 기법&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;fork&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;부모 프로세스는 &lt;b&gt;fork&lt;/b&gt; 시스템 호출을 통해 자신의 복사본을 자식 프로세스로 생성&lt;/li&gt;
&lt;li&gt;이때 부모 프로세스의 자원들(메모리 내의 내용, 열린 파일 목록 등)이 자식 프로세스에 상속&lt;/li&gt;
&lt;li&gt;자식 프로세스의 PID 값이나 저장된 메모리 위치는 다름&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;exec&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만들어진 복사본(자식 프로세스)은 &lt;b&gt;exec&lt;/b&gt; 시스템 호출을 통해 자신의 메모리 공간을 새로운 프로그램으로 덮어씀&lt;/li&gt;
&lt;li&gt;코드 영역과 데이터 영역의 내용이 실행할 프로그램의 내용으로 바뀌고, 나머지 영역은 초기화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;10-3. 스레드&lt;/b&gt;&lt;/h3&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;
&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;하나의 프로세스는 여러 개의 스레드를 가질 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프로세스 &lt;/b&gt;: 스레드를 몇 개 가지느냐에 따라 &lt;b&gt;단일 스레드 프로세스&lt;/b&gt;와 &lt;b&gt;멀티 스레드 프로세스&lt;/b&gt;로 나뉨&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단일 스레드 프로세스&lt;/b&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;하나의 작업이 끝나야 다음 작업을 시작할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;
&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;러 작업을 동시에 처리하여 효율을 높임.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;&amp;nbsp;멀티프로세스&lt;/b&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;를 동시에 실행하는 것&lt;/li&gt;
&lt;li&gt;다른 프로세스와 자원 공유 X&lt;/li&gt;
&lt;li&gt;각각의 프로세스는 운영체제로부터 자신만의 독립된 메모리 영역 할당 -&amp;gt; 다른 프로세스의 자원에 직접 접근 X&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;멀티스레드&lt;/b&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;로 나누어 동시에 실행하는 것&lt;/li&gt;
&lt;li&gt;다른 스레드와 자원 공유 O&lt;/li&gt;
&lt;li&gt;같은 프로세스&amp;nbsp;내의 스레드들은 &lt;b&gt;코드, 데이터, 힙 영역을 서로 공유&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;단, 각 스레드는 자신만의 스택 영역은 별도로 가짐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>혼자 공부하는 컴퓨터구조 + 운영체제</category>
      <author>s0hlog</author>
      <guid isPermaLink="true">https://s0h22.tistory.com/6</guid>
      <comments>https://s0h22.tistory.com/entry/%ED%98%BC%EC%9E%90-%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-%EC%BB%B4%ED%93%A8%ED%84%B0%EA%B5%AC%EC%A1%B0%EB%A1%A0-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-10%EB%8B%A8%EC%9B%90-%EC%A0%95%EB%A6%AC#entry6comment</comments>
      <pubDate>Wed, 23 Jul 2025 21:56:12 +0900</pubDate>
    </item>
    <item>
      <title>혼자 공부하는 컴퓨터구조론 + 운영체제 : 9단원 정리</title>
      <link>https://s0h22.tistory.com/entry/%ED%98%BC%EC%9E%90-%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-%EC%BB%B4%ED%93%A8%ED%84%B0%EA%B5%AC%EC%A1%B0%EB%A1%A0-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-9%EB%8B%A8%EC%9B%90-%EC%A0%95%EB%A6%AC</link>
      <description>&lt;h1 data-end=&quot;170&quot; data-start=&quot;142&quot;&gt;[혼자 공부하는 컴퓨터구조+운영체제] 9장 정리&lt;/h1&gt;
&lt;h2 data-end=&quot;188&quot; data-start=&quot;172&quot; data-ke-size=&quot;size26&quot;&gt;09-1 운영체제의 기본&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;342&quot; data-start=&quot;190&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;269&quot; data-start=&quot;190&quot;&gt;&lt;b&gt;운영체제(OS)&lt;/b&gt; 는 컴퓨터 하드웨어와 사용자 사이에서 자원을 효율적으로 관리하고, 응용 프로그램 실행을 지원하는 소프트웨어이다.&lt;/li&gt;
&lt;li data-end=&quot;342&quot; data-start=&quot;270&quot;&gt;실행할 프로그램에 필요한 자원(CPU, 메모리, 저장장치 등)을 할당하고, 프로그램이 안전하고 효율적으로 동작하도록 제어한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;347&quot; data-start=&quot;344&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;367&quot; data-start=&quot;349&quot; data-ke-size=&quot;size26&quot;&gt;09-2 운영체제의 큰 그림&lt;/h2&gt;
&lt;h3 data-end=&quot;384&quot; data-start=&quot;369&quot; data-ke-size=&quot;size23&quot;&gt;커널 (Kernel)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;607&quot; data-start=&quot;386&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;419&quot; data-start=&quot;386&quot;&gt;운영체제의 핵심 부분으로, 자원 관리와 제어를 담당한다.&lt;/li&gt;
&lt;li data-end=&quot;471&quot; data-start=&quot;420&quot;&gt;하드웨어 자원에 접근하거나, 여러 응용 프로그램 간의 충돌 없이 시스템 자원을 배분한다.&lt;/li&gt;
&lt;li data-end=&quot;607&quot; data-start=&quot;472&quot;&gt;커널에 &lt;b&gt;포함되지 않는 서비스&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;607&quot; data-start=&quot;498&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;516&quot; data-start=&quot;498&quot;&gt;사용자 인터페이스 (UI)&lt;/li&gt;
&lt;li data-end=&quot;541&quot; data-start=&quot;519&quot;&gt;그래픽 유저 인터페이스 (GUI)&lt;/li&gt;
&lt;li data-end=&quot;607&quot; data-start=&quot;544&quot;&gt;커맨드 라인 인터페이스 (CLI)&lt;br /&gt;&amp;rarr; 이런 것들은 운영체제의 &amp;lsquo;껍데기(shell)&amp;rsquo; 역할을 하는 부분이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;612&quot; data-start=&quot;609&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;655&quot; data-start=&quot;614&quot; data-ke-size=&quot;size23&quot;&gt;이중 모드(Dual Mode)와 시스템 호출(System Call)&lt;/h3&gt;
&lt;h4 data-end=&quot;678&quot; data-start=&quot;657&quot; data-ke-size=&quot;size20&quot;&gt;이중 모드(Dual Mode)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;906&quot; data-start=&quot;680&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;906&quot; data-start=&quot;680&quot;&gt;CPU는 명령어를 두 가지 모드 중 하나에서 실행한다:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;906&quot; data-start=&quot;715&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;813&quot; data-start=&quot;715&quot;&gt;&lt;b&gt;사용자 모드(User Mode)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;813&quot; data-start=&quot;743&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;761&quot; data-start=&quot;743&quot;&gt;운영체제 서비스를 제공받을 수 없는 &lt;b&gt;실행&lt;/b&gt;되는 모드&lt;/li&gt;
&lt;li data-end=&quot;789&quot; data-start=&quot;766&quot;&gt;커널 영역의 코드 실행 X&lt;/li&gt;
&lt;li data-end=&quot;813&quot; data-start=&quot;794&quot;&gt;하드웨어 자원 직접 접근 불가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;906&quot; data-start=&quot;816&quot;&gt;&lt;b&gt;커널 모드(Kernel Mode)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;906&quot; data-start=&quot;845&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;871&quot; data-start=&quot;845&quot;&gt;운영체제 서비스 코드를 실행할 수 있는 모드&lt;/li&gt;
&lt;li data-end=&quot;906&quot; data-start=&quot;876&quot;&gt;자원(CPU, 메모리, 하드웨어)에 직접 접근 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-end=&quot;983&quot; data-start=&quot;908&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;983&quot; data-start=&quot;910&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이중 모드의 목적&lt;/b&gt;&lt;br /&gt;&amp;rarr; 응용 프로그램이 하드웨어 자원에 직접 접근하지 못하게 막아 시스템 안정성과 보안을 확보하기 위함&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 data-end=&quot;1009&quot; data-start=&quot;985&quot; data-ke-size=&quot;size20&quot;&gt;시스템 호출(System Call)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1221&quot; data-start=&quot;1011&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1062&quot; data-start=&quot;1011&quot;&gt;사용자 모드에서 실행 중인 프로그램이 운영체제의 서비스 요청&amp;nbsp;&lt;/li&gt;
&lt;li data-end=&quot;1149&quot; data-start=&quot;1063&quot;&gt;시스템 호출이 발생하면:
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1149&quot; data-start=&quot;1081&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;1098&quot; data-start=&quot;1081&quot;&gt;CPU가 커널 모드로 전환&lt;/li&gt;
&lt;li data-end=&quot;1121&quot; data-start=&quot;1101&quot;&gt;커널 코드가 실행되어 요청 처리&lt;/li&gt;
&lt;li data-end=&quot;1149&quot; data-start=&quot;1124&quot;&gt;작업이 끝나면 다시 사용자 모드로 돌아감&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1221&quot; data-start=&quot;1151&quot;&gt;시스템 호출은 일종의 인터럽트
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1221&quot; data-start=&quot;1172&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1193&quot; data-start=&quot;1172&quot;&gt;&lt;b&gt;소프트웨어 인터럽트&lt;/b&gt;라고도 함&lt;/li&gt;
&lt;li data-end=&quot;1221&quot; data-start=&quot;1196&quot;&gt;명령어에 의해 의도적으로 발생하는 인터럽트&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1226&quot; data-start=&quot;1223&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1253&quot; data-start=&quot;1228&quot; data-ke-size=&quot;size23&quot;&gt;시스템 호출 예시 (하드 디스크 접근)&lt;/h3&gt;
&lt;blockquote data-end=&quot;1304&quot; data-start=&quot;1255&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1304&quot; data-start=&quot;1257&quot; data-ke-size=&quot;size16&quot;&gt;한 응용 프로그램이 하드 디스크에 데이터를 저장하려고 할 때의 과정을 예로 들어보자.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1427&quot; data-start=&quot;1306&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;1343&quot; data-start=&quot;1306&quot;&gt;응용 프로그램이 하드 디스크에 저장 요청 &amp;rarr; 시스템 호출 발생&lt;/li&gt;
&lt;li data-end=&quot;1386&quot; data-start=&quot;1344&quot;&gt;커널 모드로 전환되어, 커널 영역에 있는 하드 디스크 저장 코드를 실행&lt;/li&gt;
&lt;li data-end=&quot;1427&quot; data-start=&quot;1387&quot;&gt;작업이 끝나면 사용자 모드로 복귀하여 응용 프로그램의 실행을 이어감&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;1506&quot; data-start=&quot;1429&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;style5&quot; /&gt;
&lt;h3 data-end=&quot;181&quot; data-start=&quot;171&quot; data-ke-size=&quot;size23&quot;&gt;운영체제의 핵심 서비스&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;356&quot; data-start=&quot;183&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;227&quot; data-start=&quot;183&quot;&gt;&lt;b&gt;프로세스(Process)&lt;/b&gt;&lt;br /&gt;&amp;rarr; 실행 중인 프로그램을 말한다.&lt;/li&gt;
&lt;li data-end=&quot;280&quot; data-start=&quot;228&quot;&gt;예) 윈도우의 작업 관리자를 열어 보면 수많은 프로세스가 실행 중인 것을 확인할 수 있다.&lt;/li&gt;
&lt;li data-end=&quot;356&quot; data-start=&quot;281&quot;&gt;사용자가 컴퓨터를 사용하는 동안 메모리 안에서는 계속해서 새로운 프로세스가 생성되고, 사용되지 않는 프로세스는 메모리에서 삭제된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;621&quot; data-start=&quot;618&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;637&quot; data-start=&quot;623&quot; data-ke-size=&quot;size23&quot;&gt;자원 접근 및 할당&lt;/h3&gt;
&lt;p data-end=&quot;764&quot; data-start=&quot;639&quot; data-ke-size=&quot;size16&quot;&gt;모든 프로세스는 실행을 위해 CPU, 메모리, 입출력 장치 등 여러 자원을 필요로 한다. 운영체제는 이러한 자원을 적절히 할당해 주고, 여러 프로세스가 동시에 동작할 때 충돌 없이 공정하게 자원을 사용할 수 있도록 관리한다.&lt;/p&gt;
&lt;h4 data-end=&quot;774&quot; data-start=&quot;766&quot; data-ke-size=&quot;size20&quot;&gt;CPU&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;989&quot; data-start=&quot;776&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;841&quot; data-start=&quot;776&quot;&gt;일반적으로 메모리에는 여러 프로세스가 적재되어 있지만, &lt;b&gt;CPU는 한 번에 하나의 프로세스만 실행 가능&lt;/b&gt;하다.&lt;/li&gt;
&lt;li data-end=&quot;887&quot; data-start=&quot;842&quot;&gt;따라서 CPU를 사용 중인 프로세스가 있으면, 다른 프로세스는 대기해야 한다.&lt;/li&gt;
&lt;li data-end=&quot;989&quot; data-start=&quot;888&quot;&gt;운영체제는 어떤 프로세스부터 CPU를 사용하게 할지, 얼마나 오래 CPU를 사용하게 할지를 결정한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;989&quot; data-start=&quot;949&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;989&quot; data-start=&quot;949&quot;&gt;이를 &lt;b&gt;CPU 스케줄링&lt;/b&gt;이라 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;994&quot; data-start=&quot;991&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-end=&quot;1004&quot; data-start=&quot;996&quot; data-ke-size=&quot;size20&quot;&gt;메모리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적재된 프로세스들&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1061&quot; data-start=&quot;1022&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1032&quot; data-start=&quot;1022&quot;&gt;크기가 다르다.&lt;/li&gt;
&lt;li data-end=&quot;1061&quot; data-start=&quot;1035&quot;&gt;적재되는 메모리 주소도 매번 다를 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1266&quot; data-start=&quot;1006&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1113&quot; data-start=&quot;1062&quot;&gt;운영체제는 새로운 프로세스를 메모리에 적재할 때마다 어느 위치에 적재할지 결정해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1271&quot; data-start=&quot;1268&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-end=&quot;1284&quot; data-start=&quot;1273&quot; data-ke-size=&quot;size20&quot;&gt;입출력 장치&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1468&quot; data-start=&quot;1286&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1330&quot; data-start=&quot;1286&quot;&gt;입출력 서비스 루틴은 운영체제가 제공하는 기능으로 &lt;b&gt;커널 영역&lt;/b&gt;에 있다.&lt;/li&gt;
&lt;li data-end=&quot;1426&quot; data-start=&quot;1331&quot;&gt;입출력 장치가 CPU에 하드웨어 인터럽트 신호를 보내면:
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1426&quot; data-start=&quot;1367&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;1391&quot; data-start=&quot;1367&quot;&gt;CPU는 수행 중이던 작업을 잠시 백업&lt;/li&gt;
&lt;li data-end=&quot;1426&quot; data-start=&quot;1394&quot;&gt;커널 영역에 있는 &lt;b&gt;인터럽트 서비스 루틴&lt;/b&gt;을 실행&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1468&quot; data-start=&quot;1427&quot;&gt;운영체제는 이러한 인터럽트 서비스 루틴을 통해 입출력 작업을 수행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1473&quot; data-start=&quot;1470&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-end=&quot;1489&quot; data-start=&quot;1475&quot; data-ke-size=&quot;size20&quot;&gt;파일 시스템 관리&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1671&quot; data-start=&quot;1491&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1521&quot; data-start=&quot;1491&quot;&gt;컴퓨터 사용 시 파일을 열고, 생성하고, 삭제한다.&lt;/li&gt;
&lt;li data-end=&quot;1549&quot; data-start=&quot;1522&quot;&gt;여러 파일은 디렉터리(폴더)로 묶어 관리한다.&lt;/li&gt;
&lt;li data-end=&quot;1616&quot; data-start=&quot;1550&quot;&gt;&lt;b&gt;파일 시스템(file system)&lt;/b&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;핵심 정리&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;66&quot; data-start=&quot;27&quot;&gt;운영체제의 핵심 서비스를 제공하는 부분을 &lt;b&gt;커널&lt;/b&gt;이라 한다.&lt;/li&gt;
&lt;li data-end=&quot;119&quot; data-start=&quot;67&quot;&gt;사용자 프로세스가 커널 서비스를 받으려면, 사용자 모드에서 커널 모드로 전환해야 한다.&lt;/li&gt;
&lt;li data-end=&quot;165&quot; data-start=&quot;120&quot;&gt;이 전환은 &lt;b&gt;시스템 호출(system call)&lt;/b&gt; 을 통해 이루어진다.&lt;/li&gt;
&lt;li data-is-last-node=&quot;&quot; data-end=&quot;234&quot; data-start=&quot;166&quot;&gt;커널의 대표적인 서비스:
&lt;ul style=&quot;list-style-type: disc;&quot; data-is-last-node=&quot;&quot; data-end=&quot;234&quot; data-start=&quot;184&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;197&quot; data-start=&quot;184&quot;&gt;&lt;b&gt;프로세스 관리&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;216&quot; data-start=&quot;200&quot;&gt;&lt;b&gt;자원 접근 및 할당&lt;/b&gt;&lt;/li&gt;
&lt;li data-is-last-node=&quot;&quot; data-end=&quot;234&quot; data-start=&quot;219&quot;&gt;&lt;b&gt;파일 시스템 관리&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>혼자 공부하는 컴퓨터구조 + 운영체제</category>
      <author>s0hlog</author>
      <guid isPermaLink="true">https://s0h22.tistory.com/4</guid>
      <comments>https://s0h22.tistory.com/entry/%ED%98%BC%EC%9E%90-%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-%EC%BB%B4%ED%93%A8%ED%84%B0%EA%B5%AC%EC%A1%B0%EB%A1%A0-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-9%EB%8B%A8%EC%9B%90-%EC%A0%95%EB%A6%AC#entry4comment</comments>
      <pubDate>Wed, 16 Jul 2025 14:19:04 +0900</pubDate>
    </item>
    <item>
      <title>[백준/JAVA] 9012번: 괄호 | STACK 공부</title>
      <link>https://s0h22.tistory.com/entry/%EB%B0%B1%EC%A4%80JAVA-9012%EB%B2%88-%EA%B4%84%ED%98%B8-STACK-%EA%B3%B5%EB%B6%80</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;9012번: 괄호 바로 가기&quot; href=&quot;https://www.acmicpc.net/problem/9012&quot;&gt;https://www.acmicpc.net/problem/9012&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스택 공부를 위해 찾은 9012번.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내내 파이썬으로 코딩테스트를 공부하다가 자바로 공부하기로 마음을 먹었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 1년 전에 풀었다가 다시 푸는 문제!&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;((()()(()))(((())))() 이렇게 이어져있는 String문에서 하나씩 보는게 어려웠다.&lt;br /&gt;파이썬은 그냥 s[i]를 통해 접근했었는데 ㅠㅠ&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;pre id=&quot;code_1751610256373&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;package stack;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.Stack;


public class B9012 {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int num1 = Integer.parseInt(br.readLine());
        for (int i = 0; i &amp;lt; num1; i++) {
            String result = &quot;YES&quot;;
            String s = br.readLine();

            Stack&amp;lt;String&amp;gt; st = new Stack&amp;lt;&amp;gt;();

            for (int j = 0; j &amp;lt; s.length(); j++) {
                char c = s.charAt(j);
                if (c == '(') {
                    st.add(&quot;(&quot;);
                } else if (c == ')') {
                    if (st.isEmpty()) {
                        result = &quot;NO&quot;;
                        break;
                    } else {
                        st.pop();
                    }
                }
            }

            if (!st.isEmpty()) {
                result = &quot;NO&quot;;
            }
            System.out.println(result);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;339&quot; data-start=&quot;326&quot; data-ke-size=&quot;size23&quot;&gt;간단한 코드 설명&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;606&quot; data-start=&quot;341&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;375&quot; data-start=&quot;341&quot;&gt;입력으로 테스트케이스 개수 num1을 먼저 받는다.&lt;/li&gt;
&lt;li data-end=&quot;413&quot; data-start=&quot;376&quot;&gt;그다음 각 줄마다 괄호 문자열을 받아서 한 글자씩 확인한다.&lt;/li&gt;
&lt;li data-end=&quot;471&quot; data-start=&quot;414&quot;&gt;여는 괄호 (가 나오면 스택에 push하고, 닫는 괄호 )가 나오면 스택에서 pop한다.&lt;/li&gt;
&lt;li data-end=&quot;526&quot; data-start=&quot;472&quot;&gt;만약 닫는 괄호를 pop하려는데 스택이 비어있으면 &amp;rarr; 이미 짝이 안 맞으니 NO 출력.&lt;/li&gt;
&lt;li data-end=&quot;583&quot; data-start=&quot;527&quot;&gt;마지막까지 돌고 난 뒤에도 스택이 비어있지 않으면 &amp;rarr; 남은 여는 괄호가 있으니 NO 출력.&lt;/li&gt;
&lt;li data-end=&quot;606&quot; data-start=&quot;584&quot;&gt;모두 잘 맞으면 YES 출력.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;687&quot; data-start=&quot;608&quot; data-ke-size=&quot;size16&quot;&gt;스택으로 괄호 짝 맞추는 전형적인 문제인데, 자바에서는 charAt()으로 한 글자씩 보는 법을 배웠다.&lt;/p&gt;
&lt;p data-end=&quot;687&quot; data-start=&quot;608&quot; data-ke-size=&quot;size16&quot;&gt;스택을 쓰니 쉽게 풀렸다.&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;687&quot; data-start=&quot;608&quot; data-ke-size=&quot;size16&quot;&gt;지난번에 배운 BufferedReader도 사용해보고 싶어서 써보기 ㅎㅎ&lt;/p&gt;</description>
      <category>Java</category>
      <author>s0hlog</author>
      <guid isPermaLink="true">https://s0h22.tistory.com/3</guid>
      <comments>https://s0h22.tistory.com/entry/%EB%B0%B1%EC%A4%80JAVA-9012%EB%B2%88-%EA%B4%84%ED%98%B8-STACK-%EA%B3%B5%EB%B6%80#entry3comment</comments>
      <pubDate>Fri, 4 Jul 2025 15:26:32 +0900</pubDate>
    </item>
    <item>
      <title>[문법/JAVA] BufferedReader, BufferedWriter 등등 시간초과 대비</title>
      <link>https://s0h22.tistory.com/entry/JAVA-BufferedReader-BufferedWriter-StringBuilder-StringTokenizer-%EC%82%AC%EC%9A%A9%EB%B2%95-%EC%8B%9C%EA%B0%84%EC%B4%88%EA%B3%BC-%EB%8C%80%EB%B9%84</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;항상 Scanner, sout를 편하다는 이유로 자주 사용하다보니 시간초과가 종종 일어난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BufferedReader, BufferedWriter를 사용하면 더 빠르다는데...&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;h3 data-end=&quot;491&quot; data-start=&quot;466&quot; data-ke-size=&quot;size23&quot;&gt;BufferedReader 기본 사용법&lt;/h3&gt;
&lt;p data-end=&quot;514&quot; data-start=&quot;493&quot; data-ke-size=&quot;size16&quot;&gt;장점 : 훨씬 빠르다!&lt;/p&gt;
&lt;p data-end=&quot;514&quot; data-start=&quot;493&quot; data-ke-size=&quot;size16&quot;&gt;단점 : 무조건 String으로 읽어온다!&lt;/p&gt;
&lt;p data-end=&quot;514&quot; data-start=&quot;493&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;514&quot; data-start=&quot;493&quot; data-ke-size=&quot;size16&quot;&gt;예제 - 정수 하나 입력받기&lt;/p&gt;
&lt;pre id=&quot;code_1751448048852&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        
        int n = Integer.parseInt(br.readLine());
        System.out.println(n);
    }
}&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;pre id=&quot;code_1751449512539&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BufferedReader br = new BufferedReader(new InputStreamReader(System.in));&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;그동안 Scanner를 정의하듯이, 이를 정의하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;readLine()은 한 줄 통째로 문자열로 읽는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열이니까 int 로 받으려면 꼭 형변환하기&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;throws IOException은, 입출력은 항상 오류가 날 수 있어 반드시 사용해줘야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종종 까먹어도 컴파일러가 늘 밑줄로 경고해준다!&lt;/p&gt;
&lt;h2 data-end=&quot;1077&quot; data-start=&quot;1052&quot; data-ke-size=&quot;size26&quot;&gt;StringTokenizer 기본 사용법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;장점 : 빠르고, 공백으로 쉽게 나눌 수 있다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;단점&amp;nbsp;&lt;/span&gt;: nextToken()으로 하나하나 꺼내야 해서 조금 귀찮다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;1089&quot; data-start=&quot;1079&quot; data-ke-size=&quot;size16&quot;&gt;백준에서 입력이&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1751450142319&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;10 20 30 40&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이렇게 한 줄에 여러 개 들어올 때 &lt;/span&gt;&lt;b&gt;split()&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt; 보다 속도 면에서 유리하다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제 - 공백으로 구분된 여러 수 입력받기&lt;/p&gt;
&lt;pre id=&quot;code_1751450182587&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.StringTokenizer;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        
        String str = br.readLine();
        StringTokenizer st = new StringTokenizer(str);

        while (st.hasMoreTokens()) {
            int num = Integer.parseInt(st.nextToken());
            System.out.println(num);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;1831&quot; data-start=&quot;1807&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1831&quot; data-start=&quot;1807&quot; data-ke-size=&quot;size18&quot;&gt;StringTokenizer는 문자열을 토큰 단위로 잘라준다.&lt;/p&gt;
&lt;pre id=&quot;code_1751450461504&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;StringTokenizer st = new StringTokenizer(str);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;1831&quot; data-start=&quot;1807&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1831&quot; data-start=&quot;1807&quot; data-ke-size=&quot;size18&quot;&gt;기본으로 공백 기준으로 자른다.&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1831&quot; data-start=&quot;1807&quot; data-ke-size=&quot;size18&quot;&gt;다른 기준으로 자르고 싶다면 두 번째 매개변수에 구분자를 적는다.&lt;/p&gt;
&lt;p data-end=&quot;1831&quot; data-start=&quot;1807&quot; data-ke-size=&quot;size18&quot;&gt;예시 - 콤마(,)를 구분자로 사용&lt;/p&gt;
&lt;pre id=&quot;code_1751450631277&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;StringTokenizer st = new StringTokenizer(&quot;10,20,30&quot;, &quot;,&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;1831&quot; data-start=&quot;1807&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1831&quot; data-start=&quot;1807&quot; data-ke-size=&quot;size18&quot;&gt;다음 코드를 보면&lt;/p&gt;
&lt;pre id=&quot;code_1751450665260&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;while (st.hasMoreTokens()) {
    int num = Integer.parseInt(st.nextToken());
    System.out.println(num);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;1831&quot; data-start=&quot;1807&quot; data-ke-size=&quot;size18&quot;&gt;입력을 받고, 바로 문자열을 토큰 단위로 자르라는 코드이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1831&quot; data-start=&quot;1807&quot; data-ke-size=&quot;size18&quot;&gt;hasMoreTokens()을 통해 아직 꺼낼 토큰이 남아있는지 확인하고 (Boolean),&lt;/p&gt;
&lt;p data-end=&quot;1831&quot; data-start=&quot;1807&quot; data-ke-size=&quot;size18&quot;&gt;nextToken() 를 통해 토큰을 하나 꺼내온다.&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;1831&quot; data-start=&quot;1807&quot; data-ke-size=&quot;size26&quot;&gt;BufferedWriter 기본 사용법&lt;/h2&gt;
&lt;p data-end=&quot;1906&quot; data-start=&quot;1833&quot; data-ke-size=&quot;size16&quot;&gt;백준에서 출력이 많으면 System.out.println()도 느리다. 이럴 때 쓰는 게 BufferedWriter이다.&lt;/p&gt;
&lt;p data-end=&quot;1906&quot; data-start=&quot;1833&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;장점 : 출력 속도가 매우 빠르다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;1968&quot; data-start=&quot;1908&quot; data-ke-size=&quot;size16&quot;&gt;단점 : flush()를 직접 해줘야 한다.&lt;/p&gt;
&lt;hr data-end=&quot;1973&quot; data-start=&quot;1970&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1996&quot; data-start=&quot;1975&quot; data-ke-size=&quot;size23&quot;&gt;예제 - 숫자 여러 줄 출력하기&lt;/h3&gt;
&lt;pre id=&quot;code_1751450204022&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        
        int n = Integer.parseInt(br.readLine());
        
        for (int i = 1; i &amp;lt;= n; i++) {
            bw.write(i + &quot;\n&quot;);
        }
        
        bw.flush(); 
        bw.close();
    }
}&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;BufferedReader은, 앞에서 배웠듯이 입력받는 방법이다.&lt;/p&gt;
&lt;pre id=&quot;code_1751450950246&quot; class=&quot;haxe&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;2534&quot; data-start=&quot;2511&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2534&quot; data-start=&quot;2511&quot; data-ke-size=&quot;size16&quot;&gt;이는 출력을 위해 선언해준다.&lt;/p&gt;
&lt;h4 data-end=&quot;435&quot; data-start=&quot;425&quot; data-ke-size=&quot;size20&quot;&gt;write()&lt;/h4&gt;
&lt;p data-end=&quot;483&quot; data-start=&quot;437&quot; data-ke-size=&quot;size16&quot;&gt;: 문자열을 출력 버퍼에 기록한다. sout과 다르게 바로 출력되지 않고, 버퍼에 잠깐 모아둔다.&lt;/p&gt;
&lt;h4 data-end=&quot;533&quot; data-start=&quot;523&quot; data-ke-size=&quot;size20&quot;&gt;flush()&lt;/h4&gt;
&lt;p data-end=&quot;596&quot; data-start=&quot;535&quot; data-ke-size=&quot;size16&quot;&gt;: 버퍼에 모아둔 데이터를 한 번에 출력한다. BufferedWriter를 썼다면 꼭 해줘야 한다!&lt;/p&gt;
&lt;h4 data-end=&quot;638&quot; data-start=&quot;628&quot; data-ke-size=&quot;size20&quot;&gt;close()&lt;/h4&gt;
&lt;p data-end=&quot;680&quot; data-start=&quot;640&quot; data-ke-size=&quot;size16&quot;&gt;: 스트림을 닫고 자원을 정리한다.&amp;nbsp;마지막에 꼭 호출하는 것이 좋다.&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;h2 data-end=&quot;2534&quot; data-start=&quot;2511&quot; data-ke-size=&quot;size26&quot;&gt;StringBuilder 기본 사용법&lt;/h2&gt;
&lt;p data-end=&quot;2581&quot; data-start=&quot;2536&quot; data-ke-size=&quot;size16&quot;&gt;백준에서 가장 많이 쓰이는 출력 방식이 StringBuilder 이다.&lt;/p&gt;
&lt;p data-end=&quot;2648&quot; data-start=&quot;2583&quot; data-ke-size=&quot;size16&quot;&gt;장점 : 문자열을 한 번에 모아서 출력 &amp;rarr; 속도 빠름&lt;br /&gt;단점 : 코드가 조금 길어질 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;2648&quot; data-start=&quot;2583&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2648&quot; data-start=&quot;2583&quot; data-ke-size=&quot;size16&quot;&gt;예제 - StringBuilder로 출력&lt;/p&gt;
&lt;pre id=&quot;code_1751450232238&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        
        int n = Integer.parseInt(br.readLine());
        StringBuilder sb = new StringBuilder();
        
        for (int i = 1; i &amp;lt;= n; i++) {
            sb.append(i).append(&quot;\n&quot;);
        }
        
        System.out.print(sb);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;154&quot; data-start=&quot;111&quot; data-ke-size=&quot;size16&quot;&gt;StringBuilder는 문자열을 계속 더할 때 쓰는 클래스이다.&lt;/p&gt;
&lt;p data-end=&quot;222&quot; data-start=&quot;156&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; + 연산으로 문자열을 합치는 것보다 훨씬 빠르다.&lt;/p&gt;
&lt;p data-end=&quot;222&quot; data-start=&quot;156&quot; data-ke-size=&quot;size16&quot;&gt;BufferedWriter보다 편해서 많은 사람들이 쓰곤 한다.&lt;/p&gt;
&lt;p data-end=&quot;222&quot; data-start=&quot;156&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1751451142945&quot; class=&quot;armasm&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;StringBuilder sb = new StringBuilder();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StringBuilder 객체를 만든다. 빈 문자열 상태로 만들어진다.&lt;/p&gt;
&lt;h4 data-end=&quot;340&quot; data-start=&quot;329&quot; data-ke-size=&quot;size20&quot;&gt;append()&lt;/h4&gt;
&lt;p data-end=&quot;376&quot; data-start=&quot;342&quot; data-ke-size=&quot;size16&quot;&gt;: 문자열을 뒤에 계속 붙인다. 기존 문자열 + 새 문자열&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1751451188146&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sb.append(i).append(&quot;\n&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;append(i) &amp;rarr; 숫자 i를 문자열로 바꿔서 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;append(&quot;\n&quot;) &amp;rarr; 줄바꿈 문자 추가&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;이렇게 계속 더해진 값을 sout을 통해 마지막에 출력해주면 된다.&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 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;/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;BufferedReader &amp;rarr; 빠르게 입력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StringTokenizer &amp;rarr; 공백으로 잘라 쓰기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BufferedWriter &amp;rarr; 빠르게 출력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StringBuilder &amp;rarr; 문자열 모아서 한 번에 출력&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;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;</description>
      <category>Java</category>
      <author>s0hlog</author>
      <guid isPermaLink="true">https://s0h22.tistory.com/2</guid>
      <comments>https://s0h22.tistory.com/entry/JAVA-BufferedReader-BufferedWriter-StringBuilder-StringTokenizer-%EC%82%AC%EC%9A%A9%EB%B2%95-%EC%8B%9C%EA%B0%84%EC%B4%88%EA%B3%BC-%EB%8C%80%EB%B9%84#entry2comment</comments>
      <pubDate>Wed, 2 Jul 2025 19:15:31 +0900</pubDate>
    </item>
  </channel>
</rss>