본문 바로가기
자바/스터디

[자바 스터디] 13주차 과제 - I/O

by jeonghaemin 2021. 2. 19.
728x90

본 게시글은 백기선 님의 live-study 과제를 수행하면서 작성한 글입니다.

목표

자바의 Input과 Ontput에 대해 학습하세요.

학습할 것 (필수)

  • 스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O
  • InputStream과 OutputStream
  • Byte와 Character 스트림
  • 표준 스트림 (System.in, System.out, System.err)
  • 파일 읽고 쓰기

자바 NIO

자바 4부터 새로운 입출력(New Input/Output)이라는 뜻에서 java.nio패키지가 포함되었는데, 자바 7부터 자바 IO와 NIO 사이의 일관성 없는 클래스 설계를 바로 잡고, 비동기 채널 등의 네트워크 지원을 대폭 강화한 NIO.2 API가 추가되었다. NIO.2는 기존 java.nio dml의 하위 패키지 java.nio.channels, java.nio.charset, java.nio.file에 통합되어있다.

자바 IO와 NIO의 차이

구분 IO NIO
입출력 방식 스트림 방식 채널 방식
버퍼 방식 넌버퍼 버퍼
비동기 방식 지원 안함 지원
블로킹/넌블로킹 블로킹 방식만 지원 블로킹/넌블로킹 방식 모두 지원

스트림과 채널

스트림 기반의 IO는 입력과 출력을 위한 스트림을 각각 생성해야 한다.
채널 기반의 NIO는 스트림과 달리 양방향으로 입출력이 가능하여, 스트림과 달리 입력과 출력을 위한 별도의 채널을 생성할 필요가 없다.

넌버퍼와 버퍼

입출력 시 버퍼를 사용하여 여러 바이트를 한꺼번에 입력 또는 출력하는 것이 좋은 성능을 보인다.
IO는 버퍼를 제공하는 BuffredInputStream, BuffredOutputStream과 같은 보조 스트림을 사용하기도 한다.
NIO는 기본적으로 버퍼를 사용해서 입출력을 하기 때문에 IO보다 성능이 좋다.
또한 IO는 스트림에서 읽은 데이터를 즉시 처리하기 때문에 입력된 전체 데이터를 별도로 저장하지 않으면 데이터의 위치를 자유롭게 이동할 수 없다. 반면에 NIO는 읽은 데이터를 무조건 버퍼에 저장하기 때문에 버퍼 내에서 위치를 이동해가면서 필요한 부분만 읽고 쓸 수 있다.

블로킹과 넌블로킹

IO에서 read() 메서드를 호출하여 내용을 읽어 들인다고 했을 때, 데이터가 입력되기 전까지 작업 스레드는 대기 상태로 전환되며, 이를 블로킹이라고 한다. 블로킹 상태에 있는 동안 이 쓰레드는 다른 작업을 수행할 수 없고, 인터럽트를 통해 블로킹을 빠져나올 수도 없다.
NIO는 넌블로킹이랑 블로킹이 둘 다 가능하도록 되어 있다. 그런데 블로킹에서도 IO와 한 가지 중요한 차이점이 있는데 그것은 바로 NIO에서는 블록 된 상태에서도 인터럽트가 가능하도록 되어 있다는 것이다. 넌블로킹의 핵심은 바로 셀렉터(Selector)인데, 여러 개의 채널 중에서 바로 읽고 쓸 준비가 완료된 채널을 제공하는 역할을 한다. 이렇게 제공된 채널만을 가지고 작업 스레드가 처리하기 때문에 넌블로킹 방식이라고 말할 수 있다.

IO와 NIO의 선택

NIO는 다수의 연결이나 파일들을 넌블로킹이나 비동기 처리할 수 있어서 많은 스레드의 생성을 피하고 스레드를 효과적으로 재사용한다는 장점이 있다. 그렇기 때문에 NIO는 연결 수가 많고 하나의 입출력 처리 작업이 오래 걸리지 않는 경우에 사용하는 것이 좋을 것이다. 스레드에서 입출력 처리가 오래 걸린다면 대기하는 작업의 수가 늘어나게 되므로 장점이 사라진다.
많은 데이터를 처리하는 경우 IO가 좋을 수 있다. NIO는 버퍼의 크기가 문제가 되고, 모든 입출력 작업에 버퍼를 무조건 사용해야 하므로 즉시 처리하는 IO보다 성능 저하가 있을 수 있다. 연결 클라이언트 수가 적고 전송되는 데이터가 대용량이면서 순차적으로 처리되야하는 경우 IO로 구현하는 것이 좋을 수 있다.

스트림(Stream)

자바에서 입출력을 수행하려면 두 대상을 연결하여 데이터를 전송할 수 있도록 해주는 연결통로가 필요한데 이것을 스트림이라고 한다.

스트림은 연속적으로 데이터를 주고받으며, 큐와 같은 FIFO(First In First Out)구조로 되어있다고 생각하면 이해가 쉽다.

스트림은 연속적인 데이터의 흐름을 물에 비유해서 붙여진 이름으로, 물이 한쪽 방향으로 흐르는 것과 같이 스트림도 단방향 통신만 가능하여 하나의 스트림으로 입력과 출력을 동시에 처리할 수 없다.

그래서 입출력을 동시에 처리하려면 입력 스트림, 출력 스트림 총 2개의 스트림이 필요하다.

InputStream과 OutputStream - 바이트 기반 스트림

스트림은 바이트 단위로 데이터를 전송한다. 입력 스트림과 출력 스트림의 종류는 다음과 같다.

입력스트림 출력스트림 입출력 대상의 종류
FileInputStream FileOutputStream 파일
ByteArrayInputStream ByteArrayOutputStream 메모리(byte 배열)
PipedInputStream PipedOutputStream 프로세스(프로세스간의 통신)
AudioInputStream AudioOutputStream 오디오장치

어떤 대상에 대해서 입출력 작업을 할 것인지에 따라서 적절한 스트림을 선택해서 사용하면 된다.

이들은 InputStream과 OutputStream의 자손들이며, 읽고(read) 쓰는(write)데 필요한 추상 메서드를 자신에 맞게 구현하였다.

자바는 입출력을 처리할 수 있는 표준화된 방법을 제공하여 입출력의 대상이 달라져도 동일한 방법으로 입출력을 처리할 수 있다. java.io 패키지를 보면 많은 종류의 입출력 클래스들을 제공하고 있는 것을 볼 수 있다.

InputStream의 메서드

  • int available() : 스트림으로부터 읽어 올 수 있는 데이터의 크기를 반환한다.
  • void close() : 스트림을 닫아 사용하고 있던 자원을 반환한다.
  • void mark(int readlimit) : 현재 위치를 표시해놓고, reset()에 의해서 이 위치로 다시 돌아올 수 있다. readlimit은 되돌아 갈 수 있는 바이트의 수.
  • boolean markSupported() : mark()와 reset()은 선택적으로 지원하는 기능이기 때문에, 해당 기능을 지원하는지 확인한다.
  • void reset() : 스트림에서의 위치를 마지막으로 mark()가 호출된 위치로 되돌린다.
  • long skip(long n) : 스트림에서 n만큼의 길이를 건너뛴다.
  • abstract int read() : 스트림으로부터 1바이트를 읽고 읽은 바이트를 반환. 더 이상 읽어올 데이터가 없으면 -1을 반환한다.
  • int read(byte[] b) : 스트림으로부터 읽은 바이트들을 배열에 저장하고 읽은 바이트 수를 반환한다.
  • int read(byte[] b, int off, int len) : 스트림으로부터 배열의 off위치에 len개 만큼의 바이트를 읽어서 읽은 바이트 수를 반환한다.

read 메서드들을 보면 반환 값이 byte가 아닌 int인 이유는 읽어올 데이터가 없을 때 -1을 반환하기 때문이다.

read(byte[] b)는 read(byte[], int off, int len)를 사용하고 read(byte[], int off, int len)는 다시 abstract read()를 호출하기 때문에 abstract int read()를 꼭 구현해야 한다.

OutputStream의 쓰기 메서드

  • void close() : 사용하고 있던 자원을 반환한다.
  • void flush() : 스트림의 버퍼에 있는 모든 내용을 출력 소스에 쓴다.
  • abstsract void write(int b) : 주어진 값을 출력소스에 쓴다.
  • void write(byte[] b) : 주어진 배열에 저장된 내용을 출력소스에 쓴다.
  • void write(byte[] b, int off, int len) : 배열의 off위치부터 len개 만큼을 읽어서 출력 소스에 쓴다.

프로그램이 종료될 때, 사용하고 닫지 않은 스트림을 JVM이 닫아 주기는 하지만, 스트림을 사용하고 나서 close()를 호출해서 반드시 닫아주어야 한다.

그러나 ByteArrayInputStream과 같이 메모리는 사용하는 스트림과 System.in, System.out과 같은 표준 입출력 스트림에서는 닫아주지 않아도 된다.

ByteArrayInputStream과 ByteArrayOutputStream

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

public class App 
{
    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[4];

        ByteArrayInputStream input = null;
        ByteArrayOutputStream output = null;

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

        //read()와 write()가 IOException을 발생시킬 수 있다.
        try {
            while(input.available() > 0) { //읽어올 수 있는 데이터의 크기가 0보다 크다면?
                int len = input.read(temp);  //읽은 데이터를 temp에 저장하고 읽은 길이를 저장.
                output.write(temp, 0, len); //temp로부터 읽어온 데이터만큼 write.
            }
        } catch (IOException ie) { }

        outSrc = output.toByteArray();

        System.out.println("inSrc = " + Arrays.toString(inSrc));
        System.out.println("outSrc = " + Arrays.toString(outSrc));
    }
}

//결과
//inSrc = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
//outSrc = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

FileInputStream과 FileOutputStream

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

FileInputStream 생성자

  • FileInputStream(String name) : 지정된 파일 이름을 가진 실제 파일과 연결된 파일 입력 스트림 생성한다.
  • FileInputStream(File file) : 파일의 이름이 아닌 File인스턴스로 파일 입력 스트림 생성한다.
  • FileInputStream(FileDescriptor fbObj) : 파일 디스크립터로 파일 입력 스트림을 생성한다.

FileOutputStream의 생성자

  • FileOutputStream(String name) : 지정된 파일 이름을 가진 실제 파일과 연결된 파일 출력 스트림을 생성한다.
  • FileOutputStream(String name, boolean append) : append를 true로 넘기면 출력 시 기존 파일 내용의 뒤에 이어서 출력한다. false로 넘기면 기본 파일의 내용을 덮어쓴다.
  • FileOutputStream(File file), FileOutputStream(File file, boolean append) : 파일 이름이 아닌 File 인스턴스 사용하여 출력 스트림 생성한다.
  • FileOutputStream(FileDescriptor fbObj) : 파일 디스크립터로 파일 출력 스트림을 생성한다.

파일을 복사하는 간단한 예제를 작성해보았다.

public class FileCopy {
    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 ie) {
            ie.printStackTrace();
        }
    }
}

hello.txt 를 복사하여 hello-copy.txt 파일을 생성해보도록 하자.

java FileCopy hello.txt hello-copy.txt

정상적으로 복사가 실행된 것을 볼 수 있다.

보조 스트림

스트림의 기능을 향상하거나 새로운 기능을 추가한 보조 스트림이 제공된다. 보조 스트림은 스트림처럼 실제 데이터를 주고받는 입출력 기능을 제공하지는 않고, 말 그대로 스트림을 보조하는 역할을 한다.

보통 버퍼를 사용하여 입출력을 많이 하는데, 예를 들어 파일을 읽을 때 버퍼 보조 스트림을 사용하는 코드는 다음과 같다.

//파일 입력 스트림 생성
FileInputStream fis = new FileInputStream("sample.txt");

//버퍼 보조 스트림 생성
BuffredInputStream bis = new BuffredInputStream(fis);

//코드 상으로는 보조스트림이 입력 기능을 수행하는 것 같지만 실제 입력 작업은 FileInputStream이 하고,
//BuffredInputStream은 버퍼 기능만을 제공한다.
bis.read();

입력 스트림 클래스 구조

바이트 기반의 보조 스트림

  • FilterInputStream, FilterOutputStream : 모든 보조 스트림의 조상
  • BuffredInputStream, BuffredOutputStream : 버퍼를 이용한 입출력 성능 향상
  • DataInputStream, DataOutputStream : 프리미티브 타입 단위로 데이터를 처리하는 기능
  • SequenceInputStream : 두 개의 스트림을 하나로 연결
  • LineNumberInputStream : 읽어온 데이터의 라인 번호를 카운트(JDK1.1부터 LineNumberReader로 대체)
  • ObjectInputStream, ObjectOutputStream : 데이터를 객체 단위로 읽고 쓰는 데 사용
  • PrintStream : 버퍼를 이용하여, 추가적인 print관련 기능(print, printf, println 메서드)
  • PushbackInputStream : 버퍼를 이용해서 읽어온 데이터를 다시 되돌리는 기능

FilterInputStream과 FilterOutputStream

InputStream, OutputStream의 자식이면서 모든 보조 스트림의 조상이다.

생성자

  • protected FilterInputStream(InputStream in)
  • public FilterOutputStream(OutputStream out)

FilterInputStream과 FilterOutputStream의 모든 메서드는 단순히 기반 스트림의 메서드를 그대로 호출한다. 이것은 FilterInputStream과 FilterOutputStream 자체로는 아무 기능을 하지 않는 것을 뜻한다.

BuffredInputStream과 BuffredOutputStream

스트림의 입출력 효율을 높이기 위해 버퍼를 사용하는 보조 스트림이다.

한 바이트씩 입출력하는 것보다 버퍼를 이용하여 한번에 여러 바이트를 입출력하는 것이 더 빠르기 때문에 대부분의 입출력 작업에서 사용된다.

BuffredInputStream 생성자

  • BuffredInputStream(InputStream in[, int size]) : 지정한 size의 버퍼를 가진 BuffredInputStream 인스턴스를 생성한다. size를 지정하지 않을 시엔 8192byte 크기의 버퍼를 생성한다.

BuffredOutputStream의 생성자와 메서드

  • BuffredOutputStream(OutputStream out[, int size]) : 지정한 size의 버퍼를 가진 BuffredOutputStream 인스턴스를 생성한다. size를 지정하지 않을 시엔 8192byte 크기의 버퍼를 생성한다.
  • flush() : 버퍼에 있는 모든 내용을 출력하여 버퍼를 비운다.
  • close() : 스트림을 닫고 자원을 반환한다.

보조 스트림을 사용할 때는 기반 스트림의 close()나 flush()를 사용하지 않아도된다. 보조 스트림의 close()메서드 안에서 기반스트림의 close()와 flush()를 호출해준다.

실제 보조 스트림의 조상인 FilterOutputStream의 flush()와 close()를 보면 이해가 될 것이다.

        @Override
    public void flush() throws IOException {
        out.flush(); //단순히 기반스트림의 flush()호출
    }        

        @Override
    public void close() throws IOException {
        if (closed) {
            return;
        }
        synchronized (closeLock) {
            if (closed) {
                return;
            }
            closed = true;
        }

        Throwable flushException = null;
        try {
            flush(); //내부적으로 기반스트림 flush() 호출
        } catch (Throwable e) {
            flushException = e;
            throw e;
        } finally {
            if (flushException == null) {
                out.close(); //기반스트림 close
            } else {
                try {
                    out.close(); //기반스트림 close
                } catch (Throwable closeException) {
                   if ((flushException instanceof ThreadDeath) &&
                       !(closeException instanceof ThreadDeath)) {
                       flushException.addSuppressed(closeException);
                       throw (ThreadDeath) flushException;
                   }

                    if (flushException != closeException) {
                        closeException.addSuppressed(flushException);
                    }

                    throw closeException;
                }
            }
        }
    }

DataInputStream과 DataOutputStream

DataInputStream과 DataOutputStream은 각각 DataInput, DataOutput인터페이스를 구현하여 byte 단위가 아닌 자바의 프리미티브 타입 단위로 입출력할 수 있다.

DataInputStream의 생성자와 메서드

  • DataInputStream(InputStream in) : DataInputStream 인스턴스를 생성한다.
  • XX readXX() : 각 타입에 맞게 값을 읽어온다. 읽을 값이 없으면 EOFException 발생.
  • void readFully(byte[] b[, int off, int len]) : 입력 스트림에서 전달된 배열의 길이만큼, 또는 off의 위치부터 len길이만큼 데이터를 읽어온다. 파일의 끝에 도달하면 EOFException, I/O에러가 발생하면 IOException 발생.
  • String readUTF() : UTF-8 형식으로 쓰인 문자를 읽는다. 읽을 값이 없으면 EOFException 발생.
  • static String readUFT(DataInput in) : 입력 스트림에서 UTF-8 형식의 유니코드를 읽어온다.
  • int skipBytes(int n) : 현재 위치에서 n만큼을 건너뛴다.

EOFException : End Of File. 입력 중에 예기치 않게 파일의 끝이나 스트림의 끝에 도달했을 때 발생하는 예외이다.

DataOutputStream의 생성자와 메서드

  • DataOutputStream(OutputStream out) : DataOutputStream 인스턴스를 생성한다.
  • void writeXX(XX) : 각 타입에 알맞은 값들을 출력한다.
  • void writeUTF(String s) : UTF 형식으로 문자를 출력한다.
  • void writeChars(String s) : 주어진 문자열을 출력한다. writeChar()를 여러 번 호출한 것과 같다.
  • int size() : 지금까지 DataOutputStream에 쓰인 byte의 수를 반환한다.
public class App 
{
    public static void main( String[] args ) {

        try(FileOutputStream fos = new FileOutputStream("Sample.dat");
            DataOutputStream dos = new DataOutputStream(fos)) {

            dos.writeInt(10);
            dos.writeFloat(15.0f);
            dos.writeLong(20L);
            dos.writeBoolean(true);

        } catch (IOException ie) {
            ie.printStackTrace();
        }

        try(FileInputStream fis = new FileInputStream("Sample.dat");
            DataInputStream dis = new DataInputStream(fis)) {

            //입력한 순서대로 출력해야된다.
            System.out.println(dis.readInt());
            System.out.println(dis.readFloat());
            System.out.println(dis.readLong());
            System.out.println(dis.readBoolean());

        } catch (EOFException e) {
            System.out.println("파일의 끝에 도달하였습니다.");
        } catch (IOException ie) {
            ie.printStackTrace();
        }
    }

    //결과
    //10
    //15.0
    //20
    //true
}

SequenceInputStream

여러 개의 입력 스트림을 연속적으로 연결해서 하나의 스트림으로 데이터를 읽는 것처럼 처리할 수 있다.

다른 보조 스트림들과 달리 FilterInputStream이 아닌 InpuStream을 바로 상속받아 구현되어있다.

SequenceInputStream의 생성자

  • SequenceInputStream(Enumeration e) : Enumeration에 저장된 순서대로 입력스트림을 하나의 스트림으로 연결한다.
  • SequenceInputStream(InputStream s1, InputStream s2) : 두 개의 스트림을 하나의 스트림으로 연결한다.
//firstFile.txt : 12345
//secondFile.txt : 678910

public class App {
    public static void main(String[] args) throws IOException {
        FileInputStream first = new FileInputStream("firstFile.txt");
        FileInputStream second = new FileInputStream("secondFile.txt");

        SequenceInputStream sis = new SequenceInputStream(first, second);

        int data = 0;
        while((data = sis.read()) != -1) {
            System.out.print(data + " ");
        }
        System.out.println();

        first.close();
        second.close();
        sis.close();
    }

//결과 : 1 2 3 4 5 6 7 8 9 10
}

PrintStream

데이터를 기반 스트림에 데이터를 다양한 형태로 출력할 수 있도록 한다.

다양한 값을 출력 시 사용했던 System.out, System.err이 PrintStream이다.

데이터를 적절한 문자로 출력하는 문자 기반 스트림의 역할을 하는데, JDK1.1부터 보다 향상된 기능의 PrintWriter가 추가되었다.

문자 기반 스트림 - Reader, Writer

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

여러 종류의 인코딩과 자바에서 사용하는 UTF-16간의 변환을 자동적으로 처리해준다.

바이트 기반 스트림의 조상이 InputStream, OutputStream인 것과 같이 문자 기반 스트림의 조상은 Reader, Writer이다.

종류

  • FileReader, FileWriter
  • CharArrayReader, CharArrayWriter
  • PipedReader, PipedWriter(쓰레드간의 통신에 사용)
  • StringReader, StringWriter

메서드를 보면 사용 방법은 바이트 기반의 스트림과 거의 같은 것을 알 수 있다.

Reader의 메서드

  • abstract void close() : 입력 스트림을 닫고, 자원을 반환한다.
  • void mark(int readlimit) : 현재 위치를 표시하여, reset()에 의해 다시 이 위치로 돌아올 수 있다.
  • boolean markSupported() : mark()와 reset()을 지원하는지 알려준다.
  • int read() : 하나의 문자를 읽어온다. 입력 스트림의 마지막에 도달하면 -1 반환한다.
  • int read(char[] c) : 배열의 크기만큼 읽어서 배열에 저장한다. 읽어온 데이터의 개수 또는 —1을 반환한다.
  • abstract int read(char[] c, int off, int len) : off위치부터 len개의 문자를 읽어서 반환한다.
  • int read(CharBuffer target) : 입력 소스로부터 읽어서 문자 버퍼에 저장한다.
  • boolean ready() : 데이터를 읽을 준비가 되었는지 알려준다.
  • void reset() : mark()가 호출되었던 위치로 되돌린다.
  • long skip(long n) : 현재 위치에서 n만큼 건너뛴다.

Writer의 메서드

  • Writer append(char c) : 전달된 문자를 출력한다.
  • Writer append(CharSequence c[, int start, int end]) : 전달된 문자열 또는 문자열의 일부를 출력한다.
  • abstract void close() : 출력 스트림을 닫고, 자원을 반환한다.
  • abstract void flush() : 버퍼를 사용하는 스트림의 경우, 버퍼를 비워 모든 내용을 출력한다.
  • void wirte(int b) : 전달된 값을 출력 소스에 쓴다.
  • void write(char[] c) : 전달된 배열에 저장된 모든 내용을 출력 소스에 쓴다.
  • abstract void write(char[] c, int off, int len) : 전달된 배열의 off위치부터 len 길이만큼 출력 소스에 쓴다.
  • void write(String str[, int off, int len]) : 전달된 문자열 또는 일부를 출력 소스에 쓴다.

FileReader와 FileWriter

파일로부터 텍스트를 읽고, 파일에 쓰는 데 사용된다.

public class App {
    public static void main(String[] args) throws IOException {
        String fileName = "sample.txt";
        FileWriter fw = new FileWriter(fileName);
        FileReader fr = new FileReader(fileName);

        fw.write("백기선님과 함께하는 Live study!!");
        fw.close();

        int data = 0;
        while((data = fr.read()) != -1) {
            System.out.print((char)data);
        }
        System.out.println();

        fr.close();
    }

    //결과 : 백기선님과 함께하는 Live study!!
}

PipedReader와 PipedWriter

다른 스트림들과 달리 입출력을 모두 하나의 스트림으로 스레드 간에 데이터를 주고받는다.

스레드 간에 데이터를 주고받을 때 사용된다.

스트림을 생성하고 어느 한쪽 스레드에서 connect()를 호출하여 입려 스트림과 출력 스트림을 연결한다.

입출력 작업을 마친 후에 어느 한쪽 스트림만 닫아도 나머지 한쪽 스트림도 자동으로 닫힌다.

public class App {
    public static void main(String[] args) throws IOException {
        InputThread   inputThread = new InputThread();
        OutputThread outputThread = new OutputThread();

        inputThread.connect(outputThread.getWriter());

        inputThread.start();
        outputThread.start();

    }
}

class InputThread extends Thread {
    PipedReader reader = new PipedReader();

    @Override
    public void run() {
        try {
            int data = 0;

            while((data= reader.read()) != -1) {
                System.out.print((char)data);
            }
            System.out.println();

        } catch(IOException e) {}
    }

    public PipedReader getReader() {
        return reader;
    }

    /**
     * Reader와 Writer를 연결해주는 메서드
     */
    public void connect(PipedWriter writer) {
        try {
            reader.connect(writer);
        } catch(IOException e) {}
    }
}

class OutputThread extends Thread {
    PipedWriter writer = new PipedWriter();

    @Override
    public void run() {
        try {
            writer.write("Hello");
            writer.close();
        } catch (IOException e) {
        }
    }

    public PipedWriter getWriter() {
        return writer;
    }
}

//결과 : Hello

StringReader와 StringWriter

입출력 대상이 메모리인 스트림으로, StringWriter에 출력되는 데이터는 내부의 StringBuffer에 저장된다.

public class App {
    public static void main(String[] args) throws IOException {
        StringReader reader = new StringReader("Hello World!");
        StringWriter writer = new StringWriter();

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

        //내부의 StringBuffer를 가져오는 메서드
        StringBuffer buffer = writer.getBuffer();

        //출력된 문자열을 반환한다.
        String s = writer.toString();
    }
}

문자 기반의 보조 스트림

BuffredReader와 BuffredWriter

버퍼를 이용해서 입출력을 처리할 수 있도록 하여 입출력의 효율을 높일 수 있도록해준다.

BuffredReader의 readLine() 메서드로 라인 단위로 데이터를 읽을 수 있다.

BuffredWriter의 newLine() 메서드로 줄 바꿈을 할 수 있다.

InputStreamReader와 OutputStreamWriter

바이트 기반 스트림을 문자기반 스트림으로 연결시켜준다.

바이트기반 스트림의 데이터를 지정된 인코딩의 문자 데이터로 변환한다.

InputStreamReader

  • InputStreamReader(InputStream in[, String encoding]) : OS 기본 인코딩 또는 지정된 인코딩의 문자로 변환하는 InputStreamReader인스턴스를 생성한다.
  • String getEncoding() : 인코딩을 알려준다.

OutputStreamWriter

  • OutputStreamWriter(OutputStream out[, String encoding]) : OS 기본 인코딩 또는 지정된 인코딩의 문자로 변환하는 OutputStreamWriter인스턴스를 생성한다.
  • String getEncoding() : 인코딩을 알려준다.

표준 입출력 - System.in, System.out, System.err

표준 입출력은 콘솔을 통한 데이터의 입출력을 의미한다.

자바 애플리케이션 실행 시 자동적으로 생성되기 때문에 개발자가 별도의 스트림을 생성하는 코드를 작성하지 않아도 사용할 수 있다.

  • System.in : 콘솔로부터 데이터를 입력받는 데 사용
  • System.out : 콘솔로 데이터를 출력하는 데 사용
  • System.err : 콘솔로 오류를 출력하는데 사용

입출력 대상을 콘솔이 아닌 다른 대상으로 변경할 수 있다.

  • static void setOut(PrintStream out)
  • static void setErr(PrintStream out)
  • static void setIn(InputStream in)

RandomAccessFile

순차적으로 읽고 쓰는 다른 입출력 클래스들에 비해 RandomAccessFile은 파일의 어느 위치에든 읽고 쓸 수 있다.

DataInput, DataOutput 인터페이스를 구현하여 프리미티브 타입을 읽고 쓸 수 있다.

입출력 클래스들은 내부적으로 파일 포인터라는 것을 사용해서 어떤 위치에서 입출력을 할 것인지를 결정하는데 RandomAccessFile은 사용자가 파일 포인터의 위치를 직접 변경할 수 있는 것이다.

RandomAccessFile의 생성자

  • RandomAccessFile(File file, String mode)
  • RandomAccessFile(String fileName, String mode)

mode의 값은 r, rw, rws, rwd 중 사용 가능하다.

  • r : 읽기만을 수행할 때
  • rw : 파일에 읽기와 쓰기. 지정된 파일이 없으면 새로운 파일을 생성한다.
  • rws, rwd : 기본적으로 rw와 같지만 출력 내용이 파일에 지연 없이 바로 쓰이게 한다. rwd는 파일의 내용만, rws는 메타정보도 포함한다.

RandomAccessFile의 메서드

  • FileChannel getChannel() : 파일의 파일 채널을 반환한다.
  • FileDescriptor getFD() : 파일의 파일 디스크립터를 반환한다.
  • long getFilePointer() : 파일 포인터의 위치를 알려준다.
  • long length() : byte단위로 파일의 크기를 알려준다.
  • void seek(long pos) : byte단위로 파일의 처음부터 pos 크기만큼 떨어진 곳으로 파일 포인터의 위치를 변경한다.
  • void setLength(long newLength) : 파일의 크기를 지정된 크기로 변경한다.(byte 단위)
  • int skipBytes(int n) : 전달된 수만큼의 byte를 건너뛴다.

파일(File)

자바에서는 File 클래스를 통해 파일과 디렉토리를 다룰 수 있도록 하고 있다.

File의 생성자

  • File(String pathname) : 전달된 문자열을 이름으로 갖는 파일을 위한 File인스턴스를 생성. pathname은 주로 경로를 포함하지만, 파일 이름만 전달할 경우 프로그램이 실행되는 위치가 경로로 지정된다.
  • File(String pathName, String fileName), File(File pathName, String fileName) : 파일의 경로와 이름을 따로 지정하여 인스턴스를 생성한다.
  • File(URI uri) : 전달된 uri로 파일 인스턴스 생성한다.

파일의 경로와 관련된 메서드의 반환 값을 출력하는 간단한 예제로 알아보자.

File file = new File("/Users/jeonghaemin/LiveStudy.java");

System.out.println("파일 이름 : " + file.getName());
System.out.println("경로를 포함한 파일 이름 : " + file.getPath());
System.out.println("파일의 절대 경로 : " + file.getAbsolutePath());
System.out.println("파일의 정규 경로 : " + file.getCanonicalPath());
System.out.println("파일의 속해 있는 디렉토리 : " + file.getParent());

/* 출력 결과
    파일 이름 : LiveStudy.java
    경로를 포함한 파일 이름 : /Users/jeonghaemin/LiveStudy.java
    파일의 절대 경로 : /Users/jeonghaemin/LiveStudy.java
    파일의 정규 경로 : /Users/jeonghaemin/LiveStudy.java
    파일의 속해 있는 디렉토리 : /Users/jeonghaemin
*/

위 예제를 보면 절대 경로와 정규 경로가 있는데 출력 값을 보면 같은 값이 출력되는데 이 둘의 차이는 무엇일까?

결론부터 말하면 절대 경로는 상위 디렉토리를 뜻하는 ..과 현재 디렉토리를 뜻하는 . 와 같은 것을 사용할 수 있지만 정규 경로에서는 이것들을 사용할 수 없다. 즉, 절대 경로는 여러 경우의 수를 가질 수 있지만 절대 경로는 하나의 경우의 수만 있다.

File file1 = new File("/Users/../jeonghaemin/LiveStudy.java");
File file2 = new File("/Users/./././jeonghaemin/LiveStudy.java");

System.out.println("파일1의 절대 경로 : " + file1.getAbsolutePath());
System.out.println("파일1의 정규 경로 : " + file1.getCanonicalPath());

System.out.println("파일2의 절대 경로 : " + file2.getAbsolutePath());
System.out.println("파일2의 정규 경로 : " + file2.getCanonicalPath());

/* 출력 결과
파일1의 절대 경로 : /Users/../jeonghaemin/LiveStudy.java
파일1의 정규 경로 : /jeonghaemin/LiveStudy.java

파일2의 절대 경로 : /Users/./././jeonghaemin/LiveStudy.java
파일2의 정규 경로 : /Users/jeonghaemin/LiveStudy.java
*/

그 밖의 File 클래스의 메서드

  • boolean canRead() : 읽을 수 있는 파일인지 확인한다.
  • boolean canWrite() : 쓸 수 있는 파일인지 확인한다.
  • boolean canExecute() : 실행할 수 있는 파일인지 확인한다.
  • boolean exists() : 파일이 존재하는지 확인한다.
  • boolean isDirectory() : 디렉토리인지 확인한다.
  • boolean isFile() : 파일인지 확인한다.
  • boolean isHidden() : 파일의 속성이 숨김인지 확인한다. 파일이 존재하지 않아도 false를 반환한다.
  • boolean createNewFile() : 새로운 파일을 생성한다. 파일이 이미 존재하면 생성되지 않는다.
  • static File createTempFile(String prefix, String suffix) : 임시파일을 시스템의 임시 디렉토리에 생성한다.
  • boolean delete() : 파일을 삭제한다.
  • void deleteOnExit() : 프로그램 종료 시 파일을 삭제한다. 주로 실행 시에 사용된 임시 파일을 삭제하는 데 사용한다.
  • long lastModified() : 파일이 마지막으로 수정된 시간을 반환한다.
  • boolean setLastModified(long t) : 파일이 마지막으로 수정된 시간을 변경한다.
  • long length() : Byte 단위로 파일의 크기를 반환한다.
  • String[] list() : 디렉토리의 파일 목록(디렉토리 포함)을 String 배열로 반환한다.
  • String[] list(FilenameFilter filter), File[] list(FilenameFilter filter) : FilenameFilter 인스턴스에 구현된 조건에 맞는 파일을 String 배열 또는 File 배열로 반환한다.
  • File[] listFiles() : 디렉토리의 파일 목록(디렉토리 포함)을 File 배열로 반환한다.
  • File[] listFiles(FileFilter filter), File[] listFiles(FilenameFilter filter) : filter의 조건과 일치하는 File 배열을 반환한다.
  • static File[] listRoots() : 파일 시스템의 루트의 목록을 반환한다.
  • long getFreeSpace() : 파일이 루트일 때 비어있는 공간을 바이트 단위로 반환한다.
  • long getTotalSpace() : 파일이 루트일 때 전체 공간을 바이트 단위로 반환한다.
  • long getUsableSpace() : 파일이 루트일 때 사용 가능한 공간을 바이트 단위로 반환한다.
  • boolean mkdir() : 파일에 지정된 경로로 디렉토리를 생성한다.
  • boolean mkdirs() : 필요시 부모 디렉토리까지 파일에 지정된 경로로 디렉토리를 생성한다.
  • boolean reanmeTo(File dest) : 전달된 파일로 이름을 변경한다.
  • Path toPath() : 파일을 Path로 변환하여 반환한다.
  • URI toURI() : 파일을 URI로 변환하여 반환한다.

파일의 속성을 변경하는 메서드(ownerOnly가 true이면, 파일의 소유자만 해당 속성 변경 가능)

댓글