https://www.acmicpc.net/problem/1021

문제

지민이는 N개의 원소를 포함하고 있는 양방향 순환 큐를 가지고 있다. 지민이는 이 큐에서 몇 개의 원소를 뽑아내려고 한다.

지민이는 이 큐에서 다음과 같은 3가지 연산을 수행할 수 있다.

  1. 첫 번째 원소를 뽑아낸다. 이 연산을 수행하면, 원래 큐의 원소가 a1, ..., ak이었던 것이 a2, ..., ak와 같이 된다.
  2. 왼쪽으로 한 칸 이동시킨다. 이 연산을 수행하면, a1, ..., ak가 a2, ..., ak, a1이 된다.
  3. 오른쪽으로 한 칸 이동시킨다. 이 연산을 수행하면, a1, ..., ak가 ak, a1, ..., ak-1이 된다.

큐에 처음에 포함되어 있던 수 N이 주어진다. 그리고 지민이가 뽑아내려고 하는 원소의 위치가 주어진다. (이 위치는 가장 처음 큐에서의 위치이다.) 이때, 그 원소를 주어진 순서대로 뽑아내는데 드는 2번, 3번 연산의 최솟값을 출력하는 프로그램을 작성하시오.

입력

첫째 줄에 큐의 크기 N과 뽑아내려고 하는 수의 개수 M이 주어진다. N은 50보다 작거나 같은 자연수이고, M은 N보다 작거나 같은 자연수이다. 둘째 줄에는 지민이가 뽑아내려고 하는 수의 위치가 순서대로 주어진다. 위치는 1보다 크거나 같고, N보다 작거나 같은 자연수이다.

출력

첫째 줄에 문제의 정답을 출력한다.


단순하게 접근했다.

양방향 데이터 접근이 가능한 deque를 사용하여

앞 / 뒤 쪽 더 가까운 곳을 확인 후 카운팅하면서 빼내었다.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.StringTokenizer;

// BOJ_1021번
// 회전하는 큐
public class Main {

	static int N, M;

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

		N = Integer.parseInt(st.nextToken());
		M = Integer.parseInt(st.nextToken());
		int target;
		int idx=1;
		int cnt =0;
		Deque<Integer> dq = new ArrayDeque<Integer>();

		for (int i = 0; i < N; i++)
			dq.add(i + 1);

		st = new StringTokenizer(br.readLine());

		for (int i = 0; i < M; i++) {
			target = Integer.parseInt(st.nextToken());
			idx = 1;
			Iterator<Integer> it = dq.iterator();
			while (it.hasNext()) {
				if (it.next() == target)
					break;
				idx++;
			}
			
			int front = idx - 1;
			int back =  dq.size() +1 -idx;
			
			if(front < back)
			{
				for(int j =0; j<front; j++) {
					dq.addLast(dq.pollFirst());
					cnt++;
				}
				dq.pollFirst();
			}
			else 
			{
				for(int j=0; j<back; j++) {
					dq.addFirst(dq.pollLast());
					cnt++;
				}
				dq.pollFirst();
			}
		}
		System.out.println(cnt);
	}
}

 

문제

차세대 영농인 한나는 강원도 고랭지에서 유기농 배추를 재배하기로 하였다. 농약을 쓰지 않고 배추를 재배하려면 배추를 해충으로부터 보호하는 것이 중요하기 때문에, 한나는 해충 방지에 효과적인 배추흰지렁이를 구입하기로 결심한다. 이 지렁이는 배추근처에 서식하며 해충을 잡아 먹음으로써 배추를 보호한다. 특히, 어떤 배추에 배추흰지렁이가 한 마리라도 살고 있으면 이 지렁이는 인접한 다른 배추로 이동할 수 있어, 그 배추들 역시 해충으로부터 보호받을 수 있다.

(한 배추의 상하좌우 네 방향에 다른 배추가 위치한 경우에 서로 인접해있다고 간주한다)

한나가 배추를 재배하는 땅은 고르지 못해서 배추를 군데군데 심어놓았다. 배추들이 모여있는 곳에는 배추흰지렁이가 한 마리만 있으면 되므로 서로 인접해있는 배추들이 몇 군데에 퍼져있는지 조사하면 총 몇 마리의 지렁이가 필요한지 알 수 있다.

예를 들어 배추밭이 아래와 같이 구성되어 있으면 최소 5마리의 배추흰지렁이가 필요하다.

(0은 배추가 심어져 있지 않은 땅이고, 1은 배추가 심어져 있는 땅을 나타낸다.)

입력

입력의 첫 줄에는 테스트 케이스의 개수 T가 주어진다. 그 다음 줄부터 각각의 테스트 케이스에 대해 첫째 줄에는 배추를 심은 배추밭의 가로길이 M(1 ≤ M ≤ 50)과 세로길이 N(1 ≤ N ≤ 50), 그리고 배추가 심어져 있는 위치의 개수 K(1 ≤ K ≤ 2500)이 주어진다. 그 다음 K줄에는 배추의 위치 X(0 ≤ X ≤ M-1), Y(0 ≤ Y ≤ N-1)가 주어진다.

출력

각 테스트 케이스에 대해 필요한 최소의 배추흰지렁이 마리 수를 출력한다.

 


전형적인 BFS / DFS 방법으로 풀 수 있는 문제다.

선언된 2차원 배열에서 각 자리에서 DFS가 수행 될 때, 몇 번의 전체 카운트가 되는지만 생각해주면 될 것 이다.

2차원 배열 각자리에서 하나씩 방문여부를 체크하면서 돌렸는데

더 효율적인 방법도 있을 것 같다..

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

// BOJ_1025 유기농 배추

// DFS 몇번의 뭉탱이로 돌아가는지 카운트
public class Main {

	static boolean map[][];
	static boolean visited[][];
	static int dx[] = { 1, 0, -1, 0 };
	static int dy[] = { 0, 1, 0, -1 };
	static int M, N, K, cnt;

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

		int T = Integer.parseInt(st.nextToken());

		for (int t = 0; t < T; t++) {
			st = new StringTokenizer(br.readLine());
			M = Integer.parseInt(st.nextToken()); // 가로길이
			N = Integer.parseInt(st.nextToken()); // 세로길이
			K = Integer.parseInt(st.nextToken()); // 배추 심어져 있는 위치 개수

			map = new boolean[N + 1][M + 1];
			visited = new boolean[N + 1][M + 1];

			for (int k = 0; k < K; k++) {
				st = new StringTokenizer(br.readLine());
				int X = Integer.parseInt(st.nextToken());
				int Y = Integer.parseInt(st.nextToken());

				map[Y][X] = true;
			}
			cnt = 0;

			for (int i = 0; i < N; i++) {
				for (int j = 0; j < M; j++) {
					if (map[i][j] && !visited[i][j]) {
						cnt++;
						visited[i][j] = true;
						DFS(i, j);
					}
				}
			}
			System.out.println(cnt);
		}
	}

	public static void DFS(int y, int x) {
		for (int i = 0; i < 4; i++) {
			int nx = x + dx[i];
			int ny = y + dy[i];

			if (ny < 0 || N < ny || nx < 0 || M < nx)
				continue;

			if (!visited[ny][nx] && map[ny][nx]) {
				visited[ny][nx] = true;
				DFS(ny, nx);
			}
		}
	}
}

문제

우현이는 어린 시절, 지구 외의 다른 행성에서도 인류들이 살아갈 수 있는 미래가 오리라 믿었다. 그리고 그가 지구라는 세상에 발을 내려 놓은 지 23년이 지난 지금, 세계 최연소 ASNA 우주 비행사가 되어 새로운 세계에 발을 내려 놓는 영광의 순간을 기다리고 있다.

그가 탑승하게 될 우주선은 Alpha Centauri라는 새로운 인류의 보금자리를 개척하기 위한 대규모 생활 유지 시스템을 탑재하고 있기 때문에, 그 크기와 질량이 엄청난 이유로 최신기술력을 총 동원하여 개발한 공간이동 장치를 탑재하였다. 하지만 이 공간이동 장치는 이동 거리를 급격하게 늘릴 경우 기계에 심각한 결함이 발생하는 단점이 있어서, 이전 작동시기에 k광년을 이동하였을 때는 k-1 , k 혹은 k+1 광년만을 다시 이동할 수 있다. 예를 들어, 이 장치를 처음 작동시킬 경우 -1 , 0 , 1 광년을 이론상 이동할 수 있으나 사실상 음수 혹은 0 거리만큼의 이동은 의미가 없으므로 1 광년을 이동할 수 있으며, 그 다음에는 0 , 1 , 2 광년을 이동할 수 있는 것이다. ( 여기서 다시 2광년을 이동한다면 다음 시기엔 1, 2, 3 광년을 이동할 수 있다. )

김우현은 공간이동 장치 작동시의 에너지 소모가 크다는 점을 잘 알고 있기 때문에 x지점에서 y지점을 향해 최소한의 작동 횟수로 이동하려 한다. 하지만 y지점에 도착해서도 공간 이동장치의 안전성을 위하여 y지점에 도착하기 바로 직전의 이동거리는 반드시 1광년으로 하려 한다.

김우현을 위해 x지점부터 정확히 y지점으로 이동하는데 필요한 공간 이동 장치 작동 횟수의 최솟값을 구하는 프로그램을 작성하라.

입력

입력의 첫 줄에는 테스트케이스의 개수 T가 주어진다. 각각의 테스트 케이스에 대해 현재 위치 x 와 목표 위치 y 가 정수로 주어지며, x는 항상 y보다 작은 값을 갖는다. ( 0 ≤ x < y < 231)

출력

각 테스트 케이스에 대해 x지점으로부터 y지점까지 정확히 도달하는데 필요한 최소한의 공간이동 장치 작동 회수를 출력한다.


 

#include <fstream>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
 
int bruteForce(ll remaining, int speed)
{
    int ret = 0;
    while (remaining > 0)
    {
        if (remaining > speed * 2)
        {
            ret += 2;
            remaining -= speed * 2;
            speed++;
        }
        else
        {
            int quotient = remaining / speed;
            remaining -= quotient * speed;
            ret += quotient;
            speed--;
        }
    }
    return ret;
}
 
int main()
{
    int cases;
    cin >> cases;
    for (int i = 0; i < cases; i++)
    {
        ll x, y;
        cin >> x >> y;
 
        cout << bruteForce(y - x, 1) << endl;
    }
 
    return 0;
}

문제

두 정수 A와 B를 입력받은 다음, A/B를 출력하는 프로그램을 작성하시오.

입력

첫째 줄에 A와 B가 주어진다. (0 < A, B < 10)

출력

첫째 줄에 A/B를 출력한다. 절대/상대 오차는 10-9 까지 허용한다.


단순하게 나누어서 a / b 로 출력을 하면 틀렸다는 결과를 얻게 된다.

자리 값을 생각하지 않아서 나온 결과이다. 

C++에서는 아래의 코드가 필요하다.

cout << fixed; // 소수점을 고정시키겠다.

cout.precision(9); // 9자리까지 표현할 것이다 ( 10번째자리에서 반올림)

 

반대로 해제하려면 아래의 코드가 필요하다.

cout.unsetf(ios::fixed); // 소수점 고정을 해제하겠다.

 

#include <iostream>


using namespace std;

int main()
{
	double a, b;

	cin >> a >> b;

	cout << fixed;

	cout.precision(9);

	cout << a / b << endl;
}

문제

두 정수 A와 B를 입력받은 다음, A-B를 출력하는 프로그램을 작성하시오.

입력

첫째 줄에 A와 B가 주어진다. (0 < A, B < 10)

출력

첫째 줄에 A-B를 출력한다.


백준 1000번 문제와 마찬가지로

튜토리얼 같은 문제! 백준을 처음 입문하면서

입출력 방식을 테스팅 해보기 좋은 예제라고 생각한다.

#include <stdio.h>
int main()


{
	int a, b;

	scanf("%d %d",&a, &b);
	
	if (0<a<10 && 0<b<10){
		printf("%d", a - b);
	}
	
}

문제

두 정수 A와 B를 입력받은 다음, A+B를 출력하는 프로그램을 작성하시오.

입력

첫째 줄에 A와 B가 주어진다. (0 < A, B < 10)

출력

첫째 줄에 A+B를 출력한다.

 


백준 알고리즘의 입문을 위한 문제라고 생각한다.

튜토리얼 같은 문제이므로 코드 설명은 패스!

#include <stdio.h>
int main()


{
	int a, b;

	scanf("%d %d",&a, &b);
	
	if (0<a<10 && 0<b<10){
		printf("%d", a + b);
	}
	
}

문제

어떤 동물원에 가로로 두칸 세로로 N칸인 아래와 같은 우리가 있다.

이 동물원에는 사자들이 살고 있는데 사자들을 우리에 가둘 때, 가로로도 세로로도 붙어 있게 배치할 수는 없다. 이 동물원 조련사는 사자들의 배치 문제 때문에 골머리를 앓고 있다.

동물원 조련사의 머리가 아프지 않도록 우리가 2*N 배열에 사자를 배치하는 경우의 수가 몇 가지인지를 알아내는 프로그램을 작성해 주도록 하자. 사자를 한 마리도 배치하지 않는 경우도 하나의 경우의 수로 친다고 가정한다.

입력

첫째 줄에 우리의 크기 N(1≤N≤100,000)이 주어진다.

출력

첫째 줄에 사자를 배치하는 경우의 수를 9901로 나눈 나머지를 출력하여라.


경우의 수를 3가지로 나누어 생각해볼 수 있다.

1) n번째 줄에 사자가 왼쪽에 있는경우 

n-1번째 줄의 오른쪽 칸에 사자가 있거나 + 양쪽 칸에 사자가 모두 없는 경우

DP[i][1] = (DP[i - 1][0] + DP[i - 1][2])

 

2) n번째 줄에 사자가 오른쪽에 있는경우 

n-1번째 줄의 왼쪽 칸에 사자가 있거나 + 양쪽 칸에 사자가 모두 없는 경우

DP[i][2] = (DP[i - 1][0] + DP[i - 1][1])

 

3) n번째 줄에 사자가 없는 경우 

n-1번째 줄에 왼쪽 오른쪽 모두 사자가 없는 경우 + 왼쪽에 있는 경우 + 오른쪽에 있는 경우

D[DP[i][0] = (DP[i - 1][0] + DP[i - 1][1] + DP[i - 1][2])

 

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

// BOJ_1309
// 동물원
// DP
public class Main {

	static int N;
	static int DP[][];

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

		N = Integer.parseInt(st.nextToken());

		int[][] DP = new int[N + 1][3];

		DP[1][0] = 1;
		DP[1][1] = 1;
		DP[1][2] = 1;

		for (int i = 2; i <= N; i++) {
			DP[i][0] = (DP[i - 1][0] + DP[i - 1][1] + DP[i - 1][2]) % 9901;
			DP[i][1] = (DP[i - 1][0] + DP[i - 1][2]) % 9901;
			DP[i][2] = (DP[i - 1][0] + DP[i - 1][1]) % 9901;
		}
		System.out.println((DP[N][0] + DP[N][1] + DP[N][2]) % 9901);
	}
}

| final, finally, finalize 차이점

 

1. final

 

final은 사용되는 문맥에 따라 다르다.

변수에 적용하면 : 해당 변수의 값은 변경이 불가능해진다. 상수가 되기 때문

변수의 참조에 적용하면 : 참조 변수가 힙 내의 다른 객체를 가리키도록 변경할 수 없다.

 

메서드에 적용하면 : 해당 메서드를 상속받는 하위 클래스에서 오버라이딩 할 수 없다.(상속받는 하위 클래스에서도 변경이 되지 않아야 하는 메서드의 경우 final을 붙이면 된다.)

 

클래스에 적용하면 : 해당 클래스를 다른 클래스가 상속받을 수 없다. 즉, final 클래스의 하위 클래스를 정의할 수 없다. ->이건 언제 쓰는 걸까??

 

 

 

2. finally

 

finally는 try-catch 블록 뒤에 둘 수 있는 선택적인 블록인데 try-catch문이 끝나기전에 항상 꼭 실행되어야하는 로직이 있을 경우 finally절에 두면된다.

try-catch블록과 함께 사용되며 예외가 던져지더라도 항상 실행될 코드를 지정하기 위해 사용된다. finally블록은 try와 catch블록이 전부!!! 실행된 후, 그리고 제어 흐름이 원래 지점으로 돌아가기 전에 실행된다.(코드 확인할 것)

 즉, 보통 뒷 마무리 코드를 작성하는데 사용된다.

(DB연결을 하거나 I/O 스트림을 열었을때 다 사용하거나면 닫아주는 작업 등)

public class TryEx {

    public static String lem(){
        System.out.println("lem");
        return "return from lem";
    }// lem()
     
    public static String foo(){
        int x = 0; 
        int y = 5;
         
        try{
            System.out.println("start try");
            int b = y/x;
            System.out.println("end try");
            return "returned from try";
        }catch(Exception e) {
            System.out.println("catch");
            // catch문 실행 된 후(return문 까지 실행 된 후) finally문 실행된 후 마지막 catch문의 return 값이 반환된다.
            return lem() + " | returned from catch";
        }finally{
            System.out.println("finally");
        }
    }// foo()
     
    public static void bar(){
        System.out.println("start bar");
        String v = foo();
         
        System.out.println(v);
        System.out.println("end bar");

    }// bar()
     
    public static void main(String[] args){
        bar();
    }// main()
     
}// class
/*
 *      try 블럭을 실행하다가 예외를 만나면 catch 블럭으로 간 후, catch블럭이 실행된다. 이때 catch블럭의 return문까지 모두 다 실행 된 후, 
 *      finally블럭이 실행 된 다음에서야 return이 이루어지는 것을 확인할 수 있다.
 *      즉, finally 블럭은 try 블럭이 종료되는 순간 실행된다. 만약 try블럭 안에 return문을 넣더라도 finally블록은 try 블록이 종료되는
 *      순간 실행된다. try블럭에서 return | continue | break 문을 실행하거나 예외를 던진다거나해도 실행되는 것이다.
 * 
 *      단,  try/catch 블록 수행 중에 가상머신이 종료되거나
 *          try/catch를 수행하고 있던 쓰레드가 죽어버리면 finally블럭이 실행되지 않는다.
 * */

 

 

 
<결과>
start bar
start try
catch
lem
finally
return from lem | returned from catch
end bar
 
=>    즉, 코드 수행 순서가
  try문 실행-> 예외 발생 -> catch문 실행( return문에 있는 코드 까지 모두 실행) -> finally문 실행 -> return 수행
 
 
 

Q) Java의 finally 블록은 try-catch-finally의 try블록 안에 return 문을 넣어도 실행되나?

A) try 블럭을 실행하다가 예외를 만나면 catch 블럭으로 간 후, catch블럭이 실행된다. 이때 catch블럭의 return문까지 모두 다 실행 된 후, finally블럭이 실행 된 다음에서야 return이 이루어지는 것을 확인할 수 있다.

즉, finally 블럭은 try 블럭이 종료되는 순간 실행된다. 만약 try블럭 안에 return문을 넣더라도 finally블록은 try 블록이 종료되는 순간 실행된다. try블럭에서 return | continue | break 문을 실행하거나 예외를 던진다거나해도 실행되는 것이다.

 

단, try/catch 블록 수행 중에 가상머신이 종료되거나

 

  try/catch를 수행하고 있던 쓰레드가 죽어버리면 finally블럭이 실행되지 않는다.

 

 
 
3. finalize()
 
finalize() 메서드는 java garbage collector가 더 이상의 참조가 존재하지 않는 객체를 발견한 순간 호출하는 메서드다. 즉 더이상 사용되지 않는 객체가 있을때 메모리 낭비를 막기 위해서 garbage collector가 이 객체를 없애버리는데 이때 해당객체의 finallize 메서드를 호출해서 없앤다. 이 finallize()는 java.lang.Object클래스로 부터 상속받아 모든 클래스의 객체가 가지고 있는 메서드다. 즉, 만약 객체가 삭제되기 직전에 실행되어야 하는 로직같은게 있으면 Object 클래스에 정의된 finalize() 메서드를 오버라이딩하여 정의할 수 있다. 
 
하지만!! finalize()는 언제 호출될지 알 수 없기 때문에, finalize()를 오버라이딩 했다고해서 여기에 의존하는 방식의 코딩은 좋은 방법은 아닐 수 있다.

 

BufferedReader, BufferedWriter를 활용한 빠른 입출력

 

BufferedReader/BufferedWriter는 Buffer에 있는 IO 클래스입니다. 입력된 데이터가 바로 전달되지 않고 중간에 버퍼링이 된 후에 전달되됩니다. 출력도 마찬가지로 버퍼를 거쳐서 간접적으로 출력장치로 전달되기에 시스템의 데이터처리 효율성을 높여주며 버퍼스트림을InputStreamReader / OutputStreamWriter를 같이 사용하여 버퍼링을 하게 되면 입출력 스트림으로부터 미리 버퍼에 데이터를 갖다 놓기 때문에 보다 효율적인 입출력이 가능합니다.

▶BufferedReader 

Java를 처음 접하시는 분들이 주로 받는 입력방식은 Scanner입니다. Scanner를 통해 입력을 받을경우 Space Enter를 모두 경계로 인식하기에 입력받은 데이터를 가공하기 매우 편리합니다. 하지만 그에비해 BufferedReader는 Enter만 경계로 인식하고 받은 데이터가 String으로 고정되기때문에 입력받은 데이터를 가공하는 작업이 필요할경우가 많습니다. Scanner에 비해 다소 사용하기 불편하죠. 하지만 많은 양의 데이터를 입력받을경우 BufferedReader를 통해 입력받는 것이 효율면에서 훨씬 낫습니다. 입력시 Buffer 메모리줌으로써 작업속도 차이가 많이납니다.

 

BufferedReader 사용법

BufferedReader bf = new BufferedReader(new InputStreamReader(System.in)); //선언 
String s = bf.readLine(); //
String int i = Integer.parseInt(bf.readLine()); //Int

 선언은 위에 있는 예제와 같이 하시면 됩니다. 입력은 readLine();이라는 메서드를 활용하시면 되는데요. 여기서 주의할점이 두가지가 있습니다. 첫번째는 readLine()시 리턴값을 String으로 고정되기에 String이 아닌 다른타입으로 입력을 받을려면 형변환을 꼭 해주어야한다는 점입니다. 두번째는 예외처리를 꼭 해주어야한다는 점입니다. readLine을 할때마다 try & catch를 활용하여 예외처리를 해주어도 되지만 대개 throws IOException을 통하여 작업합니다.

 

Read한 데이터 가공

StringTokenizer st = new StringTokenizer(s); //StringTokenizer인자값에 입력 문자열 넣음 
int a = Integer.parseInt(st.nextToken()); //첫번째 호출 
int b = Integer.parseInt(st.nextToken()); //두번째 호출  
String array[] = s.split(" "); //공백마다 데이터 끊어서 배열에 넣음

 Read한 데이터는 Line단위로만 나눠지기에 공백단위로 데이터를 가공하려면 따로 작업을 해주어야하는데요. 위의 두가지 방법이 대표적입니다. 첫번째 방법으로는 StringTokenizer에 nextToken()함수를 쓰면 readLine()을 통해 입력받은 값을 공백단위로 구분하여 순서대로 호출할 수 있습니다. 두번째방법으로는 String.split()함수를 활용하여 배열에 공백단위로 끊어서 데이터를 넣고 사용하는 방식입니다.

 

▶BufferedWriter 

일반적으로 출력을할때 System.out.println(""); 방식을 사용하고는 합니다. 적은양의 출력일 경우 성능차이가 미미하겠지만 많은 양의 출력에서는 입력과 마찬가지로 Buffer를 활용해주시는것이 좋습니다.

 

BufferedWriter 사용법

BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));//선언 String s = "abcdefg";//출력할 문자열 bw.write(s+"\n");//출력 bw.flush();//남아있는 데이터를 모두 출력시킴 bw.close();//스트림을 닫음

BufferedWriter 의 경우 버퍼를 잡아 놓았기 때문에 반드시 flush() / close() 를 반드시 호출해 주어 뒤처리를 해주어야합니다. 그리고 bw.write에는 System.out.println();과 같이 자동개행기능이 없기때문에 개행을 해주어야할 경우에는 \n를 통해 따로 처리해주어야합니다.

 

주요 Method

메서드명   기능
 BufferedReader(Reader rd)  rd에 연결되는 문자입력 버퍼스트림 생성
 BufferedWriter(Writer wt)   wt에 연결되는 문자출력 버퍼스트림 생성​
 int read()  스트림으로부터 한 문자를 읽어서 int 형으로 리턴
 int read(char[] buf)  문자배열 buf의 크기만큼 문자를 읽어들임.  읽어들인 문자 수를 리턴
 int read(char[] buf, int offset, int length)  buf의 offset위치에서부터 length 길이만큼 문자를 스트림으로부터 읽어들임​
 String readLine()  스트림으로부터 한 줄을 읽어 문자열로 리턴​​
 void mark()   현재우치를 마킹, 차 후 reset() 을 이용하여 마킹위치부터 시작함
 void reset()   마킹이 있으면 그 위치에서부터 다시시각, 그렇지 않으면 처음부터 다시시작
 long skip(int n)  n 개의 문자를 건너 뜀
 void close()  스트림 닫음
 void write(int c)  int 형으로 문자 데이터를 출력문자스트림으로 출력
 void write(String s, int offset, int length)  문자열 s를 offset 위치부터 length 길이만큼을 출력스트림으로 출력
 void write(char[] buf, int offset, int length)  문자배열 buf의 offset 위치부터 length 길이만큼을 출력스트림으로 출력​​​
 void newLine()  줄바꿈 문자열 출력
 void flush()   남아있는 데이터를 모두 출력시킴.
 

 

쿠키와 세션을 이용한 자동 로그인 방식에 대해서 정리해 보겠습니다.


[    1. 쿠키와 세션이란?    ]


: 쿠키와 세션은 매우 유사하면서도 다른 특징을 지니고 있는데요.

- 공통점 : 사용자의 정보(데이터)를 저장할 때 이용된다.

- 차이점 : 

- 쿠키 : 1) 사용자의 로컬에 저장되었다가 브라우저가 요청시 왔다갔다하게 됨(보안에 취약)

     2) 세션과 달리 여러 서버로 전송이 가능함

     3) 세션이 브라우저 단위로 생성되어 브라우저 종료시 사라지는데 반해, 쿠키는 유효시간 설정을 할 수 있음. ex) 7일

- 세션 : 1) 서버에 데이터를 저장하여 쿠키에 비해 보안에 안전함

     2) 브라우저 단위로 생성됨 => 익스플로러를 켜고 크롬을 켜고 하면 각각 2개의 세션이 생성되는 것


[    2. why 쿠키와 세션을 이용한 로그인 처리를 하게 될까?    ]


: 세션은 위에서 설명한대로 기본 단위가 "웹 브라우저"입니다. 따라서, 웹 브라우저 종료시 소멸하게 되죠...

  그에 반해 쿠키는 사용자 PC에 저장되기 때문에 서버 요청시 전달되는 동안 네트워크 상에서 보안상 취약할 수는 있지만 유효시간을

  길게 설정할 수 있어 브라우저가 종료되는 것과 별개로 7일 30일 등 기간을 길게 설정할 수 있습니다.

  하지만, 

  그렇다고 쿠키에 로그인할 사용자의 정보를 담고 있는다면 정말 정말 너무 너무 보안상 취약할 것을 알 수 있겠죠?

  따라서, 자동 로그인을 구현할 때에는 "< 세션과 쿠키를 동시에 사용하는 것 >"이 바람직하다고 생각합니다.


[    3. 세션과 쿠키를 이용한 자동 로그인 구현에 대한 개요    ]


: 사용자가 로그인 폼에서 로그인을 할 당시, 자동로그인을 설정하겠다는 CheckBox를 클릭할 경우 사용자의 정보를 저장시키고 유효

기간을 설정한다는 것 까지는 알겠는데 그럼 도대체 어떤 사용자의 정보를 저장시켜 놓아야할까요?


먼저, 사용자가 로그인에 성공한 경우! -> 세션에 사용자 객체(UserVO)를 저장시켰었는데 앞에서 이 객체를 쿠키에 저장시킨다면, 굉장히 보안상 취약합니다. 비밀번호, 아이디 그 외 정보까지 UserVO에 들어 있었죠...

따라서, 로그인에 성공했을 때 사용자 DB 테이블에 sessionId와 유효시간 속성에 값을 지정하는 겁니다. 그리고 쿠키에는 세션Id를

넣어 놓는거죠... 그리고 "인터셉터"에서 해당 쿠키값이 존재하면 사용자 DB 테이블 내에서 유효시간 > now() 즉, 유효시간이 아직 

남아 있으면서 해당 세션 Id를 가지고 있는 사용자 정보를 검색해 해당 사용자 객체를 반환하는 겁니다.


당연히, 쿠키가 유효시간이 다되면 해당 자동완성 기능은 동작하지 않게 되고 다시 쿠키를 사용하겠다는 선택을 했을 때 동작하게 되겠죠

그럼, 다음으로 코드상에서 직접 한번 알아 봅시다.





출처: https://rongscodinghistory.tistory.com/3 [악덕고용주의 개발 일기]

+ Recent posts