본문 바로가기
JAVA/[JAVA] 바구니

[JAVA] Stream

by oncerun 2021. 4. 19.
반응형

 

여기서 말하는 스트림은 자바 8에서 추가된 Stream API가 아니다.

 

자바에서는 파일이나 콘솔의 입출력을 직접 다루지 않고, 스트림이라는 흐름을 통해 다룬다.

 

스트림이란 실제의 입력이나 출력이 표현된 데이터의 이상화된 흐름을 의미합니다. 

즉 스트림은 운영체제에 의해 생성되는 가상의 연결 고리를 의미하며, 중간 매개자 역할을 한다고 한다.

 

입출력 스트림

 

스트림은 한 방향으로만 통신할 수 있으므로, 입력과 출력을 동시에 처리할 수는 없습니다.

java.nio는 채널을 제공해주는 데 이 채널은 입출력을 동시에 처리할 수 있는 양방향 채널입니다.

 

하지만 스트림에서는 단 방향만통신할 수 있기 때문에 입력 스트림과 출력 스트림으로 구분됩니다.

 

자바에서는 java.io 패키지를 통해 InputStream과 OutputStream 클래스를 별도로 제공하고 있습니다.

 

즉 자바에서의 스트림 생성이란 이러한 스트림 클래스 타입의 인스턴스를 생성한다는 의미입니다.

 

 

우선 InputStream부터 살펴 봅니다.

 

추상 메서드로 read()가 존재하는데 상황에 맞게 적절히 구현하여 입력 스트립을 생성하여 사용할 수 있습니다.

이미 정의된 read() 메서드가 존재하는데 오버 로딩이 되어 있다.

  public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }
public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int c = read();
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;

        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
    }

 

 

여기서 보면 오버 로딩을 작성할 때 좋은 예제가 하나 존재합니다.

 

인자의 개수가 가장 많은 메서드를 구현하고 이후 메서드들은 기본값을 가진 상태로 해당 메서드를 호출하는 것을 볼 수 있습니다.(또 하나 배움)

 

read()를 보면 byte [] 배열과, int형의 off, len을 받습니다.

이상한 것이 바이트를 읽어 들었지만 int형을 반환합니다.  그 이유는 더 이상 읽어 들일 바이트가 없으면 , -1을 반환해야 합니다. 그런데 반환 타입을 byte로 하면, 0~255까지 바이트 정보는 표현할 수 있지만 -1은 표현할 수가 없기 때문에 int형으로 선언하고 있습니다.

 

추상 메서드 read를 구현한 구현체를 찾아봤습니다.

각 의미에 맞게 각자 구현체들이 있기 때문에 필요에 의해 가져와 사용할 수 있습니다.

 

하지만 io패키지는 blocking방식이지 non-blocking방식이 아닙니다. 따라서 비동기적으로 실행할 수 없기 때문에 성능적으로 불편함을 감수해야 합니다.

 

 

확정은 아니지만 자바 프로그램에서 입력을 받는다는 것은 다음과 같이 이루어질 것 같습니다.

 

자바단에 하나의 버퍼인 스트림을 두고 읽기 시스템 콜을 할 것입니다. 그러면 커널은 해당 명령을 처리하도록 할 것이고, 반환된 입력값을 시스템 내부 버퍼에 담을 것입니다. 이 버퍼가 차면 flush 하며 이 플러시 된 내용은 다시 스트림에 들어오고 그 값을 읽어서 변수에 저장될 것 같습니다.

이러한 복잡한 과정이 있기에 자바에서의 입/출력은 느릴 수밖에 없었지만 nio패키지가 나오고 나서는 성능이 개선된 걸로 생각됩니다.

 

nio에 관한 정리 글 

homoefficio.github.io/2016/08/06/Java-NIO% EB% 8A%94-%EC%83% 9D% EA% B0%81% EB% A7% 8C% ED%81% BC-non-blocking-%ED%95%98% EC% A7%80-%EC%95% 8A% EB% 8B% A4/

 

Java NIO는 생각만큼 non-blocking 하지 않다

일부러 낚시 냄새가 독하게 풍기는 제목을 지어봤다. Java NIO는 New IO의 줄임말인데, Non-blocking IO 의 줄임말이라고 알고 있는 개발자도 많은 것 같다.(나도 그랬다..) 그만큼 NIO는 Non-blocking이라는

homoefficio.github.io

 

 

기본적으로 자바에서 스트림은 바이트 단위로 데이터를 전송합니다.

따라서 바이트 기반의 입출력 스트림을 제공합니다. 또한 다른 스트림의 기능을 향상하거나 새로운 기능을 추가해주는 스트림인 보조 스트림이 존재합니다.

 

하지만 자바에서는 가장 작은 문자 타입은 char로 2바이트입니다. 1바이트씩 전송되는 바이트 기반 스트림으로는 원활한 처리가 힘들 수 있습니다. 따라서 자바에서는 문자 기반의 스트림도 별도로 제공합니다.

 

이러한 문자 기반 스트림은 Stream대신 Reader 및 Writer로 변경하여 사용하면 됩니다.

ex) FileReader , FileWriter

 

 

다형성을 생각하면 Read나 Writer클래스 타입을 사용하면 될 것 같습니다.

여기서 알아둬야 할 것이 부모 타입으로 선언된다면 자식 타입이 정의한 메서드는 사용하지 못한다는 것을 알아둬야 한다. 

 

docs.oracle.com/javase/8/docs/api/java/lang/class-use/Readable.html

 

Uses of Interface java.lang.Readable (Java Platform SE 8 )

Contains the collections framework, legacy collection classes, event model, date and time facilities, internationalization, and miscellaneous utility classes (a string tokenizer, a random-number generator, and a bit array).

docs.oracle.com

Reaable을 구현하고 있는 구현 체중에 BufferedReader 가 존재합니다.

 public int read(java.nio.CharBuffer cb) throws IOException;

 

설명란에는 다음과 같이 설명하고 있습니다.

 

문자, 배열 및 줄을 효율적으로 읽을 수 있도록 문자를 버퍼링 하여 문자 입력 스트림에서 텍스트를 읽습니다.

 

버퍼 크기를 지정하거나 기본 크기를 사용할 수 있습니다. 기본값은 대부분의 용도에 충분히 큽니다.

일반적으로 Reader의 각 읽기 요청은 해당 읽기 요청이 기본 문자 또는 바이트 스트림으로 이루어지도록 합니다.따라서 FileReaders 및 InputStreamReaders와 같이 read () 작업에 비용이 많이들 수 있는 Reader 주위에 BufferedReader를 래핑 하는 것이 좋습니다. 

 

예를 들면

BufferedReader in = new BufferedReader (new FileReader ( "foo.in")); 지정된 파일의 입력을 버퍼링 합니다.

 

버퍼링이 없으면 read () 또는 readLine ()을 호출할 때마다 파일에서 바이트를 읽고 문자로 변환 한 다음 반환할 수 있습니다. 이는 매우 비효율적 일 수 있습니다.

 

문자를 버퍼링 하며, 줄을 효율적으로 읽을 수 있다고 했습니다. 

 

  try {
            String path = ExampleInputStream.class.getResource("").getPath();
            System.out.println(path);

            BufferedReader reader = new BufferedReader(new FileReader(path+File.separator+"text.txt"));

            String str = reader.readLine();

            System.out.println(str);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

 

 

 

이러니까 한 줄은 잘 읽어오더라... 근데 파일 전체를 읽고 싶은데...

 

기본적으로 이 성능이 괜찮은 BufferReader는 언제 사용될까?

1. 기본적으로 BufferedReader는 한 줄을 통째로 입력받는 방법으로 주로 쓰입니다.

 

2. readLine() 메서드는 값을 읽어올 때, String값으로 개행 문자(엔터 값)를 포함해 한 줄을 전부 읽어오는 방식입니다.

 

확인해 봤는데 키보드로 입력을 받게 될 시 개행 문자는 필수다. 

하지만 파일에서 한 줄을 가져올 때 엔터 안쳤는데 그냥 가져오더라.

BufferedReader reader = new BufferedReader(new FileReader(path+File.separator+"text.txt"));

            String str = reader.readLine();
            System.out.println(str.contains("\n"));

false 나오더라

 

 

그냥 여러 줄 읽을 땐 Scanner 써야 하나?

반응형

'JAVA > [JAVA] 바구니' 카테고리의 다른 글

짧)[JAVA] 객체지향 세계  (0) 2021.04.25
[JAVA] JAVA Serialize  (0) 2021.04.23
[JAVA] 디자인 패턴  (0) 2021.04.13
[JAVA] 예외 처리  (0) 2021.03.27
Java의 기본 log : Logger  (0) 2021.02.26

댓글