라봉이의 개발 블로그

java의 정석 14강. 입출력 I/O (상) 본문

Java/Java의 정석 읽고 정리

java의 정석 14강. 입출력 I/O (상)

Labhong 2018. 6. 13. 16:36
반응형

1. 자바에서의 입출력

1.1 입출력이란?


입출력이란 컴퓨터 내부 또는 외부의 장치와 프로그램간의 데이터를 주고받는 것을 말한다.



1.2 스트림


스트림이란 일종의 추상적인 개념인데 입출력 기기나 프로세스, 파일 등 데이터가 어디로 가는 지, 어디로 나왔는 지 상관없이 통일된 방식으로 데이터를 다루기 위한 가상의 개념이다.

Node.js의 stream을 알고 싶다면 다음의 링크를 타고가면 된다.

http://psyhm.tistory.com/26


스트림은 먼저 보낸 데이터를 먼저 받게 되어 있으며 중간어 건너뜀 없이 연속적으로 데이터를 주고받는다. 큐와 같은 FIFO 구조로 되어 있다고 생각하면 이해하기 편할 것이다.



1.3 바이트기반 스트림 - InputStream, OutputStream


스트림은 바이트단위로 데이터를 전송하며 입출력 대상에 따라 다음과 같은 입출력스트림이 있다.


 입력 스트림

출력 스트림

압출력 대상의 종류 

 FileInputStream

 FileOutputStream 

  파일

 ByteArrayInputStream

 ByteArrayOutputStream

  메모리(byte 배열) 

 PipedInputStream

 PipedOutputStream 

  프로세스(프로세스간 통신)

 AudioInputStream 

 AudioOutputStream 

  오디오장치 


어떤 작업을 할 것인지에 따라 나뉘므로 적절한 상황에 맞게 사용하면 된다.

이들 모두 InputStream, OutputStream의 자손들이며, 각각 읽고 쓰는데 필요한 추상메서드를 자신에 맞게 구현해 놓았다.


stream은 입출력을 처리할 수있는 표준화된 방법을 제공함으로써 입출력의 대상이 달라져도 동일한 방법으로 입출력이 가능하다.


 InputStream

OutputStream 

 abstract int read() 

 abstract void write(int b) 

 int read(byte[] b) 

 void write(byte[] b) 

 int read(byte[] b, int off, int len) 

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


위의 표는 InputStream과 OutputStream에 정의된 읽기와 쓰기를 수행하는 메서드이다.



1.4 보조스트림


위의 표1의 스트림의 기능을 보완하기 위한 보조스트림이 제공된다.

보조스트림은 실제 데이터를 주고받는 스트림이 아니기 때문에 데이터를 입출력할 수 있는 기능은 없지만, 스트림 기능을 향상시키거나 새로운 기능을 추가할 수 있다.


예를 들어 test.txt라는 파일을 읽기위해 FileInputStream을 사용할 때, 입력 성능을 향상시키기 위해 버퍼를 사용하는 보조스트림인 BufferedInputStream을 사용하는 코드는 다음과 같다.

// 먼저 기반스트림을 생성
FileInputStream fis = new FileInputStream("test.txt");
// 기반스트림을 이용해서 보조스트림을 생성한다.
BufferedInputStream bis = new BufferedInputStream(fis);

bis.read(); // 보조스트림인 bis로부터 데이터를 읽는다.


보조스트림의 종류

 입력

출력 

설명 

 FilterInputStream

 FilterOutputStream 

 필터를 이용한 입출력 처리 

 BufferedInputStream

 BufferedOutputStream 

 버퍼를 이용한 입출력 성능 향상 

 DataInputStream 

 DataOutputSttream 

 int, float와 같은 기본형 단위로 데이터를 처리하는 기능 

 SequenceInputStream

 SequenceOutputStream 

 두 개의 스트림을 하나로 연결 

 LineNumberInputStream 

 없음 

 읽어 온 데이터의 라인 번호를 카운트(jdk 1.1부터 LineNumberReader로 대체) 

 ObjectInputStream

 ObjectOutputStream 

 데이터를 객체단위로 읽고 쓰는데 사용. 주로 파일을 이용하며 객체 직렬화와 관련 있음 

 없음

 PrintStream 

 버퍼를 이용하며, 추가적인 print 관련 기능 

 PushbackInputStream 

 없음 

 버퍼를 이용해서 읽어 온 데이터를 다시 되돌리는 기능 



1.5 문자기반 스트림 - Reader, Writer


지금까지 알아본 스트림은 모두 바이트기반의 스트림이었다.

자바에서는 문자를 의미하는 char형이 2 byte이기 때문에 바이트기반의 스트림으로 2 byte문자를 처리하는 데는 어려움이 있다.

이 점을 보완하기 위해서 문자기반의 스트림이 제공된다. 문자데이터를 입출력할 때는 문자기반 스트림을 사용하자

  • InputStream -> Reader

  • OutputStream -> Writer


바이트기반과 문자기반 스트림은 이름만 조금 다를 뿐 활용방법은 거의 같다.



2. 바이트기반 스트림

2.1 InputStream과 OutputStream


InputStream 메서드

 메서드 명

설 명 

 int available()

 스트림으로부터 읽어 올 수 있는 데이터의 크기를 반환한다. 

 void close() 

 스트림을 닫음으로써 사용하고 있던 자원을 반환한다. 

 void mark(int readlimit)  

 현재 위치를 표시해 놓는다. 후에 reset()에 의해서 표시해 놓은 위치로 다시 돌아갈 수 있다. 

 readlimit: 되돌아갈 수 있는 byte의 수

 boolean markSupported() 

 mark()와 reset()을 지원하는 지를 알려 준다. 이 메서드로 먼저 확인을 해야한다.

 abstract int read() 

 1 byte를 읽어온다. 더 이상 읽을 데이터가 없다면 -1을 반환한다. 

 int read(byte[] b) 

 배열 b의 크기만큼 읽어서 배열을 채우고 읽어 온 데이터의 수를 반환한다.

 int read(byte[] b, int off, int len) 

최대 len 개의 byte를 읽어서 배열 b의 위치off부터 저장한다. 

 void reset() 

 스트림에서 위치를 마지막으로 mark()이 호출되었던 위치로 되돌린다. 

 long skip(long n) 

 스트림에서 주어진 길이n만큼 건너뛴다. 


OutputStream 메서드

 메서드 명

설 명

 void close()

 입력소스를 닫음으로써 사용하고 있던 자원을 반환한다. 

 void flush() 

 스트림의 버퍼에 있는 모든 내용을 출력소스에 쓴다. 

 abstract void write(int b)

 주어진 값을 출력소스에 쓴다.

 void write(byte[] b) 

 주어진 배열 b에 저장된 모든 내용을 출력소스에 쓴다. 

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

 주어진 배열 b에 저장된 내용 중에서 off번째 부터 len개 만큼만을 읽어서 출력소스에 쓴다. 



2.2 ByteArrayInputStream과 ByteArrayOutputStream


두 스트림은 메모리, 즉 바이트배열에 데이터를 입출력하는데 사용되는 스트림이다. 주로 다른 곳에 입출력하기전에 데이터를 임시로 바이트배열에 담아서 변환 등의 작업을 하는데 사용된다.


자주 사용하지는 않지만 스트림의 종류가 달라도 읽고 쓰는 방법은 동일하므로 예제를 통해서 스트림에 읽고 쓰는 방법을 잘 익혀두기 바란다.



public class Main {
    public static void main(String[] args) {
        byte[] inSrc = {0,1,2,3,4,5,6,7,8,9};
        byte[] outSrc = null;

        ByteArrayInputStream input = null;
        ByteArrayOutputStream output = null;

        input = new ByteArrayInputStream(inSrc);
        output = new ByteArrayOutputStream();

        int data = 0;

        while((data = input.read()) != -1) {
            output.write(data);
        }

        outSrc = output.toByteArray();

        System.out.println("Input Source: " + Arrays.toString(inSrc));
        System.out.println("output Source: " + Arrays.toString(outSrc));
    }
}

출력 결과:

Input Source: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Input Source: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]


ByteArrayInputStream ByteArrayOutputStream을 이용해서 바이트배열 inSrc의 데이터를 outSrc로 복사하는 예제이다.

하지만 이 코드는 while문 한번에 1 byte만 읽고 쓰므로 작업효율이 떨어진다.


public class Main {
    public static void main(String[] args) {
        byte[] inSrc = {0,1,2,3,4,5,6,7,8,9};
        byte[] outSrc = null;

        ByteArrayInputStream input = null;
        ByteArrayOutputStream output = null;

        input = new ByteArrayInputStream(inSrc);
        output = new ByteArrayOutputStream();

        int data = 0;

        while((data = input.read()) != -1) {
            output.write(data);
        }

        outSrc = output.toByteArray();

        System.out.println("Input Source: " + Arrays.toString(inSrc));
        System.out.println("output Source: " + Arrays.toString(outSrc));
    }
}

출력 결과:

Input Source: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

temp Source: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

output Source: [5, 6, 7, 8, 9]


int read(byte[] b, int off, int len)와 void write(byte[] b, int off, int len)을 이용해서 입출력하는 방법을 보여주는 예이다.

이전 예제와는 달리 byte배열을 이용해서 한 번에 배열의 크기만큼 읽고 쓸 수 있다.

만약 스트림의 크기를 정확히 모를 때는 일정한 크기의 바이트를 계속해서 받아서 써야한다.


public class Main {
    public static void main(String[] args) {
        byte[] inSrc = {0,1,2,3,4,5,6,7,8,9};
        byte[] outSrc = null;

        byte[] temp = new byte[10];

        ByteArrayInputStream input = null;
        ByteArrayOutputStream output = null;

        input = new ByteArrayInputStream(inSrc);
        output = new ByteArrayOutputStream();

        input.read(temp, 0, temp.length);       // 읽어 온 데이터를 temp에 담는다.
        output.write(temp, 5, 5);           // temp[5]부터 5개의 데이터를 write한다.

        outSrc = output.toByteArray();

        System.out.println("Input Source: " + Arrays.toString(inSrc));
        System.out.println("temp Source: " + Arrays.toString(temp));
        System.out.println("output Source: " + Arrays.toString(outSrc));

    }
}

출력 결과:

Input Source: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

temp Source: [8, 9, 6, 7]

output Source: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]



2.3 FileInputStream과 FileOutputStream


위의 두 스트림은 파일에 입출력을 하기 위한 스트림이다.

public class Main {
    public static void main(String[] args) {
        try {
            FileInputStream fis = new FileInputStream(args[0]);
            FileOutputStream fos = new FileOutputStream(args[1]);

            int data = 0;
            while((data = fis.read()) != -1) {
                fos.write(data);
            }

            fis.close();
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

명령어:

> java -classpath <class파일 위치> Main Main.java Main.bak


이렇게 하면 Main.java 파일의 내용을 Main.bak 파일이 가지게 된다.


반응형
Comments