2. Stream
스트림
Last updated
스트림
Last updated
출력 스트림
자바의 기본 출력 클래스는 java.io.OutputStream 이다.
[OutputStream을 상속받는 여러 클래스들]
TCP/IP에서 write() 출력 메서드에서 한번에 한 바이트씩 출력하면 매우 비효율적이게 된다. 이걸 한 줄씩 byte[] 배열에 저장 후 OutputStream에 ‘out.write(line)‘ 의 형태처럼 버퍼링 하게 구현하게 구현이 된다. 그래서 TCP/IP 에서는 메모리에 데이터를 쌓아 두고, 일정 수치에 도달하거나 특정 시간을 초과할 경우 데이터를 전송한다. 이렇게 한번에 전송하는 데이터가 1바이트씩 반복해서 보내는 것보다 훨씬 빠르게 된다. 스트림은 또한 네트워크 하드웨어가 아닌 자바 코드 내에서 직접 버퍼링 할 수 있다.(BufferedOutputStream이나 BufferedWriter사용)
이런 버퍼링이 사용되기 때문에 출력 스트림을 사용시 flush()가 중요해진다. HTTP Keep-Alive 를 기본으로 지원하는 HTTP 1.1버전을 예로들어보자. 서버에 300바이트에 해당하는 데이터 요청을 보냈다고 가정해보자. Non-Pipeline이라는 가정하에서는 요청을 보낸 후 추가 다른 요청을 보내기 전에 이미 보낸 요청을 기다리게 될 것입니다. 만약 출력 스트림의 크기가 1,024 바이트 크기를 가지고 있다면, 출력 스트림은 버퍼가 가득 차지 않았기 때문에 버퍼 안의 데이터를 전송하지 않고 추가적인 데이터가 올 때까지 기다립니다. 서버로부터 받을 데이터가 없어 응답이 오지 않게 된다면 버퍼가 가득차지 않은 상태에서 flush()메소드를 사용해 버퍼 내용을 강제로 전송함으로써 데드락(deadlock)상태를 해제합니다.
스트림이 버퍼링이 되는지를 판단 후 플러시를 하는 것 보다 항상 플러시를 하는 것이 좋다. 특정 스트림의 참조를 획득하는 방법에 따라 해당 스트림의 버퍼링 여부를 판단하기 어려운 상황도 발생하기 때문이다.(System.out은 알아서 버퍼링을 함) 필요없는 스트림에 불필요한 플러시가 발생할 수는 있지만부하는 무시해도 될 수준이다. 하지만 플러시가 꼭 필요한 상황에 플러시를 하지 않게 될 경우 프로그램 종료, 데드락 등의 상황이 나타날 수 있으며 진단하기가 매우 어렵다는 큰 단점이 있다.
다 쓴 스트림은 항상 닫기전에 플러시해야 하며, 그렇지 않을 경우 스트림이 닫힐 때 버퍼 안에 남아 있는 데이터가 손실될 수 있다.
마지막으로 스트림 사용이 끝나면 close() 메소드를 호출하여 스트림을 닫는다. (leak 이슈)파일이나 포트같은 스트림에서는 리소스를 해제한다. 네트워크 연결 스트림이면 네트워크 연결이 종료된다. 출력 스트림을 닫은 후에 쓰기를 시도하면 IOException이 발생한다. 그러나 몇몇 종류의 스트림은 닫은 후에도 해당 스트림의 객체(object)에 일부 작업이 허용된다. 예를 들어 이미 닫힌 ByteArrayOutputStream은 여전히 바이트 배열로의 전환이 가능하며, DigetOutputStream은 여전히 다이제스트(digest)값이 반환된다.
OutputStream은 Closeable interface를 상속받는다. 그리고 close메서드를 자동으로 호출하므로 finally 구문에서의 인스턴스 해제 패턴(dispose pattern)은 필요하지 않다.
public interface Closeable extends AutoCloseable {...}
public abstract class OutputStream implements Closeable, Flushable{...}
자바가 제공하는 기본 입력 클래스틑 java.io.InputStream 이다. 데이터를 읽는데 사용하는 클래스.
[InputStream을 상속받는 여러 클래스들]
offset 위치부터 len길이만큼 읽기를 시도한다. 스트림의 끝에 도달하면 -1을 반환한다. 실제 읽어야 하는 바이트의 크기가 보장되어야 하는 상황이라면, 배열의 크기가 가득 찰 때까지 루프 안에 read() 메서드를 위치시켜 문제를 해결할 수 있다. 이렇게 아직 읽지 않은 데이터가 남아있는 상태에서 스트림이 종료되더라도 멀티바이트 read() 메서드는 버퍼를 비울 때까지 데이터를 모두 읽어 반환한다. 만약 필요한 데이터를 모두 읽을 수 있을 때까지 실행이 대기되는 것을 원하지 않는 경우라면, 대기 없이 즉시 읽을 수 있는 바이트 수를 반환하는 available()메서드를 사용하여 읽을 바이트 수를 결정할 수 있다. 입력 스트림도 출력 스트림과 마찬가지고 스트림을 다 쓴 후에는 close() 메서드를 호출하여 리소스를 해제한다.
위치의 표시와 재설정
InputStream은 잘 사용되지 않는 세가지 메서드를 제공한다.
mark() : 스트림의 현재 위치를 표시한다.
reset() : 스트림을 재설정한다.
markSupport() : 지원 유무를 확인한다.
스트림 위치의 표시를 항상 지원하는 유일한 입력 스트림 클래스는 BufferedIinputStream과 ByteArrayInputStream이 있다.
BufferedOutputStream 클래스는 쓰인 데이터를 버퍼가 가득 차거나 스트림이 플러시될 때까지 버퍼에 저장한다. 그리고 버퍼에 저장된 데이터를 내장된 출력 스트림에 한번에 쓴다. 여러 바이트를 한번에 쓰는 것이 나눠 쓰는 것보다 더 나은 성능을 보인다. 네트워크 출력을 버퍼에 저장하여 성능을 크게 향상시킬 수 있다.
BufferedInputStream의 첫 번째 인자는 버퍼링 되지 않은 데이터를 읽게 될 하위 스트림 BufferedOutputStream의 첫 번째 인자는 버퍼링 된 데이터가 쓰일 하위 스트림
메소드에 두 번째 인자는 버퍼에 저장할 수 있는 바이트의 양을 지정한다. 버퍼의 크기를 지정하지 않으면 기본 2,048바이트가 설정되고, 출력 버퍼는 512바이트가 설정된다. 네트워크의 경우 일반 패킷의 크기보다 조금 더 큰 버퍼가 필요하다. 그러나 일반 패킷의 크기는 예측하기 어렵고 네트워크 연결과 프로토콜에 따라 다양할 수가 있다.
BufferedInputStream은 InputStream을 오버라이드할 뿐 추가 메소드를 제공하지 않는다. 스트림 위치의 표시과 재설정은 지원한다. 멀티바이트 read()메서드로 입력 스트림으로부터 필요한 만큼 반복해서 읽고 배열의 일부를 완벽하게 채우는 형태로 동작한다. 배열이 가득차거나 스트림이 끝에 도달하거나 하위 스트림이 추가적인 읽기를 차단할 때 반환한다.
BufferedOutputStream은 OutputStream을 오버라이드할 뿐 추가 메소드를 제공하지 않는다. BufferedOutputStream의 메소드는 다른 출력 스트림을 사용하는 것처럼 똑같이 사용할 수 있다. 차이점이라면 데이터를 하위 스트림에 직접 쓰지 않고 버퍼에 쓴다는 점이다. 따라서 데이터를 전송(out)하는 시점에는 항상 스트림에 플러시(flush)를 해야한다.
DataInputStream과 DataOutputStream 클래스는 자바의 기본 데이터 타입과 바이너리 포맷의 문자열을 읽고 쓰기 위한 메서드를 제공한다.
DataInputStream에는 바이너리 데이터를 읽기 위한 9개의 메서드를 제공하며 DataOutputStream이 제공하는 11개의 메서드에 대응한다.
아래는 DataInputStream의 두개의 readFully 메서드 중 하나이다. 입력 스트림에서 배열로 반복해서 데이터를 끝까지 읽는다. 이 메서드는 정확히 얼마만큼의 데이터를 읽을 수 있는지 (총 길이/양) 알고 있을 때 특히 유용하다. 예를 들어, HTTP 요청의 응답을 처리할 때 HTTP 헤더의 Content-length 필드를 읽어서 본문의 길이를 미리 알 수 있다. 이때 readFully()메서드를 유용하게 사용할 수 있다. 요청된 바이트
자바의 기본 인코딩은 UTF-16 유니코드 인코딩이다. 인코딩이 더 이상 아스키가 아닐 때, 바이트와 문자(char)가 근본적으로 같다는 생각을 버려야 한다. 그 결과, 자바는 입출력 스트림 클래스 계층에 거의 완벽하게 대응하고 바이트 대신 문자를 다룰 수 있도록 설계된 클래스를 제공한다.
writer
java.io.Writer 클래스는 문자를 쓰기 위한 API를 명시함
OutputStreamWriter는 실행중인 프로그램으로부터 유니코드 문자를 전달받고, 지정된 인코딩을 사용하여 바이트로 변환함. 그리고 바이트를 내장된 하위 출력 스트림에 씀.
다른 인코딩을 사용하면 바이트 출력결과는 달라지며, 정확한 출력 결과는 인코딩에 달려 있다.
OutputStreamWriter
Writer의 가장 중요한 서브 클래스이다. 인코딩에 따라서 바이트로 변환한다. 그렇게 변환된 바이트를 내장된 하위 출력 스트림에 쓴다. OutputStreamWriter 생성자는 출력할 출력 스트림과 인코딩 타입을 인자로 받는다.
reader
java.io.Reader 클래스는 문자를 읽기 위한 API를 명시함
InputStreamReader는 raw 바이트를 읽을 수 있는 하위 입력 스트림을 포함하고 있고, 이 하위 스트림을 통해 읽은 raw 바이트를 지정된 인코딩에 따라 유니코드 문자로 변환함.
InputStreamReader
내장된 하위 입력 스트림으로부터 바이트를 읽고, 지정된 타입에 따라 문자로 변환하여 반환한다. InputStreamReader의 생성자는 읽을 입력 스트림과 인코딩 타입을 인자로 받는다.
필터 reader와 필터 writer
InputStreamReader과 OutputStreamWriter는 입출력 스트림을 바이트 중심의 인터페이스에서 문자 중심의 인터페이스로 변환하는 역할을 함. java.io.FilterReader와 java.io.FilterWriter를 사용해서 reader나 writer의 상위에 문자 중심의 또 다른 필터를 올려놓을 수 있음.
BufferedReader, BufferedWriter : 문자 기반의 클래스로써 바이트 기반의 BufferedInputStream, BufferedOutputStream 클래스와 같은 역할을 함. 후자는 바이트 배열을 버퍼로 사용하고 전자는 내부적으로 문자 배열을 버퍼로 사용한다는 차이점이 있다.
BufferedReader는 많은 내용을 읽어 버퍼를 채워 읽기 속도를 향상시키고 BufferedWriter에 텍스트를 출력하면 출력된 텍스트는 BufferedWriter의 버퍼에 추가된다. 버퍼가 가득 차거나 writer를 명시적으로 플러시하는 경우에만 텍스트가 입력 스트림이나 목적지로 이동한다. 이렇게 쓰기 속도를 개선시킨다.
또한 BufferedReader 클래스는 한 줄의 텍스트를 읽어 문자열로 반환하는 readLine()메서드를 제공한다. public String readLine() throws IOException
BufferedReader를 InputStreamReader에 연결함으로써 플랫폼 기본 인코딩 타입이 아닌 다른 문자 집합의 줄도 올바르게 읽을 수 있다는 것이다.
BufferedWriter 클래스는 자신의 슈퍼클래스에는 없는 newLine()이라는 메서드가 있다. 줄을 바꿔주는 일을 한다. public String newLine() throws IOException
줄 구분자(line-seperator) 문자열을 출력에 추가한다.