반응형

Java 바이트 스트림(byte기반) 입출력 클래스

 

파일 입출력 개념

파일입출력이란 위와 같이 메모리에서 동작하는 프로그램을 기준으로 데이터를 전송하는 개념을 말한다. 키보드를 통해 입력을 받거나 모니터로 출력을 하거나 HDD의 파일에 데이터를 저장하거나 불러오거나 네트워크를 통해 데이터를 업로드하거나 다운로드하는 개념들을 입출력이라고 이야기 할 수 있다.
 이 때 프로세스(메모리)로 데이터가 들어오면 입력 나가면 출력이라고 이해하면 된다.
물론 기준이 달라지면 입출력을 말하는 방향도 다르게 표현될 수는 있다.

 

Java는 기본적으로 1byte기반의 입출력(바이트 스트림)을 지원한다. 즉 아스키코드 기반의 데이터만 입출력이 가능하다는 것을 의미한다. 자바가 발전하면서 다국어를 지원하기 위해 문자기반의 스트림(2byte 기반)이 만들어졌고 문자기반 스트림 역시 바이트기반 스트림을 이용하여 동작되므로 먼저 바이트 스트림을 이해하도록 한다.
 
클래스와 상속을 통한 추상화와 다형성을 이해하지 못하면 파일입출력의 내용을 이해하기 어렵다. 왜냐하면 입출력을 위한 기능을 개발자가 직접 만드는 것이 아니라 제공되는 입출력 기능의 클래스들을 활용해야 하는데 이러한 클래스들이 상속을 기반으로 만들어져 있기 때문이다.
바이트 스트림을 이해하기 위해 관련 클래스들의 상속 관계를 확인해보자.
입출력 관련 클래스는 java.io 패키지에 존재한다.

상속 관계도(일부)


위와 같은 많은 클래스들이 존재하는데 다음과 같은 분류를 참조하여 공부하도록 한다.


일단 앞서 확인한 많은 클래스들을 모두 외우고 사용하는 것은 아니고 어떤 클래스가 어떤 기능의 입출력을 하는지 찾아서 활용할 수 있으면 된다. 상속관계에 따라 의존적인 클래스들도 있으니 전체 구조를 필요할 때마다 확인하도록 하는 것이 좋다.

 

바이트 스트림을 이용한 출력

 

 

위 개념에서 먼저 1byte스트림으로 입출력을 하는 클래스를 활용하여 입출력을 테스트 해보자.

package exam;

import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;

public class Exam {
	public static void main(String[] args) throws IOException // 실습 편의를 위해 예외전가 설정
	{
		FileDescriptor out = FileDescriptor.out; // 표준 출력 방향
		FileOutputStream fos = new FileOutputStream(out); // 바이트 스트림 생성
		byte[] arr = new byte[] { 'h', 'e', 'l', 'l', 'o' };
		fos.write(65); // 아스키 코드 출력
		fos.write(arr); // 배열 출력
		fos.close(); // 스트림 닫기
	}
}

FileDescriptor는 OS의 표준 입력 스트림(1)/표준 출력스트림(0)/표준 에러 스트림(2) 값을 가진다.
FileOutputStream클래스는 1바이트 단위로 출력하는 write()메서드를 갖는다.
객체 생성 시 출력할 대상을 전달해야 하기 때문에 표준 출력 방향을 정해 스트림을 생성한 것이다.

실행하면 write()메서드를 통해 지정된 스트림으로 값을 출력한다.
스트림 사용이 끝나면 반드시 스트림을 닫아 주어야 한다.

 

실행 결과


위 예제에서 표준 출력 스트림을 생성하는 부분을 File클래스를 이용하여 실제 파일을 지정해 보도록 한다.
예제코드

package exam;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class Exam {
	public static void main(String[] args) throws IOException // 실습 편의를 위해 예외전가 설정
	{
		File file = new File("test.txt"); // 출력할 대상 파일 생성
		FileOutputStream fos = new FileOutputStream(file); // 바이트 스트림 생성
		// FileOutputStream fos = new FileOutputStream(file, true); //추가모드로 바이트 스트림 생성
		byte[] arr = new byte[] { 'h', 'e', 'l', 'l', 'o' };
		fos.write(65); // 아스키 코드 출력
		fos.write(arr); // 배열 출력
		fos.close(); // 스트림 닫기
	}
}

실행결과는 콘솔에 아무런 값도 출력되지 않는다.

출력 대상이 콘솔이 아니라 test.txt파일이기 때문이다.

따라서 상대경로로 지정된 파일을 확인해본다.

이클립스에서 Package Exploerer에서 프로젝트를 새로고침 하거나 실제 프로젝트 폴더로 이동해본다.

 

이클립스에서 확인

 

실제 프로젝트 폴더에서 확인


생성된 test.txt파일을 열어보면 앞서 콘솔에 출력했던 내용과 동일한 내용이 파일에 쓰여진 것을 볼 수 있다.

이클립스에서 확인

 

직접 메모장으로 열어서 확인


앞으로 실습의 편의를 위해 한 곳에서만 확인하도록 한다.(예제에서는 이클립스에서 확인)
그리고 위에서 주석 처리된 부분을 보면 스트림 생성 시 true값을 전달하는데 이와 같이 사용하면 기존의 파일의 내용에 추가하도록 파일스트림이 생성된다. 기본 값은 false이기 때문에 파일이 항상 새로 쓰여진다.

FileOutputStream만 있으면 끝이 아니라 다른 클래스를 활용하는 상황도 있다.
위 예제에서 FileOutputStream의 write()메서드를 호출하면 운영체제가 그 즉시 지정된 곳으로 출력한다. 그래서 많은 데이터를 출력하려는 경우 굉장히 비효율적이게 된다.
이를 해결하기 위해 일정량을 모아두었다가(버퍼) 한번에 출력할 수 있도록 효율을 향상시킬 수 있는 클래스를 제공한다.

BufferedOutputStream클래스가 버퍼 기능이 추가된 클래스이다.

FileOutputStream을 이용하여 추가된 기능(버퍼)을 사용할 수 있는 BufferedOutputStream클래스를 테스트 해보도록 하자.
코드는 기존 코드에 추가만 하도록 한다.

예제코드

package exam;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class Exam {
	public static void main(String[] args) throws IOException // 실습 편의를 위해 예외전가 설정
	{
		File file = new File("test.txt"); // 출력할 대상 파일 생성
		FileOutputStream fos = new FileOutputStream(file); // 바이트 스트림 생성
		// FileOutputStream fos = new FileOutputStream(file, true); //추가모드로 바이트 스트림 생성
		BufferedOutputStream bos = new BufferedOutputStream(fos);
		byte[] arr = new byte[] { 'h', 'e', 'l', 'l', 'o' };
		bos.write(65); // 아스키 코드 출력
		bos.write(arr); // 배열 출력 //
		bos.close(); // 스트림 닫기
	}
}

위 코드를 실행하면 파일에 아무런 값도 출력되지 않는데 이것은 출력 대상에 출력하기 전에 임시로 출력할 데이터를 저장하는 버퍼라는 임시 공간이 있다. 그곳에 출력이 되면 버퍼가 꽉 차서 넘치거나 직접 비워주지 않으면 원하는 곳에 출력하지 않는다.

버퍼 스트림을 비우는 방법을 알아보자.
위에서 bos.close() 부분을 주석해제하고 실행하면 스트림이 닫히면서 버퍼의 내용을 모두 지정된 스트림으로 흘려보낸다.(출력)

그리고 스트림을 닫지 않고 버퍼의 데이터를 출력으로 비우고자 할 때는 다음과 같이 flush()메서드를 호출하면 된다.


이외에 또 다른 버퍼를 비우는 방법으로 버퍼의 데이터가 넘치는 경우 버퍼를 비우게 된다.
이를 확인하기 위해서 위의 코드를 수정하여 보자.

package exam;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class Exam {
	public static void main(String[] args) throws IOException // 실습 편의를 위해 예외전가 설정
	{
		File file = new File("test.txt"); // 출력할 대상 파일 생성
		FileOutputStream fos = new FileOutputStream(file); // 바이트 스트림 생성
		// FileOutputStream fos = new FileOutputStream(file, true);	//추가모드로 바이트 스트림 생성 		
		BufferedOutputStream bos = new BufferedOutputStream(fos, 512); // 버퍼의 크기를 512byte로 지정
		for (int i = 0; i < 512; i++) {
			bos.write(65); // 아스키 코드 출력
		}
	}
}

 

버퍼사이즈지정

버퍼 기능을 추가한 스트림으로 객체를 생성할 때 버퍼의 크기를 지정할 수 있다.
위 예제는 65를 512번을 출력한다.

이 코드의 결과는 버퍼의 데이터가 꽉 차있는 상태가 된다.(버퍼가 비워지지 않음)
그래서 for문의 반복을 한 번 더 진행하도록 513으로 변경하고 실행해 본다.
그러면 버퍼가 넘쳐서 비워지고 이것은 지정된 곳으로 출력이 된다.(A가 512번 출력됨)
그리고 마지막 513번째 데이터는 다시 비어있는 버퍼에 하나가 담긴다.

버퍼를 이용하면 운영체제가 출력 요청 때마다 동작하지 않고 버퍼를 비울 때만 출력이 된다.
그 외에는 출력 시 버퍼에 추가하므로 부하가 많이 출어 들게 된다.

 

다음으로 Filter기능을 통해 자료형 별로 출력이 가능한 DataOutputStream클래스를 이용하는 예제를 보자. 자바는 기본 바이트 스트림을 사용한다고 했다. 즉 아스키코드로 표현할 수 없는 데이터는 바이트 기반 입출력에서 활용하지 못한다. DataOutputStream 클래스는 이를 가능하게 하기 위해서 버퍼기능을 통해 자료형 별로 출력하는 메서드들이 정의되어 있는 클래스이다.

 

예제코드

package exam;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class Exam {
	public static void main(String[] args) throws IOException // 실습 편의를 위해 예외전가 설정
	{
		File file = new File("test.txt"); // 출력할 대상 파일 생성
		FileOutputStream fos = new FileOutputStream(file); // 바이트 스트림 생성
		DataOutputStream dos = new DataOutputStream(fos);
		dos.writeUTF("안녕하세요");
		dos.writeByte(1);
		dos.writeInt(1900000000);
		dos.writeDouble(3.14);
		dos.close();
	}
}

실행결과

 

위와 같이 출력된 결과를 볼 수 있다.

값이 제대로 표현되지 않았지만 데이터는 잘 들어가 있는 것이다.
이 값을 제대로 확인하려면 DataInputStream을 이용하여 읽어 들여야 하는데 다음 파트의 입력부분에서 확인해 보도록 한다.

여기까지 출력에 대한 내용을 확인하였는데 이제부터는 입력에 대한 내용을 확인하도록 하자.

 

 

바이트 스트림을 이용한 입력

 
입력은 외부에서 프로그램으로 읽어들이는 것을 의미하며 위에서 사용한 Output클래스들의 반대 개념의 클래스들이 존재한다.
    FileInputStream
    BufferedInputStream
    DataInputStream

...

 

예제를 통해 확인해보자.

package exam;

import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;

public class Exam {
	public static void main(String[] args) throws IOException // 실습 편의를 위해 예외전가 설정
	{
		FileDescriptor in = FileDescriptor.in;
		FileInputStream fis = new FileInputStream(in); // 바이트 스트림 생성
		byte[] arr = new byte[10];
		int n = fis.read(); // 문자를 입력 받아 아스키 코드를 반환
		fis.read(); // '\r' 문자 처리
		fis.read(); // '\n' 문자 처리
		System.out.println(n);
		int size = fis.read(arr); // 입력 받은 문자들을 배열에 저장 후 개수 반환
		for (int i = 0; i < size; i++) {
			System.out.print((char) arr[i]);
		}
		fis.close(); // 스트림 닫기
	}
}

실행결과


키보드로 입력 받은 문자를 읽어 들이는 기능인 것이다.
키보드가 아닌 파일을 지정하여 파일로부터 데이터를 읽어 들여보자.
프로젝트에 텍스트 파일 생성


예제코드

package exam;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class Exam {
	public static void main(String[] args) throws IOException // 실습 편의를 위해 예외전가 설정
	{
		File file = new File("test.txt"); // 표준 입력 방향
		FileInputStream fis = new FileInputStream(file); // 바이트 스트림 생성
		byte[] arr = new byte[10];
		int n = fis.read(); // 문자를 입력 받아 아스키 코드를 반환
		System.out.println((char) n);
		int size = fis.read(arr); // 입력 받은 문자들을 배열에 저장 후 개수 반환
		for (int i = 0; i < size; i++) {
			System.out.print((char) arr[i]);
		}
		fis.close(); // 스트림 닫기
	}
}

실행결과


입력 버퍼 추가 예제

package exam;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class Exam {
	public static void main(String[] args) throws IOException // 실습 편의를 위해 예외전가 설정
	{
		File file = new File("test.txt"); // 표준 입력 방향
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); // 바이트 스트림 생성
		
		bis.mark(0); // 현재 지점으로 mark
		
		byte[] arr = new byte[10];
		
		int n = bis.read(); // 문자를 입력 받아 아스키 코드를 반환
		System.out.println((char) n);
		
		bis.reset(); // mark된 지점으로 이동
		
		int size = bis.read(arr); // 입력 받은 문자들을 배열에 저장 후 개수 반환
		for (int i = 0; i < size; i++) {
			System.out.print((char) arr[i]);
		}
		
		bis.reset(); // mark된 지점으로 이동
		
		size = bis.read(arr); // 입력 받은 문자들을 배열에 저장 후 개수 반환
		for (int i = 0; i < size; i++) {
			System.out.print((char) arr[i]);
		}
		bis.close(); // 스트림 닫기
	}
}

BufferedInputStream클래스는 mark()메서드와 reset()메서드를 사용할 수 있다.

다음은 DataInputStream클래스의 기능을 이용하는 테스트다.
앞서 만들었던 DataOutputStream예제를 가져와서 다음 예제 코드를 추가한다.

예제코드

package exam;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Exam {
	public static void main(String[] args) throws IOException // 실습 편의를 위해 예외전가 설정
	{
		File file = new File("test.txt"); // 출력할 대상 파일 생성
		FileOutputStream fos = new FileOutputStream(file); // 바이트 스트림 생성
		DataOutputStream dos = new DataOutputStream(fos);
		
		dos.writeUTF("안녕하세요");
		dos.writeByte(1);
		dos.writeInt(1900000000);
		dos.writeDouble(3.14);
		
		dos.close(); // 입력
		
		FileInputStream fis = new FileInputStream(file);
		DataInputStream dis = new DataInputStream(fis);
		
		String str = dis.readUTF();
		byte by = dis.readByte();
		int n = dis.readInt();
		double d = dis.readDouble();
		
		dis.close();
		
		System.out.println("읽어온 문자열 : " + str);
		System.out.println("읽어온 byte정수 : " + by);
		System.out.println("읽어온 int정수 : " + n);
		System.out.println("읽어온 double실수 : " + d);
	}
}

실행결과


읽어오는 메서드의 순서를 출력한 자료형 순서와 다르게 해보면 제대로 읽어오지 못한다.

 

위와 같이 출력 자료형 순서와 입력 자료형 순서를 다르게 하여 실행한다.

 

Data입출력 스트림은 자료형 단위로 출력하고 입력하므로 서로 출력/입력하는 자료형의 순서가 같아야 정상적으로 동작한다.

 

여기까지 내용을 통해 입출력 클래스들을 활용하여 바이트기반 입출력을 구현할 수 있다.
개념을 활용하기 위한 실습 테스트로 파일을 복사하는 프로그램을 만들어보면 좋을 것이다.

 

반응형

'교육자료 > Java' 카테고리의 다른 글

Java IO 객체 입출력(byte기반 객체단위)  (0) 2017.07.02
Java IO 문자스트림(char기반)  (0) 2017.07.02
Java IO - File class  (0) 2017.06.30
Java Thread (쓰레드)  (0) 2017.06.30
Java Exception(예외처리)  (0) 2017.06.28

+ Recent posts