ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JAVA] InputStream/OutputStream
    Language/Java 2019. 12. 7. 15:31

     

     

    오늘은 자바의 입출력에 관한 내용을 정리해 보려고 합니다. 

     

    입출력 즉, I/O란 프로그램이 프로그램 외부와 데이터를 주고받는 것을 의미합니다. 자바에서는 이러한 입출력 동작을 위해 java.io 패키지를 통해 다양한 클래스를 제공합니다.

     

    이번 포스팅에서는 InputStream/OutputStream과 그 하위 클래스들에 대해 정리해보겠습니다.

     

    Stream

    우선 Stream이라는 용어에 대해 알아보겠습니다. 입출력을 위해 어느 한쪽과 다른 한쪽이 데이터를 주고받기 위해서는 데이터를 전송할 수 있는 통로가 필요합니다. 이 통로 역할을 하는 것을 Stream이라고 하는데요. Stream은 하나의 방향으로, 순차적으로 전송이 이루어집니다.

     

    InputStream/OutputStream

     

    InputStream과 OutputStream은 byte기반의 스트림의 최상위 클래스입니다. byte를 단위로 입력을 받고 출력을 하는 것을 바이트 단위 스트림이라고 하는데요,  InputStream과 OutputStream은 여러 종류의 바이트 스트림들의 부모가 되는 클래스입니다. 

     

    두 클래스 모두 abstract클래스(추상 클래스)이므로 이를 상속받은 후손 클래스를 통해 구현하여 사용할 수 있습니다.

     

     

     

     

     

    inputStream에는 더 많은 종류의 하위 클래스들이 있지만 이는 입출력의 대상에 따라 다른 것이고 읽고 쓰는 방식은 동일합니다. 따라서, FileInputStream을 살펴보도록 하겠습니다.

     

    InputStream OutputStream
    int read() void write(int c)
    int read(byte cbuf[]) void write(byte cbuf[])
    int read(byte cbuf[], int offset, int length) void write(byte cbuf[], int offset, int length)

     

    추상 클래스인 InputStream과 OutputStream이 제공하는 읽고 쓰기 위한 메소드입니다. 추상 클래스의 추상메소드이기 때문에 입출력 대상에 따라 알맞게 구현해서 사용할 수 있습니다.

     

    read() 메소드가 한 바이트만 읽어 들인다면 read(byte cbuf[])메소드는 사용자가 지정한 byte []을 이용하여 한꺼번에 원하는 양의 데이터를 읽어 들일 수 있습니다. 그리고 offset과 length를 파라미터로 받아 배열의 offset위치부터 length만큼의 데이터를 읽어오거나 쓸 수 있습니다.

    앞서 InputStream과 OutputStream은 byte를 단위로 입출력이 이루어지는 byte단위 입출력 클래스라고 했습니다. 그런데 왜 read()의 리턴 타입, write()의 파라미터 타입은 int형인 걸까요?

     

    byte의 범위는 0~255입니다. read()메소드는 파라미터로 받은 데이터를 하나의 byte씩 읽어 반환하는데, 이 데이터를 모두 읽어서 더 이상 읽을 데이터가 없을 때 이를 표현하기 위해 -1을 반환합니다. 따라서 -1은 0~255의 범위에 들 수 없으므로 이를 표현하기 위한 정수형 데이터 타입, 그중에서도 정수형 default 데이터 타입인 int형을 사용하는 것입니다.

     

    File InputStream/File OutputStream

    file에 입출력을 하기 위한 스트림입니다.

    file스트림을 생성하는 방법은 크게 3가지가 있습니다.

    FileInputStream(String filePath) filePath로 지정한 파일에 대한 입력 스트림을 생성
    FileInputStream(File fileObj) fileObj로 지정한 파일에 대한 입력 스트림을 생성 
    FileInputStream(FileDescriptor fdObj) fdObj로 기존의 접속을 나타내는 파일 시스템의 입력 스트림을 생성

     

    FileOutputStream(String filePath) filePath로 지정한 파일에 대한 OutputStream을 생성
    FileOutputStream(String filePath, boolean append) 지정한 파일로 OutputStream을 생성하고, append 모드를 설정
    FileOutputStream(File fileObj) fileObj로 지정된 파일에 대한 OutputStream을 생성
    FileOutputStream(File fileObj, boolean append) fileIObj로 지정된 파일에 대한 OutputStream을 생성하고, append 모드를 설정
    FileOutputStream(FileDescriptor fdObj) fdObj로 기존의 접속을 나타내는 파일 시스템의 OutputStream을 생성

     

    * append=true => 해당 파일에 데이터를 추가

    append=false => 해당 파일에 데이터를 덮어쓰기

    * FileDescriptor => 시스템으로부터 할당받은 파일을 대표하는 0이 아닌 정수 값

     

    import java.io.*;
    
    class FileCopy {
    	public static void main(String args[]) {
    		try {
    			FileInputStream fis = new FileInputStream("testRead.txt");
    			FileOutputStream fos = new FileOutputStream("testWrite.txt");
    
    			int data =0;
    
    			while((data=fis.read())!=-1) {
    				fos.write(data);
    			}
    
    			fis.close();
    			fos.close();
    		} catch (IOException e) {
    			e.printStackTrace();		
    		}
    	}
    }
    
    

     

    이렇게 testRead파일에 있는 데이터를 읽어와 testWrite파일에 써주는 작업을 수행할 수 있습니다.

     

    보조 스트림

     

    앞서 살펴 본 InputStream/OutputStream은 데이터의 내용을 1바이트씩 읽고 씁니다. 이는 효율이 떨어져 성능이 저하됩니다. 스트림의 기능을 보완하기 위한 보조 스트림이 있습니다. 보조 스트림은 실제 데이터를 입출력하는 스트림은 아니지만, 스트림의 성능 향상을 위한 역할을 합니다. 따라서 스트림을 먼저 생성한 후, 이를 통해 보조 스트림을 생성합니다.

    모든 보조 스트림은 FilterInputStream/FilterOutputStream을 상속받은 하위 클래스입니다.

     

    예를 들어 버퍼를 사용하여 입출력할 수 있도록 하는 보조 스트림인 BufferedInputStream을 사용하기 위해서는 다음과 같은 과정이 필요합니다.

     

    FileInputStream fis = new FileInputStream("testRead.txt");
    BufferedInputStream bis = new BufferedInputStream(fis);
    int data=0;
    while((data=bis.read())!=-1){
    	System.out.println((char)data);
     }
    

     

    test.txt 파일에 있는 데이터를 bis(BufferedInputStream)를 통해 읽어 오는 것처럼 보이지만 사실은 FileInputStream이 실제 입출력을 수행합니다. 

     

    이렇게 버퍼를 사용하게 되면 write() 메소드와 함께 flush()메소드를 알아야 합니다. 버퍼를 사용하는 경우 write()메소드 실행 시 버퍼에 데이터가 담기게 됩니다. 이 데이터를 flush()메소드를 호출함으로써 실제 데이터를 출력할 대상에 전달하게 되는 것입니다. flush()메소드는 버퍼가 가득 찼을 때 알아서 실행됩니다. 하지만 데이터를 모두 버퍼에 담았는데 버퍼가 가득 차지 않은 경우에는 데이터를 모두 읽었지만 전달이 되지 않는 상황이 발생할 수 있습니다. 따라서 InputStream과 OutputStream을 사용하는 경우 사용이 끝나면 close()메소드를 통해 닫아주는 과정이 필요합니다.

     

    FileInputStream fis = new FileInputStream("test.txt");
    BufferedInputStream bis = new BufferedInputStream(fis);
    int data=0;
    while((data=bis.read())!=-1){
    	System.out.println((char)data);
     }
    
    bis.close();

     

    다음과 같이 close()메소드를 호출하면 자동으로 flush()메소드를 실행해주고, 실제 스트림인 fis.close() 기능도 자동으로 수행됩니다.

    'Language > Java' 카테고리의 다른 글

    [JAVA] Generic Type  (0) 2020.02.01
    [JAVA] Reader/Writer  (0) 2019.12.07
    [JAVA] Thread와 Multi Thread  (0) 2019.11.12
    [JAVA] 다형성(Polymorphism)  (0) 2019.11.09
    [JAVA] 추상클래스와 인터페이스  (0) 2019.11.09
Designed by Tistory.