바이트 기반의 스트림은 어떤 것들이 있을까?
바이트기반 스트림 종류
--
InputStream & OutputStream
모든 바이트기반 스트림의 부모이며 아래와 같은 메서드들을 제공한다.
- markSupported()를 통해서 mark()와 reset()을 지원하는 스트림인지 확인 후
스트림의 종류에 따라서 mark()와 reset()을 사용하면 이미 읽은 데이터를 되돌려서 다시 읽을 수 있다. - flush()는 버퍼가 있는 출력 스트림의 경우에만 의미가 있으며,
OutputStream에 정의된 flush()는 아무런 일도 하지 않는다. - 프로그램이 종료될 때 사용하고 닫지 않은 스트림은 JVM이 자동으로 닫아 주기는 하지만,
스트림을 이용해서 모든 작업을 마치면 close()를 호출하여 반드시 닫아 주어야 한다. - 하지만 ByteArrayInputStream처럼 메모리를 사용하는 스트림과
System.in, System.out과 같이 표준 입출력 스트림은 닫아 주지 않아도 된다.
ByteArrayInputStream & ByteArrayOutputStream
메모리를 통해 바이트배열에 데이터를 입출력하는 데 사용되는 스트림이다.
(주로 입출력하기 전에 데이터를 임시로 바이트배열에 담아서 변환 등의 작업을 하는 데 사용된다.)
예시 코드 ( 바이트배열 inSrc의 데이터를 outSrc로 복사)
class Test {
public static void main(String[] args) {
byte[] inSrc = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
byte[] outSrc = null;
ByteArrayInputStream input = null;
ByteArrayOutputStream output = null;
input = new ByteArrayInputStream(inSrc); // 스트림 생성
output = new ByteArrayOutputStream();
int data = 0;
// read()를 호출한 반환값을 변수 data에 저장 후 해당 값이 -1과 비교
while((data = input.read()) != -1) { // read()는 1byte를 읽어옴
output.write(data); // output에 data값 쓰기
}
outSrc = output.toByteArray(); // output의 내용을 byte배열로 변환후 넣기
System.out.println("Input Source : " + Arrays.toString(inSrc));
System.out.println("Output Source : " + Arrays.toString(outSrc));
}
}
------------------------------------------------------------------
Input Source : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Output Source : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
바이트배열은 사용하는 자원이 메모리 뿐이라서 가비지 컬렉터에 의해 자동적으로 자원을 반환한다.
즉, close()를 통해 스트림을 꼭 닫지 않아도 된다.
다만 read()와 write(int b)를 사용하기 때문에 한 번에 1byte만 읽고 쓰기만 가능하여 작업 효율이 떨어진다.
예시 코드 (위 코드보다 효율적으로 변경)
class Test {
public static void main(String[] args) {
byte[] inSrc = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
byte[] ouSrc = null;
byte[] temp = new byte[10];
ByteArrayInputStream input = null;
ByteArrayOutputStream output = null;
input = new ByteArrayInputStream(inSrc);
output = new ByteArrayOutputStream();
input.read(temp, 0, temp.length); // 읽어 온 데이터를 배열 temp에 담는다.
output.write(temp, 5, 5); // temp[5]부터 5개의 데이터를 wrtie한다.
outSrc = output.toByteArray();
System.out.println("Input Source : " + Arrays.toString(inSrc));
System.out.println("temp : " + Arrays.toString(temp));
System.out.println("Output Source : " + Arrays.toString(outSrc));
}
}
-----------------------------------------------------------------------------
Input Source : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
temp : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Output Source : [5, 6, 7, 8, 9]
배열을 이용한 입출력은 작업의 효율을 증가시키므로
가능하면 입출력 대상에 따라 알맞는 크기의 배열을 사용하는 것이 좋다.
class Test {
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();
System.out.println("Input Source :" + Arrays.toString(inSrc);
// read()와 write()가 IOException을 발생시킬 수 있어 try-catch문에 작성
try {
// avilable()은 블락킹없이 읽어 올 수 있는 바이트의 수를 반환
while(input.available() > 0) {
input.read(temp); // input의 내용을 temp에 옮긴다.
output.write(temp); // temp의 내용을 output에 쓴다.
outSrc = output.toByteArray();
printArrays(temp, outSrc);
}
} catch (IOException e) { }
}
static void printArrays(byte[] temp, byte[] outSrc) {
System.out.println("temp : " + Arrays.toString(temp));
System.out.println("Output Source : " + Arrays.toString(outSrc));
}
}
---------------------------------------------------------------------
Input Source : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
temp : [0, 1, 2, 3]
Output Source : [0, 1, 2, 3]
temp : [4, 5, 6, 7]
Output Source : [0, 1, 2, 3, 4, 5, 6, 7]
temp : [8, 9, 6, 7]
Output Source : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 6, 7]
블락킹이란
데이터를 읽어 올 때 데이터를 기다리기 위해 멈춰있는 것을 의미한다.
ex) 사용자가 데이터를 입력하기 전까지 기다리는 상태를 "블락킹" 상태라고 부른다.
위 코드 결과에서 마지막 temp에서 [8, 9, 6, 7]인 이유는
데이터를 붙여 넣는 방식이라 이전에 담았던 [4, 5, 6, 7]에서 앞에 8, 9만 변경되고 뒤에 6, 7은 이전 값으로 남아있는 것이다.
이를 수정하기 위해서는
write()에서 읽어온 만큼(len)만 출력하도록 변경해 주면 된다.
class Test {
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 ByteaArrayInputStream(inSrc);
output = new ByteArrayOutputStream();
System.out.println("Input Source :" + Arrays.toString(inSrc);
try {
while(input.available() > 0) {
int len = input.read(temp); // temp에 옮긴 후 개수 반환
output.write(temp, 0, len); // 읽은 개수(len) 만큼 쓴다.
}
} catch (IOException e) { }
}
outSrc = output.toByteArray();
System.out.println("Input Source : " + Arrays.toString(inSrc));
System.out.println("temp : " + Arrays.toString(temp));
System.out.println("Output Source : " + Arrays.toString(outSrc));
}
---------------------------------------------------------------------
Input Source : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
temp : [8, 9, 6, 7]
Output Source : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
--
바이트기반의 보조 스트림 종류
--
FilterInputStream & FilterOutputStream
이 보조 스트림 또한 InputStream과 OutputStream의 자식이면서
모든 보조 스트림의 부모다.
상속을 통해 FilterInputStream과 FilterOutputStream의 read()와 write()를 원하는 기능대로 오버라이딩해야 한다.
public class FilterInputStream extends InputStream {
protected volatile InputStream in;
protected FileterInputStream(InputStream in) {
this.in = in;
}
public int read() throws IOException {
return in.read();
}
...
}
생성자의 접근제어자가 "protected"이기 때문에 인스턴스를 생성해서 사용은 불가능하고,
상속을 통해서 오버라이딩을 해야 한다.
BufferedInputStream & BUfferedOutputStream
스트림의 입출력 효율을 높이기 위해 버퍼(byte[])를 사용하는 보조 스트림이다.
기존 1byte씩 입출력하는 것보다 버퍼(byte[])를 이용해서 한 번에 여러 바이트를 입출력하는 것이다.
버퍼크기는 입력소스로부터 한 번에 가져올 수 있는 데이터의 크기로 지정하면 좋다.
일반적으로 4096byte 크기로 사용하며
버퍼의 크기를 변경해 가며 테스트를 통해 최적의 버퍼크기를 알아낼 수 있다.
[ 입력 소스 예시 ]
프로그램에서 입력 소스로부터 데이터를 읽기 위해 처음으로 read메서드를 호출하면,
입력 소스로부터 버퍼의 크기만큼의 데이터를 읽어서 자신의 내부 버퍼에 저장한다.
이후에 버퍼에 저장된 데이터를 읽어오면 된다.
외부의 입력 소스로부터 읽는 것보다 내부의 버퍼로부터 읽는 것이 훨씬 빠르다.
프로그램이 모든 데이터를 다 읽고 다음 데이터를 읽기 위해 read메서드가 호출되면,
BufferedInputStream은 입력 소스로부터 다시 버퍼크기만큼의 데이터를 읽은 후 버퍼에 저장하는 것을 반복한다.
[ 출력 소스 예시 ]
입력 소스로부터 데이터를 읽을 때와는 반대로,
프로그램에서 write메서드를 이용한 출력이 BufferedOutPutStream의 버퍼에 저장된다.
버퍼가 가득 차면 버퍼의 모든 내용을 출력 소스에 출력하고
버퍼를 비우고 다시 프로그램으로부터의 출력을 저장할 준비를 한다.
버퍼가 가득 찼을 때만 출력 소스에 출력하기 때문에,
마지막 출력 부분이 출력 소스에 쓰이지 못하고 버퍼에 남아있는 채로 프로그램이 종료될 수 있다.
그래서 모든 출력 작업을 마친 후 BufferedOutputStream에서 close()나 flush()를 호출하여
마지막에 버퍼에 담긴 모든 내용을 출력소스에 출력되도록 해야 한다.
DataInputStream & DataOutputStream
데이터를 읽고 쓰는데 byte단위가 아닌 8가지의 기본형 단위로 읽고 쓰는 보조 스트림이다.
DataOutputStream이 출력하는 형식은
각 기본형 값을 16 지수로 표현하여 저장한다.
ex) int값 출력 => 4byte의 16진수로 출력
각 자료형의 크기가 다르므로, 출력할 때와 입력할 때 순서에 주의해야 한다.
SequenceInputStream
여러 입력 스트림을 연결하여 하나의 스트림처럼 다룰 수 있게 한다.
해당 보조 스트림은 다른 보조 스트림과는 달리 FilterInputStream의 자식이 아니라
InputStream을 바로 상속받아서 구현되어 있다.
사용 예시 코드
Vector files = new Vector();
files.add(new FileInputStream("file.001"));
files.add(new FileInputStream("file.002"));
SequenceInputStream in = new SequenceInputStream(files.elements());
Vector에 연결할 입력 스트림들을 저장한 다음
Vector의 Enumeration elements()를 호출하여 생성자의 매개변수로 사용하고
매개변수로 사용한 입력 스트림을 순서대로 연결한다.
사용 에시 코드
FileInputStream file1 = new FileInputStream("file.001");
FIleInputStream file2 = new FileInputStream("file.002");
SequenceInputStream in = new SequenceInputStream(file1, file2);
두 개의 입력 스트림을 하나로 연결한다.
PrintStream
데이터를 다양한 형식을 문자로 출력하는 기능을 제공하는 보조 스트림이다.
데이터를 기반 스트림에 다양한 형태로 출력할 수 있는 print, println, printf와 같은 메서드를 오버로딩하여 제공한다.
데이터를 적절한 문자로 출력하는 것이기 때문에 문자기반 스트림의 역할을 수행하게 된다.
그래서 JDK1.1에서 PrintStream보다 향상된 기능의 문자기반 스트림인 PrintWriter가 추가되었다.
PrintStream과 PrintWriter는 거의 같은 기능을 가지고 있지만
PrintWriter가 보다 다양한 언어의 문자를 처리하는데 적합하기 때문에 PrintWriter를 사용하는 것을 권장
print() 또는 println()을 이용해서 출력하는 도중에서 PrintStream의 기반 스트림에서
IOException이 발생하면 checkError()를 통해 인지할 수 있다.
print()나 println()은 매우 자주 사용되기 때문에 예외를 내부에서 처리하도록 정의되어 있다.
(내부 처리가 아닌 예외를 던지도록 정의되어 있으면 사용하는 모든 곳에 try-catch문을 사용해야 한다.)
public class PrintStream extends FilterOutputStream implements Applendable, Closeable{
...
private boolean trouble = false;
public void print(int i) {
write(String.valueOf(i));
}
private void write(String s) {
try {
...
} catch (IOException x) {
trouble = true;
}
}
...
public boolean checkError() {
if(out != null) flush();
return trouble;
}
}
--
'Language > Java' 카테고리의 다른 글
[입출력] 문자기반 스트림, 보조 스트림 (0) | 2025.02.12 |
---|---|
입출력 (바이너리/문자 기반 스트림, 보조 스트림) (0) | 2025.02.10 |
스트림 (Stream) (1) | 2025.01.14 |
람다식 (+ 메서드 참조, 함수형 인터페이스) (0) | 2025.01.12 |
스레드의 동기화 (1) | 2025.01.09 |