자유/대외 활동

[혼공학습단 9기] #9. 입출력 스트림 아직도 헷갈려?

Chipmunks 2023. 2. 18.
728x90

안녕하세요, 다람쥐입니다!

 

드디어 혼공학습단 혼공자바도 마지막 주차네요.

 

1월 새해부터 쭉 달려와서 6주차로 마무리 하네요. ☺️☺️

 

진짜 독자분들도 나중에 혼공학습단 10기가 나오면 꼭 해보세요!

 

한빛미디어에서도 혼공학습단 모집을 꽤 자주 하는 것 같더라고요!

 

혼공자바 6주차 진도

 

혼공학습단 9기 혼공자바 6주차 진도는 입출력 스트림입니다!

 

기본 미션으로는 입출력 스트림을 요약 정리하는 포스팅을 쓰는 겁니다!

 

선택 미션으로는 632 페이지 문제 2번을 풀고 풀이 과정을 설명합니다.

 

저도 입출력 스트림이 생각보다 머리에 안 들어와서 헷갈렸던 주제인데요.

 

이번 기회를 통해서 입출력 스트림을 정리해보았네요!

 

혼공자바 6주차 입출력 스트림 한 번 정리해 보아요~

 

입출력 스트림

자바에서 데이터는 스트림을 통해 입출력됩니다.

프로그램이 도착지이면 입력 스트림이고 키보드, 파일, 다른 프로그램에서 올 수 있습니다.

프로그램이 출발지이면 출력 스트림이고 모니터, 파일, 다른 프로그램입니다.

 

입출력 스트림 종류

java.io 패키지에는 여러 가지 종류의 스트림 클래스를 제공하고 있습니다.

스트림 클래스는 크게 두 가지 종류로 구분합니다.

 

  • 바이트 기반 스트림 : 그림, 멀티미디어 등의 바이너리 데이터를 읽고 출력할 때 사용합니다.
  • 문자 기반 스트림 : 문자 데이터를 읽고 출력할 때 사용합니다.

바이트 기반인지 문자 기반인지 확인하는 법은 최상위 클래스를 보면 됩니다.

 

바이트 기반 스트림 최상위 클래스는 InputStream (입력 스트림), OutputStream (출력 스트림) 입니다.

하위 클래스로는 XXXInputStream (입력 스트림), XXXOutputStream (출력 스트림) 이름이 보편적입니다.

 

문자 기반 스트림 최상위 클래스는 Reader (입력 스트림), Writer (출력 스트림) 입니다.

하위 클래스로는 XXXReader (입력 스트림), XXXWriter (출력 스트림) 이름이 보편적입니다.

 

바이트 출력 스트림 : OutputStream

모든 바이트 기반 출력 스트림 클래스는 OutputStream 클래스를 상속받습니다.

상속한 클래스로는 FileOutputStream, PrintStream, BufferedOutputStream, DataOutputStream, ByteArrayOutputStream, FilterOutputStream, ObjectOutputStream, PipedOutputStream 등이 있습니다.

 

OutputStream 클래스의 주요 메서드는 다음과 같습니다.

  • void write(int b) : 1 byte 를 출력합니다.
  • void write(byte[] b) : 매개값으로 주어진 배열 b의 모든 바이트를 출력합니다.
  • void write(byte[] b, int off, int len) : 매개값으로 주어진 배열 b[off] 부터 len 개 까지의 바이트를 출력합니다.
  • void flush() : 출력 버퍼에 잔류하는 모든 바이트를 출력합니다.
  • void close() : 출력 스트림을 닫습니다.

write(int b) 메소드

매개 변수로 주어지는 int(4byte) 에서 끝 1 byte 만 출력 스트림으로 보냅니다.

매개 변수가 int 타입이지만 4byte 모두를 보내지 않습니다.

import java.io.*;

public class Example {
    public static void main(String[] args) throws IOException {
        OutputStream os = new FileOutputStream("test1.db");

        byte a = 10;
        byte b = 20;
        byte c = 30;

        os.write(a);
        os.write(b);
        os.write(c);

        os.flush();
        os.close();

        InputStream is = new FileInputStream("test1.db");

        System.out.println(is.read()); // 10
        System.out.println(is.read()); // 20
        System.out.println(is.read()); // 30

        is.close();

    }
}

만약에 b 매개변수의 값이 1바이트 보다 크면 어떨까요?

1바이트는 8비트이므로, 2^8 값에 해당합니다. 이 값에 10을 더해 1바이트를 넘는 값을 출력해보면 어떻게 될까요?

 

import java.io.*;

public class Example {
    public static void main(String[] args) throws IOException {
        OutputStream os = new FileOutputStream("test1.db");

        int a = (int)Math.pow(2, 8) + 10;

        os.write(a);

        os.flush();
        os.close();

        InputStream is = new FileInputStream("test1.db");

        System.out.println(is.read()); // 10

        is.close();

    }
}

파일을 읽으면 10이 나타나는 걸 확인할 수 있습니다.

1바이트보다 높은 24비트는 무시되는 걸 확인할 수 있습니다.

write(int b) 메소드의 API 주석에서도 낮은 순서의 8비트가 쓰이고, 높은 24 비트는 무시된다고 합니다.

    /**
     * Writes the specified byte to this output stream. The general
     * contract for <code>write</code> is that one byte is written
     * to the output stream. The byte to be written is the eight
     * low-order bits of the argument <code>b</code>. The 24
     * high-order bits of <code>b</code> are ignored.
     * <p>
     * Subclasses of <code>OutputStream</code> must provide an
     * implementation for this method.
     *
     * @param      b   the <code>byte</code>.
     * @exception  IOException  if an I/O error occurs. In particular,
     *             an <code>IOException</code> may be thrown if the
     *             output stream has been closed.
     */
    public abstract void write(int b) throws IOException;

 

write(byte[] b) 메소드

매개값으로 주어진 배열의 모든 바이트를 출력 스트림으로 보냅니다.

import java.io.*;

public class Example {
    public static void main(String[] args) throws IOException {
        OutputStream os = new FileOutputStream("test1.db");

        byte[] byteArray = { 10, 20, 30 };

        os.write(byteArray);

        os.flush();
        os.close();

        InputStream is = new FileInputStream("test1.db");

        byte[] readByteArray = new byte[3];
        is.read(readByteArray);

        System.out.println(readByteArray[0]); // 10
        System.out.println(readByteArray[1]); // 20
        System.out.println(readByteArray[2]); // 30

        is.close();

    }
}

 

내부 메소드를 확인하면 write(byte[] b, int off, int len) 메소드를 호출합니다.

    /**
     * Writes <code>b.length</code> bytes from the specified byte array
     * to this output stream. The general contract for <code>write(b)</code>
     * is that it should have exactly the same effect as the call
     * <code>write(b, 0, b.length)</code>.
     *
     * @param      b   the data.
     * @exception  IOException  if an I/O error occurs.
     * @see        java.io.OutputStream#write(byte[], int, int)
     */
    public void write(byte b[]) throws IOException {
        write(b, 0, b.length);
    }

 

write(byte[] b, int off, int len) 메소드

b[off] 부터 len 개의 바이트를 출력 스트림으로 보냅니다.

 

import java.io.*;

public class Example {
    public static void main(String[] args) throws IOException {
        OutputStream os = new FileOutputStream("test1.db");

        byte[] byteArray = { 10, 20, 30, 40, 50 };

        os.write(byteArray, 1, 3);

        os.flush();
        os.close();

        InputStream is = new FileInputStream("test1.db");

        byte[] readByteArray = new byte[3];
        is.read(readByteArray);

        System.out.println(readByteArray[0]); // 20
        System.out.println(readByteArray[1]); // 30
        System.out.println(readByteArray[2]); // 40

        is.close();

    }
}

 

바이트 입력 스트림 : InputStream

 InputStream 은 바이트 기반 입력 스트림의 최상위 클래스로 추상 클래스입니다.

모든 바이트 기반 입력 스트림은 InputStream 클래스를 상속받습니다.

그 종류로 FileInputStream, BufferedInputStream, DataInputStream, ByteArrayInputStream, FilterInputStream, ObjectInputStream, PipedInputStream, SequenceInputStream, StringBufferInputStream 등이 있습니다.

 

다음은 InputStream 클래스의 주요 메서드입니다.

  • int read() : 1 byte 를 읽고 읽은 바이트를 리턴합니다.
  • int read(byte[] b) : 읽은 바이트를 매개값으로 주어진 배열에 저장하고 읽은 바이트 수를 리턴합니다.
  • int read(byte[] b, int off, int len) : len 개의 바이트를 읽고 매개값으로 주어진 배열에서 b[off]부터 len개까지 저장합니다. 그리고 읽은 바이트 수를 리턴합니다.
  • void close() : 입력 스트림을 닫습니다.

 

출력 스트림과 동일하게 read() 메소드가 동작합니다.

더 이상 입력 스트림으로부터 바이트를 읽을 수 없다면 read() 메소드에서 -1 을 리턴합니다.

 

import java.io.*;

public class Example {
    public static void main(String[] args) throws IOException {
        OutputStream os = new FileOutputStream("test1.db");

        byte[] byteArray = { 10, 20, 30 };

        os.write(byteArray);

        os.flush();
        os.close();

        InputStream is = new FileInputStream("test1.db");

        while (true) {
            int data = is.read();
            if (data == -1) break;

            System.out.println(data); // 10 20 30
        }

        is.close();

    }
}

read() 메소드에서 -1 을 받았을 때 반복문을 빠져나오도록 했습니다.

1 바이트씩 읽으며 모니터로 데이터를 출력해주고 있습니다.

 

read(byte[] b) 메소드

read(byte[] b) 메소드는 입력 스트림으로부터 매개값으로 주어진 배열의 길이만큼 바이트를 읽고 해당 배열에 저장합니다.

그리고 읽은 바이트 수를 리턴합니다.

 

실제 읽은 바이트 수가 배열의 길이보다 적을 경우, 읽은 수 만큼만 리턴합니다.

길이가 3인 배열로, 5 바이트를 두 번 읽을 수 있습니다.

 

입력 스트림에서 바이트를 더 읽을 수 없다면 마찬가지로 -1 을 리턴합니다.

 

import java.io.*;

public class Example {
    public static void main(String[] args) throws IOException {
        OutputStream os = new FileOutputStream("test1.db");

        byte[] byteArray = new byte[] { 0, 0, 20, 30, 40 };

        os.write(byteArray);

        os.flush();
        os.close();

        InputStream is = new FileInputStream("test1.db");

        byte[] buffer = new byte[100];

        while (true) {
            int readByteNumber = is.read(buffer);
            if (readByteNumber == -1) break;

            System.out.println("readByteNumber: " + readByteNumber); // 5

            for (int i = 0; i < readByteNumber; i++) {
                System.out.println(buffer[i]); // 0 0 20 30 40
            }
        }
        is.close();
    }
}

 

buffer 100개의 바이트가 들어갈 수 있는 배열을 정의합니다.

100개의 바이트 배열로 버퍼로 사용하여 읽습니다.

readByteNumber 변수에 is.read(buffer) 의 결과로 5를 저장하는데요!

is.read(buffer) 의 결과로 100개의 바이트가 들어가는 버퍼 중 5개만 들어가게 됩니다.

 

read(byte[] b, int off, int len) 메소드

입력 스트림에서 len 개 까지의 바이트만 읽습니다. 그리고 매개값으로 주어진 b[off] 부터 len 개 바이트만 저장합니다.

그리고 읽은 바이트 수인 len 개를 리턴합니다.

실제로 읽은 바이트 수가 len 개 보다 작을 경우에는 읽은 수만큼만 리턴합니다.

 

read(byte[][ b, int off, int len) 역시 입력 스트림에서 바이트를 더 이상 읽을 수 없으면 -1 을 리턴합니다.

read(byte[] b) 메소드와의 차이점은 한 번에 읽어들이는 바이트 수를 len 매개값으로 조절할 수 있다는 점입니다.

배열에서 저장이 시작되는 인덱스를 지정할 수 있습니다.

 

off : 0, len : 배열의 크기 로 설정한다면 read(byte[] b) 와 같습니다.

 

import java.io.*;

public class Example {
    public static void main(String[] args) throws IOException {
        OutputStream os = new FileOutputStream("test1.db");

        byte[] byteArray = new byte[] { 0, 0, 20, 30, 40 };

        os.write(byteArray);

        os.flush();
        os.close();

        InputStream is = new FileInputStream("test1.db");

        byte[] buffer = new byte[5];

        while (true) {
            int readByteNumber = is.read(buffer, 2, 3);
            if (readByteNumber == -1) break;

            System.out.println("readByteNumber: " + readByteNumber); // 5

            for (int i = 0; i < buffer.length; i++) {
                System.out.println(buffer[i]); // 0 0 20 30 40
            }
        }
        is.close();
    }
}
readByteNumber: 3
0
0
0
0
20
readByteNumber: 2
0
0
30
40
20

 

첫 번째 반복문에서는 len (3) 개 만큼 읽습니다.

그리고 바이트 배열의 2 번째 위치부터 저장합니다.

초기 값이 0 이므로, 세 번째 순서부터 0 0 20 을 저장합니다.

두 번째 반복문에서는 세 번째 순서부터 나머지 30 40 50 을 저장하게 됩니다.

 

문자 출력 스트림: Writer

Writer 는 문자 기반 출력 스트림의 최상위 클래스로, 추상 클래스입니다. 

모든 문자 기반 출력 스트림 클래스는 Writer 클래스로 상속받아서 만들어집니다.

 

Writer 클래스의 주요 메소드는 다음과 같습니다.

  • void write(int c) : 매개값으로 주어진 한 문자를 보냅니다.
  • void write(char[] cbuf) : 매개값으로 주어진 배열의 모든 문자를 보냅니다.
  • void write(Char[] cbuf, int off, int len) : 매개값으로 주어진 배열에서 cbuf[off] 부터 len 개 까지의 문자를 보냅니다.
  • void write(String str) :  매개값으로 주어진 문자열을 보냅니다.
  • void write(String str, int off, int len) : 매개값으로 주어진 문자열에서 off 순번부터 len 개까지의 문자를 보냅니다.
  • void flush() : 버퍼에 잔류하는 모든 문자를 출력합니다.
  • void close() : 출력 스트림을 닫습니다.

write(int c) 메소드

write(int c) 메소드는 매개 변수로 전달하는 int(4 byte)에서 끝 2 byte (1개의 문자)만 출력 스트림으로 보냅니다.

2바이트 위의 16 비트는 무시됩니다.

 

import java.io.*;

public class Example {
    public static void main(String[] args) throws IOException {
        Writer writer = new FileWriter("test1.db");

        char a = 'A';
        char b = 'B';
        char c = 'C';

        writer.write(a);
        writer.write(b);
        writer.write(c);

        writer.flush();
        writer.close();

        Reader reader = new FileReader("test1.db");

        while (true) {
            int readCharacter = reader.read();
            if (readCharacter == -1) break;

            System.out.println((char)readCharacter); // A B C
        }

        reader.close();
    }
}

 

write(int  c) 메소드 정의를 보면 아래와 같습니다.

    /**
     * Writes a single character.  The character to be written is contained in
     * the 16 low-order bits of the given integer value; the 16 high-order bits
     * are ignored.
     *
     * <p> Subclasses that intend to support efficient single-character output
     * should override this method.
     *
     * @param  c
     *         int specifying a character to be written
     *
     * @throws  IOException
     *          If an I/O error occurs
     */
    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);
        }
    }

 

writerBuffer 이라는 char 배열 필드를 사용하고 있으며 WRITE_BUFFER_SIZE (1024) 크기를 할당하여 사용합니다.

버퍼 필드의 첫 번째 값을 덮어 씌우고 다른 write() 메소드로 len (1) 개 만큼을 출력 스트림으로 보냅니다.

 

바이트 출력 스트림과 다르게 문자 출력 스트림은 동기화 블록을 사용하고 있는 게 눈에 보이네요!

문자 출력 스트림은 기본적으로 스레드에 안전하게 설계가 되어 있네요.

 

write(char[] cbuf) 메소드

매개값으로 주어진 char[] 배열의 모든 문자를 출력 스트림으로 보냅니다.

 

import java.io.*;

public class Example {
    public static void main(String[] args) throws IOException {
        Writer writer = new FileWriter("test1.db");

        char[] charBuf = { 'A', 'B', 'C' };

        writer.write(charBuf);

        writer.flush();
        writer.close();

        Reader reader = new FileReader("test1.db");

        while (true) {
            int readCharacter = reader.read();
            if (readCharacter == -1) break;

            System.out.println((char)readCharacter); // A B C
        }

        reader.close();
    }
}

 

write(char[] cbuf, int off, int len) 메소드

cbuf[off] 부터 len 개 까지의 문자를 출력 스트림으로 보냅니다.

 

import java.io.*;

public class Example {
    public static void main(String[] args) throws IOException {
        Writer writer = new FileWriter("test1.db");

        char[] charBuf = { 'A', 'B', 'C', 'D', 'E' };

        writer.write(charBuf, 1, 3);

        writer.flush();
        writer.close();

        Reader reader = new FileReader("test1.db");

        while (true) {
            int readCharacter = reader.read();
            if (readCharacter == -1) break;

            System.out.println((char)readCharacter); // B C D
        }

        reader.close();
    }
}

 

write(String str)와 write(String str, int off, int len) 메소드

문자열로 많이 쓰는 String 객체를 사용하는 메소드를 제공합니다.

 

import java.io.*;

public class Example {
    public static void main(String[] args) throws IOException {
        Writer writer = new FileWriter("test1.db");

        String str = "ABC";

        writer.write(str);

        writer.flush();
        writer.close();

        Reader reader = new FileReader("test1.db");

        while (true) {
            int readCharacter = reader.read();
            if (readCharacter == -1) break;

            System.out.println((char)readCharacter); // A B C
        }

        reader.close();
    }
}

 

문자 입력 스트림: Reader

Reader는 문자 기반 입력 스트림의 최상위 클래스로 추상 클래스입니다.

모든 문자 기반 입력 스트림은 Reader 클래스를 상속받습니다.

 

Reader 클래스의 주요 메소드는 다음과 같습니다.

  • int read() : 1개의 문자를 읽고 리턴합니다.
  • int read(char[] cbuf) : 읽은 문자들을 매개값으로 주어진 문자 배열에 저장하고 읽은 문자 수를 리턴합니다.
  • int read(char[] cbuf, int off, int len) : len 개의 문자를 읽고 매개값으로 주어진 문자 배열에서 cbuf[off] 부터 len 개까지 저장합니다. 그리고 읽은 문자 수를 리턴합니다.
  • void close() : 입력 스트림을 닫습니다.

read() 메소드

read() 메소드는 입력 스트림에서 1개의 문자(2byte)를 읽고 int(4byte) 타입으로 리턴합니다.

더 이상 입력 스트림에서 문자를 읽을 수 없다면 -1 을 리턴합니다.

얻은 문자를 char 타입으로 변환하여 사용합니다.

 

read(char[] cbuf) 메소드

매개값으로 주어진 문자 배열의 길이만큼 문자를 읽고 배열에 저장합니다.

그리고 읽은 문자 수를 리턴합니다.

실제로 읽은 문자 수가 배열의 길이보다 적을 경우 읽은 수 만큼만 리턴합니다.

3개의 문자가 들어온다면 길이가 2인 문자 배열로 두 번 읽을 수 있습니다.

 

import java.io.*;
import java.util.Arrays;

public class Example {
    public static void main(String[] args) throws IOException {
        Writer writer = new FileWriter("test1.db");

        String str = "안녕하세요";

        writer.write(str);

        writer.flush();
        writer.close();

        Reader reader = new FileReader("test1.db");

        char[] readBuf = new char[4];

        while (true) {
            Arrays.fill(readBuf, (char) 0);

            int readCharacterNumber = reader.read(readBuf);
            if (readCharacterNumber == -1) break;

            System.out.println(readBuf);
        }

        reader.close();
    }
}
/*
안녕하세
요
 */

 

보조 스트림

보조 스트림은 다른 스트림과 연결이 되어 여러 가지 편리한 기능을 제공해주는 스트림을 말합니다.

자체적으로 입출력을 수행할 수 없기에 입출력 소스와 바로 연결되는 InputStream, OutputStream, Reader, Writer 등에 연결해서 입출력을 수행합니다.

보조 스트림은 문자 변환, 입출력 성능 향상, 기본 타입 입출력 등의 기능을 제공합니다.

 

보조 스트림 연결하기

보조 스트림을 연결하려면 보조 스트림을 생성할 때 자신이 연결될 스트림을 생성자의 매개값으로 제공합니다.

보조스트림 변수 = new 보조스트림(연결스트림)

InputStream을 문자 변환 보조 스트림인 InputStreamReader 에 연결하는 코드는 다음과 같습니다.

InputStream is = ...;
InputStreamReader reader = new InputStreamReader(is);

 

보조 스트림의 생성자 매개값은 InputStream, OutputStream, Reader, Writer 이외에 또 다른 보조스트림이 될 수 있습니다.

보조 스트림을 연속적으로 연결이 가능합니다.

 

위 코드에서 성능 향상 보조 스트림인 BufferedReader 에 연결할 수 있습니다.

InputStream is = ...;
InputStreamReader reader = new InputStreamReader(is);
BufferedReader br = new BufferedReader(reader);

 

문자 변환 보조 스트림

소스 스트림이 바이트 기반 스트림(InputStream, OutputStream, FileInputStream, FileOutputStream) 이면서 입출력 데이터가 문자라면, Reader 와 Writer 로 변환해서 사용하는 걸 고려할 수 있습니다.

Reader, Writer 클래스가 문자 입출력 메소드를 지원해주기 때문인데요.

 

OutputStreamWriter

OutputStreamWriter 는 바이트 기반 출력 스트림에 연결되어 문자 출력 스트림인 Writer 로 반환하는 보조 스트림입니다.

FileOutputStream fos = new FileOutputStream("test1.txt");
Writer writer = new OutputStreamWriter(fos);

 

InputStreamReader

마찬가지로 바이트 기반 입력 스트림에 연결되어 문자 입력 스트림인 Reader 로 반환하는 보조 스트림입니다.

FileInputStream fis = new FileInputStream("test1.txt");
Reader reader = new InputStreamReader(fis);
import java.io.*;

public class Example {
    public static void main(String[] args) throws IOException {
        write("문자 변환 스트림을 사용합니다.");
        String data = read();
        System.out.println(data); // 문자 변환 스트림을 사용합니다.
    }

    public static void write(String str) throws IOException {
        FileOutputStream fos = new FileOutputStream("test1.txt");
        Writer writer = new OutputStreamWriter(fos);
        writer.write(str);
        writer.flush();
        writer.close();
    }

    public static String read() throws IOException {
        FileInputStream fis = new FileInputStream("test1.txt");
        Reader reader = new InputStreamReader(fis);
        char[] buffer = new char[100];
        int readCharNum = reader.read(buffer);
        reader.close();

        String data = new String(buffer, 0, readCharNum);
        return data;
    }
}

 

성능 향상 보조 스트림

프로그램의 실행 성능은 입출력이 가장 늦은 장치를 따라갑니다.

CPU와 메모리가 아무리 뛰어나도 하드 디스크의 입출력이 늦어지면 프로그램의 실행 성능은 하드 디스크의 처리 속도에 맞춰집니다.

네트워크로 데이터를 전송할 때도 마찬가지입니다.

느린 네트워크 환경이라면 컴퓨터 사양이 좋아도 네트워크 입출력 속도는 느릴 수 밖에 없습니다.

 

프로그램이 입출력 소스로 바로 보내지 않고 중간에 메모리 버퍼를 사용해 실행 성능을 향상시킬 수 있습니다.

하드 디스크에 데이터를 보내지 않고 메모리 버퍼에 데이터를 보냄으로써 쓰기 속도를 향상시킬 수 있습니다.

버퍼의 역할은 데이터가 쌓이기를 기다렸다, 꽉 차게 되면 데이터를 한꺼번에 하드 디스크로 보냄으로써

출력 횟수를 줄여줍니다.

 

기본적으로 출력 스트림 내부에 작은 버퍼를 가지고 있지만, 이것만으로 불충분할 수 있습니다.

보조 스트림 중에선 위와 같이 메모리 버퍼를 추가로 제공하여 성능을 향상시키는 것들이 있습니다.

 

바이트 기반 스트림에서는 BufferedInputStream, BufferedOutputStream 이 있고, 문자 기반 스트림에는 BufferedReader, BufferedWriter 가 있습니다.

 

BufferedOutputStream 과 BufferedWriter

BufferedOutputStream 과 BufferedWriter 는 프로그램에서 전송한 데이터를 내부 버퍼에 쌓아 두었다 버퍼가 꽉 차면, 버퍼의 모든 데이터를 한꺼번에 보냅니다.

메모리 버퍼로 데이터를 고속 전송하기에, 출력 전송이 향상되는 효과를 얻을 수 있습니다.

 

BufferedOutputStream bos = new BufferedOutputStream(바이트 기반 출력 스트림);
BufferedWriter bw = new BufferedWriter(문자 기반 출력 스트림);

BufferedInputStream 과 BufferedReader

BufferedInputStream 은 바이트 기반 입력 스트림에 연결되어 버퍼를 제공해주는 보조 스트림입니다.

BufferedRead 는 문자 기반 입력 스트림에 연결되어 버퍼를 제공해주는 보조 스트립입니다.

내부 버퍼 크기만큼 데이터를 미리 읽고 버퍼에 저장합니다.

외부 입력 소스로부터 직접 읽는 대신에 버퍼를 읽음으로써 읽기 성능이 향상됩니다.

 

BufferedInputStream bis = new BufferedInputStream(바이트 기반 입력 스트림);
BufferedReader br = new BufferedReader(문자 기반 입력 스트림);
import java.io.*;

public class Example {
    public static void main(String[] args) throws IOException {
        String originalFilePath1 = Example.class.getResource("img.png").getPath();
        String targetFilePath1 = "targetImg.png";
        FileInputStream fis = new FileInputStream(originalFilePath1);
        FileOutputStream fos = new FileOutputStream(targetFilePath1);

        String originalFilePath2 = Example.class.getResource("img.png").getPath();
        String targetFilePath2 = "targetImg2.png";
        FileInputStream fis2 = new FileInputStream(originalFilePath2);
        FileOutputStream fos2 = new FileOutputStream(targetFilePath2);
        BufferedInputStream bis = new BufferedInputStream(fis2);
        BufferedOutputStream bos = new BufferedOutputStream(fos2);

        long nonBufferTime = copy(fis, fos);
        System.out.println("버퍼를 사용하지 않았을 때:\t" + nonBufferTime + "ns"); // 버퍼를 사용하지 않았을 때:	8163438458ns

        long bufferTime = copy(bis, bos);
        System.out.println("버퍼를 사용했을 때:\t" + bufferTime + "ns"); // 버퍼를 사용했을 때:	57437833ns

        fis.close();
        fos.close();
        bis.close();
        bos.close();
    }

    static int data = -1;
    public static long copy(InputStream is, OutputStream os) throws IOException {
        long start = System.nanoTime();

        while (true) {
            data = is.read();
            if (data == -1) break;
            os.write(data);
        }
        os.flush();
        long end = System.nanoTime();
        return (end - start);
    }
}

 

라인 단위로 읽는 보조 스트림

BufferedReader 는 라인 단위로 문자열을 읽는 readLine() 메소드를 제공합니다.

readLine() 은 엔터 키(캐리지리턴 \r + 라인피드 \n) 이전의 모든 문자열을 읽고 리턴합니다.

키보드에서 입력한 내용과 파일 내용을 라인 단위로 읽을 수 있습니다.

 

import java.io.*;

public class Example {
    public static void main(String[] args) throws IOException {
        Reader reader = new FileReader(Example.class.getResource("file.txt").getPath());
        BufferedReader br = new BufferedReader(reader);

        while (true) {
            String data = br.readLine();
            if (data == null) break;
            System.out.println(data);
        }

        br.close();
    }
}
/*
안녕하세요.
다람쥐입니다.
반갑습니다람쥐.
 */

 

기본 타입 입출력 보조 스트림

DataInputStream 과 DataOutputStream 보조 스트림을 연결하면 기본 타입인 boolean, char, short, int, long, float, double 을 입출력할 수 있습니다.

readBoolean, writeBoolean, readUTF, writeUTF 등으로 메소드를 지원합니다.

 

import java.io.*;

public class Example {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("primitive.db");
        DataOutputStream dos = new DataOutputStream(fos);

        dos.writeUTF("홍길동");
        dos.writeDouble(95.5);
        dos.writeInt(1);

        dos.writeUTF("김자바");
        dos.writeDouble(90.3);
        dos.writeInt(2);

        dos.flush();
        dos.close();

        FileInputStream fis = new FileInputStream("primitive.db");
        DataInputStream dis = new DataInputStream(fis);

        for (int i = 0; i < 2; i++) {
            String name = dis.readUTF();
            double score = dis.readDouble();
            int order = dis.readInt();
            System.out.println(name + " : " + score + " : " + order);
        }

        dis.close();
    }
}
/*
홍길동 : 95.5 : 1
김자바 : 90.3 : 2
 */

 

프린터 보조 스트림

PrintStream과 PrintWriter는 프린터와 유사하게 출력하는 print(), println() 메소드를 가지고 있는 보조 스트림입니다.

System.out 이 바로 PrintStream 타입입니다. 따라서 print(), println() 메소드를 사용할 수 있었습니다.

PrintStream 은 바이트 기반 출력 스트림과 연결되고, PrintWriter 는 문자 기반 출력 스트림과 연결됩니다.

 

println() 메소드는 출력할 데이터 끝에 개행 문자인 '\n' 을 추가합니다.

 

import java.io.*;

public class Example {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("printstream.txt");
        PrintStream ps = new PrintStream(fos);

        ps.println("[프린터 보조 스트림]");
        ps.print("마치 ");
        ps.println("프린터가 출력하는 것처럼 ");
        ps.println("데이터를 출력합니다.");

        ps.flush();
        ps.close();
    }
}
/*
[프린터 보조 스트림]
마치 프린터가 출력하는 것처럼
데이터를 출력합니다.

 */

 

객체 입출력 보조 스트림

ObjectOutputStream 과 ObjectInputStream 보조 스트림을 연결하면 메모리에 생성된 객체를 파일 또는 네트워크로 출력할 수 있습니다.

ObjectOutputStream 은 객체를 직렬화하는 역할을 하고, ObjectInputStream 은 객체로 역직렬화하는 역할을 합니다.

직렬화란, 객체를 바이트 배열로 만드는 것을 말하고, 역직렬화란 바이트 배열을 다시 객체로 복원하는 것을 말합니다.

 

// Board.java
import java.io.Serializable;
import java.util.Date;

public class Board implements Serializable {
    private int bno;
    private String title;
    private String content;
    private String writer;
    private Date date;

    public Board(int bno, String title, String content, String writer, Date date) {
        this.bno = bno;
        this.title = title;
        this.content = content;
        this.writer = writer;
        this.date = date;
    }

    public int getBno() { return bno; }
    public void setBno(int bno) { this.bno = bno; }
    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }
    public String getContent() { return content; }
    public void setContent(String content) { this.content = content; }
    public String getWriter() { return writer; }
    public void setWriter(String writer) { this.writer = writer; }
    public Date getDate() { return date; }
    public void setDate(Date date) { this.date = date; }
}


// Example.java
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class Example {
    public static void main(String[] args) throws Exception {
        writeList();
        List<Board> list = readList();

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        for(Board board : list) {
            System.out.println(
                    board.getBno() + "\t" + board.getTitle() + "\t" +
                    board.getContent() + "\t" + board.getWriter() + "\t" +
                    sdf.format(board.getDate())
            );
        }
    }

    public static void writeList() throws Exception {
        List<Board> list = new ArrayList<>();

        list.add(new Board(1, "제목1", "내용1", "글쓴이1", new Date()));
        list.add(new Board(2, "제목2", "내용2", "글쓴이2", new Date()));
        list.add(new Board(3, "제목3", "내용3", "글쓴이3", new Date()));

        FileOutputStream fos = new FileOutputStream("board.db");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(list);
        oos.flush();
        oos.close();
    }

    public static List<Board> readList() throws Exception {
        FileInputStream fis = new FileInputStream("board.db");
        ObjectInputStream ois = new ObjectInputStream(fis);
        List<Board> list = (List<Board>) ois.readObject();
        return list;
    }
}
/*
1	제목1	내용1	글쓴이1	2023-02-18
2	제목2	내용2	글쓴이2	2023-02-18
3	제목3	내용3	글쓴이3	2023-02-18
 */

 

632P 2번 문제 풀이

FileReader 와 BufferedReader 를 이용해서 소스 코드를 읽고, 각 라인 번호를 추가해 모니터를 출력하는 프로그램을 작성합니다.

 

import java.io.*;

public class Example {
    public static void main(String[] args) throws Exception {
        String filePath = "src/main/java/Example.java";

        Reader fr = new FileReader(filePath);
        BufferedReader br = new BufferedReader(fr);

        int lineNumber = 1;
        while (true) {
            String sourceCodeLine = br.readLine();
            if (sourceCodeLine == null) break;

            String sourceCodeLineWithNumber = String.format("%-4d\t%s", lineNumber, sourceCodeLine);
            System.out.println(sourceCodeLineWithNumber);
            lineNumber++;
        }
    }
}

FileReader 으로 Example.java 소스 코드를 읽는 문자 입력 스트림을 만들고

BufferedReader 의 생성자로 넣어 버퍼 기능을 지원하는 보조 스트림을 생성합니다.

 

lineNumber (기본값 1) 변수를 만들고 readLine() 한 문자열마다 라인 줄 수를 붙입니다.

1   	import java.io.*;
2   	
3   	public class Example {
4   	    public static void main(String[] args) throws Exception {
5   	        String filePath = "src/main/java/Example.java";
6   	
7   	        Reader fr = new FileReader(filePath);
8   	        BufferedReader br = new BufferedReader(fr);
9   	
10  	        int lineNumber = 1;
11  	        while (true) {
12  	            String sourceCodeLine = br.readLine();
13  	            if (sourceCodeLine == null) break;
14  	
15  	            String sourceCodeLineWithNumber = String.format("%-4d\t%s", lineNumber, sourceCodeLine);
16  	            System.out.println(sourceCodeLineWithNumber);
17  	            lineNumber++;
18  	        }
19  	    }
20  	}

혼공학습단 9기 다른 글 보러가기

2023.02.02 - [자유/대외 활동] - [혼공학습단 9기] #8. 컬렉션 프레임워크, 자료구조 날로 먹기

 

[혼공학습단 9기] #8. 컬렉션 프레임워크, 자료구조 날로 먹기

안녕하세요, 다람쥐 입니다. 🐿 벌써 혼공학습단 9기 혼공자바 5주차 진도네요! 혼자 공부하는 자바가 책 이름대로 혼자 공부하기 편하더라고요! 빠르게 자바를 한 번 훑기에는 이보다 좋은 책

itchipmunk.tistory.com

2023.01.24 - [자유/방탈출 후기] - [혼공학습단 9기] #7. C언어에 포인터가 있다면 자바엔 스레드다!

 

[혼공학습단 9기] #7. C언어에 포인터가 있다면 자바엔 스레드다!

안녕하세요, 다람쥐 입니다! 벌써 한빛미디어 혼공학습단이 절반 지나갔네요! 저번 주에 설 연휴에는 쉬고 이제 다시 혼공학습단 9기 혼공자바 완독을 위해 달려가려고 합니다~ 한빛미디어 혼공

itchipmunk.tistory.com

2023.01.17 - [자유/대외 활동] - [혼공학습단 9기] #5. 3주차 자바 기본 API 클래스, 이건 몰랐을걸?

 

[혼공학습단 9기] #5. 3주차 자바 기본 API 클래스, 이건 몰랐을걸?

안녕하세요, 다람쥐 입니다! 한빛미디어 혼공학습단 9기 혼공자바 3주차 진도 중 두 번째 진도를 나갔습니다. 혼공학습단 9기 혼공자바 3주차 진도 중 두 번째 진도는 바로 자바 기본 API 클래스입

itchipmunk.tistory.com

2023.01.17 - [자유/대외 활동] - [혼공학습단 9기] #4. 3주차 예외, 200% 이해하기

 

[혼공학습단 9기] #4. 3주차 예외, 200% 이해하기

안녕하세요, 다람쥐 입니다. 벌써 한빛미디어 혼공학습단 9기 혼공자바 3주차 진도를 나가게 됐네요! 혼공 학습단을 하며 혼자 공부하는 자바를 공부하다보니 시간이 참 빠르네요! 연휴가 다가

itchipmunk.tistory.com

2023.01.10 - [자유/대외 활동] - [혼공학습단 9기] #3. 2주차 객체 지향 프로그래밍을 왜 쓸까?

 

[혼공학습단 9기] #3. 2주차 객체 지향 프로그래밍을 왜 쓸까?

안녕하세요! 여러분들의 궁금증을 해결해 줄 다람쥐입니다. 한빛미디어 혼공학습단 9기 혼공자바 2주차 포스팅입니다. 2주차는 혼공자바 Chapter 6 ~ 9 ( 클래스, 상속, 인터페이스, 중첩 클래스와

itchipmunk.tistory.com

2023.01.08 - [자유/대외 활동] - [혼공학습단 9기] #2. 자바 기본 정말 안다고 생각해?

 

[혼공학습단 9기] #2. 자바 기본 정말 안다고 생각해?

안녕하세요! 여러분들의 궁금증을 해결해 줄 다람쥐입니다. 한빛미디어 혼공학습단 9기 혼공자바 1주차 포스팅입니다. 1주차는 혼공자바 Chapter 1 ~ 5 ( 자바 설치, 이클립스 설치, 변수와 타입, 연

itchipmunk.tistory.com

2023.01.08 - [자유/대외 활동] - [혼공학습단 #1] 한빛미디어 혼공학습단 9기 선정

 

[혼공학습단 #1] 한빛미디어 혼공학습단 9기 선정

안녕하세요, 다람쥐입니다. 지난 말에 이메일로 한빛미디어에서 혼공학습단 9기 모집안내를 받았어요! 안그래도 직장을 퇴사하고 자바 공부를 해야하던 터였는데요~ 스스로 목표를 잡고 강제성

itchipmunk.tistory.com

 

댓글