ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Camel][Java] InputStream & OutputStream (파일 입출력)
    Java/개념 정리 2020. 3. 3. 20:09

    파일 입출력 in Java

     Java에서는 입출력 대상에 상관없이 입출력의 진행방식이 동일하도록 별로의 I/O 모델이 정의되어 있습니다. 이는 Java의 I/O 모델을 기반으로 데이터를 입출력 한다면 입출력 대상에 상관없이 동일한 형태로 데이터를 입출력할 수 있다는 것입니다. 하지만 이것은 입출력의 기본방식이 동일하다는 뜻일뿐, 입출력 대상에 따라 사용해야하는 클래스 및 메소드는 다를 수 있습니다. 

     

    Stream의 이해

     I/O 모델을 이해하기 위해서는 Stream이라는 것을 이해해야합니다. Stream이란 데이터의 흐름을 말하며 데이터의 흐름을 형성하는 통로를 의미하기도 합니다. Java에서는 각종 I/O 장치의 데이터 이동에 사용되는 인스턴스를 의미하는 용도로 사용됩니다. Stream은 크게 프로그램으로 데이터를 읽어오는 Input Stream, 프로그램으로 데이터를 내보내는 Output Stream으로 나눌수 있습니다. Stream을 형성하는 방법은 다음과 같습니다. 

    InputStream in = new FileInputStream("Camel.txt");

     위의 예시를 통해 알 수 있는 것은 Stream의 형성은 인스턴스의 생성이라는 것, 그리고 FileInputStream 클래스는 InputStream 클래스를 상속한다는 것입니다. FileInputStream 클래스는 InputStream의 형성을 위한 파일을 대상으로 하는 클래스입니다. Stream을 형성해 인슨턴스를 생성한 것은 데이터의 흐름을 위한 통로를 만든 것입니다. 

     

    InputStream 클래스

     InputStream 클래스는 Byte 단위로 데이터를 읽어오는 모든 Input Stream 클래스의 최상위 클래스입니다. Input Stream클래스에 정의되어 있는 대표적인 메소드는 다음과 같습니다. 

    public abstract int read() throws IOException
    public void close() throws IOException

     read 메소드의 경우 abstract로 선언되어 있는데, 그 이유는 데이터를 읽어들이는 기본 방식은 대상에 따라 다를 수 밖에 없기 때문입니다. 그렇기 때문에 InputStream 클래스를 상속하는 하위 클래스들은 read 메소드를 오버라이딩하기 때문에  read 메소드에서 하는 일 역시 차이가 있습니다. 그리고 read 메소드의 반환형이 int형인 이유는 더이상 읽어 들일 데이터가 없다면 -1을 반환하기 때문입니다. 

     

     close 메소드는 생성했던 Stream을 소멸시키는 메소드입니다. JVM이 할당했던 각종 자원들을 메모리상에서 소멸시키기 위함입니다. 사용 방법은 아래와 같습니다.

    import java.io.*;
    
    class CamelIO {
    	public static void main ( String[] args ) {
        		InputStream in = new FileInputStream("camel.exe");
            	while (1) {
            		int myData = in.read();
                		if(myData == -1) break;
            	}
            	in.close();
        	}
    }

    OutputStream 클래스

     OutputStream는 InputStream에 대응하는 클래스입니다. OutputStream 클래스는 OutputStream 클래스가 상속하는 최상위 클래스입니다. OutputStream 클래스의 대표적인 메소드는 다음과 같습니다. 

    public abstract void write(int a) throws IOException
    public void close() throws IOException

    write 메소드가 abstract로 선언된 이유는 read 메소드와 동일하며, close 메소드의 기능 역시 동일합니다. write 메소드의 경우 매개변수가 int형으로 선언되어 있는데, 이 때 매개변수로 전달된 4byte 중 하위 1Byte만 전달되고 나머지 3Byte는 무시됩니다.

     

     

    InputStream & OutputStream 사용 예시

    import java.io.*;
    
    class CamelIO {
    	public static void main ( String[] args ) {
        		InputStream in = new FileInputStream("camel.txt");
                	OutputStream out = new FileOutputStream("pig.txt");
            	while (1) {
            		int myData = in.read();
                		if(myData == -1) break;
                        	out.write(myData);
            	}
            	in.close();
                	out.close();
        	}
    }

    이 프로그램은 파일의 내용을 복사하는 프로그램입니다. 하지만 파일의 내용이 매우 크게 될 경우 한번에 1Byte씩 복사하는 이런 프로그램은 소요시간이 굉장히 길 수 밖에 없습니다. 이러한 단점을 보완하기 위해선 다음의 메소드를 사용하면 됩니다. 

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

    위 두 메소드는 Byte형 배열의 참조를 전달합니다. read 메소드의 경우 InputStream을 통해 읽어온 데이터는 배열에 저장되게 되는 것입니다. write 메소드의 경우 전달된 배열을 대상으로 off 인덱스 위치부터 len길이의 Byte 만큼 OutputStream을 통해 전송하는 메소드 입니다. 활용 예시는 아래와 같습니다.  

    import java.io.*;
    
    class CamelIOUserBuffer {
    	public static void main ( String[] args ) {
        		InputStream in = new FileInputStream("camel.bin");
                	OutputStream out = new FileOutputStream("pig.bin");
                    
                    int len;
                    byte buff[] = new byte[1024];
                    
            	while (1) {
            		len = in.read(buff);
                		if(myData == -1) break;
                        	out.write(buff, 0, len);
            	}
            	in.close();
                	out.close();
        	}
    }

     

     

    Filter Stream (필터 스트림)

     위에서 다뤘던 FileInputStream과 FileOutputStream 클래스의 경우는 데이터의 입출력 대상이 직접 연결되는 클래스입니다. 반면에 이런 클래스들과는 다르게 기능을 보조하는 성격의 스트림도 존재합니다. 그것이 바로 필터 스트림입니다. 

     

     이러한 필터 스트림은 int, float과 같은 기본 자료형 데이터를 파일로부터 읽거나 쓰는 일을 하기 위해 사용됩니다. 우리는 기존에 필터 스트림 없이 파일로부터 데이터를 읽거나 쓸때 배열의 형태로 데이터를 읽어왔습니다. 하지만 필터 스트림을 사용하면 우리가 원하는 형태로 데이터를 읽어오거나 쓸 수 있습니다.  

     

     간단한 예로 우리의 실생활에서의 면뽑는 기계라고 생각하면 됩니다. 기계에 소면을 뽑을 필터를 달면 소면을 뽑을 수 있고, 중면을 뽑을 필터를 달면 중면을 뽑을 수 있는 것과 같은 이치입니다. 

     

    DataInputStream & DataOutputStream 클래스

     Java에는 다양한 종류의 필터 스트림이 존재하지만 그 중 하나인 DataInputStream & DataOutputStream 클래스에 대해 설명하겠습니다. DataInputStream & DataOutputStream 클래스를 사용하면 기본 자료형 데이터의 입출력을 간단하게 할 수 있습니다. 

    import java.io.*;
    
    class DataFilter {
    	public static void main ( String[] args ) {
        		
                OutputStream out = new FileOutputStream("pig.bin");
                //출력 스트림의 참조 값을 이용해서 DataOutputStream 클래스의 인스턴스를 생성한다. 
                //이를 통해 출력스트림에 필터를 연결할 수 있다. 
                DataOutputStream outFilter = new DataInputStream(out);
                outFilter.writeInt(183);
                outFilter.close();
                
                InputStream in = new FileInputStream("camel.bin");
                DataInputStream inFilter = new DataInputStream(in);
                int num = inFilter.readInt();
                inFilter.close();
                
                System.out.println(num); // 183 출력
        	}
    }

    위 코드의 DataOutputStream outFilter = new DataInputStream(out); 부분은 출력 스트림의 참조 값을 이용해서 DataOutputStream 클래스의 인스턴스를 생성하는 것을 의미합니다. 이를 통해 출력스트림에 필터를 연결한 것입니다. 

     

    BufferedInputStream & BufferedOutputStream 클래스

     BufferedInputStream & BufferedOutputStream 클래스는 필터 스트림 중에서 상대정으로 사용하는 경우가 많은 클래스 입니다. 우선은 사용 예시부터 보이겠습니다.   

    import java.io.*;
    
    class BufferedCopy {
    	public static void main ( String[] args ) {
        		InputStream in = new FileInputStream("camel.bin");
                	OutputStream out = new FileOutputStream("pig.bin");
                
                	BufferedInputStream buffIn = new BufferedInputStream(in);
                	BufferedOutputStream buffOut = new BufferedOutputStream(out);  
                
                    int myData;
                    
            	while (1) {
            		myData = buffIn.read();
                		if(myData == -1) break;
                        	buffOut.write(myData);
            	}
            	buffIn.close();
                	buffOut.close();
        	}
    }

     클래스의 이름에서부터 알 수 있듯이 위 프로그램은 파일 복사 기능을 수행하는 프로그램입니다. 위에서 설명했던 Byte형 배열의 선언 없이 파일을 복사하는 프로그램과는 어떠한 차이점이 있을까? 그 차이점은 바로 '복사 속도'입니다. 이러한 방식은 Byte형 배열의 선언을 사용한 방식과 거의 동등한 속도를 보입니다.

     

     BufferedInputStream과  BufferedOutputStream은 버퍼, 즉 Byte형 배열을 지니고 있습니다. 내부에 지니고 있는 버퍼의 크기는 디폴트 값은 2MB입니다. 하지만 아래와 같은 생성자를 통해 버퍼의 크기를 조절할 수 있습니다. 

    public BufferedInputStream(InputStream in, int buffSize);
    public BufferedOutputStream(OutputStream out, int buffSize);

     

     필터 스트림인 BufferedInputStream이 입력 스트림에 연결되면 read 메소드를 사용해 프로그램상에서 데이터를 요청하지 않아도 입력 스트림으로부터 데이터를 읽어와 버퍼에 저장하게 됩니다. BufferedInputStream의 버퍼를 채우는 방식은 1Byte씩 채우는 겂이 아니라 한번에 많은 양의 데이터를 채웁니다. 그렇기 때문에 우리는 속도 저하를 느끼지 못하는 것입니다.

     

     BufferedOutputStream의 경우에는 컴퓨터상에서 프로그램과 파일의 거리가 멀기때문에 한번에 최대한 많은 양의 데이터를 모았다가 전달해야 효율적입니다. 그렇기 때문에 BufferedOutputStream은 자신의 버퍼가 가득차게 되면 출력스트림으로 데이터를 전송하게 됩니다. 

     

    flush 메소드

     데이터가 버퍼에 저장 중이었지만, 갑작스럽게 프로그램이 종료되는 경우에는 버퍼에 저장되어 있던 데이터가 소멸되게 되는 문제가 생길 수 있습니다. 이런 경우를 대비해서 우리에게는 flush라는 메소드가 있습니다. flush 메소드는 버퍼가 꽉차지 않았더라도 출력 스트림을 통해서 데이터를 보내고 버퍼를 비워주는 메소드입니다. 

     

     이러한 flush 메소드는 안전을 위해서 호출을 해주는 것이 좋지만 너무 빈번하게 호출하게되면 성능적인 면에서 문제가 될 수 있습니다. close 메소드의 경우에도 스트림을 종료시키면서 내부에 존재하던 버퍼를 모두 비운 후 종료되게 됩니다. flush 메소드와 같은 기능이 포함되 있는 것입니다.  

     

    댓글

Camel`s Tistory.