Front: Vue란?, CSR과 SSR, Vue 기본 구조
Vue.js 란?JavaScript의 프레임워크 중 하나.  (react.js, angular 등 다른 프레임워크들도 있음)다른 프레임워크에 비해 직관적이기 때문에 배우기가 더 용이함템플릿 기반의 구문을 사용해 HTML과 JavaScript를 명확히 분리 가능 (가독성 좋음)         CSR(Client-Side Rendering)과 SSR(Server-Side Rendering)  CSR :장점-  초기 로딩 후에는 모든 렌더링이 클라이언트 측에서 처리됨 -> 빠르게 느껴짐-  서버 부하가 감소, 서버는 단지 데이터(JSON)만 전송하면 됨단점- 애플리케이션의 자바스크립트 전체를 다운로드해야하므로 초기 속도는 느릴 수 있음-  검색 엔진이 제대로 반영되지 못할 수 있음SSR :장점-  서버에서 H..
2024.11.05
Front: AJAX란, 비동기와 동기, 비동기 동작 순서, XMLHttpRequest, Promise
AJAX란?Asynchronous JavaScrpt and XML비동기적인 방식으로 서버와 통신새로고침을 하지 않아도 서버와 통신해 웹페이지를 갱신할 수 있다.한번에 서버의 데이터를 모두 받아오기 때문에 처음엔 느리지만, 이후로는 빠르게 처리할 수 있음서버 입장에서는 부하가 감소하는 효과클라이언트 입장에서는 더 빠르게 느낌 AJAX의 동작 방식: 동기 vs 비동기 동기 :- 요청을 보낸 이후 서버로부터 응답을 받아야 다음 작업을 진행.- 코드가 단순하고 직관적이기에 개발자 입장에서는 더 용이할 수 있음- 하지만 한 가지 요청이 막히면, 다음 요청까지 막혀 멈춰버리는 현상 발생비동기 :- 페이지를 처음 불러들일 때, 대부분의 데이터를 모두 받아둠.- 어떠한 요청이 막혀도, 바로 다음 작업을 진행할 수 있기..
2024.11.03
백준 11478: 서로 다른 부분 문자열의 개수[Silver 3] / Java
백준 11478: 서로 다른 부분 문자열의 개수https://www.acmicpc.net/problem/11478    문제 설명문자열 S가 주어졌을 때, S의 서로 다른 부분 문자열의 개수를 구하는 프로그램을 작성하시오.부분 문자열은 S에서 연속된 일부분을 말하며, 길이가 1보다 크거나 같아야 한다.예를 들어, ababc의 부분 문자열은 a, b, a, b, c, ab, ba, ab, bc, aba, bab, abc, abab, babc, ababc가 있고, 서로 다른것의 개수는 12개이다.    입력첫째 줄에 문자열 S가 주어진다. S는 알파벳 소문자로만 이루어져 있고, 길이는 1,000 이하이다.    출력첫째 줄에 S의 서로 다른 부분 문자열의 개수를 출력한다.    내 답연속되는 모든 부분 문자..
2024.11.02
백준 2252: 줄 세우기[Gold 3] / Java
백준 2252: 줄 세우기https://www.acmicpc.net/problem/2252    문제 설명N명의 학생들을 키 순서대로 줄을 세우려고 한다. 각 학생의 키를 직접 재서 정렬하면 간단하겠지만, 마땅한 방법이 없어서 두 학생의 키를 비교하는 방법을 사용하기로 하였다. 그나마도 모든 학생들을 다 비교해 본 것이 아니고, 일부 학생들의 키만을 비교해 보았다.일부 학생들의 키를 비교한 결과가 주어졌을 때, 줄을 세우는 프로그램을 작성하시오.  입력첫째 줄에 N(1 ≤ N ≤ 32,000), M(1 ≤ M ≤ 100,000)이 주어진다. M은 키를 비교한 횟수이다. 다음 M개의 줄에는 키를 비교한 두 학생의 번호 A, B가 주어진다. 이는 학생 A가 학생 B의 앞에 서야 한다는 의미이다.학생들의 번호..
2024.10.30
no image
Spring : 프레임워크, 의존성 주입 방법
프레임워크란?개발자가 일정 규칙과 구조 안에서 코드를 작성하도록 돕는 도구복잡한 로직을 단순화하거나 생략할 수 있도록 해 코드 재사용성, 유지보수성을 높임객체 생성, 의존성 관리 등 잡다한 부분을 자동화하여 필요한 비즈니스 로직에 집중할 수 있게 해준다. (생산성 증가) Spring 프레임워크 특징POJO (Plain Old Java Object)특별한 인터페이스나 클래스를 상속받지 않고 순수한 자바 객체를 사용해 개발 진행한다.이는 코드 재사용성, 독립성을 높임의존성 주입의존성 주입을 통해 객체 간 결합도를 낮춘다.AOP (관점 지향 프로그래밍)횡단 관심사 분리(Cross-Cutting Concerns)하여 핵싱 기능들이 수행될 때 수반해야할 부수 기능들을 모듈화할 수 있도록 도움트랜잭션 관리선언적 트..
2024.10.28
백준 15686: 치킨 배달 [Gold 2] / Java
백준 15686: 치킨 배달https://www.acmicpc.net/problem/15686   문제 설명크기가 N×N인 도시가 있다. 도시는 1×1크기의 칸으로 나누어져 있다. 도시의 각 칸은 빈 칸, 치킨집, 집 중 하나이다. 도시의 칸은 (r, c)와 같은 형태로 나타내고, r행 c열 또는 위에서부터 r번째 칸, 왼쪽에서부터 c번째 칸을 의미한다. r과 c는 1부터 시작한다.이 도시에 사는 사람들은 치킨을 매우 좋아한다. 따라서, 사람들은 "치킨 거리"라는 말을 주로 사용한다. 치킨 거리는 집과 가장 가까운 치킨집 사이의 거리이다. 즉, 치킨 거리는 집을 기준으로 정해지며, 각각의 집은 치킨 거리를 가지고 있다. 도시의 치킨 거리는 모든 집의 치킨 거리의 합이다.임의의 두 칸 (r1, c1)과 (..
2024.10.16
알고리즘 : KMP (패턴 매칭) 알고리즘 (Java)
KMP 알고리즘 패턴 매칭 알고리즘의 하나로, 주어진 문자열에서 패턴과 일치하는 부분 문자열이 있는지 찾는 알고리즘일반적인 반복문을 통한 해결방법은 O(N^M)의 시간복잡도를 가지지만, 해당 알고리즘은 O(N+M)의 시간복잡도로 매우 효율적이다.패턴에서 접두사와 접미사를 이용해 중복된 검색을 줄이는 방식임    동작 원리1. 패턴 문자열에서 0부터 i번째 문자열로 잘랐을 때 접미사와 접두사가 같은 최대 길이를 기록한 배열(LPS)을 생성한다. 2. 텍스트에서 패턴을 비교하고, 검색 중 불일치가 발생하면 LPS배열을 참고해 패턴의 다음 탐색  시작 위치를 결정한다.    예시 텍스트: "ABC ABCDAB ABCDABCDABDE"패턴: "ABCDABD" 1. LPS (부분 일치 테이블) 생성0부터 i번째 ..
2024.10.15
Cookie, Session, HTTP
HTTP란?HyperText Transfer Protocol웹에서 데이터를 주고 받을 때 지켜야 할 통신 규약!대표적 HTTP 메서드 : GET, POST, PUT, DELETEURL : 요청을 보낼 때, 어디로 요청을 보낼지에 대한 정보.헤더(Header): 요청 보낼 때 필요한 추가 정보 (ex. 브라우저 종류, 언어 설정 등본문(Body) : POST의 경우, 서버에 보낼 데이터를 본문에 포함시킬 수 있음.비연결성 : 요청과 응답이 종료되면 서로의 상태를 기억하지 않는다!무 상태: 서버가 클라이언트 상태를 저장하지 않는다. => 쿠키와 세션이 필요     Cookie 쿠키란?웹 서버가 만들어 클라이언트의 브라우저에 저장하는 작은 데이터 조각클라이언트 요청 시 상황에 따라 서버로 같이 전송브라우저가 ..
2024.10.14
JSP 특징, 장단점, Scriptlet 등
JSP란?서버 측에서 동적으로 HTML, XML등의 문서를 생성하기 위해 사용하는 자바 기반 기술HTML 문서에서 Java코드를 작성할 수 있도록 혼합JSTL등의 태그 라이브러리가 지원되어 편의성 향상작성된 후 서블릿으로 변환된 후 실행      JSP 구성 요소지시자 페이지 설정 관련 정보 지정을 위해 사용스크립트 요소(스크립트릿, 표현식, 선언부)문서 내용을 동적으로 생성하기 위해 사용기본객체요청과 응답을 읽고 처리하기 위해 기본으로 생성되어 있음표현언어(Expression Language)JSP를 더 간편하게 작성하기 위해 사용       JSP 태그스크립트릿 : 자바 코드를 작성하는 구간 (메서드 영역) 선언: 변수와 메서드를 선언하는 구간(클래스 영역)표현식: 특정 값이나 결과물을 문자열로 출력..
2024.10.08
Java Servlet 특징, 생명주기, 처리방식 등
Servlet?Server + Applet의 합성어Servlet은 웹 애플리케이션 개발에서 동적 웹페이지 생성클라이언트의 요청을 해석하고 응답을 생성하는 서버측 요소자바 코드 안에 HTML을 포함주로 HTTP 프로토콜을 사용해 클라이언트와 상호작용Apache Tomcat과 같은 웹서버에서 실행된다!      Servlet 생명주기0. Servlet 인스턴스가 없을 경우, Servlet 클래스를 불러오고, 웹컨테이너에 의해 Servlet 인스턴스를 초기화, 생성함.  1. init()Servlet이 처음 로드될 때 한 번만 호출된다. 따라서 여러 변수나 조건들을 초기화하는 기능을 한다.  2. service()클라이언트의 요청을 처리할 때마다 호출되며, 요청 메서드(GET, POST 등)에 따라 적절한 처..
2024.10.06

Vue.js 란?

  • JavaScript의 프레임워크 중 하나.  (react.js, angular 등 다른 프레임워크들도 있음)
  • 다른 프레임워크에 비해 직관적이기 때문에 배우기가 더 용이함

  • 템플릿 기반의 구문을 사용해 HTML과 JavaScript를 명확히 분리 가능 (가독성 좋음)

 

 

 

 

 

 

 

 

 

CSR(Client-Side Rendering)과 SSR(Server-Side Rendering)

 

  1. CSR :

    장점

    -  초기 로딩 후에는 모든 렌더링이 클라이언트 측에서 처리됨 -> 빠르게 느껴짐

    -  서버 부하가 감소, 서버는 단지 데이터(JSON)만 전송하면 됨


    단점

    - 애플리케이션의 자바스크립트 전체를 다운로드해야하므로 초기 속도는 느릴 수 있음

    -  검색 엔진이 제대로 반영되지 못할 수 있음


  2. SSR :


    장점

    -  서버에서 HTML을 완성해 보내기 때문에, 검색엔진 최적화에는 더 적절

    - 첫 페이지에서 모든 정보를 다운로드할 필요가 없으므로 첫 페이지 로딩은 더 빠


    단점

    - 모든 요청이 서버에서 렌더링되므로 부하가 증가

    - 클라이언트와 서버 간 상태를 동기화하는 것이 복잡할 수 있음.

 



 

 

 

 

 

 

 

기본 구조

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <title>FirstVueApp</title>
    ///////////////////////////////////////
    // Vue를 사용하기 위해 필수. CDN방식 
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  </head>

  <body>
    <div id="app">
      <h1>{{ message }}</h1>
      <button v-on:click="count++">Count is: {{ count }}</button>
    </div>
    
    ///////////////////////////////////////////
    <script>
    
      // Vue 객체에서 createApp과 ref를 구조분해 할당으로 생성
      const { createApp, ref } = Vue;

	  // createApp 함수 호출하여 Vue 앰 인스턴스를 생성
      const app = createApp({	
        setup() {	// <- Composition API에서 컴포넌트의 반응형 상태와 함수를 정의
        
          // ref로 반응형 데이터 생성
          const message = ref("Hello vue");
          const count = ref(0);
		
          // setup함수에서 반환하는 객체들. 템플릿에서 사용하려면 반드시 반환해주어야 함
          return {
            message,
            count,
          };
        },
      });

      app.mount("#app");
    </script>
    /////////////////////////////////////////////
  </body>
</html>

 

 

 

createApp 함수를 사용해 앱 객체를 생성하고, 반응형 데이터 생성을 위해 ref객체를 생성한다.

 

const {createApp, ref} = Vue   -> 구조분해할당으로 객체 생성

 

 

 

 

 

템플릿 영역에서의 unwrap

 

 

Vue에서는 동적으로 화면을 바꾸고, 렌더링하기 위해

 

ref() 를 사용해 반응형 데이터를 생성한다.

 

즉 ref() 객체 안에 값을 집어 넣는 것.

 

이를 자바스크립트 안에서 값을 참조하기 위해선

 

.value를 사용해 값을 참조할 수 있고,

 

템플릿(HTML) 영역에서는 알아서 unwrap해주기 때문에 .value를 사용할 필요가 없다!!

 

 

<!DOCTYPE html>
<html lang="ko">

<head>
  <meta charset="UTF-8" />
  <title>FirstVueApp</title>
  ///////////////////////////////////////
  // Vue를 사용하기 위해 필수. CDN방식
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>

<body>
  <div id="app">
    <h1>{{ message }}</h1>
    <button v-on:click="count++">Count is: {{ count }}</button>
  </div>

  <script>
    const App = {
      setup() {
        const count = ref(0);

        function increment() {
          count.value++;
        }

        return { count, increment }
      }
    };

    const app = createApp(App);
    app.mount('#app');
  </script>
</body>

 

AJAX란?

  • Asynchronous JavaScrpt and XML

  • 비동기적인 방식으로 서버와 통신

  • 새로고침을 하지 않아도 서버와 통신해 웹페이지를 갱신할 수 있다.

  • 한번에 서버의 데이터를 모두 받아오기 때문에 처음엔 느리지만, 이후로는 빠르게 처리할 수 있음

  • 서버 입장에서는 부하가 감소하는 효과

  • 클라이언트 입장에서는 더 빠르게 느낌

 

AJAX의 동작 방식: 동기 vs 비동기

 

  1. 동기 :

    - 요청을 보낸 이후 서버로부터 응답을 받아야 다음 작업을 진행.

    - 코드가 단순하고 직관적이기에 개발자 입장에서는 더 용이할 수 있음

    - 하지만 한 가지 요청이 막히면, 다음 요청까지 막혀 멈춰버리는 현상 발생




  2. 비동기 :

    - 페이지를 처음 불러들일 때, 대부분의 데이터를 모두 받아둠.

    - 어떠한 요청이 막혀도, 바로 다음 작업을 진행할 수 있기 때문에 빠른 반응속도 유도할 수 있음

    - 코드가 복잡해져 개발 시 어려울 수 있음


AJAX는 비동기 방식!



 

 

비동기 처리 순서

AJAX의 비동기적 처리는 XMLHttpRequest 객체를 이용하거나 Promise 방식을 사용해 진행된다.

1. XMLHttpRequest 객체를 생성하고, 요청을 초기화한다.

 

2. 요청을 서버로 비동기적으로 전송한다.

 

3. 서버로부터 응답 대기. 그동안 사용자는 페이지 다른 부분을 사용할 수 있다.

 

4. 응답이 도착하면 지정해둔 콜백 함수가 실행돼 응답 처리.

 

 

 

XMLHttpRequest 방식

XMLHttpRequest는 서버와 HTTP 통신을 가능하게 하는 객체이다.

 

var xhr = new XMLHttpRequest();	// 객체 생성

xhr.open("GET", "data.json", true); // 메서드와 URL 설정(초기화)

// 요청 성공 시 발생할 콜백함수를 지정
xhr.onload = function () {
	// 서버 응답 결과를 처리할 콜백함수
    if (xhr.status === 200) {
        console.log("Received: " + xhr.responseText);
    }
};
// 요청 전송
xhr.send();

 

 

 

 

Promise 방식

Promise는 비동기 작업을 더 쉽게 처리하도록 하는 객체.

동기 코드처럼 읽고 쓸수 있어 가독성도 상승함.

 

 

function fetchData() {
	// resolve : 요청 성공 시 호출할 함수
    // reject : 요청 실패 시 호출할 함수
    return new Promise((resolve, reject) => {
        var xhr = new XMLHttpRequest();
        xhr.open("GET", "data.json");
        xhr.onload = function () {
            if (xhr.status === 200) {
                resolve(xhr.responseText);
            } else {
                reject("Failed to fetch data");
            }
        };
        xhr.onerror = function () {
            reject("Network error");
        };
        xhr.send();
    });
}

fetchData().then(data => console.log(data)).catch(err => console.error(err));
백준 11478: 서로 다른 부분 문자열의 개수
 
 

문제 설명

문자열 S가 주어졌을 때, S의 서로 다른 부분 문자열의 개수를 구하는 프로그램을 작성하시오.

부분 문자열은 S에서 연속된 일부분을 말하며, 길이가 1보다 크거나 같아야 한다.

예를 들어, ababc의 부분 문자열은 a, b, a, b, c, ab, ba, ab, bc, aba, bab, abc, abab, babc, ababc가 있고, 서로 다른것의 개수는 12개이다.

 

 

 

 

입력

첫째 줄에 문자열 S가 주어진다. S는 알파벳 소문자로만 이루어져 있고, 길이는 1,000 이하이다.

 

 

 

 

출력

첫째 줄에 S의 서로 다른 부분 문자열의 개수를 출력한다.

 

 

 

 

내 답

연속되는 모든 부분 문자열을 반복문 2개를 통해 추출하고,

 

 

중복 제거의 경우 Set을 이용해 중복을 제거했다.

 

 

시간 복잡도를 줄이고자 StringBuilder를 사용해 문자열을 담았으나,

 

 

결과적으로 950ms의 아쉬운 실행속도가 나왔다.

 

 

 

 

원인은 중복 제거 시 Set 자료구조를 사용할 때 중복을 제거하는 과정의 시간복잡도가

 

 

O(N^2)이기 때문에 시간이 오래 걸린 것으로 추측된다.

 

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Set;

public class S3_서로다른부분문자열의개수 {
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String str = br.readLine();
		
		Set<String> set = new HashSet<>();;
		
		int len = str.length();
		for (int i=0; i<len; i++) {
			StringBuilder sb = new StringBuilder();
			for (int j=i; j<len; j++) {
				sb.append(str.charAt(j));
				set.add(sb.toString());
			}
		}
		
		System.out.println(set.size());
	}
}

 

 

 

 

 

 

보완점

 

 

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

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String input = reader.readLine();
        int n = input.length();

        // 접미사 배열 생성
        String[] suffix = new String[n];
        for (int i = 0; i < n; i++) {
            suffix[i] = input.substring(i);
        }

        // 접미사 배열 정렬
        Arrays.sort(suffix);
        int len = 0;

        // LCP 계산
        for (int i = 1; i < n; i++) {
            int curr = 0;
            int minLen = Math.min(suffix[i].length(), suffix[i - 1].length());
            while (curr < minLen && suffix[i].charAt(curr) == suffix[i - 1].charAt(curr)) {
                curr++;
            }
            len += curr;
        }

        // 총 부분 문자열 개수에서 중복된 부분을 뺀 개수 출력
        int total = (n * (n + 1)) / 2; // 총 부분 문자열 개수
        System.out.println(total - len);
    }
}

 

 

접미사 배열을 생성하여 정렬한 뒤,

 

 

정렬된 접미사들을 앞 뒤 접미사와 비교하며 공통 접두사 길이를 계산해 중복 부분 문자열을 제거하는 방식인

 

 

LCP 알고리즘 풀이를 사용했다.

 

 

처음 보는 알고리즘 방식이라 참신하고 좋은 방법이라는 생각이 든다.

 

 

익혀놓아야 겠다.

 

 

 

 

 

 

LCP 배열 계산

 

1. 가능한 모든 접미사를 저장해둔다.

 

apple => apple, pple, ple, le, e

 

 

 

2. 접미사들을 사전 순으로 정렬한다.

 

apple, e, le, ple, pple

 

 

 

3. 각 접미사들을 순회하며 이전 접미사와 비교해 공통 접두사의 길이를 계산한 LCP 배열에 저장해둔다.

 

apple : e => 0

e : le => 0

le : ple => 0

ple : pple => 1

 

 

 

 

따라서 중복되는 경우는 총 1가지이다!

 

백준 2252: 줄 세우기
 
 

문제 설명

N명의 학생들을 키 순서대로 줄을 세우려고 한다. 각 학생의 키를 직접 재서 정렬하면 간단하겠지만, 마땅한 방법이 없어서 두 학생의 키를 비교하는 방법을 사용하기로 하였다. 그나마도 모든 학생들을 다 비교해 본 것이 아니고, 일부 학생들의 키만을 비교해 보았다.

일부 학생들의 키를 비교한 결과가 주어졌을 때, 줄을 세우는 프로그램을 작성하시오.

 

 

입력

첫째 줄에 N(1 ≤ N ≤ 32,000), M(1 ≤ M ≤ 100,000)이 주어진다. M은 키를 비교한 횟수이다. 다음 M개의 줄에는 키를 비교한 두 학생의 번호 A, B가 주어진다. 이는 학생 A가 학생 B의 앞에 서야 한다는 의미이다.

학생들의 번호는 1번부터 N번이다.

 

 

 

출력

첫째 줄에 학생들을 앞에서부터 줄을 세운 결과를 출력한다. 답이 여러 가지인 경우에는 아무거나 출력한다.

 

 

 

내 답

1차 시도 (오답)

문제를 계속 째려보다 보니 위상정렬 알고리즘이 생각났다.

 

주어지는 입력에서 키를 비교한 입력값 각각을 간선이라고 생각하고,

 

앞에 오는 숫자가 시작노드, 뒤에 오는 숫자가 종점 노드가 되는 방향 그래프를 그려

 

나를 향하는 노드가 없는 노드부터 큐에 담아 순서대로 출력하는 그림을 그린 후 코드를 작성했다.

 

 

 

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class Main {
	static int N, M;
	static int[][] adjArr;
	static boolean[] visited;
	static StringBuilder sb;

	public static void main(String[] args) throws IOException {
		init(); // 변수 선언 및 초기화 메서드

		wisangSort();	// 위상정렬

		print(); // 출력 메서드
	}

	// 변수 선언 및 초기화 메서드
	static void init() throws IOException {
		sb = new StringBuilder();
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());

		N = Integer.parseInt(st.nextToken()); // 학생 수
		M = Integer.parseInt(st.nextToken()); // 학생 수

		adjArr = new int[N + 1][N + 1]; // 인접행렬
		for (int r = 0; r < M; r++) {
			st = new StringTokenizer(br.readLine());
			adjArr[Integer.parseInt(st.nextToken())][Integer.parseInt(st.nextToken())] = 1;
		} // 인접행렬 작성

		visited = new boolean[N + 1]; // 방문배열
	}

	// 위상정렬 수행 메서드
	private static void wisangSort() {
		Queue<Integer> q = new LinkedList<>();

		// 자기 자신을 향하는 노드가 없는 시작지점 노드를 큐에 삽입
		out: for (int e = 1; e <= N; e++) {
			for (int s = 1; s <= N; s++) {
				if (adjArr[s][e] > 0)
					continue out;
			}
			q.add(e);
			visited[e] = true;
		}
		
		while (!q.isEmpty()) {
			int curr = q.poll();	// 큐에서 뽑기
			sb.append(curr).append(' ');	// 스트링빌더에 추가
			
			// 인접행렬을 뒤져 아직 방문 안했으면서 인접한 노드를 큐에 삽입
			for (int i=1; i<=N; i++) {
				if (adjArr[curr][i] > 0 && !visited[i]) {
					q.add(i);
					visited[i] = true;
				}
			}
		}
	}

	// 출력 메서드
	private static void print() {
		System.out.println(sb);
	}
}

 

 

 

 

결과는 메모리 초과.

 

원인으로 의심되는 것들은 크게 2가지였다.

 

1. 방문배열을 추가적으로 선언하여 불필요한 메모리 사용

2. 인접리스트 대신 인접 행렬을 사용하여 불필요한 메모리 사용

 

하지만 둘 모두 수정해보아도 메모리 초과는 여전했다.

 

 

 

 

 

2차 시도 (정답)

 

결국 원인은 위상정렬을 구현하는 과정의 문제였다.

 

위상정렬 알고리즘을 생각한 것은 잘했지만, 구체적인 구현 방법이 생각나지 않아

 

내가 머리속에서 굴린 알고리즘으로만 구현했더니 통과가 안되었다.

 

결국 위상정렬 알고리즘을 다시 찾아보고 나서야 정답을 맞출 수 있었다.

 

 

package boj;

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

public class Main {
    static int N, M;
    static List<Integer>[] adjList;
    static int[] inDegree;
    static StringBuilder sb;

    public static void main(String[] args) throws IOException {
        init(); // 변수 선언 및 초기화 메서드
        wisangSort(); // 위상정렬
        print(); // 출력 메서드
    }

    // 변수 선언 및 초기화 메서드
    static void init() throws IOException {
        sb = new StringBuilder();
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        N = Integer.parseInt(st.nextToken()); // 학생 수
        M = Integer.parseInt(st.nextToken()); // 비교 횟수

        adjList = new ArrayList[N + 1]; // 인접 리스트
        inDegree = new int[N + 1]; // 진입 차수

        for (int i = 1; i <= N; i++) {
            adjList[i] = new ArrayList<>();
        }

        for (int r = 0; r < M; r++) {
            st = new StringTokenizer(br.readLine());
            int u = Integer.parseInt(st.nextToken());
            int v = Integer.parseInt(st.nextToken());
            adjList[u].add(v); // u -> v 간선 추가
            inDegree[v]++; // v의 진입 차수 증가
        }
    }

    // 위상정렬 수행 메서드
    private static void wisangSort() {
        Queue<Integer> q = new LinkedList<>();

        // 진입 차수가 0인 노드를 큐에 삽입
        for (int i = 1; i <= N; i++) {
            if (inDegree[i] == 0) {
                q.add(i);
            }
        }

        while (!q.isEmpty()) {
            int curr = q.poll(); // 큐에서 뽑기
            sb.append(curr).append(' '); // 스트링빌더에 추가
            
            // curr와 인접한 노드들에 대해 진입 차수를 감소시키고 0이 되면 큐에 추가
            for (int neighbor : adjList[curr]) {
                inDegree[neighbor]--;
                if (inDegree[neighbor] == 0) {
                    q.add(neighbor);
                }
            }
        }
    }

    // 출력 메서드
    private static void print() {
        System.out.println(sb);
    }
}

 

 

결론은

 

방문배열을 사용하는 것이 아니라,

 

진입차수 배열을 생성하여

 

큐에서 꺼낸 노드와 인접한 노드들과의 간선을 1개씩 제거하며 진입차수를 떨어트리고,

 

진입차수가 0인 것들을 큐에 담는 과정이 필요했다...

 

 

다시한번 위상정렬 알고리즘을 공부할 수 있는 기회가 되었다.

 

다음에는 진입차수 배열을 사용하는 것을 잊지 말아야지!

 

 

프레임워크란?

  • 개발자가 일정 규칙과 구조 안에서 코드를 작성하도록 돕는 도구
  • 복잡한 로직을 단순화하거나 생략할 수 있도록 해 코드 재사용성, 유지보수성을 높임
  • 객체 생성, 의존성 관리 등 잡다한 부분을 자동화하여 필요한 비즈니스 로직에 집중할 수 있게 해준다. (생산성 증가)

 

Spring 프레임워크 특징

  1. POJO (Plain Old Java Object)
    특별한 인터페이스나 클래스를 상속받지 않고 순수한 자바 객체를 사용해 개발 진행한다.
    이는 코드 재사용성, 독립성을 높임

  2. 의존성 주입
    의존성 주입을 통해 객체 간 결합도를 낮춘다.

  3. AOP (관점 지향 프로그래밍)
    횡단 관심사 분리(Cross-Cutting Concerns)하여 핵싱 기능들이 수행될 때 수반해야할 부수 기능들을 모듈화할 수 있도록 도움

  4. 트랜잭션 관리
    선언적 트랜잭션 관리를 통해 데이터의 무결성, 안정성을 유지할 수 있게 돕는다!
    메서드가 수행되다가 예외가 발생하면 메서드 실행 전 상태로 데이터 상태를 복원해준다.

 

 

 

 

의존성 주입이란?

객체가 필요한 의존성을 직접 생성하지 않고, 외부 다른 클래스에서 주입받는 방식.

특정 구현체에 고정되지 않고, 다양한 구현체로 쉽게 변경이 가능해 유연성이 높아지고,

다른 환경에서도 재사용하기 쉽기 때문에 재사용성이 좋아진다.

 

의존관계 역전

상위 클래스가 하위 클래스에 의존하지 않고 인터페이스나 추상 클래스에 의존하도록 함으로써, 상위 모듈이 하위 모듈 변화에 영향을 덜 받도록 하는 방식

이 방식은 코드 유연성과 확장성을 높이고 의존성을 쉽게 관리할 수 있다.

 

// 인터페이스
public interface PaymentService {
    void processPayment();
}

// 인터페이스를 구현한 클래스 A
public class CreditCardPaymentService implements PaymentService {
    @Override
    public void processPayment() {
        System.out.println("Processing credit card payment...");
    }
}

// 주문서비스
public class OrderService {
	// 인터페이스에 의존
    private PaymentService paymentService;
	
    // 결제서비스를 신용카드 대신 현금으로 교체해도 해당 코드를 수정하지 않아도 됨!
    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    public void placeOrder() {
        System.out.println("Placing order...");
        paymentService.processPayment();
    }
}

 

 

위 코드에서 OrderService는 PaymentService 인터페이스에 의존하므로, 실제 구현을 쉽게 변경할 수 있음. CreditCardPaymentService 대신 다른 결제 서비스로 교체해도 OrderService의 코드는 수정할 필요가 없어진다!

 

 

Spring에서 의존성을 주입하는 방법

1. XML설정 파일을 통한 방법

<!-- applicationContext.xml -->
<beans xmlns="http://www.springframework.org/schema/beans" ...>
    <bean id="paymentService" class="com.example.CreditCardPaymentService"/>
    <bean id="orderService" class="com.example.OrderService">
        <constructor-arg ref="paymentService"/>
    </bean>
</beans>

 

2. 어노테이션을 이용한 방법

@Service
public class OrderService {
    private final PaymentService paymentService;

    @Autowired
    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    public void placeOrder() {
        System.out.println("Placing order...");
        paymentService.processPayment();
    }
}

 

3. Java Config를 통한 방법

@Configuration
public class AppConfig {
    @Bean
    public PaymentService paymentService() {
        return new CreditCardPaymentService();
    }

    @Bean
    public OrderService orderService() {
        return new OrderService(paymentService());
    }
}

 

백준 15686: 치킨 배달
 
 

문제 설명

크기가 N×N인 도시가 있다. 도시는 1×1크기의 칸으로 나누어져 있다. 도시의 각 칸은 빈 칸, 치킨집, 집 중 하나이다. 도시의 칸은 (r, c)와 같은 형태로 나타내고, r행 c열 또는 위에서부터 r번째 칸, 왼쪽에서부터 c번째 칸을 의미한다. r과 c는 1부터 시작한다.

이 도시에 사는 사람들은 치킨을 매우 좋아한다. 따라서, 사람들은 "치킨 거리"라는 말을 주로 사용한다. 치킨 거리는 집과 가장 가까운 치킨집 사이의 거리이다. 즉, 치킨 거리는 집을 기준으로 정해지며, 각각의 집은 치킨 거리를 가지고 있다. 도시의 치킨 거리는 모든 집의 치킨 거리의 합이다.

임의의 두 칸 (r1, c1)과 (r2, c2) 사이의 거리는 |r1-r2| + |c1-c2|로 구한다.

예를 들어, 아래와 같은 지도를 갖는 도시를 살펴보자.

 

0 2 0 1 0
1 0 1 0 0
0 0 0 0 0
0 0 0 1 1
0 0 0 1 2

 

0은 빈 칸, 1은 집, 2는 치킨집이다.

(2, 1)에 있는 집과 (1, 2)에 있는 치킨집과의 거리는 |2-1| + |1-2| = 2, (5, 5)에 있는 치킨집과의 거리는 |2-5| + |1-5| = 7이다. 따라서, (2, 1)에 있는 집의 치킨 거리는 2이다.

(5, 4)에 있는 집과 (1, 2)에 있는 치킨집과의 거리는 |5-1| + |4-2| = 6, (5, 5)에 있는 치킨집과의 거리는 |5-5| + |4-5| = 1이다. 따라서, (5, 4)에 있는 집의 치킨 거리는 1이다.

이 도시에 있는 치킨집은 모두 같은 프랜차이즈이다. 프렌차이즈 본사에서는 수익을 증가시키기 위해 일부 치킨집을 폐업시키려고 한다. 오랜 연구 끝에 이 도시에서 가장 수익을 많이 낼 수 있는 치킨집의 개수는 최대 M개라는 사실을 알아내었다.

도시에 있는 치킨집 중에서 최대 M개를 고르고, 나머지 치킨집은 모두 폐업시켜야 한다. 어떻게 고르면, 도시의 치킨 거리가 가장 작게 될지 구하는 프로그램을 작성하시오.

 

 

입력

첫째 줄에 N(2 ≤ N ≤ 50)과 M(1 ≤ M ≤ 13)이 주어진다.

둘째 줄부터 N개의 줄에는 도시의 정보가 주어진다.

도시의 정보는 0, 1, 2로 이루어져 있고, 0은 빈 칸, 1은 집, 2는 치킨집을 의미한다. 집의 개수는 2N개를 넘지 않으며, 적어도 1개는 존재한다. 치킨집의 개수는 M보다 크거나 같고, 13보다 작거나 같다.

 

 

 

출력

첫째 줄에 폐업시키지 않을 치킨집을 최대 M개를 골랐을 때, 도시의 치킨 거리의 최솟값을 출력한다.

 

 

 

내 답

1차 시도 (오답)

1. 도시 상황을 2차원 배열에 입력, 치킨집 좌표를 chickens 리스트에 저장

 

2. 모든 치킨집 중 M개의 치킨집을 선택하는 조합 메서드를 재귀함수를 이용해 구현. pick 배열에 저장

 

3. map을 탐색하며 집일 경우 BFS를 통해 최단거리를 구하고, 최소값을 선택.

 

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class G5_치킨배달 {
	static class Chicken {
		int r, c;

		public Chicken() {
		}

		public Chicken(int r, int c) {
			super();
			this.r = r;
			this.c = c;
		}

		@Override
		public String toString() {
			return "Chicken [r=" + r + ", c=" + c + "]";
		}
	}

	static int N, M, min;
	static int[][] map;
	static int[][] tmpMap;
	static boolean[][] visited;
	static ArrayList<Chicken> chickens;
	static Chicken[] pick;
	static int[] dr = {-1, 1, 0, 0};
	static int[] dc = {0, 0, -1, 1};

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

		N = sc.nextInt();
		M = sc.nextInt();

		min = Integer.MAX_VALUE; // 치킨 거리의 최소값을 저장할 변수

		map = new int[N][N];
		tmpMap = new int[N][N];
		visited = new boolean[N][N];
		chickens = new ArrayList<>(); // 치킨집 좌표를 담을 리스트
		pick = new Chicken[M]; // M개를 뽑은 후 저장할 배열

		// 맵 입력 및 치킨집 탐색
		for (int r = 0; r < N; r++) {
			for (int c = 0; c < N; c++) {
				map[r][c] = sc.nextInt();
				if (map[r][c] == 2) {
					chickens.add(new Chicken(r, c));
					map[r][c] = 0; // 지도상으로는 0으로 초기화
				}
			}
		}
		pick(0, 0);
		
		System.out.println(min);
	}

	private static void pick(int n, int r) {
		// 모두 골랐으면 탐색 시작
		if (r >= M) {
			editMap();
			int tmpDist = search();
			if (min > tmpDist) {
				min = tmpDist;
			}
			return;
		}
		// 남은 치킨집 수보다 골라야 할 치킨집 수가 많으면 부적절하므로 종료
		if (n >= chickens.size()) {
			return;
		}

		// 해당 치킨집을 안고른다
		pick(n + 1, r);
		// 해당 치킨집을 고른다
		pick[r] = chickens.get(n);
		pick(n + 1, r + 1);
	}

	// 선택한 치킨집만 지도상에 2로 표시
	private static void editMap() {
		for (int r=0; r<N; r++) {
			tmpMap[r] = map[r].clone();
		}
		for (Chicken chicken : pick) {
			tmpMap[chicken.r][chicken.c] = 2;
		}
	}

	// 지도 탐색하여 집일 경우 BFS탐색하여 거리 계산
	private static int search() {
		int sum = 0;
		for (int r = 0; r < N; r++) {
			for (int c = 0; c < N; c++) {
				if (tmpMap[r][c] == 1) {
					int dist = BFS(r, c);
					sum += dist;
				}
			}
		}
		return sum;
	}

	private static int BFS(int r, int c) {
		Queue<int[]> q = new LinkedList<>();

		int dist = 0;
		int size = 1;
		visited = new boolean[N][N];

		visited[r][c] = true; // 시작지점 방문체크
		q.add(new int[] { r, c }); // 큐에 삽입

		while (!q.isEmpty()) {

			for (int i = 0; i < size; i++) {
				int[] curr = q.poll();
				// 치킨집이면 탐색 종료
				if (tmpMap[curr[0]][curr[1]] == 2) {
					return dist;
				}
				// 4방탐색
				for (int dir = 0; dir<4; dir++) {
					int nr = curr[0] + dr[dir];
					int nc = curr[1] + dc[dir];
					
					if (nr<0 || nr>=N || nc<0 || nc>=N || visited[nr][nc]) continue;
					
					q.add(new int[] {nr, nc});
					visited[nr][nc] = true;
				}
			}
			size = q.size();
			dist += 1;
		}
		
		return dist;
	}
}

 

 

2차 시도 (정답)

이전 시도에서 최단거리를 구하기 위한 방법으로 BFS를 선택했으나,

 

BFS는 사방을 탐색해가며 최단거리를 구할 때 배열 크기가 커지고 집과 치킨집 간 거리가 멀어질수록

 

급격히 비효율적으로 변함을 느꼈다.

 

따라서 미리 집과 치킨집의 좌표를 저장해두고, 일일히 계산하는 방법으로 코드를 수정했다 !

 

 

 

1. 도시 상황을 2차원 배열에 입력, 치킨집과 집의 좌표를 각각 chickens, houses 리스트에 저장

 

2. 모든 치킨집 중 M개의 치킨집을 선택하는 조합 메서드를 재귀함수를 이용해 구현. pick 배열에 저장

 

3. 집과 치킨집 간 거리를 모두 계산한 후 최소값을 선택한다.

public class G5_치킨배달 {
	static class Chicken {
		int r, c;

		public Chicken() {
		}

		public Chicken(int r, int c) {
			super();
			this.r = r;
			this.c = c;
		}

		@Override
		public String toString() {
			return "Chicken [r=" + r + ", c=" + c + "]";
		}
	}
	static class House {
		int r, c;
		public House() {
			// TODO Auto-generated constructor stub
		}
		public House(int r, int c) {
			super();
			this.r = r;
			this.c = c;
		}
		@Override
		public String toString() {
			return "House [r=" + r + ", c=" + c + "]";
		}
	}
	static int N, M, min;
	static int[][] map;
	static ArrayList<Chicken> chickens;
	static ArrayList<House> houses;
	static Chicken[] pick;

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

		N = sc.nextInt();
		M = sc.nextInt();

		min = Integer.MAX_VALUE; // 치킨 거리의 최소값을 저장할 변수

		map = new int[N][N];
		chickens = new ArrayList<>(); // 치킨집 좌표를 담을 리스트
		houses = new ArrayList<>(); // 집 좌표를 담을 리스트
		pick = new Chicken[M]; // M개를 뽑은 후 저장할 배열

		// 맵 입력 및 치킨집 탐색
		for (int r = 0; r < N; r++) {
			for (int c = 0; c < N; c++) {
				map[r][c] = sc.nextInt();
				if (map[r][c] == 2) {
					chickens.add(new Chicken(r, c));
					map[r][c] = 0; // 지도상으로는 0으로 초기화
				} else if (map[r][c] == 1) {
					houses.add(new House(r, c));
				}
			}
		}
		pick(0, 0);
		
		System.out.println(min);
	}

	private static void pick(int n, int r) {
		// 모두 골랐으면 탐색 시작
		if (r >= M) {
			int tmpDist = search();
			if (min > tmpDist) {
				min = tmpDist;
			}
			return;
		}
		// 남은 치킨집 수보다 골라야 할 치킨집 수가 많으면 부적절하므로 종료
		if (n >= chickens.size()) {
			return;
		}

		// 해당 치킨집을 안고른다
		pick(n + 1, r);
		// 해당 치킨집을 고른다
		pick[r] = chickens.get(n);
		pick(n + 1, r + 1);
	}

	// 모든 집 탐색하며 선택한 각 치킨집과의 최소 거리를 구해 마을의 총 치킨거리 계산
	private static int search() {
		int sum = 0;
		for (House h : houses) {
			int tmpMin = Integer.MAX_VALUE;
			for (Chicken c : pick) {
				tmpMin = Math.min(Math.abs(h.r-c.r)+Math.abs(h.c-c.c), tmpMin);
			}
			sum += tmpMin;
		}
		return sum;
	}
}

 

복습 사항

배열 깊은 복사(deep copy)

깊은복사는 배열의 주소값이 아닌 실제 값을 복사하는 것을 말함

 

일반적으로 copyArr = originArr 의 방식으로 복사하면 원본 배열의 주소값이 복사되기 떄문에,

 

copyArr의 값을 수정하면 originArr의 값도 같이 변한다!

 

따라서 깊은 복사가 필요할 땐 아래와 같이 할 것

int[][] newMap = new int[N][N];
for (int i = 0; i < N; i++) {
    newMap[i] = map[i].clone();  // 각 행을 개별적으로 복사
}

 

KMP 알고리즘

 

  • 패턴 매칭 알고리즘의 하나로, 주어진 문자열에서 패턴과 일치하는 부분 문자열이 있는지 찾는 알고리즘

  • 일반적인 반복문을 통한 해결방법은 O(N^M)의 시간복잡도를 가지지만, 해당 알고리즘은 O(N+M)의 시간복잡도로 매우 효율적이다.

  • 패턴에서 접두사와 접미사를 이용해 중복된 검색을 줄이는 방식임

 

 

 

 

동작 원리

1. 패턴 문자열에서 0부터 i번째 문자열로 잘랐을 때 접미사와 접두사가 같은 최대 길이를 기록한 배열(LPS)을 생성한다.

 

2. 텍스트에서 패턴을 비교하고, 검색 중 불일치가 발생하면 LPS배열을 참고해 패턴의 다음 탐색  시작 위치를 결정한다.

 

 

 

 

예시

 

  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"

 

1. LPS (부분 일치 테이블) 생성

0부터 i번째 문자열로 잘랐을 때 접미사와 접두사가 같은 최대 길이를 기록한 배열(LPS)을 생성한다.

 

문자 A B C D A B D
인덱스 0 1 2 3 4 5 6
LPS 값 0 0 0 0 1 2 0

 

 

2. 문자열 검색 시작

텍스트와 패턴이 일치하지 않을 경우, LPS 테이블을 사용하여 패턴 내에서 최대한 불필요한 재비교를 피함

1) 처음 비교 시작 (i = 0, j = 0)

텍스트의 첫 번째 문자 A와 패턴의 첫 번째 문자 A를 비교

  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"

두 문자가 일치하므로 ij를 각각 1씩 증가시킵니다.

 

 

2) 다음 문자 비교 (i = 1, j = 1)

다음으로 텍스트의 두 번째 문자 B와 패턴의 두 번째 문자 B를 비교

  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"

일치하므로 ij를 다시 1씩 증가

 

 

3) 계속되는 비교 (i = 2, j = 2)

텍스트의 세 번째 문자 C와 패턴의 세 번째 문자 C도 일치

  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"

일치하므로 i = 3j = 3으로 이동

 

 

4) 불일치 발생 (i = 3, j = 3)

네 번째 문자에서 텍스트의 문자 (공백)과 패턴의 문자 D가 일치하지 않음

  • 텍스트: "ABC ** **ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"

불일치 시, LPS 테이블을 참고하여 패턴의 어느 위치로 돌아갈지 결정함. 현재 j = 3이고, LPS 테이블에서 LPS[3] = 0이므로, 패턴의 시작 부분으로 돌아야 함(j = 0). 하지만 텍스트에서 이미 비교한 문자는 건너뛰지 않고 계속 비교하므로 i는 4로 증가

 

5) 패턴 계속 비교 (i = 4, j = 0)

패턴을 다시 처음부터 시작하면서, 텍스트의 4번째 문자 A와 패턴의 첫 번째 문자 A를 비교

  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"

문자가 일치하므로 i = 5j = 1로 이동

 

6) 패턴 계속 비교 (i = 5 ~ 7, j = 1 ~ 3)

텍스트의 문자 B, C, D와 패턴의 문자 B, C, D가 차례로 일치

  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"
  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"
  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"

i = 8j = 4로 이동

 

7) 다시 불일치 발생 (i = 8, j = 4)

여기서 텍스트의 문자 A와 패턴의 문자 A가 일치

  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"

다시 일치하므로 i = 9j = 5로 이동

 

 

8) 최종 불일치 (i = 10, j = 6)

그러나 그다음 문자에서 텍스트의 문자 B와 패턴의 문자 B가 일치

  • 텍스트: "ABC ABCDABB ABCDABCDABDE"
  • 패턴: "ABCDABBD"

마지막으로 D가 일치

 

 

 

장점

  • 시간 복잡도를 효과적으로 줄일 수 있음

 

단점

  • 과정이 복잡해 이해하기 어려움

  • 초기 LPS 배열을 만드는 단계가 있어 패턴 크기가 작으면 오히려 비효율적일 수 있음

 

 

 

 

 

 

코드 구현

 

DP를 이용한 코드

 

>> 이전 항의 값을 별도 배열에 저장!

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

public class KMP알고리즘 {
	static char[] text;
	static char[] pattern;
	static int[] pi;
	static int max;
	
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		text = br.readLine().toCharArray();
		pattern = br.readLine().toCharArray();
		
		KMP();
	}

	private static void KMP() {
		getPi();
		int len = text.length;
		
		int j = 0;
		max = 0;
		for (int i=0; i<len; i++) {
			while (j>0 && text[i] != pattern[j]) {
				if (max < j) {
					max = j;
				}
				j = pi[j-1];
			}
			
			if (text[i] == pattern[j]) {
				// j 패턴 끝까지 도달했으면 종료
				if (j == pattern.length-1) {
					System.out.println(pattern.length);
					return;
				} else {
					j++;
				}
			}
		}
		System.out.println(max);
	}
	
	// 패턴 문자배열을 0부터 i번 인덱스까지 잘랐을 때 
	// 접두사와 접미사가 같은 최대 개수를 담은 배열을 생성하는 메서드
	private static void getPi() {
		int len = pattern.length;
		pi = new int[len];
		
		int j=0;
		for (int i=1; i<len; i++) {
			// i위치와 j위치 값이 다를 때
			while (j>0 && pattern[i] != pattern[j]) {
				j=pi[j-1];
			}
			// 같을 때
			if (pattern[i] == pattern[j]) {
				pi[i] = ++j;
			}
		}
	}
}

 

Cookie, Session, HTTP

친환경 개발자
|2024. 10. 14. 23:59

HTTP란?

  • HyperText Transfer Protocol

  • 웹에서 데이터를 주고 받을 때 지켜야 할 통신 규약!

  • 대표적 HTTP 메서드 :
    GET, POST, PUT, DELETE

  • URL : 요청을 보낼 때, 어디로 요청을 보낼지에 대한 정보.

  • 헤더(Header): 요청 보낼 때 필요한 추가 정보 (ex. 브라우저 종류, 언어 설정 등
  • 본문(Body) : POST의 경우, 서버에 보낼 데이터를 본문에 포함시킬 수 있음.

  • 비연결성 : 요청과 응답이 종료되면 서로의 상태를 기억하지 않는다!

  • 무 상태: 서버가 클라이언트 상태를 저장하지 않는다.
     => 쿠키와 세션이 필요

 

 

 

 

 

Cookie

 

쿠키란?

  • 웹 서버가 만들어 클라이언트의 브라우저에 저장하는 작은 데이터 조각

  • 클라이언트 요청 시 상황에 따라 서버로 같이 전송

  • 브라우저가 바뀌면 쿠키는 남아있지 않음

 

목적

  • 클라이언트의 정보를 저장하여 활용할 필요가 있을 때
  • 사용자의 패턴 분석을 위해

  • 맞춤 광고, 장바구 등

특징

  • 서버가 아닌 사용자 브라우저에 저장됨

  • 하나의 쿠키는 약 4K
  • 유효기간을 설정할 수 있음

  • 클라이언트 측에 저장되므로 보안에 상대적으로 취약

 

Session

 

세션이란?

  • 사용자가 웹 서버에 접속한 상태를 하나의 단위로 봄
  • 서버 측에서 사용자 상태를 관리하는 방법
  • sessionId를 통해 각 세션을 구분하며, 이는 보통 쿠키로 관리됨

  • 이론상으로는 메모리의 제한이 없이 생성이 가능함

 

목적

  • 사용자 인증 상태 유지(로그인 상태 유지 등)



특징

  • 서버 측에 저장됨

  • 유효기간을 설정할 수 있으며, 만료되면 데이터 사라짐.

  • 서버의 메모리를 사용하므로, 사용자가 늘어나면 부담이 될 수 있음

  • 쿠키에 비해 보안성이 좋음

 

 

 

 

요청과 응답 과정

 

1. 사용자가 웹사이트에 방문

 

2. 서버에 세션 생성, 쿠키 생성하여 클라이언트에 응답 시 돌려줌

 

3. 클라이언트에 쿠키가 저장되며 세션ID가 함께 저장

 

4. 다음 요청 시 가지고 있던 쿠키를 함께 전달

 

5. 세션을 통한 상태 유지 (장바구니, 로그인 상태 등을 계속 유지함)

JSP 특징, 장단점, Scriptlet 등

친환경 개발자
|2024. 10. 8. 23:52

JSP란?

  • 서버 측에서 동적으로 HTML, XML등의 문서를 생성하기 위해 사용하는 자바 기반 기술


  • HTML 문서에서 Java코드를 작성할 수 있도록 혼합


  • JSTL등의 태그 라이브러리가 지원되어 편의성 향상


  • 작성된 후 서블릿으로 변환된 후 실행

 

 

 

 

 

 

JSP 구성 요소

  • 지시자 
    페이지 설정 관련 정보 지정을 위해 사용


  • 스크립트 요소(스크립트릿, 표현식, 선언부)
    문서 내용을 동적으로 생성하기 위해 사용


  • 기본객체
    요청과 응답을 읽고 처리하기 위해 기본으로 생성되어 있음

  • 표현언어(Expression Language)
    JSP를 더 간편하게 작성하기 위해 사용

 

 

 

 

 

 

 

JSP 태그

  • 스크립트릿 : 자바 코드를 작성하는 구간 (메서드 영역) 
    <% %>

  • 선언: 변수와 메서드를 선언하는 구간(클래스 영역)
    <%! %>

  • 표현식: 특정 값이나 결과물을 문자열로 출력하는 태그
    <%= %>

  • 주석 : 주석 작성 구간
    <%--   --%>

  • 지시자 : JSP 페이지에 대한 설정들을 지정할 수 있는 구간
    <%@ %>

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>제목</title>
</head>
<body>
	HTML 본문 구간
	<br>
	<%!
    // 변수, 메서드 선언 !!
	int a = 1;

	static void method(int a) {
		a = a + 1;
	}
	%>

	<%
	System.out.println("자바 코드 작성 가능한 영역");
	%>

	<%="문자열 출력 가능"%>

	<%-- 주석 작성 구간 --%>


</body>
</html>

 

 

 

 

 

 

지시자

 

  • page 지시자: JSP 페이지에 대한 전반적 속성을 정의한다
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page import="java.util.*" %>
  • include 지시자: 다른 JSP페이지나 파일을 현재 JSP 페이지에 삽입할 때 사용
<%@ include file="header.jsp" %>
  • taglib 지시자: JSTL등과 같은 태그 라이브러리 사용시 선언해야 함
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

Servlet?

  • Server + Applet의 합성어

  • Servlet은 웹 애플리케이션 개발에서 동적 웹페이지 생성

  • 클라이언트의 요청을 해석하고 응답을 생성하는 서버측 요소

  • 자바 코드 안에 HTML을 포함

  • 주로 HTTP 프로토콜을 사용해 클라이언트와 상호작용

  • Apache Tomcat과 같은 웹서버에서 실행된다!

 

 

 

 

 

 

Servlet 생명주기

0. Servlet 인스턴스가 없을 경우, Servlet 클래스를 불러오고, 웹컨테이너에 의해 Servlet 인스턴스를 초기화, 생성함.

 

 

1. init()

Servlet이 처음 로드될 때 한 번만 호출된다. 따라서 여러 변수나 조건들을 초기화하는 기능을 한다.

 

 

2. service()

클라이언트의 요청을 처리할 때마다 호출되며, 요청 메서드(GET, POST 등)에 따라 적절한 처리를 담당.

doGet(), doPost, doRegist() 등의 메소드가 호출된다!

 

 

3. destroy()

서버 종료되거나 Servlet이 더 이상 사용되지 않을 때 호출되고, 리소스 정리 등 종료 작업을 처리

 

 

 

 

 

 

GET vs POST 요청의 차이

항목 GET POST
용도 서버에서 데이터를 가져올 때 사용 서버에 데이터를 보낼 때 사용
파라미터 전송 방식 URL에 쿼리스트링 형태로 전송 요청 body에 포함하여 전송
보안 데이터가 URL에 노출됨 본문에 데이터가 포함되어 비교적 안전
데이터 크기 제한적 (대략 2048자 내외) 대용량 데이터 전송 가능
캐싱 브라우저에 의해 캐싱될 수 있음 일반적으로 캐싱되지 않음

 

 

 

요청과 응답 처리

 

요청(HttpServletRequest 객체)

  • 클라이언트로부터 전달된 요청 정보를 읽어들이는 역할.
  • 요청 파라미터, 헤더, 쿠키 등의 정보를 받을 수 있다.

 

응답(HttpServletResponse 개체)

  • 요청을 처리한 후 클라이언트에 전송할 응답 정보를 작성하는 데 사용.
  • HTML, JSON, 상태 코드, 헤더, 쿠키 등을 포함할 수 있다.
// GET 요청 처리
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String parameter = request.getParameter("name");
    response.setContentType("text/html");
    PrintWriter out = response.getWriter();
    out.println("<h1>안녕하세요 " + parameter + "</h1>");
}

// POST 요청 처리
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String parameter = request.getParameter("name");
    response.setContentType("text/html");
    PrintWriter out = response.getWriter();
    out.println("<h1>안녕하세요 " + parameter + " (POST)</h1>");
}

 

 

 

 

 

- 포워드 방식

  • 서버 내부에서 요청을 처리한 후 알아서 새로운 웹페이지로 전달
  • 따라서 URL의 변화가 없고, 클라이언트는 이를 인지할 수 없음.
response.sendRedirect("newPage.jsp");

 

 

-리다이렉트 방식

  • 요청을 받은 후 처리를 완료하고 클라이언트에게 또다시 해당 URL로 요청하도록 응답을 보냄
  • 클라이언트는 해당 응답을 받고 그에 맞는 URL을 재요청함.
  • 따라서 URL의 변화가 있다.
RequestDispatcher dispatcher = request.getRequestDispatcher("nextServlet");
dispatcher.forward(request, response);