필기노트

자바 FileInputStream, FileOutputStream 본문

JAVA

자바 FileInputStream, FileOutputStream

우퐁코기 2022. 7. 20. 08:28
반응형
목차

1. FileInputStream & FileOutputStream (바이트 입출력 스트림)

2. DataInputStream & DataOutputStream (기본 자료형 필터 스트림)

3. BufferedInputStream & BufferedOutputStream (버퍼 필터 스트림)

4. DataBufferedInputStream & DataBufferedOutputStream

1. FileInputStream & FileOutputStream

먼저 Stream에 대해서 알아보자.

스트림이란? '데이터의 흐름', 또는 '데이터의 흐름을 형성해 주는 통로'를 의미한다.

프로그램으로 데이터를 읽어 들여야 하는 상황이라면, 입력 스트림을 형성해야 한다.

 

run.exe라는 파일에 저장된 데이터를 읽어들이기 위한 스트림을 형성한다고 가정해보자.

 

InputStream in = new FileInputStream("run.exe");

 

위의 한 문장을 통해서 우리는 다음의 두 가지 사실을 알 수 있다.

스트림의 형성이라는 것이 결국은 인스턴스의 생성이다.

FileInputStream 클래스는 InputStream 클래스를 상속한다.

 

FileInputStream은 입력 스트림의 형성을 위한 클래스이다.

그런데 그 대상이 파일이다.

즉 파일과의 입력 스트림 형성을 위한 클래스이다.

결국 위의 문장을 통해서 파일 run.exe에 저장되어 있는 데이터를 읽어 들이는 통로가 형성되는 셈이다.

 

이제 FileInputStream에 정의되어 있는 메소드를 통해서 데이터를 읽어 들이기만 하면 된다.

그런데 FileInputStream의 인스턴스를 InputStream의 참조변수로 참조하고 있음을 알 수 있다.

이는 FileInputStream 클래스가 InputStream 클래스를 상속하기 때문에 가능한 일인데,

InputStream 클래스는 바이트 단위로 데이터를 읽어 들이는 모든 입력 스트림이 상속하는 최상위 클래스이다

 

InputStream의 대표적인 메소드 둘은 다음과 같다.

• public abstract int read() throws IOException

• public void close() throws IOException

 

read 메소드는 1바이트의 데이터를 읽어서 반환하는 메소드이다.

그런데 이 메소드는 abstract로 선언되어 있다. 데이터를 읽어 들이는 기본 방식은 대상에 따라서 차이가 날 수밖에 없다.

즉 InputStream 클래스를 상속하는 하위 클래스에서 입력의 대상에 맞게 적절히 read 메소드를 정의하도록 하고 있다.

따라서 FileInputStream 클래스는 파일로부터 데이터를 읽어들이도록 read 메소드가 정의되고, 오버라이딩에 의해 FileInputStream의 read 메소드가 호출된다.

 

 

■ 입력 스트림과 출력 스트림을 동시에 생성하는 예제

import java.io.*;

class ByteFileCopy
{
    public static void main(String[] args) throws IOException
    {
        InputStream in = new FileInputStream("org.bin");	// 7행
        OutputStream out = new FileOutputStream("cpy.bin");
        
        int copyByte=0;
        int bData;
        
        while(true)				// 13행
        {
            bData=in.read();
            if(bData==-1)
                break;
                
            out.write(bData);
            copyByte++;
        }					// 21행
        
        in.close();				// 23행
        out.close();
        System.out.println("복사돈 바이트의 크기 "+copyByte);
    }
}
  • 7행 : 원본 파일인 org.bin을 대상으로 입력 스트림을 형성하고 있다. 해당 파일이 위 예제의 컴파일 결과인 class와 동일한 위치에 저장되어야 하며, 그렇지 않을 경우에는 파일의 이름에 경로정보도 함께 표시해야 한다.
  • 13~21행 : 원본에서 1바이트씩 읽어서 복사본에 저장하는 반복문을 구성하고 있다.
  • 23행 : 항상 잊지 말아야 할 것이 바로 close 메소드의 호출이다. 입출력이 온료되면 생성한 스트림의 소멸을 위해서 반드시 close 메소드를 호출해 주자!

 

■ 보다 빠른 속도의 파일 복사 프로그램

 

이번에는 바이트 단위 복사가 아닌, 버퍼를 이용한 복사를 진행해보고자 한다.

여기서 말하는 버퍼란 byte형 배열을 의미한다.

1KB 정도되는 byte 배열을 생성해서 1KB 단위의 복사를 진행해 보려고 한다.

이는 욕조의 물을 종이컵으로 퍼는 것과 바가지로 물을 퍼는 것에 비유할 수 있다.

 

public int read(byte[] b) throws IOException

 

이 메소드의 인자로는 byte형 배열의 참조를 전달한다.

그러면 입력 스트림을 통해서 읽어 들여진 데이터들이 배열에 저장된다.

그리고 위 메소드는 실제 읽어 들인 데이터의 바이트 크기를 반환한다.

예를 들어서 배열의 길이가 10이라면 최대 10바이트를 읽어 들일 수 있다.

그리고 실제로 10바이트 전부를 읽어 들였다면, 위의 메소드는 10을 반환된다.

그러나 5바이트를 읽어 들였다면(남아 있는 데이터가 5바이트라서), 5가 반환된다.

물론 더 이상 읽을 데이터가 존재하지 않으면 -1이 반환된다. 

 

public void write(byte[] b, int off, int len) throws IOException

 

위 메소드는 매개변수 b로 전달된 배열을 대상으로 off의 인덱스 위치서부터 시작해서 len 바이트를 출력 스트림을 통해서 전송하는 메소드이다.

import java.io.*;

class ByteFileCopy
{
    public static void main(String[] args) throws IOException
    {
        InputStream in = new FileInputStream("org.bin");
        OutputStream out = new FileOutputStream("cpy.bin");	
        
        int copyByte=0;
        int readLen;
        byte buf[]=new byte[1024];	// 12행
        
        while(true)
        {
            readLen=in.read(buf);	// 16행
            if(readLen==-1)
                break;
                
            out.write(buf, 0, readLen);	// 19행
            copyByte+=readLen;
        }
        
        in.close();
        out.close();
        System.out.println("복사돈 바이트의 크기 "+copyByte);
    }
}
  • 12행 : 길이가 1024인 byte형 배열을 생성하였다. 즉 1KB짜리 버퍼를 생성한 셈이다.
  • 16행 : read 메소드의 호출을 통해서 배열 buf에 데이터를 채워 넣고 있다. 실제로 채워진 데이터의 크기는 변수 readLen에 저장된다.
  • 19행 : buf에 저장된 데이터를 인덱스 0의 위치서부터 시작해서 readLen의 크기만큼 전송하고 있다. 즉 16행을 통해서 읽어 들인 데이터 전부를 전송하고 있는 셈이다.

 


2. DataInputStream & DataOutputStream

필터 스트림이란? 그 자체로 파일과 같은 소스로부터 데이터를 읽는 기능은 지니고 있지 않다.

다만 입출력 스트림으로부터 읽혀진 데이터를 다양하게 가공하는 기능만 있을 뿐이다.

 

야! 누가 배열에 담아달래? int형 변수에 담아줘야지! 

 

기본 자료형 데이터를 읽고 쓰게 하는 필터 스트림

public class DataFilterStream {
    public static void main(String[] args) throws IOException {
        OutputStream out = new FileOutputStream("data.bin");	// 7행
        DataOutputStream filterOut = new DataOutputStream(out); // 8행
        filterOut.writeInt(275);
        filterOut.writeDouble(45.79);
        filterOut.close();	// 11행

        InputStream in = new FileInputStream("data.bin");
        DataInputStream filterIn = new DataInputStream(in);
        int num1 = filterIn.readInt();	// 15행
        double num2 = filterIn.readDouble();
        filterIn.close();

        System.out.println(num1);
        System.out.println(num2);
    }
}
  • 7, 8행 : 7행에서 파일 대상의 출력 스트림을 생성하고, 8행에서는 이 출력 스트림의 참조 값을 이용해서 DataOutputStream 클래스의 인스턴스를 생성하고 있다. 8행에서 보이는 이 과정이 출력 스트림에 필터를 연결하는 과정이다.
  • 11행 : 필터 스트림을 대상으로 close 메소드를 호출하면, 필터 스트림은 물론이거니와 필터에 연결된 입출력 스트림도 함께 소멸된다. 따라서 7행에서 생성한 스트림을 대상으로 close 메소드를 호출할 필요는 없다.
  • 13, 14행 : 이번엔 반대로 입력 스트림을 생성하고, 이를 기본 자료형 단위로 데이터를 반환하는 필터 입력 스트림에 연결하고 있다. 이로써 바이트 단위로 데이터를 읽는 게 아니라 int, double과 같은 기본 자료형 단위로 데이터를 읽을 수 있게 되었다.
  • 15행 : 필터 입력 스트림을 통해서 int형 데이터 하나를 읽기 위해서 readInt 메소드를 호출하고 있다. 이 메소드가 호출되면 13행에서 생성한 입력 스트림을 통해서 4바이트가 읽혀지고, 이는 다시 필터 입력 스트림에 의해서 하나의 정수로 묶여서 반환된다.

 


3. BufferedInputStream & BufferedOutputStream

파일이라는 녀석을 대상으로 데이터를 송수신 하는 일은 비교적 오래 걸리는 작업이다.

이유는 하드디스크에 저장되어 있는 파일과 현재 실행중인 자바 프로그램과의 거리가 너무 멀기 때문이다.

따라서 먼 거리에 데이터를 전송할 때에는, 한번에 많은 양을 묶어서 보내는 것이 효율적이다.

종이컵으로 물을 퍼는 것 보다, 바가지로 한번에 많은 양의 물을 옮기는 것이 시간적으로 훨씬 효율적이지 않은가?

데이터의 전송도 이와 마찬가지이다.그래서 입출력 스트림에서도 버퍼를 활용하면 성능 향상에 많은 도움이 된다. 

 

버퍼스트림은 내부적으로 버퍼(쉽게 말해서 byte형 배열)를 지니고 있다.

아래 예제에서는 버퍼의크기를 지정하지 않았기 때문에 디폴트 크기의 버퍼(디폴트 크기는 2MB이다)가 만들어지지만,

다음의 생성자들을 이용하면 버퍼의 크기도 원하는 대로 지정할 수 있다.

public BufferedInputStream(InputStream in, int size) / public BufferedOutputStream(outputStream out, int size)

import java.io.*;

class ByteBufferdFileCopy
{
    public static void main(String[] args) throws IOException
    {
        InputStream in = new FileInputStream("org.bin");
        OutputStream out = new FileOutputStream("cpy.bin");
        
        BufferedInputStream bin = new BufferedInputStream(in);		// 10행
        BufferedOutputStream bout = new BufferedOutputStream(out);	// 11행
        
        int copyByte=0;							// 13행
        int bData;
        
        while(true)
        {
            bData=bin.read();
            if(bData==-1)
                break;
                
            bout.write(bData);
            copyByte++;
        }
        
        bin.close();
        bout.close();
        System.out.println("복사된 바이트 크기 "+ copyByte);		// 28행
    }
}
  • 10, 11행 : 7, 8행에서 생성한 입출력 스트림에 버퍼 필터 입출력 스트림을 연결하고 있다.

첫 번째 예제와 비교해서 아무것도 달라지지 않았다.

바이트 단위로 데이터를 읽어서 바이트 단위로 데이터를 전송하면서 복사를 진행하고 있다.

호출하는 메소드의 이름도 동일하다.

 10행과 11행에서 생성한 필터 입출력 스트림을 기반으로 복사를 진행할 뿐이다. 

 

그러나 read 메소드는 이름은 같지만 동작방식에서는 매우 큰 차이를 보인다.

이전 예제에서는 read 메소드가 호출되면, 파일과 입력된 입력 스트림을 통해서 1바이트를 읽어 들였다.

즉 파일로부터 1바이트를 읽어 들인 것이다.

하지만 위 예제에서는 read 메소드가 호출되면, 파일이 아닌 메모리상에 이미 저장되어 있는 데이터를 읽어 들인다.

이는 배열에서 값을 읽는 것과 그 형태가 유사하다.

따라서 매우 빠른 속도로 데이터를 읽을 수 있는 것이다.

 

스트림을 종료시킬 때 close 메소드를 호출한다.

그런데 이 메소드가 호출되면, 그리고 내부적으로 유지하고 있는 버퍼가 있다면, 이 버퍼가 비워진 다음에 스트림이 종료된다.

따라서 늘 flush 메소드를 호출해야 하는 것은 아니다.

 


4. DataBufferedInputStream & DataBufferedOutputStream

파일에 double형 실수를 저장하고픈데, 버퍼링 기능까지 추가하려면 어떻게 해야겠는가?

 

DataOutputStream의 생성자를 함께 보자

-> public DataOutputStream(OutputStream out)

 

BufferedOutputStream 인스턴스는 OutputStream을 상속하기 때문에

다음과 같은 코드이 구성이 가능하다.

public class DataBufferFilterStream {
    public static void main(String[] args) throws IOException {
        OutputStream out = new FileOutputStream("data.bin");	// 7행
        BufferedOutputStream bufFilterOut = new BufferedOutputStream(out);
        DataOutputStream DataFilterOut = new DataOutputStream(bufFilterOut);

        DataFilterOut.writeInt(275);
        DataFilterOut.writeDouble(45.79);
        DataFilterOut.close();	// 13행

        InputStream in = new FileInputStream("data.bin");
        BufferedInputStream bufFilterIn = new BufferedInputStream(in);
        DataInputStream dataFilterIn = new DataInputStream(bufFilterIn);
        
        int num1 = dataFilterIn.readInt();
        double num2 = dataFilterIn.readDouble();
        dataFilterIn.close();

        System.out.println(num1);
        System.out.println(num2);
    }
}
  • 7~9행 : 출력 스트림을 대상으로 두 개의 필터 스트림을 연결하고 있다.
  • 13행 : 가장 마지막에 연결된 필터 스트림만 종료를 하면, 이에 연결된 모든 스트림이 차례대로 종료된다. 그래서 DataOutputStream을 대상으로 close 메소드를 호출하고 있다.

 


출처 : 난 정말 JAVA를 공부한 적이 없다구요! - Chapter24 파일과 I/O 스트림

반응형
Comments