본문 바로가기

WhiteShip Java Study : 자바 처음부터 멀리까지

I/O

선장님과 함께하는 자바 스터디입니다.

자바 스터디 Github

github.com/whiteship/live-study

 

whiteship/live-study

온라인 스터디. Contribute to whiteship/live-study development by creating an account on GitHub.

github.com

나의 Github

github.com/cmg1411/whiteShip_live_study

 

cmg1411/whiteShip_live_study

✍ 자바 스터디할래. Contribute to cmg1411/whiteShip_live_study development by creating an account on GitHub.

github.com

 

 

 

 

 

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

 

 

 

I/O : Input / Output

  • 프로그램에서 데이터를 외부에서 읽어오고(Input), 다시 외부로 출력하는(Output) 것을 말한다.
  • Input : 키보드, 파일, 프로그램으로 부터 읽어온다.
  • Output : 모니터, 파일, 프로그램으로 출력한다.

 

 

스트림(Stream)

  • 자바의 I/O 는 스트림 으로 데이터를 읽어오고, 출력한다.
  • 데이터를 읽어오는 것을 입력 스트림, 출력하는 것을 출력 스트림 이라 한다. (프로그램을 기준으로)
  • 스트림은 단방향 통신만 가능 하기 때문에 하나의 스트림으로 입력, 출력을 동시에 할 수 없고, 스트림이 따로 필요하다.
  • java.io 에서는 바이트 단위 스트림과 문자 단위 스트림으로 나눌 수 있다.

 

 

java.io 패키지

  • 자바에서 입출력 구현을 위한 클래스들은 java.io 패키지에 있다.
  • 이 패키지의 주요 최상위 클래스들은 다음과 같다.

  • 더 세세한 패키지 구조는 다음과 같다.

출처 - java in a nutshell 책 

 

 

java.io - 데코레이터 패턴

  • java.io 패키지는 데코레이터 패턴으로 만들어졌다.
  • 데코레이터 패턴이란, A 클래스에서 B 클래스를 생성자로 받아와서, B 클래스에 추가적인 기능을 덧붙여서 제공하는 패턴이다.
BufferedReader 클래스

private Reader in;

public BufferedReader(Reader in) {
    this(in, defaultCharBufferSize);
}

 

  • BufferedReader 는 Reader 의 하위 클래스중 하나를 받아와서, 버퍼를 이용한 기능을 추가한 기능을 제공한다.
  • BufferedReader 처럼 출력을 담당하는 래퍼 클래스는 출력을 하는 주체가 아니라 도와주는 역할이다.
  • Stream 을 사용한 클래스들에서 이렇게 도와주는 클래스들을 보조 스트림이라 한다.

 

 

InputStream - 바이트 단위 입력

 

  • InputStream 은 바이트단위의 입력 스트림을 위한 클래스들의 최상위 추상클래스이다.
  • 이 추상 클래스는 아래 세개의 주요 메서드를 가지고 있다.
java.io.InputStream

public abstract int read() throws IOException;

public int read(byte b[]) throws IOException {...};

public int read(byte b[], int off, int len) throws IOException {...};

 

 

read() 

  • 추상 메서드지만, 하위 구현체의 재정의 메서드들의 공통점이 있다.
  • 1 byte 를 읽어오는 것이다. 그리고 리턴타입이 int 이므로, 4 bytes 이다.
  • 1 byte 의 데이터는 int 형 4bytes 의 마지막에 들어간다.
  • 읽어올 값이 없으면 -1 을 리턴한다.
    ab.txt
    my name is min geor

 

    // ab.txt 읽어오기
    public static void main(String[] args) throws IOException {

        int count = 0;

        try (InputStream in = new FileInputStream("파일경로/ab.txt")) {
            int readByte;

            while((readByte = in.read()) != -1) {
                System.out.print((char) readByte);
                count++;
            }
        }

        System.out.println();
        System.out.println(count);
    }

 

 

read(byte b[])

  • 위 read() 와 읽어와서 int 형으로 반환하는 것은 같다.
  • 다른점은, byte[] 이 매개변수로 들어간다. 1byte 씩이 아니라 지정해준 byte[] 의 크기씩 읽어온다.
  • 읽어온 값은 (내부적으로 read() 를 사용하기 떄문에) int 형으로 byte[] 에 저장된다.
  • 메서드의 리턴은 실제로 읽은 바이트 수를 리턴한다.
  • my name is min geor 은 위에서 봤듯이, 19 바이트였고, 5바이트 단위로 읽어온다면, 5554 가 찍힐 것이다. 그리고 루프는 4번 돌 것이다. 
    public static void main(String[] args) throws Exception {

        int count = 0;

        try (InputStream in = new FileInputStream("파일경로/ab.txt")) {
            int readByte;
            byte[] size = new byte[5];

            while((readByte = in.read(size)) != -1) {
                for (byte b : size) {
                    System.out.print((char) b);
                }
                System.out.print("  이번 루프에 읽은 바이트 수 : " + readByte);
                System.out.println();
                count++;
            }
        }

        System.out.println("루프가 돈 횟수 : " + count);
    }
}

 

 

 

 

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

  • int off, int len 매개변수가 추가되었다. 나머지는 위와 같다.
  • 지정한 바이트만큼 읽어와서 b[]의 off (offset) 인덱스부터 b[]에 저장하는데, len 갯수만큼 저장한다.
  • 리턴값은 마찬가지로 읽어온 갯수인데, len 과 같다. 마지막이라 len 개를 채우지 못하면 읽어온 갯수를 리턴한다.
  • 아래 예제는 byte[] 의 크기 중 off 부터 len 만큼의 범위 외에는 0이 들어가기 때문에 0 은 띄워쓰기로 출력했다.
    public static void main(String[] args) throws Exception{
        int count = 0;

        try (InputStream in = new FileInputStream("파일경로/ab.txt")) {
            int readByte;
            byte[] size = new byte[5];

            while ((readByte = in.read(size, 1, 3)) != -1) {
                for (int i = 0; i < size.length; i++) {
                    if (size[i] == 0)
                        System.out.print(" ");
                    else
                        System.out.print((char) size[i]);
                }
                System.out.print("이번 루프에 읽은 바이트 수 : " + readByte);
                System.out.println();
                count++;
            }
        }

        System.out.println("루프가 돈 횟수 : " + count);
    }

이번 ~ 앞은 모두 5 바이트의 배열들임을 알 수 있다.

그리고 예제에서 1 부터 3 개 로 지정했기 때문에, 인덱스 1 부터 3개씩 채워진 것을 볼 수 있다. (0, 4 인덱스에는 띄워쓰기)

 

마지막의 읽은 바이트 수는 1 인데 출력이 [ reo ] 인 이유는, 매개변수로 전달된 배열은 매번 루프마다 초기화하는 것이 아니라, 하나하나씩 바꾸기 때문에 뒤 eo 는 아직 갱신되지 못한 위의 값을 그대로 가지고 있는 것이다.

 

 

이외 메서드들

  • int available() : 읽어올 수 있는 바이트 반환
  • void close() : 자원을 닫는다. Closable 인터페이스를 구현해야 한다. 되도록 try-with-resource 를 쓰자.
  • void mark(int readlimit) : 스트림에서 현재 위치를 마크한다. reset() 을 이용해서 다시 이 위치로 올 수 있다. 매개변수 readlimit 은 마크 이후 readlimit 만큼의 바이트 이후 마크가 사라진다.
  • void reset() : 최근에 마크한 곳으로 돌아간다.
  • long skip(long n) : n 바이트만큼 건너뛴다.

 

 

 

 

 

 

 

OutputStream - 바이트 단위 출력

  • OutputStream 은 바이트단위의 출력 스트림을 위한 클래스들의 최상위 추상클래스이다.
  • 이 추상 클래스는 아래 세개의 주요 메서드를 가지고 있다.
java.io.OutputStream

public abstract void write(int b) throws IOException;

public void write(byte b[]) throws IOException {...}

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

public void flush() throws IOException {}

 

 

write(int b) 

  • 추상 메서드지만, 하위 구현체의 재정의 메서드들의 공통점이 있다.
  • 매개변수로 들어온 값을 출력 스트림으로 보낸다.
  • 매개변수가 int 값이라 4 byte 로 보이지만, byte 자료형 1 byte 만 보내진다.
    public static void main(String[] args) throws Exception {
        try(OutputStream outputStream = new FileOutputStream("파일경로/ab.txt")) {
            byte[] nickName = "Tomas".getBytes();

            for (byte b : nickName) {
                outputStream.write(b);
            }
        }
    }

 

 

write(byte b[])

  • 인자로 넘어온 byte 배열의 모든 바이트를 출력 스트림으로 보낸다.
    public static void main(String[] args) throws Exception {
        try(OutputStream outputStream = new FileOutputStream("파일경로/ab.txt")) {
            byte[] nickName = "Cristin".getBytes();

            outputStream.write(nickName);
        }
    }

 

 

 

 

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

  • b[off] 부터 len 개의 바이트를 출력 스트림으로 보낸다.
    public static void main(String[] args) throws Exception {
        try (OutputStream os = new FileOutputStream("파일경로/ab.txt")) {
            byte[] nickName = "QJimmyQ".getBytes();
            os.write(nickName, 1, 5);
        }
    }

 

 

flush()

  • 출력 스트림은 내부에서 버퍼를 사용한다.
  • i/o 횟수를 줄이기 위해 출력하기 전에 버퍼에서 쌓여있다가 순서대로 한꺼번에 출력된다.
  • 출력 스트림 버퍼에 있는 내용을 강제로 출력 스트림으로 보낸다.
  • OutputStream 을 다 쓰면 flush(), close() 를 호출해야 한다.
  • OutputStream 은 flushable 인터페이스를 구현하고 있다.

 

Close()

  • 자원을 모두 사용하고 닫는다.
  • 내부적으로 flush() 를 호출한다. 하지만 따로 flush() 도 써 주는게 좋다고 한다.
  • try-with-resource 쓰자.

 

 

 

 

 

 

Reader

  • 문자 기반 입력 스트림의 최상위 추상 클래스.
  • 다음은 Reader 의 주요 메서드들이다.
public int read() throws IOException {
    char cb[] = new char[1];
    if (read(cb, 0, 1) == -1)
        return -1;
    else
        return cb[0];
}
    
public int read(char cbuf[]) throws IOException {
    return read(cbuf, 0, cbuf.length);
}
    
abstract public int read(char cbuf[], int off, int len) throws IOException;

 

 

read()

  • 입력 스트림으로부터 한 개의 문자 (2 bytes) 를 읽고 4 bytes int 타입으로 리턴한다.
  • 영어는 1 바이트지만, 한글은 2 바이트이다.
  • 한글 두 글자를 입력 받을 때는, read() 를 두번 하면 되는 것이다.
  • 읽어 올 것이 없을 때, -1 을 리턴한다,
    public static void main(String[] args) throws Exception {
        Reader reader = new FileReader("파일경로/ab.txt"); // '바다' 저장되어 있음
        int readData;
        int readCount = 0;
        while ((readData = reader.read()) != -1) {
            System.out.print((char) readData);
            readCount++;
        }

        System.out.println();
        System.out.println("읽어온 횟수 : " + readCount);
    }

 

 

read(char[] cbuf)

  • 한번의 입력 스트림에 cbuf 문자배열의 크기만큼 읽는다.
  • 읽어올 문자가 없으면 -1 을 반환한다.
  • 리턴값은 읽어온 문자 수이고, 읽어온 문자는 cbuf 배열에 저장한다.
  • 배열을 매번 초기화하지 않기 때문에, 마지막에 읽어올 때 읽어온 문자 나머지 배열 부분에는 이전 문자열이 남아있을 수 있다.
  • 문자의 양이 많을 떄는 위의 read() 대신 이것을 쓰는 것이 좋다.
ab.txt
가나다라마바사아자차카타파하가나다라마바사아자차카타파하가나다라마바사아자차카타파하가나다라마바사아자차카타파하가나다라마바사아자차카타파하가
    public static void main(String[] args) throws Exception {
        try (Reader reader = new FileReader("파일경로/ab.txt")) {
            char[] cbuf = new char[10];

            int readData;
            int readCount = 0;
            while ((readData = reader.read(cbuf)) != -1) {
                for (char c : cbuf) {
                    System.out.print(c);
                }
                System.out.println();
                System.out.println("읽어온 문자 수 : " + readData);
                readCount++;
            }

            System.out.println();
            System.out.println("읽어온 횟수 : " + readCount);
        }
    }

 

마지막에 읽어온 문자는 가 하나이고 나머지 뒷부분은 전에 읽어온 문자들이 그대로 남아있다.

 

 

read(char[] cbuf, int off, int len)

  • 추상 메서드이다.
  • 입력 스트림으로 부터 len개의 문자만큼 읽고 매개값으로 주어진 문자 배열 cbuf[]에 chub[off] 부터 cbuf[off + len -1] 까지 저장한다.
  • 리턴값은 읽어온 문자 수이고, 읽을 문자가 없으면 -1 을 반환한다.
    public static void main(String[] args) throws Exception {
        try (Reader reader = new FileReader("파일 경로/ab.txt")) {
            char[] cbuf = new char[5];

            int readData;
            int readCount = 0;
            while ((readData = reader.read(cbuf, 1, 3)) != -1) {
                for (char c : cbuf) {
                    if ((int) c == 0) {
                        System.out.print("[빈배열]");
                    }
                    System.out.print(c);
                }
                System.out.println();
                System.out.println("읽어온 문자 수 : " + readData);
                System.out.println();
                readCount++;
            }

            System.out.println();
            System.out.println("읽어온 횟수 : " + readCount);
        }
    }

 

코드가 깔끔하지는 않지만.. 

byte[] 의 크기는 5로 잡았고 offset = 1, len = 3 으로 잡았다.

그럼 인덱스가 0, 4 인 배열은 빼고 1,2,3 에 3개씩 넣어질 것이다.

결과를 보면 3개씩 읽어오고 알맞은 인덱스에 문자가 저장된 것을 볼 수 있다.

 

 

 

close()

  • 자원을 닫아주는 메서드.

 

 

 

 

 

Writer

  • 문자 기반 출력 스트림의 최상위 추상 클래스.
  • 아래의 주요 메서드들이 있다.
public void write(int c) throws IOException {
    synchronized (lock) {
    if (writeBuffer == null){
        writeBuffer = new char[WRITE_BUFFER_SIZE];
    }
    writeBuffer[0] = (char) c;
    write(writeBuffer, 0, 1);
    }
}
    
public void write(char cbuf[]) throws IOException {
    write(cbuf, 0, cbuf.length);
}

abstract public void write(char cbuf[], int off, int len) throws IOException;

public void write(String str) throws IOException {
    write(str, 0, str.length());
}

public void write(String str, int off, int len) throws IOException {
    synchronized (lock) {
        char cbuf[];
        if (len <= WRITE_BUFFER_SIZE) {
            if (writeBuffer == null) {
                writeBuffer = new char[WRITE_BUFFER_SIZE];
            }
            cbuf = writeBuffer;
        } else {    // Don't permanently allocate very large buffers.
            cbuf = new char[len];
        }
        str.getChars(off, (off + len), cbuf, 0);
        write(cbuf, 0, len);
    }
}

abstract public void flush() throws IOException;

 

 

write(int c)

  • 출력 스트림으로 c 한 문자를 보낸다.

write(char[] cbuf)

  • 출력 스트림으로 주어진 문자 배열 cbuf 전체를 보낸다.

write(char[] cbuf, int off, int len)

  • cbuf[off] 부터 len 개의 문자를 출력 스트림으로 보낸다.

write(String str)

  • 주어진 문자열을 출력 스트림으로 보낸다.

write(String str, int off, int len)

  •  

 

  •  
  •  

 

 

 

 

 

표준 스트림 - System.out, System.in, System.err

  • 콘솔 (터미널, 명령 프롬프트) 을 사용하여 데이트를 입력받고 출력할 때 사용한다.
  • System 은 java.lang 의 클래스이다.

 

System.out

  • 콘솔에 데이터를 출력하기 위해서 System.out 을 사용할 수 있다.
  • System 에는 아래의 정적 필드를 가지고 있다.
public final static PrintStream out = null;

 

  • 정적 필드기 때문에 System.out 으로 참조할 수 있다.
  • out 의 타입이 PrintStream 이기 때문에 상위 타입인 OutputStream 을 이용하여 아래와 같이 변환해서 사용 가능하다.
OutputStream os = System.out;

 

  • PrintStream 의 계층 구조는 OutputStream > FilterStream > PrintStream 이다.
  • 따라서 System.out 은 PrintStream 을 포함한 OutputStream, FilterStream 의 메서드를 사용할 수 있다.

 

  • 컴파일타임에 자동으로 PrintStream 객체가 System.out 에 들어간다. 따라서 다음의 코드는 에러가 나지 않는다.
    public static void main(String[] args) {
        Optional<PrintStream> isNull = Optional.ofNullable(System.out);

        PrintStream p = isNull.orElseThrow(() -> new IllegalArgumentException("널이 들어있습니다."));
    }

 

  • 이 코드는 Optional 을 써서 바이트코드가 복잡하지만, 잘 살펴보면 아래와 같은 코드가 있다.
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;

 

  • 따라서 우리는 스트림을 따로 생성하지 않고도 System.out 을 통해 PrintStream 의 메서드를 사용할 수 있는 것이다.
  • 흔히 사용하는 println 도 PrintStream 의 메서드이다.

 

 

out 에 들어가는 구현체? (어디까지나 나의 추측, 완전히 믿지는 말것)

  • 물론 PrintStream 형의 객체가 out 에 들어가긴 한다. 하지만 null 이다.
  • 위 글에서 형 이라는 단어에 굵은 글씨를 쓴 이유는, 실제로 들어가는 구현체는 FileOutputStream 을 감싼 BufferedOutputStream 이다.
  • out 이 초기화 되는 순서는 다음과 같다. (out 은 static 이므로 처음 정적 아이템들이 초기화 될때 이다.)
1. System 클래스의 정적 메서드 initializeSystemClass() 가 실행된다.
  그 안의 set~ 함수에서 out,in,err 의 초기화가 이루어진다.
   여기는 다음과 같은 메서드 실행이 정의되어 있다.
        FileInputStream fdIn = new FileInputStream(FileDescriptor.in);
        FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
        FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err);
        
        setIn0(new BufferedInputStream(fdIn));
        setOut0(newPrintStream(fdOut, props.getProperty("sun.stdout.encoding")));
        setErr0(newPrintStream(fdErr, props.getProperty("sun.stderr.encoding")));
2. out 의 경우 newPrintStream() 이 실행된다. 이 메서드는 System 클래스에 정적 메서드로 선언되어 있다.
   매개변수로 FileOutputStream 객체를 넘긴다.
   newPrintStream() 메서드는 BufferedOutputStream 로 위에서 만든 FileOutputStream 을 감싸서 반환한다.
    private static PrintStream newPrintStream(FileOutputStream fos, String enc) {
       if (enc != null) {
            try {
                return new PrintStream(new BufferedOutputStream(fos, 128), true, enc);
            } catch (UnsupportedEncodingException uee) {}
        }
        return new PrintStream(new BufferedOutputStream(fos, 128), true);
    }
3. FileOutput 객체로 만든 BufferedOutputStream 을 반환받아 setOut0() 를 실행한다.
    setOut0() 은 native 메서드이다.
    이 부분에서 out 의 초기화가 이루어지는 것 같다. native 라서 확신할 수가 없다 ... 

PrintStream 의 native. setOut0 부분은 372 line 에 있다.

hg.openjdk.java.net/jdk10/jdk10/jdk/file/9b8c96f96a0f/src/share/native/java/lang/System.c

 

 

 

System.in

  • System 클래스에는 아래의 정적 필드를 가지고 있다.
  • 콘솔에서부터 데이터를 읽어올 때 사용할 수 있다.
public final static InputStream in = null;

 

  • in 은 InputStream 타입의 객체이기 때문에 InputStream 의 메서드들을 사용할 수 있다.
  • 위에서 학습하였듯, read 라는 이름을 가진 메서드들을 말한다.

 

  • in 또한 null 로 초기화되어 있고, initializeSystemClass() 를 통해 초기화되는데, InputStream 클래스 객체를 감싼 BufferedInputStream 객체가 들어가게 된다.
  • read 이름을 가진 메서드들을 사용할 수 있고, mark, skip, close 등의 메서드들을 사용할 수 있다.

 

 

 

System.err

  • 콘솔로 에러를 출력하는데 사용한다.
  • System 클래스에 아래의 정적 필드가 있다.
public final static PrintStream err = null;

 

  • 에러를 출력할 때 사용한다.
  • 내부 구조는 out 과 같은데, 콘솔에 빨간색으로 출력된다.
  • (native 에서 그렇게 만들었나보다 .. )

 

 

setOut(PrintStream out), setIn(InputStream in), setErr(PrintStream err)

  • System 클래스에 정의된 메서드들이다.
    public static void setIn(InputStream in) {
        checkIO();
        setIn0(in);
    }


    public static void setOut(PrintStream out) {
        checkIO();
        setOut0(out);
    }


    public static void setErr(PrintStream err) {
        checkIO();
        setErr0(err);
    }

 

  • out , in, err 정적 필드를 setIn0(), setOut0(), setErr0() 으로 초기화한다는 것을 위에서 학습하였다.
  • 표준 입출력 스트림은 기본으로 콘솔과 입출력을 한다.
  • 하지만 이 정적 메서드를 사용하면 다른 대상과 입출력을 하도록 설정할 수 있다.
    public static void main(String[] args) {
        try (FileOutputStream fo = new FileOutputStream("파일경로/ab.txt")) {

            PrintStream ps = new PrintStream(new BufferedOutputStream(fo, 128), true);

            System.out.println("Tomas loves Cristin.");
            System.err.println("Cristin also loves Tomas .");

            System.setOut(ps);
            System.setErr(ps);

            System.out.println("Tomas loves Cristin.");
            System.err.println("Cristin also loves Tomas .");

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

 

원래라면 두번씩 출력되었어야 할 것이 중간의 setOut, setErr 메서드에 의해 뒷 부분은 파일에 적히게 된다.

콘솔 출력(왼), 파일 출력(오)

 

 

 

 

 

보조 스트림

  • 보조 스트림은 다른 스트림과 연결되어 여러가지 편리한 기능을 제공하는 스트림이다.
  • 데코레이션 패턴을 이용한다.
  • 데이터를 입출력하는 스트림을 감싸는 스트림이다.
  • 입출력 기능을 담당하지는 않고, 아래에 나열한 추가 기능들을 덧붙이는 식이다.
  • 원래의 입출력 스트림에 문자 변환, 입출력 성능 향상, 기본 데이터 타입 입출력, 객체 입출력 등의 추가적인 기능을 제공한다.
  • 필드에 입출력 기능을 담당하는 필드를 두고 생성자로 받아오는 wrapper 클래스이다.

 

1. 문자 변환 보조 스트림 - InputStreamReader, OutputStreamWriter

  • 입출력 스트림이 바이트 기반 스트림이고, 입출력 데이터가 문자라면 문자기반 스트림 Reader 와 Writer로 변환할 필요가 있다.
  • InputStreamReader : 바이트 기반 입력 스트림을 Reader 로 바꾼다.
Reader reader = new InputStreamReader(바이트 기반 입력 스트림);

 

  • OutputStreamWriter : 바이트 기반 출력 스트림을 Writer 로 바꾼다.
Writer writer = new OutputStreamWriter(바이트 기반 출력 스트림);

 

 

2. 성능 향상 보조 스트림

- BufferedInputStream, BufferedReader / BufferedOutputStream, BufferedWriter

  • 메모리 버퍼를 사용하면 입출력 횟수를 줄이면서 성능 향상을 기대할 수 있다.

 

  • BufferedInputStream : 바이트 입력 스트림을 감싸서 버퍼를 이용하게 하여 성능을 향상시킬 수 있다.
BufferedInputStream bis = new BufferedInputStream(바이트 기반 입력 스트림);

 

  • BufferedReader : 문자 입력 스트림을 감싸서 버퍼를 이용하게 하여 성능을 향상시킬 수 있다.
BufferedReader br = new BufferedReader(문자 기반 입력 스트림);

 

  • BufferedOutputStream : 바이트 출력 스트림을 감싸서 버퍼를 이용하게 하여 성능을 향상시킬 수 있다.
BufferedOutputStream bos = new BufferedOutputStream(바이트 기반 출력 스트림);

 

  • BufferedWriter : 문자 출력 스트림을 감싸서 버퍼를 이용하게 하여 성능을 향상시킬 수 있다.
BufferedWriter bw = new BufferedWriter(문자 기반 출력 스트림);

 

 

 

3. 기본 타입 입출력 보조 스트림 - DataOutputStream, DataInputStream

  • 바이트 기반 스트림은 바이트 단위로 입출력하기에, 자바의 기본 타입 boolean, char, short, int, long, double, float 로 입출력 할 수 없기에, 이 보조 스트림을 이용할 수 있다.
  • 보조 스트림으로 변환한 이후 다음의 메서드들로 입출력을 할 수 있다.

 

DataInputStream dis = new DataInputStream(바이트 기반 입력 스트림);
DataOutputStream dis = new DataOutputStream(바이트 기반 출력 스트림);

 

 

 

4. 프린터 보조 스트림 - PrintWriter, PrintStream

  • println(), printf(), print() 를 가지고 있는 메서드이다. 기본으로 콘솔 출력을 할 수 있고, set~ 메서드를 통해 다른 곳으로 프린트할 수 있다.
  • System.out 필드도 PrintStream 타입이었기에 println() 같은 메서드를 사용하는 것이다.
  • 또한 이 클래스들이 보조 스트림이기 때문에 BufferedOutputStream 들을 감쌀 수 있었던 것이다.
PrintStream ps = new PrintStream(바이트 기반 출력 스트림);
PrintWriter pw = new PrintWriter(문자 기반 출력 스트림);

 

 

 

5. 객체 입출력 보조 스트림 - ObjectInputStream, ObjectOutputStream

  • 자바는 메모리 힙영역에 생성된 객체를 파일 또는 네트워크로 출력할 수 있다.
  • 객체는 문자가 아니기 때문에 바이트 기반 스트림으로 출력해야 한다.
  • 이 때, 객체를 일렬로 늘어선 바이트 형태로 바꿔야 하는데, 이를 직렬화 라 한다.
  • 반대로 네트워크를 통해 늘어선 바이트 형태로 읽어온 객체를 원래 객체로 바꾸는 과정을 역 직렬화라 한다.

 

  • ObjecOutputStream : 바이트 기반 스트림을 객체를 직렬화하여 출력할 수 있는 스트림으로 만든다.
  • writeObject() 로 객체를 직렬화할 수 있다.
// 바이트 입력 스트림을 직렬화 할 수 있는 스트림으로 바꿈.
ObjectInputStream ois = new ObjectInputStream(바이트 입력 스트림);

// 객체를 직렬화하여 출력 스트림으로 보냄
ois.writeObject(객체);

 

  • ObjectInputStream : 바이트 기반 스트림을 객체를 역직렬화하여 입력할 수 있는 스트림으로 만든다.
  • readObject() 로 객체를 역직렬화할 수 있다.
// 바이트 출력 스트림을 직렬화 할 수 있는 스트림으로 바꿈.
ObjectOutputStream oos = new ObjectOutputStream(바이트 출력 스트림);

// 읽어온 것을 역직렬화하여 객체에 저장
객체타입 변수 = oos.readObject();

 

 

 

 

파일 입출력

  • java.io 패키지에는 File 클래스가 있다.
  • 이 클래스는 파일의 크기, 파일 속성, 파일 이름 등의 파일 정보를 얻는 메서드가 있다.
  • 또, 파일을 생성, 삭제하는 기능이 있다.
  • 파일의 데이터를 읽고 쓰는 기능은 없다.
File file = new File(String pathName);
  • pathName 이 파일이라면, 경로와 마지막에 파일명.확장자 를 적어준다.
  • pathName 이 디렉토리라면, 경로와 마지막에 폴더명 을 적어준다. (폴더는 확장자가 없다.)

주의점

  • File 객체를 만드는 것은 이제 파일을 가지고 무언가를 하기 위해 위치를 지정하고 객체를 생성하는 것 뿐, 파일을 만들거나 하지 않는다.
  • 파일 경로가 잘못되더라도 오류를 발생하지 않는다.

 

파일의 존재 확인

boolean isExist = file.exists();
  • file 객체를 만들 때 지정한 경로에 파일 또는 디렉토리가 있으면 true, 아무것도 없으면 false 를 반환한다.

 

파일의 생성 및 삭제

  • 메서드들은 매개변수가 없다.

 

 

creatNewFile()

        File file = new File("경로/abc.txt");
        file.createNewFile();

 

 

mkdir()

        File file = new File("경로/myDir");
        file.mkdir();

 

 

 

파일, 디렉토리에서 정보를 얻어오는 메서드

 

        File file = new File("경로/myDir");
        long createdTime = file.lastModified();
        String fileName = file.getName();

        String pattern = "yyyy-MM-dd hh:mm aa";
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern);

        Date lastModifiedDate = new Date( createdTime );

        System.out.println( fileName + " 은 " + simpleDateFormat.format( lastModifiedDate ) + "에 마지막으로 수정되었습니다." );

 

 

  • 이미 같은 파일, 디렉토리가 만들어져 있으면 새로 만들어서 덮어쓰지 않는다.
  • lastModified() 의 반환값은 long 형으로, 유닉스 시간 (1970년 1월 1일 00시 00분 00초) 로 부터의 millisecond 를 반환하기 때문에, 적절한 형식으로 바꿔줘야 한다.

 

 

 

바이트 기반 파일 입출력 스트림 - FileInputStream, FileOutputStream

  • File 객체로는 파일의 객체의 데이터를 읽고쓰지 못하기 때문에, 파일에서 데이터를 읽고 쓸때 사용하는 스트림이다.
  • 바이트 단위로 읽고 쓴다.
  • 다음의 예시는 파일에 있는 데이터를 읽는 예시이다.
File file = new File("경로/abc.txt");

        try (FileInputStream fileInputStream = new FileInputStream(file)) {
            byte[] b = new byte[5];

            int readData;
            while ((readData = fileInputStream.read(b)) != -1) {
                for (byte data : b) {
                    System.out.print((char) data);
                }
                System.out.println();
            }
        }

 

abc.txt

hello world

 

  • 앞서 이야기한것처럼, 마지막의 worl 은 전 배열에서 지워지지 않은 값이 그대로 출력된 것이다.
  • 제일 앞의 InputStream 예제에서 FileInputStream() 의 인자로 파일의 경로를 바로 적을 수 있었던 것은 FileInputStream 에 다음과같은 생성자가 정의되어 있기 때문이다.
    public FileInputStream(String name) throws FileNotFoundException {
        this(name != null ? new File(name) : null);
        // String 으로 File 객체를 만들어줌
    }

 

 

 

  • 다음은 파일에 바이트 데이터를 작성하는 예제다.
    public static void main(String[] args) throws IOException {
        File file = new File("경로/abc.txt");

        try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
            byte[] toWrite = "Go".getBytes();
            fileOutputStream.write(toWrite);
            
            fileOutputStream.flush();
        }
    }

  • 본래의 데이터는 모두 지워지고 작성된다.
  • FileOutputStream 에는 다음과 같은 생성자가 있다.
    public FileOutputStream(String name) throws FileNotFoundException {
        this(name != null ? new File(name) : null, false);
    }

 

 

 

문자 기반 파일 입출력 스트림 - FileReader, FileWriter

  • 위 FileInputStream, FileOutputStream 과 만들어진 목적은 같지만, 문자 기반으로 데이터를 읽어오기 위한점만 다르다.
  • Reader, Wirter 의 read(), write() 를 통해 문자 단위로 데이터를 읽어오고 작성할 수 있다.

 

 

 

 

 

'WhiteShip Java Study : 자바 처음부터 멀리까지' 카테고리의 다른 글

제네릭  (0) 2021.02.24
NIO  (0) 2021.02.19
ServiceLoader  (0) 2021.02.14
Annotation  (0) 2021.02.13
enum  (0) 2021.02.08