(JAVA) BufferedWriter.write() 의 출력 타입

버퍼를 요 며칠간 써보고 있지만 아직 스캐너를 쓸 때에 비해서는 익숙치 않다.

그러던 중 백준의 알고리즘 문제를 풀면서 BufferedWriter.write() 함수를 사용할 때 마다 숫자가 아스키코드로 출력되어 문제가 되었다.

문제

예시를 한번 보자면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Main {

public static void main(String[] args) throws IOException {
BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

int x = Integer.parseInt(bf.readLine());

for (int i = x; i > 0; i--) {
bw.write(i);
bw.newLine();
}
bw.flush();
bw.close();
}
}

숫자를 입력하면 그 숫자부터 1까지 -1되며 출력되는 매우 간단한 코드이다.

그런데 실행하면 다음과 같이 출력된다.

분명 5를 입력했으니 5부터 1까지 차례로 출력되어야 하지만 글자가 깨져 출력되고있다.

이는 숫자 자체가 아닌 5에서 1까지 해당하는 아스키코드가 대응하여 출력되었기 때문이다.

아스키코드에서 ! 인 33을 입력하면 !와 32인 공백문자가 출력되는걸 볼 수 있다.

원인

원인은 bw.write(i) 부분에서 정수 타입인 i를 그대로 출력하려고 했기 때문이다.

해결

정수형을 그대로 출력하려고 했을때 문제가 발생했기에 형변환을 해주어야 한다.

따라서 정수 타입 변수를 버퍼를 통해 출력하고 싶으면 문자열로의 형변환이 필요하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Main {

public static void main(String[] args) throws IOException {
BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

int x = Integer.parseInt(bf.readLine());

for (int i = x; i > 0; i--) {
bw.write(String.valueOf(i));
bw.newLine();
}
bw.flush();
bw.close();
}
}

bw.write(i) 처럼 정수형으로 바로 출력하는 대신 bw.write(String.valueOf(i)) 처럼 문자열로 형변환하면 다음처럼 출력이된다.

- 편안 -

(JAVA) BufferedReader와 BufferedWriter를 통한 입출력

백준의 자바 알고리즘 문제를 풀어보던 중 버퍼를 활용한 입출력을 사용해야 하나는 문제가 있었다.

나는 자바에서 입출력이라고는 Scanner와 println()밖에 몰랐기에 버퍼를 통한 입출력은 사용해본적이 없었다.

찾아보니 버퍼 입출력이 스캐너보다 효율면에서 훨씬 낫다는것을 알게되었고, 그로인해 작업속도차이가 많이 난다는것도 알게되었다.

그래서! 이번 포스팅의 목표는 버퍼를 활용한 입출력에대해 알아보고 활용할 수 있을 정도로 공부하는게 목적이다.

Buffer


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

BufferedReader


1
2
3
BufferedReader bf = new BufferedReader(new InputStreamReader(System.in)); // 객체 생성
Stirng s = bf.readLine(); // 문자열의 경우
int i = Integer.parseInt(bf.readLine()); // 정수의 경우

객체 생성은 1라인에서 bf 라는 이름만 마음대로 바꾸면 된다.

입력은 문자열과 숫자가 받는 방식이 좀 다른데 readLine() 메소드의 결과가 문자열이기때문에 정수형 변수에 저장할 경우 Integer 로 형변환을 해주어야 한다.

주의할 점이 있다면 예외처리를 꼭 해주어야 한다는 점인데 보통은 throws IOException 을 통해 해결한다.

이렇게 하면 버퍼를 통해 읽어들인 문자열 or 숫자를 변수에 저장한 것이다.

StringTokenizer


읽어들인 데이터는 Line 단위로만 나뉘어지기 때문에 공백단위로 데이터를 가공하려면 따로 작업을 해줘야 한다.

1
2
3
4
5
6
String s = "123 456";
StringTokenizer st = new StringTokenizer(s); // 객체생성 및 s를 st의 인자로 전달
String a = st.nextToken(); // 공백으로 나뉘어진 s의 123이 문자열로 a에 저장
int b = Integer.parseInt(st.nextToken()); // s의 456이 숫자로 b에 저장

String array[] = s.split(" "); // 공백마다 데이터를 끊어 배열에 저장함

위 코드에서 보이듯 StringTokenizer에 nextToken()함수를 사용하면 readLine()을 통해 입력받은 값을 공백단위로 구분하여 순서대로 호출할 수 있다.

다음으로는 String.split()함수가 있는데 문자열을 공백단위로 끊어 배열에 저장하는 방식이다.

BufferdedWriter


일반적으로 문자를 출력할때에 System.out.println();을 사용한다.

크지않은 양의 출력일 경우 성능차이는 체감하지 못할 수 있지만 출력이 많아진다면 입력과 마찬가지로 버퍼를 활용하는것이 효율적이다.

1
2
3
4
5
6
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); // 객체생성
String s = "12345"; // 출력할 문자열
bw.write(s); // 버퍼에 "12345"가 담긴 s 문자열을 올린다.
bw.newLine(); // 줄바꿈
bw.flush(); // 버퍼에 있는 데이터를 모두 출력시킴
bw.close(); // 스트림을 닫음

BufferedWriter의 경우 버퍼를 잡아 놓았기 때문에 반드시 flush() / close() 를 반드시 호출하여 버퍼를 비우고 닫아주어야 한다.

또한, bw.write에는 System.out.println();과 같이 자동개행기능이 없기때문에 개행을 해주어야 할 경우에는 \n을 통해 따로 처리해주어야 한다.

사용 예제


백준에 있는 문제를 풀어보자.

백준 문제번호 15552번 (빠른 A+B)

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) throws IOException {
BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

int x = Integer.parseInt(bf.readLine());

for (int i = 0; i < x; i++) {
StringTokenizer st = new StringTokenizer(bf.readLine(), " ");
bw.write((Integer.parseInt(st.nextToken()) + Integer.parseInt(st.nextToken())+ "\n");
}
bw.flush();
bw.close();
}

라인 1 : throws IOException을 통해 버퍼 사용에 필요한 예외처리를 해줌

라인 2,3 : 버퍼 입출력 객체 생성

라인 5 : 정수형 변수에 반복횟수 지정을 위해 readLine()함수로 입력을 받고 정수형으로 형변환

라인 8 : st변수명으로 StringTokenizer 객체 생성과 동시에 입력을 공백으로 나누어 받음

라인 9 : 버퍼에 라인 8에서 입력된 나눠진 문자열을 첫번째와 두번째 것만 정수로 변환한 뒤 더하고 그 값을 버퍼에 올린다. 이후 \n으로 버퍼내부에서 개행

라인 11 : 반복문을 통해 버퍼에 작성된 데이터를 모두 출력한다.

라인 12 : 스트림을 닫는다.


스캐너보다는 다소 복잡하다. 그래도 속도면에서 확실한 이점이 있다고 하니 버퍼 사용에 익숙해지는것이 좋을 것 같다.

Reference.

[Java] BufferedReader, BufferedWriter를 활용한 빠른 입출력 - 코딩팩토리

(JAVA) 제네릭(generic)

요즘 쓰는 포스팅의 주제는 항상 듣고있는 강의에서 나온다.

강의에서는 제네릭을 직접 언급하지는 않았지만 List 자료형이나 Map 자료형들을 사용할때 제네릭 <> 을 사용한다.

제네릭을 알고는 있었지만 모양만 알고있었을뿐 용법과, 사용처에대해서는 몰랐기에 포스팅 작성을 통해 알아보려 한다.

제네릭(generic) 이란 ?


자바에서 제네릭이란 데이터의 타입(data type)을 일반화한다(generalize)는 것을 의미한다고 한다.

….

여전히 모호하다. 그래서 다른 글을 읽어본 결과

클래스 / 인터페이스 / 메소드 에서 사용할 매개변수의 타입을 클래스 외부에서 설정하는 것 이라고 한다.

그렇다면 제네릭을 사용하는 이유는 무엇일까

제네릭을 사용하는 이유

제네릭 타입을 사용함으로써 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거할 수 있기 때문이다.

자바 컴파일러는 코드에서 잘못 사용된 타입 때문에 발생하는 문제점을 제거하기 위해 제네릭코드에 대해 강한 타입 체크를 한다.

실행시 에러가 나는 것보다 컴파일 시 미리 강하게 체크하여 에러를 사전방지하는게 더 좋기 때문.

또 제네릭 코드를 사용하면 타입을 국한하기 때문에 요소를 찾아올 때 타입 변환을 할 필요가 없어 프로그램 성능이 향상된다.

  • 강한 타입체크를 하여 에러를 사전방지하기 위함
  • 타입 변환을 할 필요가 없게 타입을 국한하여 프로그램 성능 향상을 위함

제네릭의 사용


제네릭 타입은 타입을 매개변수로 갖는 클래스와 인터페이스를 말한다.

제네릭 타입은 클래스 또는 인터페이스 이름 뒤에 <> 부호가 붙으며 사이에 타입 파라미터가 위치한다.

1
2
public class 클래스명<T> {}
public interface 인터페이스명<T> {}

타입 파라미터는 정해진 규칙은 없으며, 일반적으로 대문자 알파벳 한글자로 표현한다.

자주 사용하는 타입 파라미터

타입 파라미터
<T> Type
<E> Element
<K> Key
<N> Number
<V> Value
<R> Result

제네릭의 사용 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class exam<T> {

List<T> examList = new ArrayList<>();

public void method1(T input) {
examList.add(input);
}
}

class GenericClass {
public static void main(String[] args) {
exam<String> stringExam = new exam<>();

exam<Integer> integerExam = new exam<>();
}
}

exam 클래스는 로 제네릭이 설정되어 있고 내부의 List타입의 examList 객체도 로 제네릭 설정이 되어있다.

GenericClass 클래스는 두 개의 객체 stringExam과 intergerExam 이 있으며 두 객체의 타입은 exam 타입이다.

exam 타입은 선언부에 제네릭이 설정되어있으니 사용하는 쪽에서도 제네릭 타입을 명시 해줘야 한다.

이 코드에서는 각각 로 설정해주었다.

이제 stringExam객체는 타입이 String인 데이터만, integerExam객체는 타입이 int 인 데이터만 사용할 수 있다.

정말 그런지 확인을 해 보면

stringExam.method1의 매개변수로 문자열을 주었을때는 컴파일에러가 발생하지 않다가

문자열만 들어가야하는 List에 정수를 매개변수로 주니 컴파일 에러가 발생한다.

반대의 경우도 마찬가지.

이와같이 제네릭을 사용할 경우 클래스 내부에서 사용하는 데이터의 타입을 지정할 수 있으며,

타입을 잘못 사용하여 발생하는 에러를 최소화 할 수 있다.

이는 회원가입폼의 ID란에서 특수문자를 사용하지 못하게 예외처리를 하는것과 비슷하다.

제네릭에 사용 가능한 타입


데이터 타입의 종류

여기서 간단히 데이터 타입의 종류와 특징을 알아보면

기본형 타입 참조형 타입
종류 - 정수형 : byte, short, int, long
- 실수형 : float, double
- 문자형 : char
- 논리 타입 : boolean
기본형 이외의 모든 형태
- 배열(array[])
- 열거(enum)
- 클래스(class)
- 인터페이스(interface)
특징 - 직접 값을 넣을 수 있음
- 스택 영역에 저장
- 값들을 저장하고 있는 객체를 가르키는 ‘주소’를 넣을 수 있음
- 힙 영역에 저장

그렇다면 제네릭에는 어떤 타입을 설정할수 있을까?

제네릭의 타입으로는 참조형 데이터 타입만 설정 가능하다.

int, byte, double, char 같은 기본형 데이터 타입은 설정할 수 없다.

?????

분명 우리는 위 코드에서 정수형데이터로 Integer가 입력이 되는것을 보았다.

Integer와 같은 형태를 래퍼클래스(wrapper class) 라고 한다.

래퍼클래스는 기본형 데이터 타입을 참조형 데이터 타입으로 바꿔주는 클래스다.

기본형 데이터 타입의 래퍼클래스

위 코드에서는 기본형인 int 가 아닌 래퍼클래스인 Integer를 사용함으로써 설정이 가능했던 것이다.

Reference.

제네릭(Generic) 사용법 & 예제 총정리 - 코딩팩토리

제네릭의 개념 - TCP SCHOOL.com

Java 제네릭(Generic) 이란?? - 비실이의 개발 성장기

(JAVA) 자바 플랫폼 3대 구성요소 (JDK, JRE, JVM)

자바, 스프링 강의를 듣거나 책을 보다보면 JDK, JRE, JVM에 대한 내용이 가끔 나온다.
강의에서 나올때마다 자바의 필수요소인가보다 하며 지나쳤었지만 이번 포스팅을 통해 짚어보고 넘어가려 한다.

JDK (자바 개발 키트, Java Development Kit)


JDK란

자바 플랫폼의 등장 이래 지금까지 가장 널리 사용되고 있는 소프트웨어 개발 키트(SDK) - 위키백과

즉, Java로 소프트웨어를 개발할 수 있도록 여러 기능들을 제공하는 패키지이다.

JDK의 구성

Java로의 개발을 담당하는 패키지답게 매우 많다. 자주 들어본것만 간추려보자면

  • javac : 자바 컴파일러, 자바 소스파일(.java)을 바이트코드(.class)로 변환한다.
  • java : javac가 만든 클래스 파일을 해석 및 실행한다.
  • jar : 서로 관련있는 클래스 라이브러리들과 리소스를 하나의 파일로 묶어주는 도구이다.
  • JDB : Java Debugger, 자바 디버깅 툴
  • JRE (Java Runtime Environment) : Java가 동작하는데 필요한 JVM, 라이브러리 등 다양한 파일들을 포함한다. Java를 실행시키기 위해필요하다.
  • JVM(Java Virtual Machine) : Java가 실제로 동작하는 가상환경. JVM 덕분에 하나의 소스파일을 만들더라도 여러 환경(OS) 에서 원활히 동작이 가능하다.

JDK의 종류

  1. Java SE : Standard Edition
    표준 자바 플랫폼으로 표준적인 컴퓨팅 환경을 지원하기 위한 자바 가성머신 규격 및 API 집합을 포함한다.
    이후의 Java EE, Java ME는 구체적인 목적에 따라 Java SE를 기반으로 API를 추가하거나 자바 가상머신 규격 및 API의 일부를 택하여 정의 된다.
  2. Java EE : Enterprise Edition
    Java SE에 웹 어플리케이션 서버에서 동작하는 기능을 추가한 플랫폼
    이 스펙에 따라 제품을 구현한 것을 웹 어플리케이션 서버( WAS )라 한다.(톰캣 등)
  3. Java ME : Micro Edition
    제한된 자원을 가진 휴대전화, PDA 등에서 Java 프로그래밍 언어를 지원하기 위해 만든 플랫폼 중 하나이다.

JRE (자바 런타임 환경, Java Runtime Environment)


컴파일된 자바 프로그램을 실행시킬 수 있는 자바 환경을 말한다.
자바 프로그램을 실행시키기 위한 라이브러리, JVM, 기타 파일들을 포함하고있다.
자바 프로그램을 실행시키기 위해서는 JRE를 반드시 설치해야하고 JRE에는 개발과 관련된 도구는 포함되어 있지 않다.

JVM (자바 가상 머신, Java Virtual Machine)


JVM은 Java Virtual Machine(자바 가상머신)의 약자로 자바 소스코드로 만들어지는 자바 바이트코드 파일을 실행할 수 있다.
JVM은 자바와 다르게 플랫폼에 의존적므로 맥, 윈도우, 리눅스의 JVM은 각각 다르다.
하지만 컴파일된 바이트코드 파일은 어떤 JVM에서도 동작시킬 수 있다. 즉, 코드를 작성하면 JVM이 설치된 어떤 플랫폼에서도 동작시킬수 있다.

JVM의 역할은 다음과 같다.

  • 바이트코드를 읽는다.
  • 바이트코드를 검증한다.
  • 바이트코드를 실행한다.
  • 실행환경(Runtime Environment)의 규격을 제공한다. (필요한 라이브러리 및 기타파일)

마지막으로 각각의 관계를 살펴보면 다음 그림과 같다.
JDK, JRE, JVM의 관계

Reference.

자바 개발키트 위키백과
[Java] Java SE, JDK, JRE

onsent@3.1.1/build/cookieconsent.min.js" defer>