티스토리 뷰



이 자료들은 팁스소프트에서 제공하는 [ 알짜배기 ] 프로그램을 이용하면 더 편리하게 볼수 있습니다.
* 알짜배기 프로그램 받기 - http://www.tipssoft.com/bulletin/tb.php/QnA/8406
* 안드로이드 강좌 목록 - http://www.tipssoft.com/bulletin/tb.php/old_bbs/501
보통 시스템은 여러가지 입출력 장치를 가지고 있습니다. 예전에는 이런 입출력 장치를 다룰때
전용함수들이 존재했습니다. 전용함수가 존재한다는 뜻은 어떤 입출력을 사용할때 어떤 데이터
형식을 사용할것인지가 이미 정해져있다는 뜻이기도 합니다.
예를들어, 키보드로 키를 입력받는 경우, getch 와 같은 콘솔함수를 사용했고 이 함수는 4바이트
정수형 데이터 값을 반환하여 프로그래머는 이값을 받아서 원하는 형식으로 가공하여 처리했습니다.
이렇듯 시스템의 다양한 입출력을 사용하기 위해서는 각각의 입출력 전용함수에 대해서 알고 있어야하며
고유한 특성이나 데이터 형식도 프로그래머가 이해를 해야지만 그것을 사용할수 있는 형태였고 이러한
방식은 입출력 프로그래밍을 힘들게 하는 요소였습니다.
객체지향 기술이 발전하면서 이런 입출력에 대한 표준적인 방안을 모색하던 중에 모든 입출력의
기반이 될수 있는 기술로서 "스트림(stream)" 이라는 개념이 만들어졌습니다. 즉, 스트림 은
구체적 데이터 형식이 정해지지 않은 데이터의 입출력 흐름을 의미하는것입니다.
스트림은 표준적인 입출력 형식을 제공하는 구조로 되어 있고, 구체적 장치에 대한 입출력 기능들은
기본 스트림 구조를 확장해서 비어 있는 각 메소드들에 내용을 채워넣어 기능을 완성한 후, 해당
장치를 사용할수 있도록 제공됩니다. 이렇게 되면 프로그래머는 해당 장치에 대한 여러가지 사전지식
없이 기본 스트림의 형식만 이해하고 있다면 얼마든지 해당 장치의 입출력을 사용할수 있습니다.
결론적으로 시스템을 제공하는 측에서는 모든 입출력을 스트림을 기반으로 설계해서 통일성을
유지할수 있고 해당 시스템의 입출력을 사용하는 프로그래머는 스트림의 개념만 공부해서 다양한
장치의 입출력을 쉽게 사용할수 있게 되었습니다.
"스트림은 입출력 방식의 표준적이고, 일원화된 관리 형태를 말하는 것입니다."
자바의 스트림구조는 기본적으로 "바이트 스트림" 과 "문자 스트림" 으로 나누어지며 흔히, 바이너리라고
불리는 일반 데이터들은 바이트 스트림으로 처리하고 문자 또는 문자열을 기반으로 하는 데이터들은
문자 스트림으로 처리합니다.
스트림에 대한 내용이 좀 많아서 이 강좌에서는 바이트 스트림에 대해서만 먼저 설명할것이고
바이트 스트림 클래스들 중에서 꼭 알아야할 클래스들에 대해서만 언급할 것입니다.
1. 바이트 스트림의 기본 형식
바이트 스트림의 최상위 클래스는 InputStream 과 OutputStream 입니다. 이 두 클래스는 상속을
통하여 용도와 목적에 따라 여러가지 하위 클래스를 만들 수 있습니다. 아래의 그림은 java.io 패키지
안에 존재하는 바이트 스트림의 종류를 계층도로 구성한 것입니다.
위 그림의 스트림 클래스들은 java.io 패키지에서 제공하는 기본적인 클래스들이므로 특별한 용도로
스트림을 사용하는 경우 다른 패키지를 추가하여 사용할 수 있습니다.
프로그래밍을 하다보면 파일을 다루거나 네트워크를 사용하거나 여러가지 입출력 장치를 사용하기
때문에 스트림을 접하게됩니다. 그 때 모든 바이트 스트림의 최상위 클래스인 InputStream 과
OutputStream 을 잘 알아두면 그 하위 클래스들을 사용하는데 더욱 쉬워지고, 잘 이해하면서 사용할
수 있을 것입니다.
InputStream 과 OutputStream 클래스에 대해서는 알아두면 좋겠지만 그 하위 클래스는 종류도 많을
뿐더러 그 사용법이 최상위 클래스들과 크게 다르지 않기 때문에 지금 다 알아둘 필요는 없으며
필요시 그때그때 익혀서 사용하는 것이 좋습니다. 다음은 InputStream 과 OutputStream 클래스에
대하여 알아보도록 하겠습니다.
1.1 InputStream
InputStream 바이트 입력 스트림의 최상위 클래스로 추상 메소드를 가지고 있는 추상 클래스이기
때문에 반드시 하위 클래스를 정의하여 사용할 수 있습니다. 이 클래스가 갖는 메소드는 다음과
같습니다.
int available() throws IOException;

입력 스트림으로 읽을 수 있는 데이터의 바이트의 수를 반환합니다. InputStream 클래스의
available 메소드는 하위 클래스에서 반드시 재정의를 해주어야하며 재정의를 하지않고,
사용할 경우 반환 값은 0이 됩니다.
void close() throws IOException;

입력 스트림을 닫고 이 스트림에 관련된 시스템 리소스를 반환합니다. 그러나 하위 클래스가
아닌 InputStream 클래스의 close 메소드는 아무 일도 수행하지 않습니다.
void mark(int readlimit);

입력 시스템에서 현재 위치하는 곳을 저장해 둡니다. 입력 스트림은 데이터 바이트를 한번
읽으면 바로 다음 바이트를 읽게 되기 때문에 이전 데이터를 다시 읽을 수가 없습니다.
그러나 mark 메소드를 사용하면 메소드를 호출한 시점부터 읽혀지는 데이터가 별도로 저장되기
때문에 데이터를 읽다가 다시 저장된 위치의 데이터를 읽고 싶을 때 reset 메소드를 호출하여
해당 위치의 데이터를 다시 읽을 수 있도록 해줍니다.
readlimit 매개 인자는 현재 위치부터 데이터를 저장해둘 최대 크기를 말하며 현재 남은
데이터의 크기보다 큰 수가 지정되어야 합니다. 만약 reset 을 호출하기 전에 입력 스트림이
읽은 데이터의 크기가 readlimit 보다 커지면 더 이상 읽은 데이터를 저장해두지 않습니다.
markSupported 메소드를 호출하여 현재 스트림 클래스가 mark 기능을 지원하는지 여부를
알 수 있으며 InputStream 클래스의 경우 mark 메소드는 아무 일도 수행하지 않습니다.

void reset() throws IOException;
가장 최근에 mark 메소드가 호출되었던 위치로 스트림의 위치를 재설정합니다. markSupported
메소드가 true 를 반환하는 경우에 reset 메소드를 호출하면 정상적인 경우 별도로 저장해둔
mark 메소드를 호출한 시점부터의 데이터를 읽을 수 있습니다. 그러나 한번도 mark 메소드가
호출되지 않았거나, mark 메소드를 호출할 때 지정한 저장해둘 바이트 크기보다 지금까지 읽은
바이트 크기가 커지면 IOException 이 발생합니다.
반대로 markSupported 메소드가 false 를 반환하는 경우에 reset 메소드를 호출하면
IOException 이 발생할 것입니다.
InputStream 클래스의 경우 reset 메소드를 호출하면 IOException 이 발생합니다.
boolean markSupported();

입력 스트림이 mark 와 reset 메소드를 지원하는지 테스트합니다. 두 메소드의 지원 여부는 각
스트림 객체마다 달라지며 InputStream 클래스 객체의 경우 이 메소드를 호출하면 false 를
반환합니다.

abstract int read() throws IOException;

입력 스트림으로부터 하나의 데이터 바이트를 읽습니다. 바이트 데이터의 값은 0 ~ 255 범위를
갖으며 함수의 반환값으로 리턴됩니다. 만약 읽을 데이터가 없는 경우 -1 을 반환하고, 더 이상
읽을 데이터가 없는 경우, 읽을 수 있는 데이터가 생기거나 스트림의 끝을 발견하거나 예외가
발생할 때까지 대기상태가 됩니다.
이 메소드는 반드시 하위 클래스에서 구현하여 사용해야 합니다.
int read(byte[] b) throws IOException;

입력 스트림으로부터 데이터를 읽어서 매개 인자로 넘어온 b 배열에 저장합니다. 더 이상
읽을 데이터가 없는 경우, 읽을 수 있는 데이터가 생기거나 스트림의 끝을 발견하거나 예외가
발생할 때까지 대기상태가 됩니다.

만약 b가 null 이면 NullPointerException 예외가 발생하며 더 이상 읽을 데이터가 없는 경우
-1이 반환되고, 만약 첫번째 바이트가 스트림의 끝이거나 스트림이 close 되거나 기타 여러가지
이유로 읽을 수 없으면 IOException 예외가 발생합니다.
이 메소드로 읽을 데이터는 byte 배열의 길이이며 읽은 데이터의 길이는 메소드의 반환값이
되며 이 메소드는 read(b, 0, b.length) 와 동일한 결과를 가져옵니다.
int read(byte[] b, int off, int len) throws IOException;

지정한 개수만큼 데이터 바이트를 읽고 byte 배열의 지정한 위치에서부터 데이터를 저장합니다.
이 메소드의 반환 값이나 예외 상황은 read(byte[] b) 메소드와 동일하지만 내부적으로는
read() 메소드를 반복적으로 호출하여 데이터를 읽습니다.

long skip(long n) throws IOException;

입력 스트림에서 매개 인자만큼의 데이터를 건너 뜁니다. 매개 인자에 0 이나 음수가 넘어올
경우 이 메소드는 수행되지 않으며 이 메소드가 수행되면 byte 배열이 생성되면서 해당 배열에
데이터를 읽어들이게 됩니다.
1.2 OutputStream
OutputStream 바이트 출력 스트림의 최상위 클래스로 추상 메소드를 가지고 있는 추상 클래스이기
때문에 반드시 하위 클래스를 정의하여 사용할 수 있습니다. 이 클래스가 갖는 메소드는 다음과
같습니다.
public void close() throws IOException;
출력 스트림을 닫고 이 스트림에 관련된 시스템 리소스를 반환합니다. 한번 닫혀진 스트림은
다시 열거나 실행시킬 수 없으므로 다시 스트림을 생성하여 사용해야합니다.
OutputStream 클래스에서는 아무 일도 수행하지 않습니다.
public void flush() throws IOException;
스트림 버퍼에 저장되어 있는 데이터를 강제적으로 출력시킵니다. 기본적인 출력 스트림은
버퍼에 데이터가 가득 차면 그때 데이터를 출력시키는데 이 메소드를 사용하면 저장된 데이터의
크기에에 관계없이 바로 출력됩니다.
OutputStream 클래스에서는 아무일도 수행하지 않습니다.
abstract void write(int b) throws IOException;
출력 스트림으로 데이터를 써서 매개 인자로 넘어온 데이터를 출력합니다. 이 메소드로 출력되는
데이터는 1바이트이기 때문에 하위 8비트만 유효하게 작용하며 상위 24비트는 무시됩니다.
이 메소드는 추상 메소드이기 때문에 OutputStream 의 하위 클래스는 반드시 이 메소드를 정의해
주어야 합니다.
public void write(byte[] b) throws IOException;
출력 스트림으로 지정된 byte 배열에 저장된 데이터를 배열의 길이만큼 출력시킵니다. 이
메소드는 write(b, 0, b.length()) 와 동일한 효과를 가집니다.
public void write(byte[] b, int off, int len) throws IOException;
출력 스트림으로 byte 배열의 지정한 위치에서부터 지정한 개수만큼 데이터를 출력시킵니다.
OutputStream 클래스의 경우 이 메소드가 실행되면 배열의 각 바이트마다 출력이 발생하기
때문에 하위 클래스를 생성하는 경우 효율적으로 재구현해야 합니다.
만약 byte 배열이 null 인 경우 NullPointerException 이 발생하고, off 나 len 이 음수이거나
off 와 len의 합이 len 보다 큰 경우 IndexOutOfBoundsException 이 발생합니다.
이러한 기본 입출력 스트림은 여러가지 목적과 특성에 따라 상속을 통하여 여러가지 형태로 확장될
수 있으며 기본 메소드를 재정의하거나, 새로운 메소드를 추가하면서 기능을 구현할 수 있습니다.
2. 바이트 스트림의 종류
위의 클래스 계층도에 있는 InputStream 과 OutputStream 클래스의 하위 클래스는 다음과 같은
특징을 가지고 있습니다.
2.1 InputStream 클래스의 하위 클래스 종류
ByteArrayInputStream
스트림으로부터 읽은 데이터를 저장할 수 있는 버퍼를 가진 스트림입니다. 클래스의 내부에는
카운터가 별도로 존재하는데 이것은 다음 바이트를 읽어서 저장할 위치를 가리키고 있습니다.
FileInputStream

파일 시스템내의 파일에서 데이터를 입력받을 수 있는 기능을 제공합니다. 이미지같은 바이너리
기반의 파일은 바로 사용할 수 있지만 문자 기반의 파일을 읽을 때에는 FileReader 클래스를
사용할 것을 권장합니다.
FilterInputStream

어떤 InputStream 객체와 사용자 사이에 존재하는 클래스로써 입력 스트림에서 읽는 데이터를
변환하거나 별도의 기능을 추가로 제공합니다. 즉, FilterInputStream 클래스는 실질적으로
데이터를 입력받고 저장하는 별도의 InputStream 객체를 포함하여 자신의 메소드가 호출되면
InputStream 객체에 업무 수행을 전달하도록 메소드가 재정의되어 있습니다.
아래의 클래스들은 이 클래스는 상속받아 기능을 확장한 클래스들입니다.
- BufferedInputStream

어떤 InputStream 객체의 필터 클래스로써 버퍼를 이용한 입력 기능과 mark(), reset()
메소드를 제공하도록 기능이 확장된 클래스입니다. 이 클래스가 생성되면 배열이 함께
생성되며 InpputStream 객체로 여러 바이트의 데이터를 버퍼에 읽어오고, 이 버퍼에 저장된
데이터를 read() 하거나 skip()하여 다룰 수 있습니다.
- DataInputStream

어떤 InputStream 객체의 필터 클래스로써 다른 InputStream 객체가 읽은 바이트 데이터를
자바의 기본 데이터 타입 형식으로 읽을 수 있는 기능을 제공합니다.
- LineNumberInputStream
어떤 InputStream 객체의 필터 클래스로써 읽어들인 데이터에 행 번호를 부여해 보관하고
관리합니다. 이 클래스는 읽어오는 데이터에 \r 이나 \n 이나 \r\n 가 존재하는 경우
행 번호를 증가시킵니다.
- PushbackInputStream
어떤 InputStream 객체의 필터 클래스로써 데이터를 읽다가 unread() 메소드를 이용하여
다시 데이터 바이트를 입력 스트림에 되돌려놓는 기능을 합니다. 예를 들어, abcde 라는
데이터가 입력 스트림에서 읽히기를 대기하고 있을 때 abcd 를 읽은 후 a 를 unread()
메소드로 입력 스트림에 돌려 놓으면 다음에 읽는 2바이트는 ae 가 됩니다.
ObjectInputStream
데이터의 직렬화를 실현하도록 데이터를 하나의 객체 형태로 읽을 수 있도록 해줍니다.
PipedInputStream
멀티 쓰레드 환경에서 하나의 쓰레드에서 보낸 데이터를 다른 쓰레드가 받을때 사용합니다.
PipedOutputStream 객체에 연결하여 해당 객체가 출력하는 데이터를 이 입력 스트림이 읽을 수
있습니다.
SequenceInputStream

서로 다른 입력스트림을 논리적으로 이어주는 기능을 제공합니다. 입력 스트림을 순서대로
연결하여 하나의 입력 스트림인것처럼 사용할 수 있게해줍니다. 예를 들어 a, b, c 라는 입력
스트림을 sequence 라는 SequenceInputStream 객체에서 연결하여 사용하는 경우
a 에 123, b 에 tips 그리고 c 에 soft 라는 데이터가 존재할 때 sequence 로 데이터를 읽으면
123tipssoft 라고 읽혀집니다.
StringBufferInputStream

문자열을 바이트 입력 스트림으로 사용할 수 있도록 해줍니다. 그러나 이 클래스는 문자열을
바이트 배열로 정확하게 변환하지 못하기때문에 StringReader 클래스를 사용할 것을 권장합니다.
2.2 OutputStream 클래스의 하위 클래스 종류
ByteArrayOutputStream
출력할 데이터를 출력 스트림의 바이트 배열에 저장합니다. 스트림의 배열은 데이터가 저장됨에
따라 자동적으로 크기가 조절되며 toByteArray() 나 toString() 메소드를 이용하여 배열 내의
데이터를 반환받을 수도 있습니다.
FileOutputStream

파일에 데이터를 쓸 수 있는 기능을 제공합니다. FileInputStream 과 마찬가지로 문자 기반의
파일을 쓸때에는 FileWriter 클래스를 사용할 것을 권장합니다.
FilterOutputStream

어떤 OutputStream 객체와 사용자 사이에 존재하는 클래스로써 출력 스트림에서 출력할 데이터를
변환하거나 별도의 기능을 추가로 제공합니다. 즉, FilterOutputStream 클래스는 데이터를
출력하는 별도의 OutputStream 객체를 포함하여 자신의 메소드가 호출되면 OutputStream
객체에 업무 수행을 전달하도록 메소드가 재정의되어 있습니다.
아래의 클래스들은 이 클래스는 상속받아 기능을 확장한 클래스들입니다.

- BufferedOutputStream

어떤 OutputStream 객체의 필터 클래스로써 데이터 출력시 사용가능한 버퍼링 기능을 추가한
것입니다. 이 객체를 이용하여 데이터를 쓰면 버퍼에 데이터가 저장되다가 버퍼가 차면 그때
OutputStream 객체로 데이터를 출력하게 됩니다. 버퍼가 가득 차지 않은 시점에서 데이터를
출력하고자 할때에는 flush() 메소드를 사용하면 됩니다.
- DataOutputStream
어떤 OutputStream 객체의 필터 클래스로써 자바 기본 데이터 타입으로 데이터가 넘어오면
이 클래스가 바이트 형태로 변환하여 내부의 OutputStream 객체에게 전달합니다.
- PrintStream

어떤 OutputStream 객체의 필터 클래스로써 데이터를 자바의 기본 데이터 타입과 형식으로
출력 할 수 있습니다. 흔히 콘솔에서 출력할 때 사용하는 System.out.println 에서
System.out 이 PrintStream 객체입니다.

ObjectOutputStream
데이터의 직렬화를 실현하도록 데이터를 하나의 객체 형태로 쓸 수 있도록 해줍니다.
PipedOutputStream
멀티 쓰레드 환경에서 하나의 쓰레드에서 다른 쓰레드로 데이터를 보낼때 사용합니다.
PipedInputStream 객체에 연결하여 해당 객체가 데이터를 읽을 수 있도록 데이터를
출력해줍니다.


댓글