30. 바이트 스트림과 파일 입출력


javalogo

Overview

자바에서의 바이트 스트림파일 입출력에 대해서 알아본다.


바이트 스트림 클래스

바이트 스트림은 8비트의 바이트 단위로 바이너리 데이터가 흐르는 스트림이다.

바이트 스트림은 바이너리 데이터를 있는 그대로 입출력하기 때문에 이미지동영상의 입출력뿐만 아니라 문자들로 구성된 텍스트 파일도 입출력할 수 있다.

다음은 대표적인 바이트 입출력 스트림 클래스이다.

InputStream/OutputStream

추상 클래스이며, 바이트 입출력 처리를 위한 공통 기능을 가진 슈퍼 클래스이다.

FileInputStream/FileOutputStream

파일 입출력을 위한 클래스로서, 파일로부터 바이너리 데이터를 읽거나 파일에 바이너리 데이터를 저장할 수 있다.

DataInputStream/DataOutputStream

이 스트림을 이용하면 boolean, char, byte, short, int, long, float, double 타입의 값을 바이너리 형태로 입출력한다.

문자열도 바이너리 형태로 입출력한다.


FileInputStream을 이용한 파일 읽기

바이트 스트림 입력을 위해서 InputStream 클래스를 사용할 수 없다.

InputStream은 추상 클래스이므로 목적에 따라 InputStream을 상속받은 FileInputStream이나 DataInputStream 클래스를 이용한다.

파일에서 읽을 때는 FileInputStream 클래스를 사용한다.

FileInputStream의 생성자와 주로 사용되는 메소드는 다음 표와 같다.

생성자설명
FileInputStream(File file)file이 지정하는 파일로부터 입력받는 FileInputStream 생성
FileInputStream(String name)name이 지정하는 파일로부터 입력받는 FileInputStream 생성
메소드설명
int read()입력 스트림에서 한 바이트를 읽어 int형으로 리턴
int read(byte[] b)최대 배열 b의 크기 만큼 바이트를 읽음. 실제 읽은 바이트 수 리턴
int read(byte[] b, int off, int len)최대 len개의 바이트를 읽어 b 배열의 off 위치에 저장. 실제 읽은 바이트 수 리턴
int available()입력 스트림에서 현재 읽을 수 있는 바이트 수 리턴
void close()입력 스트림을 받고 관련된 시스템 자원 해제

FileInputStream 클래스를 사용하여 파일을 읽는 간단한 예를 들어보자.

FileInputStream 바이트 스트림 생성

FileInputStream 클래스는 파일에 연결한 바이트 스트림을 생성한다.

C:\test.txt 파일로부터 바이너리 값을 그대로 읽는 바이트 스트림을 생성하는 코드는 다음과 같다.

FileInputStream fin = new FileInputStream("C:\test.txt");

이 코드를 실행하면 C:\test.txt 파일을 찾아 열고, 이 파일을 스트림의 입력 단에 연결한 객체 fin을 생성한다.

그리고 나면 위 메소드 표에 보이는 fin 객체의 메소드를 호출하여 파일을 읽을 수 있다.

파일 읽기

이제 fin 스트림을 이용하여 파일을 읽어보자.

fin.read()는 파일 스트림으로부터 한 바이트를 읽어 리턴하며, 파일 전체를 읽어 화면에 출력하는 코드는 다음과 같다.

FileInputStream fin = new FileInputStream("C:\test.txt");
// 파일을 열고 스트림 fin 생성

int c;
while ((c = fin.read()) != -1) {
    // 파일 끝까지 한 바이트씩 c에 읽어들인다.
    System.out.println((char)c);
    // 바이트 c를 문자로 변환하여 화면에 출력한다.
}

파일의 끝(EOF) 을 만나면 fin.read() 메소드는 -1을 리턴한다.

스트림 닫기

더 이상 스트림이 필요없을 때 생성된 스트림을 닫아야 한다.

다음과 같이 close() 메소드를 호출하여 스트림을 닫는다.

fin.close();    // 스트림을 닫는다. 더 이상 스트림으로부터 읽을 수 없다.

스트림을 닫으면 연결된 파일도 함께 닫힌다.

파일을 읽어 화면에 출력하는 사례

FileInputStream을 이용하여 사용자 컴퓨터의 파일을 읽고 화면에 출력하는 코드

/**
 * FileInputStreamEx
 */
public class FileInputStreamEx {

    public static void main(String[] args) {
        FileInputStream in = null;
        try {
            in = new FileInputStream("/Users/xxx/Desktop/file_test.txt");
            // 파일과 연결된 바이트 스트림을 생성
            int c;
            while((c = in.read()) != -1) {  // read()는 파일의 끝을 만나면 -1 리턴
                System.out.print((char)c);    // 바이트 c를 문자로 변환하여 출력
            }
            in.close(); // 스트림을 닫는다. 더 이상 스트림으로부터 읽을 수 없다.
        } catch (Exception e) {
            System.out.println("입출력 오류");
        }
    }
}

실행 결과

File FileInputStream Test

Open .txt file.

SUCCESSED

FileOutputStream 클래스를 이용한 파일 쓰기

이제 출력 스트림을 생성하고 사용하는 방법을 설명해보자.

출력 바이트 스트림을 생성하기 위해 OutputStream을 직접 사용할 수 없다.

이는 추상 클래스이므로 FileOutputStream이나 DataOutputStream 클래스를 이용한다.

파일에 쓰고자 하는 경우 FileOutputStream 클래스를 이용한다.

FileOutputStream의 생성자와 메소드를 다음 표에서 볼 수 있다.

생성자설명
FileOutputStream(File file)file이 지정하는 파일에 출력하는 FileOutputStream 생성
FileOutputStream(String name)name이 지정하는 파일에 출력하는 FileOutputStream 생성
FileOutputStream(File file, boolean append)FileOutputStream을 생성하며 append가 true이면 file이 지정하는 파일의 마지막부터 데이터 저장
FileOutputStream(String name, boolean append)FileOutputStream을 생성하며 append가 true이면 name 이름의 파일의 마지막부터 데이터 저장
메소드설명
void write(int b)int 형으로 넘겨진 한 바이트를 출력 스트림으로 출력
void write(byte[] b)배열 b의 바이트를 모두 출력 스트림으로 출력
void write(byte[] b, int off, int len)len 크기만큼 off부터 배열 b를 출력 스트림으로 출력
void flush()출력 스트림에서 남아 있는 데이터 모두 출력
void close()출력 스트림을 닫고 관련된 시스템 자원 해제

FileOutputStream을 이용하여 자바 프로그램 내의 변수 값을 그대로 /Users/xxx/Desktop/file_test.txt에 기록하는 예를 들어보자.

변수 값을 기록하면 변수에 들어 있는 바이너리값이 그대로 파일에 기록되며 이 파일은 사람이 읽을 수 없는 형태가 된다.

/Users/xxx/Desktop/file_test.txt 파일에 바이너리 바이트를 기록하는 출력 스트림의 생성 코드는 다음과 같다.

FileOutputStream fout = new FileOutputStream("/Users/xxx/Desktop/file_test.txt");

이 코드는 /Users/xxx/Desktop/file_test.txt 파일을 열고, 이 파일에 연결된 스트림 객체 fout을 생성한다.

이제 다음과 같이 fout 객체의 메소드를 호출하여 파일에 데이터를 기록해보자.

FileOutputStream fout = new FileOutputStream("/Users/xxx/Desktop/file_test.txt");
int num[] = {1, 4, -1, 88, 50};
byte b[] = {7, 51, 3, 4, 1, 24};
for (int i = 0; i < num.length; i++) {
    fout.write(num[i]);
}   // 파일에 num 배열의 정수 값을 바이트 정보로 기록한다.
fout.write(b);  // 파일에 바이트 배열 b의 내용을 모두 그대로 기록한다.
fout.close();

실행 결과 /Users/pathxxx/Desktop/file_test.txt은 바이너리 파일이 되며, num[] 배열의 값 1, 4, -1, 88, 50의 바이너리 바이트가 파일에 그대로 기록된 것을 볼 수 있다.


read() 메소드

개발자들은 아래 코드와 같이 파일이나 입력 스트림을 읽는 read() 메소드를 보면서 두 가지 의문을 가질 수 있다.

FileInputStream in = new FileInputStream("c:\\test.dat");
int c = in.read();  // int 타입으로 한 바이트를 읽어 리턴한다.
while((c = in.read()) != -1) {  // 파일의 끝을 만나면 -1을 리턴한다.
    System.out.println((char)c);
}

첫째, read()는 한 바이트나 한 문자를 리턴하므로, 리턴 타입이 byte 혹은 char인 것이 합당한데, int로 선언한 이유는 무엇일까?

둘째, read()가 스트림의 끝 혹은 파일의 끝을 만나면 -1을 리턴하는데, 스트림이나 파일에 -1이 있다면 이 둘을 어떻게 구분할 수 있을까?

이 두 의문의 해답은 모두 스트림 혹은 파일의 끝 처리와 연관되어 있다.

만일 스트림이나 파일에 0xFF 같이 있다고 하면, read()0xFF를 리턴한다.

이 때, 개발자들은 ‘0xFF는 -1인데, 파일의 끝을 나타내는 -1과 혼동된다!’라고 생각할 수 있다.

그러나 read()int 타입으로 리턴하므로, 0xFF를 32비트의 0x000000FF로 리턴한다.

이것은 -1이 아니다. int 타입의 -1은 0xFFFFFFFF이다.

스트림이나 파일에서 read()0xFF의 값을 읽어 리턴하는 것과 확연히 구분된다.

이것이 바로 read()의 리턴타입이 int인 이유이다.




© 2019. by RaP0d

Powered by aiden