기록공간

Java 입출력 (Input/Output) 본문

Java

Java 입출력 (Input/Output)

입코딩 2020. 10. 2. 23:25
반응형

개요

자바에서의 모든 데이터 입출력은 스트림(Stream) 이라는 개념에 의해 이루어진다. 스트림이라는 단어의 사전적 의미는 흐르는 물의 개념이며, 이는 연속된 일련의 데이터를 일컫는다. 

 

스트림(Stream)

데이터 입출력 시 모든 데이터를 형태와 관계없이 일련된 흐름으로 전송하는 것이 스트림 입출력 모델의 기본 개념이다.

 

특징

 

  • 스트림은 FIFO 구조이다. FIFO(First In First Out) 구조란, 먼저 들어간 것이 먼저 나오는 형태로 스트림의 데이터는 순차적으로 흘러가며 순차적 접근 밖에 허용되지 않는다.

  • 스트림은 단방향이다. 그렇기 때문에 자바에서 스트림을 사용하는 기능은 읽기와 쓰기가 동시에 이루어지지 않는다. 따라서 읽기 쓰기가 모두 필요하다면 읽는 스트림과 쓰는 스트림을 하나씩만 열어서 사용해야 한다.

  • 스트림은 지연될 수 있다. 스트림에 넣어진 데이터가 처리되기 전까지 스트림에 사용하는 스레드는 대기locking) 상태에 빠진다. 예를 들면 네트워크 상에서 데이터가 모두 전송되거나 발송받기 전까지 스레드는 대기 상태가 된다.

 

바이트 스트림(Byte-Stream) 입출력

InputStream 클래스와 OutputStream 클래스, 그리고 이들의 하위 클래스를 통해서 제공되는 바이트 스트림을 위한 표준 입출력으로 8비트 크기를 갖는 바이트들의 스트림이 입출력된다. (바이트, 바이트배열, 정수 등)

 

문자 스트림(Character-Stream) 입출력

Reader 클래스와 Writer 클래스, 그리고 이들의 하위 클래스에 의해 구현되었으며, 8비트 크기를 갖는 바이트들의 스트림이 아닌 16비트 크기를 갖는 유니코드 문자들의 스트림이라는 점에서 차이가 있다. (문자, 문자배열, 문자열 등)

 

예제

바이트 스트림, 문자 스트림의 차이

import java.io.IOException;

public class Test
{
	public static void main(String[] args) throws IOException
	{
		int data;
		char ch;

		System.out.println("문자열 입력(종료:Ctrl+z)");

		// read() : InputStream 클래스의 대표적 메소드 (-> 바이트 스트림)
		while((data = System.in.read()) != -1)
		{
			ch = (char)data;
			System.out.print(ch);
		}
	}
}

위 코드는 유저가 입력한 내용을 엔터를 눌렀을때 그대로 출력하게 해주는 코드이다. 코드를 실행하면 다음과 같은 결과가 나온다.

 

영문이나 숫자를 입력하였을 경우에는 잘 출력이 된다. 하지만 한글을 입력한 경우에는 출력 값이 깨져있다. 

 

이런 현상이 일어나는 이유는 바이트 스트림과 문자 스트림을 섞어서 사용했기 때문이다. System.in.read()로 입력받은 내용을 바이트 스트림 기반으로 받아왔지만, 출력을 위해 내용을 내보낼때 System.out.print()를 사용하였는데, 이 메소드는 문자 스트림 기반이다. 1바이트로 나누어 저장된 정보를 2바이트로 묶어 내보내려고 하니까 문자가 깨져서 나오는 것이다.

 

 

import java.io.IOException;

public class Test
{
	public static void main(String[] args) throws IOException
	{
		int data;
		char ch;

		System.out.println("문자열 입력(종료:Ctrl+z)");

		// read() : InputStream 클래스의 대표적 메소드 (-> 바이트 스트림)
		while((data = System.in.read()) != -1)
		{
			ch = (char)data;
			System.out.write(ch);
		}
	}
}

이러한 문제는 다음과 같이 바이트 스트림 기반 출력 메소드인 System.out.write()를 사용하면 올바른 결과를 도출해낼 수 있다.

 

 

OutputStream, InputStream

import java.io.OutputStream;
import java.io.IOException;

public class Test
{
	public static void main(String[] args) throws IOException
	{
		// System.in	: 자바의 표준 입력 스트림
		// System.out	: 자바의 표준 출력 스트림
		// System.in, out 이 자체로 객체이다!
		OutputStream out = System.out;

		// 배열 구성
		byte[] ch = new byte[3];
		ch[0] = 65;		// A
		ch[1] = 97;		// a
		ch[2] = 122;	// z

		out.write(ch);	//-- 밖으로 내보낼 데이터(변수)를 스트림에 기록

		out.flush();    //-- 기록된 스트림을 내보내는(밀어내는) 기능을 수행.
						//   지금은 Buffer를 활용하지 않고 있는 상황이기 때문에
						//   생략이 가능한 코드
						//   (Buffered 되어 있는 스트림(stream)일 경우 생략 불가)

		out.close();	//-- 출력 스트림(물줄기)에 대한 리소스 반남.
						//   (out 스트림을(수도꼭지를) 잠가버린 상황)

		System.out.println("절대적인 신뢰를 갖고 있는 구문...");
		//-- [out.close()]를 작성한 이후 출력되지 않는 구문
		//   출력되는 스트림(물줄기)을 닫았기 때문에...
	}
}

OutputStream(그리고 InputStream)은 자바의 표준 입출력 스트림 기능을 하는 클래스이다. System.in과 System.out을 통해 자바에서 사용하는 표준 입출력 객체에 접근할 수 있다.  

 

출력 버퍼와 flush()

public class Test
{
	public static void main(String[] args)
	{
		// System.out : 자바 기본 출력 스트림

		System.out.write(65); //-- 'A'
		System.out.write(66); //-- 'B'
		

		// 한글의 유니코드는 2바이트 이므로
		// 이렇게 1바이트씩 두개를 조합하여 사용
		System.out.write(180); // 
		System.out.write(235); //-- 180 + 235 -> '대'

		// 출력 버퍼가 다 채워지지 않으면
		// 출력할 데이터(자료)를 출력 디바이스(장치) 보내지 않기 때문에
		// flush() 메소드를 통해 아직 다 채워지지 않은 
		// 출력 버퍼의 내용을 출력 장치로 밀어내어 보낼 수 있도록
		// 처리해야 한다.
		System.out.flush();
		//-- 현재 구문에서는 생략하게 되면 내용이 출력되지 않음
	}
}

flush() 메서드는 스트림에 채워진 내용을 강제로 출력 스트림으로 보내는 역할을 한다. 사용자가 원하는 상황에 원하는 출력 기능을 사용하기 위해 주로 사용한다.  위 코드처럼 출력 스트림에 버퍼가 채워지지 않은 상황에서는 아무것도 출력되지 않는다. 하지만 flush() 메서드를 사용하여 강압적으로 내보내도록 한다면 올바르게 결과가 출려된다.

 

Reader, Writer

import java.io.InputStreamReader;
import java.io.Reader;
import java.io.IOException;

public class Test
{
	public static void main(String[] args) throws IOException
	{
		int data;
		char ch;

		// System.in			: 자바 표준 입력 스트림 -> 바이트 기반
		// InputStreamReader	: 바이트 기반 스트림을 -> 문자 기반 스트림으로
		//						  변환해 주는 역할 수행
		// Reader				: 문자 기반 스트림 객체.
		Reader rd = new InputStreamReader(System.in);

		System.out.println("문자열 입력(종료:Ctrl+z)");

		while ((data = rd.read()) != -1)
		{
			ch = (char)data;

			//System.out.print(ch);
			//--==>>
			/*
			문자열 입력(종료:Ctrl+z)
			abcd
			abcd
			1234
			1234
			가나다라
			가나다라
			^Z
			계속하려면 아무 키나 누르세요...
			*/

			System.out.write(ch);
			//--==>>
			/*
			문자열 입력(종료:Ctrl+z)
			abcd
			abcd
			1234
			1234
			가나다라
			 섆|
			^Z
			계속하려면 아무 키나 누르십시오 . . .
			*/
		}
	}
}

Reader(그리고 Writer) 객체는 자바에서 제공하는 문자 기반 스트림 객체이다. 이 객체에 존재하는 메서드 중 일부는 앞서 사용한 write()나 read()와 이름이 같고 기능도 비슷하다. 하지만 주의할 점은 여기서 사용하는 메서드들은 바이트 스트림 기반이 아닌 문자 스트림 기반이라는 점이다.

 

Reader, Writer 실습

import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;

import java.io.IOException;

public class Test181
{

	public void process(InputStream is)
	{
		int data;

		System.out.println("문자열 입력(종료:Ctrl+z)");

		try
		{
			// 매개변수 is 라는 바이트 기반 객체(InputStream)를
			// 문자 스트림으로 변환하여
			// (-> InputStreamReader 가 수행)
			// Reader 타입의 rd 에서 참조할 수 있도록 처리
			Reader rd = new InputStreamReader(is);

			// 바이트 기반 스트림인 자바 기본 출력 스트림(System.out)을
			// 문자 스트림으로 변환하여
			// (-> OutputStreamWriter 가 수행)
			// Writer 타입의 wt에서 참조할 수 있도록 처리
			Writer wt = new OutputStreamWriter(System.out);

			while((data = rd.read()) != -1)
			{
				wt.write(data); //-- 스트림 물줄기에 기록
				//wt.flush();		//-- 기록한 스트림을 밀어내어 내보냄
			}


		}
		catch (IOException e)
		{
			System.out.println(e.toString());
		}

	}

	public static void main(String[] args)
	{
		Test181 ob = new Test181();
		ob.process(System.in);
	}
}

// 실행 결과
/*
문자열 입력(종료:Ctrl+z)
asdfdsafadsfasdf
asdfdsafadsfasdf
12344
12344
가나다라
가나다라
^Z
계속하려면 아무 키나 누르십시오 . . .
*/
반응형
Comments