[JAVA] ByteStream : FileInputStream / FileOutputStream

2019. 8. 6. 18:30 JAVA/Java IO

ByteStream : FileInputStream / FileOutputStream




직접 키보드를 통하여 입력하는 데이터는 대개 임시 자료인 경우가 많다. 중요한 자료는 대부분 데이터베이스에 저장되어 있거나 파일 시스템에 저장된다. FileInputStream과 FileOutputStream은 바이트 단위로 파일을 통한 입출력을 처리한다.


[JAVA/Java IO] - [JAVA] ByteStream : InputStream / OutputStream

[JAVA/Java IO] - [JAVA] File 클래스 정리 (파일정보, 파일목록, 하드디스크 정보 출력)



① FileInputStream


FileInputStream 클래스는 InputStream 클래스를 상속받은 후손 클래스로 하드 디스크상에 존재하는 파일로부터 바이트 단위의 입력을 받는 클래스이다. 이 클래스는 출발 지점과 도착 지점을 연결하는 통로, 즉 스트림을 생성하는 클래스이다.


생성자의 인자로는 File 객체를 주거나 파일의 이름을 직접 String 형태로 줄 수 있다. 일반적으로 파일의 이름을 String 형태로 주는 경우가 많은데 파일이 존재하지 않을 가능성도 있으므로 Exception 처리를 해야 한다.


* FileInputStream의 생성자


FileInputStream의 생성자 

설명 

FileInputStream(String filePath)

throws FileNotFoundException 

- filepath로 지정한 파일에 대한 입력 스트림을 생성한다. 

FileInputStream(File fileObj)

throws FileNotFoundException 

- fileObj로 지정한 파일에 대한 입력 스트림을 생성한다. 

 FileInputStream(FileDescriptor fdObj)

throws SecurityException

- fdObj 로 기존의 접속을 나타내는 파일 시스템의 입력 스트림을 생성한다. 



* FileInputStream의 메소드


FileInputStream의 메소드 

설명 

int available() throws IOException 

- 현재 읽을 수 있는 바이트 수를 반환한다.

int close() throws IOException 

- InputStream을 닫는다. 

int read() throws IOException 

- InputStream에서 한 바이트를 읽어서 int 값으로 반환한다. 

int read(byte buf[]) throws IOException 

- InputStream에서 buf[] 크기만큼을 읽어 buf에 저장하고 읽은 바이트 수를 반환한다.

int read(byte buf[], int offset, int numBytes) throws IOException 

- InputStream에서 nnumBytes만큼을 읽어 buf[]의 offset 위치부터 저장하고 읽은 바이트 수를 반환한다.

int skip(long numBytes) 

throws IOException 

- numBytes로 지정된 바이트를 스킵하고 스킵된 바이트 수를 반환한다. 

protected void finalize()

- 더이상 참조하는 것이 없을 경우 close() 메소드를 호출한다.

FileChannel getChannel() 

- FileInputStream의 유일한 FileChannel 객체를 반환한다.

FileDescriptor getFD() 

- FileInputStream에서 실제 파일에 접속에 대한 FileDescriptor 객체를 반환한다. 



② FileOutputStream 


FileOutputStream 클래스도 OutputStream 클래스의 후손 클래스로 파일로 바이트 단위의 출력을 내보내는 클래스이다.

Sink 스트림의 일종으로 3 개의 생성자가 중복 정의되어 있다. FileInputStream의 생성자와 거의 같은 형태인데 하나 더 있는 생성자의 형식은 append 처리를 위한 논리 변수를 인자로 가지고 있다. 이 값이 true로 설정되면 기존에 존재하고 있는 파일의 가장 뒷 부분에 연결하여 출력된다.


* FileOutputStream의 생성자


FileOutputStream의 생성자 

설명 

FileOutputStream(String filepath) throws FileNotFoundException 

- filepath로 지정한 파일에 대한 OutputStream을 생성

FileOutputStream(String filepath, boolean append)

throws FileNotFoundException

- 지정한 파일로 OutputStream을 생성한다. append 인자로 출력할 때 append 모드를 설정한다. 

FileOutputStream(File fileObj) throws FileNotFoundException

- fileObj로 지정된 파일에 대한 OutputStream을 생성한다. 

FileOutputStream(File fileObj, boolean append)

throws FileNotFoundException 

- fileObj로 지정된 파일에 대한 OutputStream을 생성한다. append 인자로 출력할 때 append 모드를 설정한다. 

FileOutputStream(FileDescriptor fdObj)

throws NullPointerException

- fdObj 로 기존의 접속을 나타내는 파일 시스템의 OutputStream을 생성한다.  


* FileOutputStream의 메소드


FileOutputStream의 메소드 

설명 

void close() throws IOException

- OutputStream을 닫는다. 

void flush() throws IOException 

- 버퍼에 남은 OutputStream을 출력한다. 

void write(int i) throws IOException 

- 정수 i의 하위 8비트를 출력한다. 

void write(byte buf[]) 

throws IOException 

- buf의 내용을 출력한다. 

void write(byte buf[], int index, int size) throws IOException 

- buf의 index 위치부터 size만큼의 바이트를 출력한다. 

FileChannel getChannel()

- OutputStream과 연관된 유일한 FileChannel 객체를 반환한다. 

FileDescriptor getFD() 

- OutputStream과 연관된 FileDescriptor 객체를 반환한다. 



* 예제


(1) FileInputStream / FileOutputStream을 이용한 파일 복사 예제


FileStreamTest1.zip


 

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Date;


public class FileStreamTest {
   
    public static void main(String[] args){ 
   
        FileInputStream fis = null;
        FileOutputStream fos = null;
       
        Date d = null;
       
        try{
           
            // "FileStream.txt" FileInputStream 객체를 생성한다.
            // "copyFile.txt" 파일에 FileOutputStream 객체를 생성한다.
            // 데이터 출력시 현재 데이터의 마지막에 추가된다.
            fis = new FileInputStream("FileStream.txt");
            fos = new FileOutputStream("copyFile.txt", true);
           
            int i = 0;
            d = new Date();
            long start = d.getTime();
           
            // FileInputStream에서 데이터를 1바이트씩 읽는다.
            while((i=fis.read())!=-1){
               
                // FileOutputStream에 읽은 데이터를 출력한다.
                fos.write(i);
               
            }
           
            d= new Date();
            long end = d.getTime();
           
            System.out.println("복사 시간 : " +(end-start));
           
        }catch(Exception e){
            e.printStackTrace();
       
        }finally{
           
            // 마지막에 FileInputStream과 FileOutputStream을 닫아준다.
            if(fis != null) try{fis.close();}catch(IOException e){}
            if(fos != null) try{fos.close();}catch(IOException e){}
           
        }
    }
}



- "FileStream.txt" 파일에 대한 FileInputStream 객체를 생성한다.

- "copyFile.txt" 파일에 대해 FileOutputStream 객체를 생성하며, append 매개변수를 true로 줌으로써, "copyFile.txt" 의 마지막 데이터 뒤에 출력한다.

- fis.read() 를 통해 "FileStream.txt" 에서 1바이트씩 데이터를 읽어서 i 에 저장하고 데이터가 없으면 -1 을 반환하여 while() 문을 빠져나온다.

- FileInputStream()과 FileOutputStream()은 마지막에 close() 로 닫아준다.

- 결과를 확인하면 "FileStream.txt" 파일의 데이터가 "copyFile.txt" 데이터 뒤에 추가 되어 있는 것을 볼 수 있다.



(2) 버퍼링을 통한 개선


FileStreamTest2.zip


위의 예제는 상당히 안 좋은 형태의 예제이다. 파일을 읽을 때 1바이트씩 읽고 쓰기를 반복하고 있으므로 프로그램의 효율이 매우 좋지 않다.

읽고 쓰는 입출력 작업은 기계적인 동작이며 이런 기계적인 동작은 전기적인 동작에 비교해 볼 때 엄청나게 많은 시간을 요구한다. 따라서 효율적인 프로그램을 작성하고 싶다면 기계적인 동작의 횟수를 줄이는 것이 가장 좋다.


결론적으로 입출력 횟수를 줄이면 프로그램의 효율을 높일 수 있으며 더 빠른 프로그램으로 개선할 수 있다. 이를 해결하기 위해서 1바이트씩 읽어 들이는 read() 메소드보다 한꺼번에 많은 데이터를 바이트의 배열로 읽어 들이는 read(byte[]) 메소드를 사용한다.


 

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Date;


public class FileStreamTest {
   
    public static void main(String[] args){ 
   
        FileInputStream fis = null;
        FileOutputStream fos = null;
       
        Date d = null;
       
        try{
           
            // "FileStream.txt" FileInputStream 객체를 생성한다.
            // "copyFile.txt" 파일에 FileOutputStream 객체를 생성한다.
            // 데이터 출력시 현재 데이터의 마지막에 추가된다.
            fis = new FileInputStream("FileStream.txt");
            fos = new FileOutputStream("copyFile.txt", true);
           
            // "FileStream.txt" 에서 읽을 수 있는 바이트 수를 반환하고
            // 그 크기만큼 byte[] 를 생성한다.
            int size = fis.available();
            byte[] buf = new byte[size];
           
            d= new Date();
            long start = d.getTime();
           
            // "FileStream.txt" 에서 buf 크기만큼을 읽어온다.
            // "copyFile.txt" 에 buf 배열 0 부터 readCount 만큼을 출력한다.
            int readCount = fis.read(buf);
            fos.write(buf, 0, readCount);
           
            d= new Date();
            long end = d.getTime();
           
            System.out.println("복사 시간 : " +(end-start));
           
        }catch(Exception e){
            e.printStackTrace();
       
        }finally{
           
            // 마지막에 FileInputStream과 FileOutputStream을 닫아준다.
            if(fis != null) try{fis.close();}catch(IOException e){}
            if(fos != null) try{fos.close();}catch(IOException e){}
           
        }
    }
}


-  "FileStream.txt" 파일의 데이터 바이트 수를 가져온 뒤 그 크기만큼의 byte[] 배열을 만든다.

- "FileStream.txt" 에서 그 크기만큼을 한번에 읽어오고 "copyFile.txt"에 한번에 출력한다.

- 앞선 예제에서 1 바이트씩 읽기와 쓰기가 반복되는 것과는 달리 한번에 읽어서 한번에 출력하므로, 기계적인 입출력 횟수가 급감하였다.




(3) 버퍼링 개선의 문제점과 사용자 버퍼링


FileStreamTest3.zip


위의 예제처럼 파일을 통째로 한꺼번에 읽는 것은 기계적인 동작을 최소화하기 위한 방법이다. 하지만 무조건 버퍼의 크기가 큰 것이 좋은 것인가에 대해서는 조금 더 깊이 생각해 보아야 한다.

버퍼가 크면 메모리의 낭비를 유발할 수 있다. 실제로 필요한 데이터는 4바이트 인데 통째로 데이터를 가져와서 작업을 하기 때문이다. 또한 파일의 크기가 엄청나게 클 경우 메모리가 부족해져 Exception이 발생할 수 있다.

(실제 로그 파일의 경우 파일당 GB가 넘을 수 있다.)


따라서, 버퍼의 크기를 적정선에서 만족할 수 있도록 잡아서 입력과 출력의 반복이 필요하며, 보통 1024, 2048, 4096, 8192 와 같이 배수 형식으로 버퍼의 크기를 잡는 것이 일반적이다.


아래 예제는 1024 바이트 크기의 버퍼를 잡아 입출력을 반복한다.


 

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Date;


public class FileStreamTest {
   
    public static void main(String[] args){ 
   
        FileInputStream fis = null;
        FileOutputStream fos = null;
       
        Date d = null;
       
        try{
           
            // "FileStream.txt" FileInputStream 객체를 생성한다.
            // "copyFile.txt" 파일에 FileOutputStream 객체를 생성한다.
            // 데이터 출력시 현재 데이터의 마지막에 추가된다.
            fis = new FileInputStream("FileStream.txt");
            fos = new FileOutputStream("copyFile.txt", true);
           
            // buf 배열의 크기를 1024 로 지정한다.
            byte[] buf = new byte[1024];
            int readCount = 0;;
           
            d= new Date();
            long start = d.getTime();
           
            // "FileStream.txt" 에서 buf 크기(1024) 만큼 읽는 것을 반복한다.
            while((readCount = fis.read(buf)) != -1){
               
                // 읽어온 데이터(buf)를 "copyFile.txt"에 출력한다.
                fos.write(buf, 0, readCount);
               
            }
           
            d= new Date();
            long end = d.getTime();
           
            System.out.println("복사 시간 : " +(end-start));
           
        }catch(Exception e){
            e.printStackTrace();
       
        }finally{
           
            // 마지막에 FileInputStream과 FileOutputStream을 닫아준다.
            if(fis != null) try{fis.close();}catch(IOException e){}
            if(fos != null) try{fos.close();}catch(IOException e){}
           
        }
    }
}


- 다시 while()문을 통한 입/출력의 반복으로 돌아갔다.

- 하지만 이번에는 1바이트씩의 입/출력이 아닌 buf[]의 크기 "1024" 만큼의 데이터 입/출력을 반복한다.

- FileInputStream으로 읽어오는 데이터를 모두 사용하고 용량이 크지 않다면, 앞의 예제처럼 한번에 입력과 출력을 수행하는 것이 효율적일 것이다. 

- 하지만, 한번에 메모리에 담아서 처리하기에는 용량이 크거나, 전체에 비해 일부의 데이터만 사용한다면, 한번에 읽어오는 것은 비효율을 유발할 수 있다.



JAVA에서는 BufferedInputStream과 BufferedOutputStream 과 같이 입출력을 더 효율적으로 할 수 있는 방법을 제공한다.

BufferedInputStream과 BufferedOutputStream 역시 FileInputStream,과 FileOutputStream을 이해를 해야 적절하게 사용할 수 있다.

BufferedInputStream과 BufferedOutputStream는 별도의 포스팅(트랙백)을 확인하기 바란다.



JAVA API DOC : FileInputStream

JAVA API DOC : FileOutputStream



출처: https://hyeonstorage.tistory.com/236?category=578560 [개발이 하고 싶어요]