본문 바로가기

BackEnd/Java

[java] 입출력(BufferedReader, BufferedWriter, StringBuilder)

728x90
반응형

언젠가는 한 번 정리해보려 했던 것을 이제 글 한 번 올려보려고 합니다!

 

 

◇ 입출력 전달 방법

 

입출력에 대해서 가장 먼저 배우는 것은 Scanner를 이용해 입력하고 System.print 함수들로 출력합니다.

 

Scanner는 프로그램 → 하드 디스크로 바로 데이터를 전송하면서 입출력에 시간이 오래 걸립니다.

 

하지만 Buffer는 바로 하드 디스크로 전달하는 것이 아니라 중간에 메모리 버퍼로 전달합니다.

입력할 때 하나씩 입력해서 메모리 버퍼에 쌓이면서 출력할때는 한 번에 하드 디스크로 데이터를 전달합니다.

 

아래의 그림을 보시면 좀 더 이해하기 쉬울 겁니다.

 

입력

 

출력


Scanner는 위의 그림에서 메모리 버퍼가 없습니다.

하드디스크는 원래 속도가 느리기 때문에 데이터 입출력에 시간이 오래 걸립니다. 따라서 중간에서 걸러줍니다.

 

 

1. Buffer의 단점

 

여기서 Scanner는 띄어쓰기와 개행문자(new line character, 줄바꿈)를 경계로 해서 입력 값을 인식하므로 따로 가공할 필요가 없습니다. 즉, 원하는 타입으로 입력을 받을 수 있고 경계로 구분할 수 있다는 것입니다.

 

BufferedReader는 입력을 무조건 String으로 받기 때문에 타입 변환 등이 별도로 필요합니다.

 

Scanner는 지원하는 메소드가 많고 Buffer는 지원하는 메소드가 적습니다.

 

 

2. Buffer의 장점(속도)

 

가공하는 번거로움에도 불구하고 입출력의 속도 때문에 Buffer를 사용합니다.

 

간단한 예시를 하나 들겠습니다.

 

11382번: 꼬마 정민 (acmicpc.net)

 

11382번: 꼬마 정민

첫 번째 줄에 A, B, C (1 ≤ A, B, C ≤ 1012)이 공백을 사이에 두고 주어진다.

www.acmicpc.net

 

위의 예제는 3개의 숫자를 입력받고 합을 구하는 것입니다.

 

1. Scanner 사용 예제
import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		
		long a = sc.nextLong();
		long b = sc.nextLong();
		long c = sc.nextLong();

		System.out.println(sum(a,b,c));
	}
	
	public static long sum(long a, long b, long c) {
		return a+b+c;
	}
}​


2. BufferedReader 사용 예제

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

public class Main  {
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); // 선언
		StringTokenizer st = new StringTokenizer(br.readLine());
		long a = Long.parseLong(st.nextToken());
		long b = Long.parseLong(st.nextToken());
		long c = Long.parseLong(st.nextToken());
				
		System.out.println(a + b + c);
	}
}


buffer에서 readLine을 할때마다 try & catch를 활용하여 예외처리 또는 메인 함수에 throws IOException을 통하여 작업해야합니다. 그렇지 않으면 오류가 나타납니다.

 

위의 두 코드는 모두 세 수의 합을 출력할 수 있습니다.

 

NO 입력 시간
1 Scanner 204ms
2 BufferedReader 120ms

 

하지만 이렇게 위의 표를 보시면 속도 차이를 볼 수 있습니다.

 

입력 값이 적기 때문에 시간이 2배 정도 차이가 나는 것이고 만약 더 많은 입출력이 있다면 그때는 속도, 시간 차이가 더 확실하게 나타날 것입니다.

 

 

3. Buffer의 사용 방법

 

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

public class Main  {
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); // 선언
		long a = Long.parseLong(br.readLine());
		long b = Long.parseLong(br.readLine());
		long c = Long.parseLong(br.readLine());
				
		System.out.println(a + b + c);
	}
}

//	77 77 7777
//	Exception in thread "main" java.lang.NumberFormatException: For input string: "77 77 7777"
//		at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
//		at java.base/java.lang.Long.parseLong(Long.java:711)
//		at java.base/java.lang.Long.parseLong(Long.java:834)
//		at backjoon.Main_꼬마정민3.main(Main_꼬마정민3.java:17)


위의 코드처럼 br.readLine()로 여러번 입력을 받게되면 오류가 뜨게됩니다.

따라서 여러 수를 사용할 때는 StringTokenizer로 입력을 받은 후 가공을 하면 됩니다.

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

public class Main  {
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); // 선언
		StringTokenizer st = new StringTokenizer(br.readLine());
		long a = Long.parseLong(st.nextToken());
		long b = Long.parseLong(st.nextToken());
		long c = Long.parseLong(st.nextToken());
				
		System.out.println(a + b + c);
	}
}


위의 방법대로 StringTokenizer를 사용하여 변수 선언을 별도로 해주면 빈칸의 존재 여부에 따라서 자동으로 변수에 저장할 수 있습니다.

 

별도의 배열을 생성한 후 split을 사용해서 아래와 같이 변수로 지정할 수 있습니다.

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)); // 선언

		String s = br.readLine();
		String str[] = s.split(" ");
		
		long sum = 0;
		
		for(int i = 0; i<str.length; i++) {
			sum += Long.parseLong(str[i]); 
		}
		
		System.out.println(sum);
	}
}

 

◎ BufferedReader 클래스 메소드

NO Type 메소드 기능
1 void close() 입력 스트림을 닫고, 사용하던 자원을 해제
2 void mark(int, readAheadLimit) 스트림의 현재 위치를 마킹
3 int read() 한 글자만 읽어 정수형으로 반환
4 String readLine() 한 줄을 읽음
5 boolean ready() 입력 스트림이 사용할 준비가 되었는지 확인(1이면 준비 완료)

 

 

4. BufferedWriter 사용법

 

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

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));
		
		String s = br.readLine();
		String str[] = s.split(" ");
		
		String sum = "";
		
		for(int i = 0; i<str.length; i++) {
			sum += str[i]; 
			bw.write(sum);
			bw.newLine();
		}
		
		br.close();
		bw.flush();
		bw.close();
	}
}

//	입력 : a bc def
//	출력 : 
//	a
//	abc
//	abcdef


일반적으로 출력할 때는 System.out.println()을 사용하면 되지만 양이 많아질 경우에는 입력과 동일하게 Buffer를 사용해야합니다.

줄바꿈을 하기 위해서는 newLine() 또는 bw.write("\n")을 사용해야합니다.
또한, 사용하고 난 후 flush()를 통해 남아 있는 것들을 모두 출력하고 close()를 통해 buffer를 닫아줍니다.

 

 

◎ BufferedWriter 클래스 메소드

NO Type 메소드 기능
1 void close() 스트림을 닫음
2 void flush() 스트림을 비움, 스트림을 닫기 전 수행
3 void newLine() 한 줄을 띄움
4 void write(char[] buf, int offset, int length) 버퍼 offset 위치부터 length 크기만큼 write
5 void write(int c) 한 글자 쓰기
6 void write(String s, int offset, int length) 문자열 offset에서부터 length 길이만큼 write

 

 

5. StringBuilder

 

마지막으로 StringBuilder에 대해서 알아보겠습니다!

 

1. String
String은 불변의 속성을 가지므로 concat, + 연산 등을 통해 값이 변경되면 기존의 String 메모리에서 값이 바뀌는 것이 아니라, 기존의 String에 있던 값을 버리고 새로운 값을 재할당합니다.

문자열을 자주 읽어들이면 유리하지만 추가, 삭제, 수정 등의 연산 등이 자주 일어나는 경우에 사용하게 되면 힙 메모리에 많은 Garbage가 생성되고 힙 메모리가 부족하게 되는 현상이 발생할 수 있습니다.

2. StringBuffer/ StringBuilder
String의 문제를 해결하기 위해 사용하는 것이고 가변성을 가집니다.

.append(), .delete() 등을 사용해서 동일 객체 내에서 문자열을 변경하는 것이 가능합니다. 


출처 : javapapers.com

위의 사진을 보면 append를 사용한 것이 훨씬 빠른 것을 볼 수 있습니다.

 

 

◎ StringBuilder 사용 예제

public class Main  {
	public static void main(String[] args) {
		
		StringBuilder sb = new StringBuilder();

		sb.append("1");
		sb.append("a").append("\n");
		sb.append("2").append(" ").append("b");
		
		System.out.println(sb);
	}
}

//	출력 : 
//	1a
//	2 b

 

◎ StringBuilder 메소드

NO 메소드 기능
1 append(String s) StringBuilder 뒤에 값을 붙음
2 delete(int start, int end) 특정 인덱스부터 인덱스까지를 삭제
3 insert(int offset, any primitive of a char[]) 문자를 삽입함
4 replace(int start, int end, String s) 일부를 String 객체로 치환
5 reverse() 순서를 뒤집음
6 setCharAt(int index, char ch) 주어진 문자를 치환
7 indexOf(String s) 값이 어느 인덱스에 들어있는지 확인
8 subString(int start, int end) start와 end 사이의 값을 잘라옴

 

이렇게 Buffer를 통한 입출력에 대해서 알아보았습니다.

 

이제 입출력 문제를 풀때 Scanner와 Buffer 둘 다 이용해보는 방법을 사용하면서 공부를 해야겠어요!!

 

많은 분들의 피드백은 언제나 환영합니다!  많은 댓글 부탁드려요~~

 

728x90
반응형