Notice
Recent Posts
Recent Comments
Link
«   2024/03   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
Archives
Today
Total
03-29 13:36
관리 메뉴

zyint's blog

[소설같은 자바9] 자바 스트림 본문

예전글들

[소설같은 자바9] 자바 스트림

진트­ 2007. 2. 13. 14:49

1. 자바 스트림


1.1. 배경

어떤 목표 지점에서 데이터를 읽어 들이고, 목표 지점에 기록하는 것을 데이터의 Input, Output 작업이라고 한다. 자바에서 사용하는 Input과 Output작업의 목표 지점은 아주 다양하다. 기본적으로 여러분은 모니터로 입출력하는 것을 배웠다. 가장 많이 사용하는 System.out.println에서 out은 Console 화면에 데이터를 출력하는 것을 담당하고 있다. 그리고 System.in은 키보드의 입력을 담당하고 있다. 곰곰이 생각해보면 데이터를 입력 받고 데이터를 출력하는 작업은 아주 까다로울 것이라고 생각하는데 의외로 여러분은 쉽게 이러한 입출력을 해결하고 있다. 그런데 in, out과 같이 입력과 출력을 도와주는 중간역할을 하는 것이 없다면 어떻게 해야 하는지를 한번 생각해 보자. 아마도 하드웨어적인 것까지 직접 프로그래머가 다루어야만 모니터에 뭔가를 출력할 수 있을 것이다. 입출력을 도와주는 중간 역할을 하는 것을 우리는 스트림이라고 부른다.


스트림의 정의

: 자료의 입출력을 도와주는 중간 매개체


이 스트림은 자바에서 데이터의 입출력을 담당하는 중간자 역할을 하게 된다. 입출력 장치는 아주 다양한 형태로 존재한다.

■ 파일

■ 키보드, 모니터

■ 메모리

■ 네트워크 연결


이러한 형태의 입출력 장치에 데이터를 기록하고, 읽어 들이기 위해서 각각의 스트림이 존재한다. 여러분이 Java IO를 배운다는 것은 바로 각각의 장치에 해당되는 스트림으로 데이터를 읽고 기록하는 것을 배우는 것이다. 이 장에서는 스트림의 의미, 스트림의 종류, 스트림의 사용방법에 대하여 자세하게 알아보자.


1.2. 스트림


1.2.1. 스트림이란?

스트림이란 장치, 생각하기 쉽게 하드웨어장치라고 한다. 장치로부터 데이터를 얻거나 보낼 때 사용되는 중간 매개체 역할을 하는 놈이다. 곰곰히 생각하면, 별로 어려운 것도 아닌데 스트림이 어렵다고 하는 것은 아마도 스트림의 종류가 많고 다양하기 때문일 것이다. 하지만, 그 정확한 뜻을 알고 있다면 스트림은 대단히 쉽다.

스트림! 이 스트림은 여러분들을 도와주는 아주 훌륭한 도구이다. 여러분이 하드웨어를 어케 알겠나? Computer Engineering쪽도 아닌데. 나 또한 마찬가지이다? 그런데, 우리는 하드웨어 장치를 몰라도 그 쪽에 할 건 다 하고 있다. 이것을 해결해 주는 것이 바로 스트림이다. 누군가가 미리 장치에 연결할 수 있는 방법을 만들어 둔 것이다. 어떻게 장치에 스트림을 연결하느냐가 문제지 사용하는 방법은 별로 어렵지 않다. 장치에 스트림만 연결되면 하는 일은 뻔하다. 장치로부터 데이터를 읽어들이거나 기록하거나 둘 중에 하나이다. 스트림에서 읽고 쓰는 것을 빼면 아마 스트림은 시체가 되고도 남을 것이다. 물론 모든 장치가 하드웨어계열은 아니다. 하드웨어 계열에 이러한 방법을 사용해 보고 괜찮다 싶으니까 이곳 저곳에 스트림을 막 만들어 두는 것이다. 스트림은 입력과 출력을 도와주는 매개역할을 하는 것이니, 입력과 출력이 관련된 곳이면 어디서든 동작을 하는 것 아니겠나?

스트림의 종류는 아주 다양하게 분류된다. 이유는 아주 단순하다. 데이터의 입출력 장치가 여러 종류이니 스트림의 종류도 여러 종류이다.

단순히 분류하면 입력과 출력 스트림으로 나누어 생각해 볼 수 있다. 그리고 입력이든 출력이든 각각의 입출력 장치에 해당하는 스트림이 거의 대부분 한 쌍으로 되어 있다.

만약 파일에 데이터를 읽고 기록하려 한다면, 여러분은 당연히 파일에 관련된 스트림을 찾을 것이다. 자바에서 파일 스트림은 FileInputStream과 FileOutputStream이 있다. 입력이든 출력이든 목표지점을 정확하게 주어야 파일을 읽고 기록할 것이다. 파일 입출력 스트림에 목표지점(파일 이름)을 주고 난 후 스트림을 생성하고, 기록을 하든지 읽어 들이든지 둘 중에 하나겠죠. 거짓말같지만 자바 스트림이 하는 일은 이것밖에는 없다. 특별한 것은 없다.

스트림을 설명하고자 할 때 다음과 같은 비유가 가장 좋은 것 같다.

■ 스트림은 데이터를 읽고 기록하는 중간역할을 한다.

■ 스트림은 빨대다.

■ 빨대는 음료수를 마시는 중간역할을 한다.

■ 빨대는 입에 있는 음료수를 다시 내뱉는 중간역할을 한다.

■ 스트림은 단 방향 빨대다. 음료수를 내뱉고 다시 마시려면 빨대가 2개 필요하다.


약간 지저분하긴 하지만 아주 탁월한 비유인 것 같다. 더 자세하게 입력과 출력을 나누어서 비교해 보자.


입력 스트림 비교

■ 입력 스트림은 데이터를 먼저 스트림으로 읽어 들이다. 그리고 스트림에 존재하는 데이터를 하나씩 읽어 들일 수 있다.

■ 음료수를 마실 때 빨대를 이용하여 음료수를 빨대에 모으고 빨대에 들어있는 음료수를 흡입한다. 그러면 입안으로 음료수가 들어 올 것이다.


출력 스트림의 비교

■ 출력 스트림으로 데이터를 보낸다. 그리고 출력 스트림에 보낸 데이터를 비워 버린다. 그렇게 되면 출력 스트림에 존재하던 데이터가 모두 목표지점에 저장된다.

■ 입 안에 있던 음료수를 빨대로 일단 보낸다. 빨대에 들어 있는 음료수를 불어 버린다. 그렇게 되면 음료수는 다시 컵 안으로 들어가게 된다.


이러한 절차를 스트림으로 표현하면 다음과 같다.

■ 목표로 하는 데이터를 정한다.

■ 데이터에 맞는 스트림을 생성한다.

■ 스트림 클래스의 멤버 메서드를 이용하여 쉽게 데이터를 핸들한다. 기록하거나 읽어들이거나, 보내거나 받거나!


위와 같이 스트림은 데이터의 중간자 역할을 한다. 우리는 데이터의 목표지점이 네트워크의 어떠한 장소이든, 메모리이든 상관하지 않는다. 다만 해당 데이터를 스트림으로 보내고 그리고 스트림에 존재하는 데이터를 읽어 들이거나 기록해 버리면 끝인 것이다. 이것은 목표지점이 어디라도 상관없다는 이야기이다. 그리고 복잡한 작업은 스트림이 알아서 해결해 준다는 의미이다. 스트림이 없다면 직접 다 해야 한다. 뭐. 자 그렇다면 스트림에는 어떤 종류의 스트림이 있는지를 알아보자.


1.2.2. 스트림의 종류

스트림은 가장 최초의 분류를 당연히 입력과 출력 스트림으로 나눌 수 있다. 각각의 장치에 당연히 입력과 출력은 있으니 당연한 것이다.


기본적인 분류

■ 입력 스트림(ex. FileInputStream, ObjectInputStream)

■ 출력 스트림(ex. FileOutputStrea, ObjectOutputStream)

이 기본적인 분류는 각각의 장치에 쌍으로 입출력이 존재한다고 볼 수 있다. 그리고 이러한 분류는 사실 분류도 아니다. 그렇다면 실제적인 분류는 어떠한 방식으로 나눌 수 있을까? 가장 많이 나누는 분류는 보통 문자단위로 스트림을 처리하느냐, 바이트단위로 처리하느냐에 따라서 나누어진다. 이러한 분류는 프로그램 상에서도 구분이 확연히 드러나고 있다.

현재 아래에 나타난 구조는 문자 스트림의 구성도이다. 일단 문자 스트림은 최상위의 Reader와 Writer로 이루어져 있다. 그리고 거의 대부분의 스트림의 이름에 규칙이 있다. 자세히 한번 보자.

그 규칙은 Reader 즉, 입력문자 스트림은 대부분 Reader라는 단어가 붙어있다. 반대로 Writer 출력 문자 스트림에서 Writer라는 단어가 붙어 있다. 그렇다면, 이러한 결론을 내릴 수 있다. Reader나 Writer가 붙는다면, 문자 스트림의 한 종류다라고 생각하면 편할 것이다. 문자 스트림을 구분하라고 Reader와 Writer를 고의로 붙여 둔 모양이다. 사실, 문자 관련 스트림은 대부분 Reader와 Writer를 상속받는다. 그래서 그런 것이다.

다음으로 바이트 스트림의 구성도를 한 번 보자.


위의 스트림은 바이트 스트림으로 이 스트림 또한 입력과 출력의 차이를 InputStream과 OutputStream으로 나누어 볼 수 있다. 그리고 문자 스트림에서와 같이 InputStream에는 대부분 InputStream이라는 꼬리말이 OutputStream에는 OutputStream이라는 꼬리말이 붙어 있다. 물론 아닌 놈도 몇 있는데 일단은 대부분 이 형식을 따르고 있으니 InputStream과 OutputStream이 붙어 있으면 바이트 스트림으로 보아도 좋을 것이다.


스트림의 종류 1

■ 문자 스트림 : Reader나 Writer가 붙는다.

■ 바이트 스트림 : InputStream과 OutputStream이 붙는다.


스트림의 종류는 아주 많다. 이러한 스트림을 전부 사용할 줄 안다면 다행이겠지만 이것을 모두 습득하기에는 역부족일 것이다. 솔직히 종류가 너무 많다. 그렇다고 그냥 앉아 있을 수만은 없다. 일단은 나중에 사용하더라도 어떤 규칙이라도 찾아야 할 것이다. 자 그렇다면 규칙을 찾아보자.

스트림에서 나타나는 규칙은 아주 단순하다. InputStream과 Reader 계열일 경우에는 읽어 들이는 메서드를 포함하고 있다. 그리고 OutputStream과 Writer 계열일 경우에는 기록하는 메서드를 포함 하고 있을 것이다. InputStream과 OutputStream일 경우에는 바이트를 Reader와 Writer의 경우 문자를 다루는 메서드를 포함하고 있을 것이다. 이것을 정리한다면 다음과 같이 나타낼 수 있다.


스트림의 종류 2

■ 입력 스트림 : Reader나 InputStream

■ 출력 스트림 : Writer나 OutputStream


입력계열의 스트림은 대부분 read라는 메서드를 포함하고 있다. 읽어 들인다는 의미이다. 다음은 입력 스트림에 사용되는 메서드를 보여주고 있다.


입력 스트림 계열의 멤버 메서드

바이트 단위(InputStream)

■ int read()

■ int read(byte cbuf[])

■ int read(byte cbuf[], int offset, int length)


문자 단위(Reader)

■ int read()

■ int read(char cbuf[])

■ int read(char cbuf[], int offset, int length)


출력 계열의 스트림은 대부분 write라는 메서드를 포함하고 있다. 기록한다는 의미이다. 다음은 출력 스트림에 사용되는 메서드를 보여주고 있다.


출력 스트림 계열의 멤버 메서드

바이트단위(OutputStream)

■ int write(int c)

■ int write(byte cbuf[])

■ int write(byte cbuf[], int offset, int length)

문자 단위(Writer)

■ int write(int c)

■ int write(char cbuf[])

■ int write(char cbuf[], int offset, int length)

이것은 단순히 입출력에 관한 규칙이다. 다음은 문자 스트림과 바이트 스트림의 간단한 설명을 붙인 것이다.


문자 스트림 클래스    바이트 스트림 클래스             설명

Reader               InputStream             문자/바이트 입력 스트림을 위한 추상클래스

BufferedReader         BufferedInputStream      문자/바이트 버퍼 입력, 라인 해석

LineNumberReader      LineNumberInputStream    문자/바이트 입력시, 라인 번호를 유지

CharArrayReader        ByteArrayInputStream     문자/바이트 배열에서 읽어들임

InputStreamReader                             바이트 스트림을 문자 스트림으로 변환

FileReader            FileInputStream          파일에서 바이트를 읽어들여 문자/바이트 스트림으로 변환

FilterReader           FilterInputStream         필터적용(filtered) 문자/바이트 입력을 위한 추상클래스

PushbackReader        PushbackInputStream     읽어들인 문자/바이트를 되돌림(pushback)

PipedReader           PipedInputStream         PipedWriter/PipedOutputStream에서 읽어들임

StringReader          StringBufferInputStream    문자열에서 읽어들임

Writer                 OutputStream            문자 출력 스트림을 위한 추상클래스

BufferedWriter         BufferedOutputStream     문자/바이트 스트림에 버퍼 출력, BufferedWriter는 플랫폼에서 사용하는 라인 구분자(line separator) 사용

CharArrayWriter         ByteArrayOutputStream    문자/바이트 스트림에 문자/바이트 배열 출력

FilterWriter            FilterOutputStream         필터적용(filtered) 문자/바이트 출력을 위한 추상클래스

OutputStreamWriter      (none)                  문자 스트림을 바이트 스트림으로 변환

FileWriter             FileOutputStream         문자/바이트 스트림을 바이트 파일로 변환

PrintWriter            PrintStream             Writer/Streamdp 값과 객체를 프린트

PipedWriter           PipedOutputStream       PipedReader/PipedOutputStream에 출력

StringWriter           (none)                  문자열 출력

이것을 외울 필요는 없다. 어떻게 하다보면 알지도 모르니까.


1.2.3. 바이트 스트림과 문자 스트림

스트림의 가장 기본적인 분류는 다음과 같은 두 가지 종류가 있다.

■ 바이트 스트림 : 바이트, 바이트 배열

■ 문자 스트림 : 문자, 문자 배열, 문자열


바이트 스트림부터 알아보자. 바이트 스트림은 데이터를 바이트단위로 주고받는 것을 말한다. 대표적인 바이트 스트림은 InputStream과 OutputStream이라고 배웠다. 그렇다면, InputStream과 OutputStream을 통과하는 단위는 당연히 바이트이다. 바이트가 뭔가? 8bit의 이진 비트를 묶으면 바이트가 된다. 바로 그 바이트이다. 원래 데이터는 모두 바이트이다. 알고 보면 그림도 바이트들로 이루어져 있고, 텍스트도 바이트로 이루어져 있다. 그리고 물론 zip이나 jar와 같은 압축 파일도 일단은 바이트로 되어 있다. 이 바이트들이 적절하게 변환되면 의미 있는 데이터가 되는 것이다. 원시 바이트로 주고받겠다는 것이다. 그런데, 문자 스트림은 뭘 보고 문자 스트림이라고 할까?

자바에서 사용하는 문자방식은 두 바이트로 한 문자를 표현하는 유니코드방식이다. 그래서, 바이트로 전송되어지는 것을 스트림에서 인코딩을 가하게 된다. 즉, 바이트를 문자로 가공을 한다는 말이다. 이 인코딩은 문자 스트림이 담당한다. 아래의 그림은 스트림이 문자를 인코딩하는 것을 보여주고 있다.

 


바이트 스트림은 보통 대부분 InputStream과 OutputStream 클래스를 상속하고 있으며 대부분 InputStream과 OutputStream이라는 단어로 끝난다. 이러한 바이트 스트림의 종류는 다음과 같다.

AudioInputStream,      ByteArrayInputStream,      FileInputStream, FilterInputStream, InputStream, ObjectInputStream,   PipedInputStream,          SequenceInputStream,   StringBufferInputStream,

BufferedInputStream,    ByteArrayInputStream,      DataInputStream,          FilterInputStream,

PushbackInputStream,   ByteArrayOutputStream,    FileOutputStream, FilterOutputStream, ObjectOutputStream,    OutputStream,            PipedOutputStream, BufferedOutputStream, ByteArrayOutputStream,      DataOutputStream,         FilterOutputStream

정말 많다... 그런데 한결같이 InputStream과 OutputStream이라는 단어를 달고 있다. 이러한 스트림을 바이트 스트림이라고 한다. 내부적으로 바이트단위로 스트림 처리를 하는 것이다.

문자 스트림은 Reader와 Writer를 달고 있다. 모두 문자단위로 인코딩 처리를 하는 스트림들이다. 문자 스트림들은 다음과 같다.

Writer,       BufferedWriter,    CharArrayWriter,   FilterWriter,       OutputStreamWriter, FileWriter, PipedWriter,   PrintWriter,       StringWriter,      BufferedReader,   LineNumberReader,

CharArrayReader, InputStreamReader,      FileReader,       FilterReader,      PushbackReader, PipedReader, StringReader

그냥 한번 확인해보는 것이지 특별한 의미는 없다. 위의 스트림들은 문자 스트림이든 바이트 스트림이든, 둘 다 모두 처음엔 바이트로 받아들이는 것은 마찬가지이다. 그리고 해당 스트림이 알아서 처리를 해 주는 것이다. 각각의 스트림의 역할은 가공하는 방법과 장치가 다를 뿐 전부 스트림이다. 각 장치에 맞는 스트림을 이용하는 것이 프로그래머가 하는 일이다. 결론적으로 스트림은 데이터를 프로그래머가 사용할 수 있는 데이터로 바꾸어주는 역할을 한다. 물론, 장치에 연결을 해주는 매개체역할을 하는 것은 기본이다.



1.3. 스트림 예제

기본 예제를 한번 해 보자. 여러 가지 스트림을 다양하게 테스트하는 것이 목적이기 때문에 대부분 소스와 설명으로 스트림의 느낌을 얻어 보자.


1.3.1. 스트림의 변환

스트림이란 어차피 바이트 단위로 데이터를 읽어서 사용자의 입맛에 맞게 변환할 수 있다. 일단, 가장 기본적인 변환은 앞 절에서 배운 바와 같이 바이트 스트림을 사용자의 용도에 맞게끔 변환하는 것이 주된 사용법이다. 거의 대부분의 스트림에 대한 예를 알아보면서 스트림에 대한 감각을 익혀 보자. 많은 예제들을 나열하지만 기본적인 틀은 바뀌지 않는다. 하나 둘 익혀가면서 스트림의 기본 사용법과 원리를 이해하자.


1.3.2. InputStream, OutputStream

InputStream&OutputStream 클래스는 IO패키지 중에서도 가장 기본이 되는 클래스들이다. 모든 IO 관련 예제에 50% 정도는 등장한다고 봐도 좋다. 그만큼 중요하고 기본적인 클래스이다. 예제를 보자.


InputOutputStreamTest.java (InputStream, OutputStream을 테스트한 예제)

import java.io.*;

class InputOutputStreamTest {

   public static void main(String args[]) throws IOException {

       System.out.println("아무 글이나 입력하시고 Enter를 쳐주세요");

       System.out.println(" 'S'를 입력하면 프로그램이 종료된다.");

       int ch;

       InputStream in = System.in;

       OutputStream out = System.out;

       while((ch=in.read()) != -1) {

    if(ch == 'S') {

   byte[] arr= new byte[4];

   arr[0] = 83;

   arr[1] = 84;

   arr[2] = 79;

   arr[3] = 80;

   out.write(arr);

   out.flush();

   out.close();

   in.close();

   System.exit(-1);

    }

    System.out.println("Char: "+(char)ch+", Available: "+in.available());

       }

   }

}

C:\JavaExample\09>javac InputOutputStreamTest.java

C:\JavaExample\09>java InputOutputStreamTest

아무 글이나 입력하시고 Enter 를 쳐 주세요

 'S'를 입력하면 프로그램이 종료된다.

java

Char: j, Available: 5

Char: a, Available: 4

Char: v, Available: 3

Char: a, Available: 2, Available: 1

Char: , Available: 0

S

STOP


위의 예제는 가장 기본적인 InputStream&OutputStream을 가지고 입력과 출력을 해본 것이다.

소스를 보면 우선, 키보드로부터 입력받은 값을 읽어오는 InputStream의 객체 in을 생성한다. java.lang.Object 패키지에 속해있는 System클래스의 멤버 필드인 in은 표준 입력 스트림이다. 일반적으로 이 스트림은 호스트 환경 또는 사용자가 지정한 키보드 입력 또는 또 다른 입력 소스에 대응한다.

InputStream in = System.in;


OutputStream 객체 out을 생성한다. 역시 System클래스의 멤버 필드인 out을 사용한다. 이 out은 in에 대비되는 표준 출력 스트림이다.

OutputStream out = System.out;


생성된 객체 in을 사용하여 InputStream의 멤버 메서드인 read() 메서드를 호출하여 바이트를 읽어 들인다.

while((ch=in.read()) != -1) { }


read() 메서드는 바이트값을 읽어들여 int형의 정수값으로 변환시켜 반환시키는 메서드이다. 읽어들이는 값이 없을 때는 -1을 반환한다. read() 메서드는 while 문과 같이 사용하여 바이트가 계속 입력되는지, 입력이 끝났는지를 파악한다. 그리고 읽어 들인 바이트 값과 비교를 하여, ‘S‘가 있다면 프로그램은 바이트 배열을 생성하고, 이 배열을 출력시킨다. 여기서는 ‘S‘, ‘T‘, ‘O‘, ‘P‘를 배열로 입력하고 있다. 바이트 배열을 write() 메서드를 사용하여 OutputStream에 배열값을 기록한다. 그런 후에 flush() 메서드를 이용, OutputStream에 있는 “STOP“라는 글을 도스 콘솔창에 출력하고 close() 메서드로 Stream을 닫습니다. 그리고 나서 ‘System.exit(-1);‘로 프로그램은 자동으로 종료가 된다. exit() 메서드는 현재 실행하고 있는 자바 가상머신(JVM)을 종료해준다. 좀 더 자세히 알고 싶다면 API(java.lang.System 패키지)를 참조하자.

if(ch == 'S') {

  byte[] arr= new byte[4];

  arr[0] = 83;

  arr[1] = 84;

  arr[2] = 79;

  arr[3] = 80;

  out.write(arr);

  out.flush();

  out.close();

  in.close();

  System.exit(-1);

}


☞ flush()   

: OutputStream의 멤버 메서드로 호출되면 Buffer에 있는 내용을 강제로 비우게 해서 출력시킨다.

위의 out.write(cha);라는 구문은 읽어 들인 바이트 배열을 스트림에 기록하는 역할을 한다. 그런 후 out.flush(); 라는 구문을 사용하여 기록된 스트림을 도스창(명령 프롬프트)에 출력하게 된다. 입력값들 중에 ‘S‘가 없다면 모든 입력된 값을 char형으로 변환하여 출력을 하고, available() 메서드를 사용하여 실행을 중단하지 않고 입력 스트림에서 읽을 수 있는 byte의 수를 그 옆에서 출력한다.


자! 그럼 출력된 결과를 살펴보자. 먼저 프로그램을 실행 시키면 아무 글이나 입력하라고 했다. 나는 ‘java‘를 입력하고 Enter를 쳤다. 그 결과, ‘Char: j, Available: 5‘가 첫 줄에 출력되고 있다. 우리가 입력한 것은 ‘java‘라는 글 네 개와 Enter키 1개를 포함, 모두 5가지를 입력하였죠. 그래서 Available에 5라고 나오는 것이다. Available값이 4가 아니라 왜 5냐고? 그것은 잠시 후에 보자. ‘, Available: 1는 Enter키를 뜻하는 것이다. 그 다음에 나온 ‘, Available: 0‘은 읽어 들이는 데이터가 없는 경우 버퍼가 모든 데이터를 다 보냈다는 표시를 감지한 것이다. 이 값은 표시가 안된다. 그리고 버퍼에서 더 이상 읽어 들일 것이 없으니까 Available값은 0이 나온 것이다. 그래서 아까 j의 Available 값이 5인 것이다. 두 번째로 대문자 ‘S‘를 입력하면, 위에서 말한대로  프로그램이 실행되어 종료가 되는 것이다.

간단한 예제 프로그램으로 InputStream과 OutputStream을 사용하여 키보드에서 값을 입력 받고, 그 값을 비교하는 법과 출력하는 법을 알아보았다. 재미있었나?

이렇게 InputStream과 OutputStream은 java.io 패키지에서 가장 중요하다고 말해도 틀렸다고 말하기 어려울 정도로 자주 사용되며 중요한 클래스이다. 몇 번 보고 완전히 이해하기는 어려운 클래스이니 그저 자주 보고 사용법을 익히는 것이 가장 좋을 듯 싶다.


InputStream 주요 멤버 메서드

■ public int available() : 실행을 중단하지 않고 입력 스트림에서 읽을 수 있는 바이트의 수를 반환한다.

■ public void close() : 입력 스트림을 닫고 스트림과 연관된 모든 시스템 자원을 해제한다.

■ public void mark(int readlimit) : 입력 스트림의 현재 위치를 표시한다.

■ public boolean markSupported() : 입력 스트림이 mark 및 reset 메서드를 지원하는지 여부를 테스트한다.

■ public abstract  int read() : 입력 스트림에서 데이터의 다음 바이트를 읽습니다.

■ public int read(byte[] b) : 최대 b.length 바이트의 데이터를 입력 스트림에서 바이트의 배열로 읽어 들이다.

■ public int read(byte[] b, int off, int len) : 최대 len 바이트의 데이터를 입력 스트림에서 바이트의 배열로 읽어 들인다.

■ public void reset() : 입력 스트림에서 마지막으로 mark 메서드가 호출되었을 때의 위치로 스트림 위치를 재지정한다.

■ public long skip(long n) : 입력 스트림에서 n 바이트의 데이터를 건너뛴 후 버린다.

OutputStream 주요 멤버 메서드

■ public void close() : 출력 스트림을 닫고 스트림과 연관된 시스템 자원을 해제한다.

■ public void flush() : 출력 스트림을 내보내고 강제로 버퍼된 출력 바이트가 기록되게 한다.

■ public void write(byte[] b) : 지정된 바이트 배열에서 b.length 바이트를 출력 스트림에 기록한다.

■ public void write(byte[] b, int off, int len) : len개의 바이트를 오프셋 off에서 시작하여 지정된 바이트 배열에서 출력 스트림에 기록한다.

■ public abstract  void write(int b) : 지정된 바이트를 출력 스트림에 기록한다.


1.3.3. File

File클래스는 파일 및 디렉토리를 관리할 수 있는 기능을 제공해 주는 클래스이다. 이 클래스를 이용하면 특정 파일의 존재유무 확인, 복사, 이름 변경 등의 파일에 관련된 작업을 할 수 있다. 하지만 한 가지 유의할 점은 File 클래스 자체에서는 파일의 내용을 입출력하기 위한 메서드는 제공해 주지 않는다는 점이다. 자바에서는 모든 데이터의 입출력을 스트림 기반으로 수행하기 때문에 File 클래스 내에서 이러한 메서드들을 구현할 필요가 없기 때문이다.


☞ File 클래스

: 파일과 디렉토리에 대한 정보를 제공하고 관리할 수 있게 해준다.

다음 예제는 File 객체를 하나 생성하여  File 클래스의 다양한 메서드를 이용하여 파일의 정보를 알아보는 예제이다. 이 예제를 수행하기 전에 FileTest.txt라는 파일을 만들고 .java와 같은 경로에 두자.


FileTest.java (File클래스를 테스트한 예제)

import java.io.File;

class FileTest {

   public static void main(String[] args) {

      File f1 = new File("FileTest.txt");

       System.out.println(" f1.getPath(): "+f1.getPath());

       System.out.println(" f1.getAbsolutePath(): "+f1.getAbsolutePath());

       System.out.println(" f1.getName(): "+f1.getName());

       System.out.println(" f1.getPath(): "+f1.getPath());

       System.out.println(" f1.toString(): "+f1.toString());

       try {

            System.out.println(" f1.toURL(): "+f1.toURL());

       } catch(java.net.MalformedURLException e) {

             System.out.println(" f1.toURL(): "+e);

       }

       System.out.println(" f1.canRead(): "+f1.canRead());

       System.out.println(" f1.canWrite(): "+f1.canWrite());

       System.out.println(" f1.isAbsolute(): "+f1.isAbsolute());

       System.out.println(" f1.isDirectory(): "+f1.isDirectory());

       System.out.println(" f1.isFile(): "+f1.isFile());

       System.out.println(" f1.isHidden(): "+f1.isHidden());

       System.out.println(" f1.lastModified(): "+f1.lastModified());

       System.out.println(" f1.length(): "+f1.length());

       System.out.println(" f1.exists(): "+f1.exists());

   }

}


C:\JavaExample\09>javac FileTest.java

C:\JavaExample\09>java FileTest

 f1.getPath(): FileTest.txt

 f1.getAbsolutePath(): C:\JavaExample\09\FileTest.txt

 f1.getName(): FileTest.txt

 f1.getPath(): FileTest.txt

 f1.toString(): FileTest.txt

 f1.toURL(): file:/C:/JavaExample/09/FileTest.txt

 f1.canRead(): true

 f1.canWrite(): true

 f1.isAbsolute(): false

 f1.isDirectory(): false

 f1.isFile(): true

 f1.isHidden(): false

 f1.lastModified(): 1002950524758

 f1.length(): 75

 f1.exists(): true


예제를 실행시키면 위와 같이, 생성한 파일 ‘FileTest.txt‘에 대한 정보 즉, 상대경로, 절대경로, 파일이름, 디렉토리 정보, URL 등이 함수명과 함께 출력되는 것을 볼 수 있다. 출력된 결과를 보면 메서드 이름만으로도 어떤 정보가 출력되었는지에 대해 짐작할 수 있을 것이다. 메서드에 대해서는 아래 부분에 다루고 있다.

예제를 분석하면 먼저 File 객체 f1을 생성한다. 이때 생성자로 “FileTest.txt“ 파일을 생성한다.

File f1 = new File("FileTest.txt");


파일객체 f1에 대한 정보를 각각의 메서드를 이용해서 출력해준다. 메서드의 내용은 아래의 File 주요 멤버 메서드를 살펴보자.

■ System.out.println(" f1.getPath(): " + f1.getPath());

■ System.out.println(" f1.getAbsolutePath(): " + f1.getAbsolutePath());

■ System.out.println(" f1.getName(): " + f1.getName());


URL에 관련된 부분에서 에러가 발생할 수 있으므로 try~catch 문으로 처리한다.

try {

   System.out.println(" f1.toURL(): "+f1.toURL());

} catch(java.net.MalformedURLException e) {

   System.out.println(" f1.toURL(): "+e);

}


File 클래스는 파일의 다양한 정보를 알아보고 관리할 수 있도록 하는 클래스이다. 프로그래머는 파일에 관련된 다양한 작업을 파일 클래스를 통해 할 수 있는, 반드시 알아두어야 할 클래스 중 하나라고 할 수 있다. 그러나 파일내용의 입출력 등은 파일 클래스에서 직접 담당하지 않으므로 다음에 살펴 볼 FileInputStream, FileOutputStream 등의 클래스를 이용하여 작업을 해야 한다.


File 생성자 메서드

■ public File(String path) : 주어진 경로명을 추상 경로명으로 하는 새로운 File 객체를 생성한다.

■ public File(String path, String name) : path라는 경로와 name에 대한 파일 객체를 생성한다.

■ public File(File dir, String name) : 디렉토리 dir에 name에 대한 파일 객체를 생성한다.

File 주요 멤버 메서드

■ public String getName() : 추상 경로명이 나타내는 파일, 또는 디렉토리의 이름을 반환한다.

■ public String getParent() : 추상 경로명의 부모 경로에 대한 경로명을 문자열로 반환한다.

■ public String getPath() : 추상 경로명을 경로명 문자열로 반환한다.

■ public boolean isAbsolute() : 절대 경로명인 지의 여부를 반환한다.

■ public boolean canRead() : 파일을 읽는 것이 가능한지의 여부를 반환한다.

■ public boolean exists() : 파일이 존재하는지의 여부를 반환한다.

■ public boolean isDirectory() : 디렉토리인 지의 여부를 반환한다.

■ public boolean isFile() : 파일인지의 여부를 반환한다.

■ public long length() : 파일의 크기를 얻는다.

■ public boolean mkdir() : 이 추상 경로명에 해당하는 디렉토리를 생성한다.

■ public boolean renameTo(File dest) : 파일의 이름을 변경한다.

■ public boolean setReadOnly() : 파일 또는 디렉토리에 대한 read-only 속성을 설정한다.

■ public int compareTo(File pathname) : 두 개의 추상 경로명이 같은지를 검사한다.

■ public URL toURL() throws MalformedURLException : 추상 경로명을 URL로 변환한다.


1.3.4. FileInputStream, FileOutputStream

FileInputStream, FileOutputStream 클래스는 파일 내용을 읽어들이거나, 파일에 내용을 출력할 때 사용된다. 이 클래스도 IO 패키지 중에서 사용빈도가 높은 클래스중 하나이다.

다음 예제는 FileInputStream과 FileOutputStream 클래스를 사용, 파일을 복사하는 예제이다.


FileInputOutputStreamTest.java (FileInputStream, FileOutputStream을 테스트 한 예제)

import java.io.*;

public class FileInputOutputStreamTest {

   public static void main(String[] args) {

     int i, len=0;

     InputStream in=null;

     OutputStream out=null;

       if(args.length < 2) {

         System.out.println("원본 파일과 복사될 파일의 이름을 입력하십시요!");

         System.exit(-1);

       }

       System.out.println("원본파일 : "+args[0]);

       System.out.println("목표파일 : "+args[1]);

       try {

         in = new FileInputStream(new File(args[0]));

         out = new FileOutputStream(args[1], true);

       } catch(FileNotFoundException e) {

         System.out.println(e);

       } catch(IOException e) {

         System.out.println(e);

       }

       try {

         while((i=in.read()) != -1) {

        out.write(i);

        len++;

    }

    in.close();

    out.close();

    System.out.println(len+" bytes are copied...Successfully!");

       } catch(IOException e1) {

         System.out.println(e1);

       }

   }

}

C:\JavaExample\09>javac FileInputOutputStreamTest.java

C:\JavaExample\09>java FileInputOutputStreamTest FileStreamTest01.txt FileStreamTest02.txt

원본파일 : FileStreamTest01.txt

목표파일 : FileStreamTest02.txt

94 bytes are copied...Successfully!

위의 예제는 파일을 복사하는 프로그램 예제이다. 이 프로그램을 실행할 때는 원본 파일의 이름과 목표 파일의 이름을 지정해주어야 한다. 그렇지 않으면 ‘원본 파일과 복사될 파일의 이름을 입력하십시요!‘라는 에러문을 출력하고서 프로그램이 종료된다. 또한 원본파일이 이 프로그램파일과 같은 디렉터리 안에 존재해야 한다. 파일이 다른 위치나 이름이 틀렸을 경우에는 “java.io.FileNotFoundException: Test.txt (지정된 파일을 찾을 수 없다)“ 라는 에러문이 출력된다.

if(args.length < 2) {

  System.out.println("원본 파일과 복사될 파일의 이름을 입력하십시요!");

  System.exit(-1);

}


그리고 사용자가 입력한 원본 파일으로 File객 체를 생성하고, 그 생성된 객체를 FileInputStream의 매개변수로 하여 FileInputStream의 객체 in을 생성한다.

FileInputStream in = new FileInputStream(new File(args[0]));


생성된 객체를 사용하여 FileInputStream 클래스의 멤버 메서드인 read() 메서드를 while문과 같이 사용한다.

while((i=in.read()) != -1) { };


위의 구문에서 read() 메서드는 입력 스트림에서 데이터 바이트를 읽는다. 데이터를 읽을 때 한 바이트씩 읽어 들이고, 읽은 데이터는 int형의 정수로 변환되어 반환된다. 데이터를 읽어낸 다음 포인트는 다음 바이트로 이동한다. 이 read() 메서드는 입력이 있을 때까지 실행되지 않는다. 데이터의 끝에 도달하면 -1이 반환된다.

FileOutputStream의 객체도 생성하여야 한다. 위의 예제에서는 FileOutputStream의 객체를 생성할 때 파일 이름과 boolean 타입의 변수도 같이 지정하였다. true값을 가지면 목표파일의 끝에서부터 읽은 내용을 가져다 복사를 하고, false이면 목표파일의 내용을 다 삭제한 후 읽은 내용을 복사한다.

out = new FileOutputStream(args[1], true);

out.write(i);


읽은 byte들은 write(int b) 메서드를 사용하여 지정된 byte를 FileOutputStream에 기록한다.


FileInputStream 생성자 메서드

■ public FileInputStream(File file)  : 지정된 File 객체에서 읽기 위해 입력 파일 스트림을 작성한다.

■ public FileInputStream(FileDescriptor fdObj)  : 지정된 파일 설명자에서 읽기 위해 입력 파일 스트림을 작성한다.

■ public FileInputStream(String name)  : 지정된 이름의 파일에서 읽기 위해 입력 파일 스트림을 작성한다.


FileInputStream 주요 멤버 메서드

■ public int available()  : 실행을 중단하지 않고 파일 입력 스트림에서 읽을 수 있는 바이트의 수를 반환한다.

■ public void close()  : 파일 입력 스트림을 닫고 스트림과 연관된 시스템 자원을 해제한다.

■ protected  void finalize()  : 더 이상 참조하지 않을 경우 파일 입력 스트림의 close 메서드를 닫습니다.

■ public FileDescriptor getFD()  : 스트림과 연관된 불투명(opaque) 파일 설명자 객체를 리턴한다.

■ public int read()  : 입력 스트림에서 데이터 바이트를 읽습니다.

■ public int read(byte[] b)  : 최대 b.length 바이트의 데이터를 입력 스트림에서 바이트의 배열로 읽습니다.

■ public int read(byte[] b, int off, int len)  : 최대 len 바이트의 데이터를 입력 스트림에서 바이트의 배열로 읽는다.

■ public long skip(long n)  : 입력 스트림에서 n 바이트의 데이터를 건너뛴 후 버린다.


FileOutputStream 생성자 메서드

■ public FileOutputStream(File file)  : 지정된 File 객체에 기록하기 위한 파일 출력 스트림을 작성한다.

■ public FileOutputStream(FileDescriptor fdObj)  : 지정된 파일 설명자에 기록하기 위한 출력 파일 스트림을 작성한다.

■ public FileOutputStream(String name)  : 지정된 이름의 파일에 기록하기 위한 출력 파일 스트림을 작성한다.

■ public FileOutputStream(String name, boolean append)  : 지정된 시스템 종속 파일명으로 출력 파일을 작성한다.

FileOutputStream 주요 멤버 메서드

■ public void close() : 파일 입력 스트림을 닫고 스트림과 연관된 시스템 자원을 해제한다.

■ protected void finalize() : 스트림을 더 이상 참조하지 않을 경우 파일 입력 스트림의 close() 메서드를 닫는다.

■ public FileDescriptor getFD() : 스트림과 연관된 파일 설명자를 반환한다.

■ public void write(byte[] b) : b.length개의 바이트를 지정된 바이트 배열에서 파일 출력 스트림에 기록한다.

■ public void write(byte[] b, int off, int len) : len개의 바이트를 오프셋 off에서 시작하여 지정된 바이트 배열에서 파일 출력 스트림에 기록한다.

■ public void write(int b) : 지정된 바이트를 파일 출력 스트림에 기록한다.

1.3.5. RandomAccessFile

RandomAccessFile 클래스는 FileInputStream, FileOutputStream의 스트림 인터페이스를 사용하지 않고도 파일을 다룰 수 있는 간편한 방법을 제공해 준다. 이름에서도 알 수 있듯이 파일(File)에 대한 임의의 접근(Random Access)이 가능하다는 것이다. 아래의 예제는 Test란 파일을 생성한 후 그곳에 순차적으로 값을 기록하고, 특정 위치를 찾아 그곳의 값을 다시 다른 값으로 바꿔주는 예제이다.


RandomAccessFileTest.java (RandomAccessFile을 테스트한 예제)

import java.io.*;

public class RandomAccessFileTest {

   static String s = "ILoveJava~";

   static String q = "Jabook!";

   public static void main(String[] args) throws Exception {

     RandomAccessFile rf = new RandomAccessFile("RandomAccessTest.txt", "rw");

       rf.writeChars(s);

       rf.close();

       rf = new RandomAccessFile("RandomAccessTest.txt", "rw");

       rf.seek(10); 

       rf.writeChars(q);

       rf.close();

       rf = new RandomAccessFile("RandomAccessTest.txt", "r");

       System.out.println("글내용은" + rf.readLine());

       rf.close();   

   }

}


C:\JavaExample\09>javac RandomAccessFileTest.java

C:\JavaExample\09>java RandomAccessFileTest

글내용은 I L o v e J a b o o k !


먼저 ‘RandomTest’란 파일 이름과 ’rw‘라는 매개변수를 갖고 RandomAccessFile 객체 rf를 생성하였다. 여기서 ’RandomAccessTest.txt‘라는 파일이 생성된다. ‘rw’는 생성된 ‘RandomAccessTest.txt’ 파일에 읽기(read)와 쓰기(write)가 가능하다는 것을 말한다. 그럼 만약 ‘rw’ 대신에 ‘r’을 썼다면 어떻게 되었을까? 위의 구문에서는 writeDouble 메서드를 사용하면서 기록을 하고 있는데 ’r‘로 설정되어 있다면 기록을 할 수 없기 때문에 ‘java.io.IOException: 액세스가 거부되었다‘라는 에러문을 만나게 된다.

RandomAccessFile rf = new RandomAccessFile("RandomAccessTest.txt", "rw");


☞ RandomAccessFile

: 임의로 제어가 가능한 파일을 생성하거나 접근하여 파일의 내용에 대한 작업을 할 수 있다.

“ILoveJava~“라는 글을 writeChars(s) 메서드를 사용하여 ‘RandomAccessTest.txt‘ 파일에 기록한다. 그리고 close() 메서드를 사용하여 파일 스트림을 닫고 스트림과 연관된 시스템 자원을 해제한다.

 rf.writeChars(s);

 rf.close();


다시 RandomAccessFile객체 rf를 생성한다. 파일의 이름은 ‘RandomAccessTest.txt‘이고, 속성은 ‘rw‘이다. 아까 그 파일에 다시 새 글을 쓰는 것이다.

 rf = new RandomAccessFile("RandomAccessTest.txt", "rw");

 rf.seek(10); 

 rf.writeChars(q);

 rf.close();


그런데 이번에는 seek(10) 메서드를 사용하였다. 이 메서드는 파일 포인터 오프셋을 설정하는 메서드이다. 쉽게 말하면 파일의 특정한 byte 위치를 기억하고 있다는 말이다. 그리고 그 바로 아래 구문에서 writeChars(q)  메서드를 사용하여 seek() 메서드에서 지정한 바이트 위치를 지난 후 값을 ‘Jabook!‘으로 바꿔버렸다. 그럼 그 ‘10‘라는 바이트 위치는 어디일까? 그 위치는 바로 생성된 값의 바이트와 관련이 있다. 여러분은 char를 표현할 때 한 글자는 2byte를 차지한다는 것을 잘 알고 있을 것이다. 바로 ‘10‘이란 파일의 byte가 10byte가 되는 지점을 말한다. 위의 예제에서는 “Ilove“라는 글 내용 중 ‘e‘ 출력값의 시작위치가 10byte가 된다. 하지만 위에서 말씀 드렸듯이 그 지점을 지난 후의 값-즉 “Java~“의 값이 “Jabook!“으로 바뀌었다.


☞ seek() 메서드

: 파일포인터를 특정한 절대위치로 설정하여, 해당 작업을 수행하도록  해준다. 생성자 매개변수가 ‘r‘이면 읽기 전용, ‘rw‘면 읽기, 쓰기 모두 가능하다.

또 다시 RandomAccessFile 객체 rf를 생성하였다. 이번에는 속성을 ‘r‘로만 했다. 하지만 이번에는 저장되어진 ‘RandomAccessTest.txt‘ 파일을 읽기만 하기 때문에 상관이 없다.

 rf = new RandomAccessFile("RandomAccessTest.txt", "r");

 System.out.println("글내용은" + rf.readLine());

 rf.close();


끝으로 readLine() 메서드를 사용하여 ‘RandomAccessTest.txt‘의 내용을 출력하였다. 글의 내용은 “IloveJava~“가 아닌 “IloveJabook!“이 출력된다. 못 믿겠다면 새로 생성된 ‘RandomAccessTest.txt‘을 열고 확인해 보자. 이렇게 RandomAccessFile은 FileInputStream 클래스, FileOutputStream 클래스나 DataInput 클래스, DataOutput 클래스를 사용하지 않아도 원하는 파일의 원하는 위치에 접근하여 값을 변하게 하는 때 유용하게 사용된다.

이렇게 RandomAccessFile 클래스는 파일에 접근해서 데이터를 수정할 때 편하게 사용할 수 있도록 만든 클래스이다. 사용할 때 ‘r‘과 ‘rw‘ 설정이라던가 seek() 메서드의 사용 등에 조심만 해주면 정말 유용한 클래스이다.


RandomAccessFile 생성자 메서드

■ public RandomAccessFile(File file, String mode) : File 인수로 지정된 파일에서 읽고 선택적으로 쓰기 위한 무작위 액세스 파일 스트림을 작성한다.

■ public RandomAccessFile(String name, String mode) : 정된 이름의 파일에서 읽고 선택적으로 쓰기 위한 무작위 액세스 파일 스트림을 작성한다.

RandomAccessFile 주요 멤버 메서드

■ public void close() : 위 액세스 파일 스트림을 닫고 스트림과 연관된 시스템 자원을 해제한다.

■ public long length() : 파일의 길이를 리턴한다.

■ public int read() : 파일에서 데이터의 바이트를 읽는다.

■ public int read(byte[] b) : 최대 b.length 바이트의 데이터를 파일에서 바이트의 배열로 읽어들인다.

■ public int read(byte[] b, int off, int len) : 최대 len 바이트의 데이터를 파일에서 바이트의 배열로 읽어들인다.

■ public boolean readBoolean() : 파일에서 boolean을 읽는다.

■ public byte readByte() : 파일에서 부호 비트가 있는 8 비트값을 읽는다.

■ public double readDouble() : 파일에서 double을 읽는다.

■ public int readInt() : 파일에서 부호 비트가 있는 32 비트 정수를 읽는다.

■ public String readLine() : 파일에서 텍스트의 다음 행을 읽습니다.

■ public String readUTF() : 파일에서 하나의 문자열로 읽어들인다.

■ public void seek(long pos) : 다음 읽기 또는 쓰기 발생시 파일 시작 부분에서 파일 포인터 오프셋을 설정한다.

■ public int skipBytes(int n) : 정확히 입력의 n 바이트를 건너뛴다.

■ public void write(byte[] b) : b.length개의 바이트를 오프셋 off에서 시작하여 지정된 바이트 배열에서 파일에 기록한다.

■ public void write(byte[] b, int off, int len) : len개의 바이트를 오프셋 off에서 시작하여 지정된 바이트 배열에서 파일에 기록한다.

■ public void write(int b) : 지정된 바이트를 파일에 기록한다.

■ public void writeBoolean(boolean v) : boolean을 1 바이트 값으로 파일에 기록한다.

■ public void writeByte(int v) : byte를 1 바이트 값으로 파일에 기록한다.

■ public void writeDouble(double v) : Double 클래스의 doubleToLongBits 메서드를 사용하여 double 인수를 long으로 변환한 다음 long값을 8 바이트 수량으로 상위 바이트 먼저 파일에 기록한다.

■ public void writeInt(int v) : int를 4 바이트 값으로 상위 바이트 먼저 파일에 기록한다.

■ void writeUTF(String str) : 기계에 독립적인 방법으로 UTF-8 인코딩을 사용하여 문자열을 파일에 기록한다.


1.3.6. 9.3.6 StreamTonkenizer

파일에서 데이터를 읽을 때 각 항목, 즉 토큰 단위로 나누어 읽도록 해주는 클래스가 StreamTokenizer 클래스 이다. StreamTokenizer 클래스는 입력 스트림, 또는 문자 스트림에서 스트림을 입력받아 토큰 단위로 나누어 주며, 이렇게 나누어 놓은 토큰을 한꺼번에 읽을 수 있도록 해 준다. 이 StreamTokenizer 클래스는 사용하면 ID, 숫자, 문자열, 주석 등 다양한 형태의 토큰들을 읽을 수 있다. StreamTokenizer 클래스의 일반적인 사용 절차는 다음과 같다.

① Reader개체를 위한 StreamTokenizer 객체를 만든다.

② 문자를 처리하는 방법을 정의한다.

③ nextToken() 메서드를 호출하여 다음 토큰을 가져온다.

④ ttype 인스턴스 변수를 읽어 토큰 형식을 결정한다.

⑤ sval, nval 또는 ttype 인스턴스 변수에서 토큰 값을 읽습니다.

⑥ 토큰을 처리한다.

⑦ nextToken() 메서드가 StreamTokenizer.TT_EOF를 반환할 때까지 단계 3부터 단계 6까지의 과정을 반복한다


☞ 토큰(token)?

: 스페이스, 탭 문자나 “, ; :“ 등 지정된 경계자로 나뉘어서 분리된  stream을 말한다.

예를 들어 스트림이 111:222:333 이고 이것이 경계자 ‘:‘에 의하여 tokenize되었다면, 111, 222, 333이 각각 하나의 토큰이다.


이번에는 위에서 설명한 StreamTokenizer 클래스의 일반적인 사용 절차에 맞춰 텍스트 파일에 들어 있는 문자들을 토큰으로 나누어 출력하는 프로그램을 작성해 보자. 저는 ‘야 된다!!호호호~~53‘ 이라는 문장이 들어 있는 “StreamTonkenizerTest.txt“ 파일을 만들어 시험해 보았다. 여러분도 여러 가지 문장부호를 넣어서 시험해 보자.


StreamTokenizerTest.java (StreamTokenizer을 테스트한 예제)

import java.io.*;

public class StreamTokenizerTest {

   public static void main(String[] args) throws IOException {

       InputStreamReader reader = new InputStreamReader(new FileInputStream("StreamTokenizerTest.txt"));

       StreamTokenizer tokens = new StreamTokenizer(reader);

       tokens.quoteChar('"');

       while(tokens.nextToken()!= tokens.TT_EOF) {

        switch(tokens.ttype) {

        case '"':

           System.out.println(tokens.sval);

        break;

        case StreamTokenizer.TT_WORD:

           System.out.println(tokens.sval);

        break;

        case StreamTokenizer.TT_NUMBER:

           System.out.println(tokens.nval);

        break;

        }

      }

   }

}


C:\JavaExample\09>javac StreamTokenizerTest.java

C:\JavaExample\09>java StreamTokenizerTest

된다

호호호

53.0


예제를 돌려보면 ‘StreamTonkenizerTest.txt‘ 파일에 들어있는 문자들이 문자열로 분리되어 출력되는 것을 볼 수 있을 것이다. 자 그럼, 예제를 자세히 들여다보자.

InputStreamReader를 통해 읽어 들인 ‘StreamTonkenizerTest.txt‘ 파일의 내용들을 토큰으로 분리하기 위해 tokens 객체를 생성하였다.

StreamTokenizer tokens = new StreamTokenizer(reader);


quoteChar() 메서드는 문자가 일치하는 Tokenizer의 문자열 상수를 분리하도록 지정한다. 이때 quoteChar() 메서드 뒤에 nextToken() 메서드가 문자열 상수를 발견하는 경우, ttype 필드는 문자열 분리자로 설정되고 sval필드는 문자열 본문으로 설정된다. 문자열 인용 기호가 발견되면, 문자열 인용 기호(이 문자는 포함하지 않음) 다음에서 다음 문자열 인용 기호가 나타나는 곳까지(인용 기호는 포함하지 않음) 또는, 행 종료 기호나 파일의 끝에 이르기까지의 모든 문자로 구성되는 문자열이 인식된다. 문자열 분석 시 "\n" 및 "\t"와 같은 보통 이스케이프 순서가 인식되어 단일 문자로 변환된다.

인용 기호 안의 문자열을 출력한다.

while(tokens.nextToken()!= tokens.TT_EOF) {

  switch(tokens.ttype) {

      case '"':

         System.out.println(tokens.sval);

      break;

      case StreamTokenizer.TT_WORD:

         System.out.println(tokens.sval);

      break;

      case StreamTokenizer.TT_NUMBER:

         System.out.println(tokens.nval);

      break;

  }

}


토큰이 스트림의 끝까지 도착하지 않을 때까지 계속해서 InputStreamReader에서 다음 토큰을 분석한다.

while(tokens.nextToken()!= tokens.TT_EOF)


StreamTokenizer가 단어 토큰을 읽었을 때 그 문자열을 출력하고 숫자를 읽었을 때 그 숫자값을 출력한다. 이때 주의할 점은 tokens.TT_WORD가 아닌 StreamTokenizer.TT_WORD로 적어야만 " constant expression required" 라는 에러가 나지 않는다.

case StreamTokenizer.TT_WORD:

  System.out.println(tokens.sval);

break;

case StreamTokenizer.TT_NUMBER:

  System.out.println(tokens.nval);

break;


StreamTokenizer 클래스는 String대신에 InputStream에 들어오는 데이터를 토큰단위로 나누어주는 역할을 한다. StreamTokenizer 클래스는 Character를 기반으로 하기 때문에 되도록이면 Reader 계열의 Stream을 입력소스로 사용할 것을 권장한다.


StreamTokenizer 생성자 메서드

■ public StreamTokenizer(Reader r) : 주어진 문자 스트림을 분석하는 토크나이저를 작성한다.


StreamTokenizer 주요 멤버변수

■ public static final int TT_EOF : 스트림의 끝을 읽었음을 나타내는 상수이다.

■ public static final int TT_EOL : 행의 끝을 읽었음을 나타내는 상수이다.

■ public static final int TT_NUMBER : 숫자 토큰을 읽었음을 나타내는 상수이다.

■ public static final int TT_WORD : 단어 토큰을 읽었음을 나타내는 상수이다.

■ public String sval : 현재 토큰이 단어 토큰이면 이 필드에는 단어 토큰의 문자를 제공하는 문자열이 들어 있다. 현재 토큰이 인용된 문자열 토큰이면 이 필드에는 문자열 본문이 들어 있다. 현재 토큰은 ttype 필드가 TT_WORD인 경우 하나의 단어이다. 현재 토큰은 ttype 필드값이 인용 문자인 경우 하나의 인용된 문자열 토큰이다.

■ public double nval : 현재 토큰이 숫자이면 이 필드에는 해당 숫자값이 들어 있다. 현재 토큰은 ttype 필드값이 TT_NUMBER인 경우 하나의 숫자이다.

■ public int ttype : nextToken 메서드를 호출한 후, 이 필드에는 방금 읽은 토큰의 유형이 들어 있다. 단일 문자 토큰의 경우, 값은 정수로 변환된 단일 문자이다. 인용된 문자열 토큰의 경우, 값은 인용 문자이다. 그렇지 않으면, 다음 중 하나이다.

TT_WORD는 토큰이 한 단어임을 나타낸다.

TT_NUMBER는 토큰이 하나의 숫자임을 나타낸다.

TT_EOL은 행의 끝을 읽었음을 나타낸다. 필드는 eolIsSignificant 메서드가 인수 true로 호출되는 경우에만 이 값을 가질 수 있다.

TT_EOF는 입력 스트림의 끝에 도달했음을 나타낸다.


StreamTokenizer 주요 메서드

■ public int nextToken() throws IOException : Tokenizer의 입력 스트림에서 다음 토큰을 분석하는데 다음 토큰 유형이 ttype 필드로 리턴된다. 토큰에 관한 추가 팁은 Tokenizer의 nval 필드 또는 sval 필드에 나와 있다. 이 클래스의 일반 클라이언트는 먼저 구문 테이블을 설정한 다음 nextToken을 호출하는 루프에 두어 TT_EOF가 리턴될 때까지 연속적인 토큰을 분석한다.

■ public void quoteChar(int ch) : 문자의 일치하는 쌍이 토크나이저의 문자열 상수를 분리하도록 지정한다. nextToken 메서드가 문자열 상수를 발견하는 경우, ttype 필드는 문자열 분리자로 설정되고 sval 필드는 문자열 본문으로 설정된다.

■ public void commentChar(int ch) : 문자 인수가 단일 행 주석을 시작하도록 지정한다. 스트림 토크나이저는 주석 문자에서 행의 끝에 이르기까지 모든 문자를 무시한다.

■ public int lineno() : 현재 행 번호를 리턴한다.

1.3.7. DataInputStream, DataOutputStream

ByteInputStream은 byte 단위의 입력 기능만을 제공해주지만, DataInputStream은 자바에서 제공하는 boolean, byte, char, short, int, long, float, double 등과 같은 자료의 기본형(primitive type)을 직접 읽고 쓸 수 있도록 해 준다. 여기서 제공되는 메서드는 앞서배운 RandomAccessFile에서 제공되는 메서드와 동일하다. 이 클래스에서는 유니코드 문자열을 UTF-8(UCS Transformation Format 8)형식으로 표현하며 주로 비트맵 이미지 파일과 같은 이미지 파일을 자바에서 다루려 할 때 많이 쓰인다.


☞ UTF-8

: ASCII로 텍스트 기반의 통신을 할 때 효율적인 유니코드 문자 인코딩을 말한다.

다음 예제는 DataInputStream과 DataOutputStream을 사용한 간단한 예제이다.


DataInputOutputStreamTest.java (DataInputStream, DataOutputStream을 테스트한  예제)

import java.io.*;

class DataInputOutputTest {

   public static void main(String[] args) throws IOException, FileNotFoundException {

       char c = 'A';

       char c1 = 'X';

       String str = "jabook";

       String str1 = "java";

       String file = "DataInputOutputTest.txt";

       DataInputStream in = null;

       DataOutputStream out = null;

       FileOutputStream fout = new FileOutputStream(file);

       out = new DataOutputStream(fout);

       out.writeChar(c);

       out.writeUTF(str);

       out.close();

       FileInputStream fin = new FileInputStream(new File(file));

       in = new DataInputStream(fin);

       System.out.println("c:"+c+" c1:"+c1+" str:"+str+" str1:"+str1);

       c1 = in.readChar();

       str1 = in.readUTF();

       in.close();

       System.out.println("c:"+c+" c1:"+c1+" str:"+str+" str1:"+str1);

   }

}


C:\JavaExample\09>javac DataInputOutputTest.java

C:\JavaExample\09>java DataInputOutputTest

c:A c1:X str:jabook str1:java

c:A c1:A str:jabook str1:jabook


파일 이름이 ‘DataInputOutputTest.txt‘인 파일을 생성한다. 이때 FileOutputStream이 새로운 파일에 기록하기 때문에 같은 이름을 갖는 기존의 파일은 삭제가 된다..

FileOutputStream fout = new FileOutputStream(file);


FileOutputStream에 연결된 DataOutputStream을 생성한다. 모든 데이터는 바이트로 나눠져서 스트림으로 전송된다.

out = new DataOutputStream(fout);


DataInputOutputTest.txt‘에 ‘A‘와 ‘jabook‘이라고 출력한다. Char, String형의 값을 DataOutputStream으로 출력된다. writeUTF(str)는 수정된 유니코드의 UTF-8형식으로 스트링을 출력해준다. 그리고 close() 메서드는 OutputStream을 닫아준다.

■ out.writeChar(c);

■ out.writeUTF(str);

■ out.close();


FileInputStream객체 fin을 생성하여 파일이름이 ‘DataInputOutputTest.txt‘인 파일을 불러온다. FileInputStream은 파일에서 연속적으로 데이터를 읽을 수 있도록 해준다.

FileInputStream fin=new FileInputStream(new File(file));


DataInputStream in은 FileInputStream으로부터 모든 데이터를 읽어들인다. 즉, ‘A‘와 ‘jabook‘을 읽어 들인다.

in = new DataInputStream(fin);


먼저 변수에 쓰기 전에 각각의 값을 출력했다. 그 결과는 ‘c:A c1:X str:jabook str1:java‘이다.

System.out.println("c:"+c+" c1:"+c1+" str:"+str+" str1:"+str1);


스트림으로부터 char, String에 해당하는 값을 읽어서 각각 c1, str1으로 받았다.

c1 = in.readChar();

str1 = in.readUTF();


다시 값을 출력해보면 ‘c:A c1:A str:jabook str1:jabook‘로 c1ㅡ, str1에서 출력된다.

System.out.println("c:"+c+" c1:"+c1+" str:"+str+" str1:"+str1);


DataInputStream과 DataOutputStream은 바이트 기반의 스트림으로부터 어떤 데이터 타입이던지 다 쓰고 다 읽어낸다. UTF형식으로 쓰인 메서드는 공용 표준으로 자바가 아닌 다른 언어로 쓰인 애플리케이션에서도 유니코드 스트링을 읽고 쓸 수 있다.


DataInputStream, DataOutputStream 생성자 메서드

■ public DataInputStream(InputStream in) : InputStream에 대한 DataInputStream 객체를 생성한다.

■ public DataOutputStream(OutputStream out) : OutputStream에 대한 DataOutputStream 객체를 생성한다.

DataInputStream, DataOutputStream 주요 메서드

■ public void flush() : 데이터 바이트 출력 스트림의 내용을 flush 한다.

■ public int size() : 현재 출력한 바이트 수를 반환한다.

■ public String readUTF() :UTF-8 형식의 문자열을 읽는다.

■ public void writeUTF(String str) : UTF-8 형식의 문자열을 쓴다.

■ public static String readUTF(DataInput in) : 주어진 DataInput 객체를 이용, UTF-8 형식의 문자열을 읽는다.

■ public String readLine() : 텍스트 한 줄을 읽어 들이고 각각의 byte를 char형 값으로 변환한다.

■ public boolean readBoolean() : 한 바이트를 읽어 0이면 false, 0이 아니면 ture를 반환한다.

■ public void writeBoolean(boolean v) : 주어진 boolean 값을 사용한다.

1.3.8. BufferedInputStream, BufferedOutputStream

통신의 효율을 높이기 위한 방법으로 버퍼링(Buffering)이 중요하게 사용된다. BufferedOutputStream 클래스는 flush() 메서드가 호출되거나, 버퍼가 꽉 찰 때까지 데이터를 버퍼에 저장했다가 한꺼번에 스트림에 쓰는 방식이다. 예를 들면 100byte에 해당하는 정보를 스트림에 쓰려고 한다면 기존의 방식은 1byte씩 100번의 write() 메서드를 호출해야 했지만, 버퍼링을 사용한다면 100byte를 버퍼에 모아서 1번에 write() 메서드를 호출하면 되므로 상당히 효율적이라고 할 수 있다. BufferedInputStream 클래스는 입력에 대한 버퍼링을 하고 mark()  메서드와 reset() 메서드를 지원하기 위한 기능을 제공해주고 있다.


☞ BufferedStream

: 바이트 스트림만을 사용할 경우 읽기, 쓰기 동작이 발생할 때마다 한 바이트씩 읽고 쓰게 되어 성능이 떨어질 수 있지만 버퍼 스트림은 미리 버퍼에 데이터를 담아 처리하므로 처리하는 동작이 보다 효율적이다.

다음은 BufferedInputStream과 BufferedOutputStream을 이용하여 파일을 복사하는 예제이다. 파일 두개를 준비하고 첫 번째 파일에서 두 번째 파일로 내용을 복사하는 간단한 프로그램이다. 여기서는 BufferedStreamTest01.txt과 BufferedStreamTest02.txt를 사용하였다.


BufferedInputOutputStreamTest.java (BufferedStream을 테스트한  예제)

import java.io.*;

public class  BufferedStreamTest {

   public static void main(String [] args) throws Exception {

     BufferedInputStream in = new BufferedInputStream(new FileInputStream(args[0]));

     BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(args[1]));

       int s1=0;

       while((s1 = in.read()) != -1) {

         out.write(s1);

       }

       in.close();

       out.close();

       System.out.println("Copied...Successfully!");

   }

}


C:\JavaExample\09>javac BufferedStreamTest.java

C:\JavaExample\09>java BufferedStreamTest BufferedStreamTest01.txt BufferedStreamTest02.txt

Copied...Successfully!


예제를 실행시키면 파일 ‘BufferedStreamTest01.txt‘에서 ‘BufferedStreamTest02‘ 파일로 내용이 복사된 후 파일이 무사히 복사되었다고 ‘Copied…Successfully‘란 메시지가 출력된다.

예제를 보면, BufferedInputStream 객체 in과 BufferedOutputStream 객체 out을 생성한다. 이때 파일로부터 데이터를 읽고 다시 다른 파일로 데이터를 써 주기 위해여, 생성자 매개변수로 FileInputStream과 FileOutputStream을 사용한다.

BufferedInputStream   in = new BufferedInputStream(new FileInputStream(args[0]));

BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(args[1]));

파일을 BufferedInpuStream 객체 in으로 읽어서 BufferedOutputStream 객체 out에 담아 출력한다.

while((s1 = in.read())!= -1) {

  out.write(s1);

}


‘BufferedStreamTest02.txt‘를 열어보면 ‘BufferedStreamTest01.txt‘의 글이 복사되었다는 것을 확인할 수 있다.

BufferedInputStream&BufferedOutputStream 클래스는 입력과 출력에 대한 버퍼링(buffering) 기능을 제공한다. 이것을 다른 말로 해 보면 BufferedInputStream은 InputStream 객체와 버퍼를 내부적으로 가지고 있다는 것이고, 마찬가지로 BufferedOutputStream은 OutputStream 객체와 버퍼를 가지고 있는 것이다. Buffering은 통신에 있어서 대단히 중요한 기능으로, 이를 구현한 BufferedInputStream과 Buffered-OutputStream을 적절한 곳에 쓴다면 보다 효율적인 프로그래밍을 할 수 있을 것이다.


BufferedInputStream, BufferedOutputStream 생성자 메서드

■ public BufferedInputStream(InputStream in)

   : 주어진 바이트 입력 스트림에 대한 BufferedInputStream 객체를 생성하고, 내부 버퍼의 크기인 512 바이트로 설정한다.

■ public BufferedInputStream(InputStream in, int size)

   : 주어진 바이트 입력 스트림에 대한 BufferedInputStream 객체를 생성하고, 내부 버퍼의 크기를 주어진 크기로 설정한다.

■ public BufferedOutputStream(OutputStream out)

   : 주어진 바이트 출력 스트림에 대한 BufferedOutputStream 객체를 생성하고, 내부 버퍼의 크기인 512 바이트로 설정한다.

■ public BufferedOutputStream(OutputStream out, int size) 주어진 바이트 출력 스트림에 대한 BufferedOutputStream 객체를 생성하고, 내부 버퍼의 크기를 주어진 크기로 설정한다


BufferedInputStream&BufferedOutputStream 주요 멤버 메서드

■ BufferedInputStream과 BufferedOutputStream

   : 클래스는 각각 InputStream과 OutputStream의 모든 표준 메서드를 제공한다.

1.3.9. PrintStream

PrintStream은 System.out을 통해서 콘솔로 출력하기 위하여 주로 사용된다. 효율면에서 PrintWriter 클래스에 뒤쳐지기 때문에 자주 사용되지 않는 클래스로, 이미 존재하는 스트림에 연결되어 원시 데이터 타입을 텍스트 형식으로 출력하는 여러 개의 메서드를 제공한다.

시스템의 모든 데이터 타입은 PrintStream의 메서드를 통해서 텍스트 형식으로 출력될 수 있으나, 부동점 실수의 형식에 관한 조절은 불가능하며 표현할 수 있는 최대의 정확도로 표시된다.


PrintStream 생성자 메서드

■ public PrintStream(OutputStream out) : 매개변수로 사용된 OutputStream 타입의 out 객체에 연결된 인쇄 스트림을 작성한다. 스트림은 개행문자를 만날 때마다 flush된다.

■ public PrintStream(OutputStream out, boolean autoFlush) : 매개변수로 사용된 OutputStream타입의 out 객체에 연결된 인쇄 스트림을 작성한다. autoFlush는 개행문자를 만날 때마다 스트림을 자동 flush할 것인지를 나타낸다.

PrintStream 주요 멤버 메서드

■ public void flush() : 스트림을 출력한다. 버퍼된 출력 바이트를 기본 출력 스트림에 기록한 다음 해당 스트림을 출력하여 수행된다.

■ public void close() : 스트림을 닫는다. 스트림을 출력한 다음 기본 출력 스트림을 닫아서 수행된다.

■ public boolean checkError() : 스트림을 출력하고 오류 상황를 점검한다. 오류는 누적된다. 일단 스트림에 오류가 발견되면, 이 루틴이 계속해서 모든 후속 호출에 대해 true을 리턴한다. 결과는 기본 출력 스트림에서 또는 형식 변환중 인쇄 스트림에서 오류가 발견될 경우 true, 그렇지 않으면 false를 반환한다.

■ protected void setError() : 오류가 발생했음을 나타낸다.

■ public void write(int b) : 필요할 경우, 중단하고 바이트를 기록한다. 해당 문자가 개행 문자이고 자동 플러싱이 사용 가능하게 되면, 스트림의 flush가 호출된다. 바이트는 주어진 대로 기록된다는 점에 주의하자. 플랫폼의 기본 문자 인코딩에 따라 변환되는 문자를 기록하려면, print(char) 또는 println(char) 메소드를 사용하자. 매개변수 b는 기록될 바이트를 나타낸다.

■ public void write(byte buf[], int off, int len) : 필요할 경우, 중단하고 바이트의 배열 부분을 기록한다. 매개변수 buf는 바이트 배열, off는 바이트 쓰기를 시작할 시작 인덱스 그리고 len은 기록할 바이트의 수를 나타낸다.

■ public void println() : 행 분리자를 기록하여 현재 행을 인쇄한다. 행 분리자 문자열은 시스템 특성 line.separator에 의해 정의되며, 반드시 개행('\n') 문자일 필요는 없다.

PrintStream에는 특이 사항이 있다. 모든 메서드들은 IOException을 발생시키지 않고 있다. 이 뜻은 데이터를 출력할 때마다 예외를 처리할 필요가 없다는 것이다. 대신 checkError() 메서드로 예외 발생 여부를 알아낼 수도 있다.


1.3.10. SequenceInputStream

SequenceInputStream 클래스는 서로 다른 InputStream을 논리적으로 이어주는 기능을 제공한다. 입력 스트림을 받은 순서대로 하나로 연결하여, 하나의 InputStream인 것처럼 읽을 수 있도록 해 준다. InputStream을 하나로 연결하는 방법은 InputStream이 두 개인 경우와 InputStream이 두 개 이상인 경우로 나누어 생각해 볼 수 있다. 먼저 InputStream이 두 개인 경우에는 두 개의 InputStream을 순서대로 읽어 들여서 하나의   InputStream처럼 보여주는 SequenceInputStream 객체를 생성하면 된다. 만일 InputStream이 두 개 이상인 경우에는 이어주고자 하는 InputStream들을 배열의 요소로 받은 후에 배열에서 순서대로 하나씩 차례대로 끄집어내면서, 하나의 InputStream로 연결하면 된다. 두 가지 경우를 각각 나누어 보자.


☞ Sequence InputStream

: 여러 개의 서로 다른 InputStream을 파일을 하나의 긴 InputStream로 모아준다.

먼저 두 개의 InputStream을 하나의 InputStream처럼 사용할 수 있도록 해 주는 SequenceInputStream 예제이다.


SequenceInputTest01.java (SequenceInputStream을 테스트하기 위한 예제)

import java.io.*;

import java.util.*;

class SequenceInputTest01 {

   public static void main(String[] args) throws IOException {

       int bn;

       byte[] arr = { 'J', 'a', 'v', 'a', ',' };

       ByteArrayInputStream bi = new ByteArrayInputStream(arr);

       FileInputStream fi = new FileInputStream("sequence.txt");

       SequenceInputStream si = new SequenceInputStream(bi, fi);

       while((bn = si.read()) != -1) {

         System.out.write(bn);

       }

       System.out.flush();     

   }

}

C:\JavaExample\09>javac SequenceInputTest01.java

C:\JavaExample\09>java SequenceInputTest01

Java,I Love Jabook!


Byte[]타입의 arr을 생성자 매개변수로 ByteArrayInputStream 객체 bi를 만들었다. ‘Java,‘라고 썼다.

ByteArrayInputStream bi=new ByteArrayInputStream(arr);


FileInputStream 객체 fi를 생성하였다. sample.txt는 java파일과 같은 폴더에 있어야 한다. sequence.txt에는 “I Love Jabook“라고 써 있다.

FileInputStream fi = new FileInputStream("sequence.txt");


ByteArrayInputStream과 FileInputStream을 하나로 이어줄 SequenceInputStream을 만들었다.

SequenceInputStream si = new SequenceInputStream(bi, fi);


read() 메서드는 SequenceInputStream si의 데이터를 2byte씩 읽어서 bn에 쓴다. 그 byte는 0부터  255까지의 범위의 int로서 돌려주어진다. 데이터를 다 읽어 들이면 EOF(End Of File)을 뜻하는 -1을 반환하여 while문을 종료한다.

while((bn=si.read())!=-1) {

     System.out.write(bn);               // bn을 2byte씩 읽어 들여 버퍼에 쓴다.

     System.out.flush();         // 버퍼에 저장된 데이터를 모두 출력한다.


정리해볼까? SequenceInputStream은 InputStream을 하나로 이어주는 역할을 수행한다. 이 예제에서는 InputStream 계열인 ByteArrayInputStream과 FileInputStream을 연결해주었다. 그래서 ByteArrayInputStream의 ‘Java,‘와 FileInputStream의 ‘IloveYou‘를 하나로 이어주었다. 그 결과 ‘Java, IloveYou‘라는 글이 도스 콘솔창에 출력되었다.


다음은 여러 개의 InputStream을 하나의 InputStream처럼 사용할 수 있도록 해 주는 SequenceInputStream 예제이다.


SequenceInputTest02.java (SequenceInputStream을 테스트하기 위한 예제)

import java.io.*;

import java.util.*;

class SequenceInputTest02 {

   public static void main(String[] args) throws IOException {

       int bn = 0;

       FileInputStream fi1 = new FileInputStream("sequence01.txt");

       FileInputStream fi2 = new FileInputStream("sequence02.txt");

       FileInputStream fi3 = new FileInputStream("sequence03.txt");

       Vector v = new Vector();

       v.addElement(fi1);

       v.addElement(fi2);

       v.addElement(fi3);

       SequenceInputStream si = new SequenceInputStream(v.elements());

       while((bn = si.read()) != -1) {

         System.out.write(bn);

       }

       System.out.flush();     

   }

}


C:\JavaExample\09>javac SequenceInputTest02.java

C:\JavaExample\09>java SequenceInputTest02

1.I Love Java!2.jabook-Home3.http:// www.jabook.org


첫 번째 FileInputStream 객체 fi1을 만들었다. sequence01.txt는 .java 파일과 같은 폴더에 있어야 한다. sequence01.txt의 내용은 “1.I Love Java!“이다.

FileInputStream fi1 = new FileInputStream("sequence01.txt");


두 번째 FileInputStream 객체 fi2를 생성한다. sequence02.txt도 .java 파일과 같은 폴더에 있어야 한다. sequence02.txt의 내용은 “2.jabook-Home“이다.

FileInputStream fi2 = new FileInputStream("sequence02.txt");


세 번째 FileInputStream 객체 fi3를 생성한다. sequence03.txt 역시 .java 파일과 같은 폴더에 있어야 한다. sequence03.txt의 내용은 “3.http://www.jabook.org“이다.

FileInputStream fi3=new FileInputStream("sequence03.txt ");


벡터를 만들고 InputStream을 집어넣었다.

Vector v=new Vector();

v.addElement(fi1);

v.addElement(fi2);

v.addElement(fi3);


FileInputStream들을 하나로 이어줄 SequenceInputStream을 생성한다.


SequenceInputStream si=new SequenceInputStream(v.elements());


read() 메서드는 SequenceInputStream객체 si의 데이터를 2byte씩 읽어서 bn에 쓴다. 그 byte는 0부터 255까지의 범위의 int형으로 돌려주어진다. 데이터를 다 읽어 들이면 EOF(End Of File)을 뜻하는 -1을 반환하여 while 문을 종료한다.

while((bn=si.read())!=-1) {         // bn을 2byte씩 읽어 들여 버퍼에 쓴다.

     System.out.write(bn);               // 버퍼에 저장된 데이터를 모두 출력한다.

     System.out.flush();


결과를 정리해 보자. 두 개 이상의 FileInputStream을 벡터에 넣은 후, SequenceInputStream에서 벡터안의 모든 InputStream을 하나로 연결해 주었다. 그리고 나서 SequenceInputStream의 내용을 도스 콘솔창에 출력해보니, ‘1.ILoveJava!2.Jabook-Home3.http://www.jabook.org‘라는 글이 출력되었다.


SequenceInputStream은 여러 개의 InputStream을 하나의 InputStream으로 이어주는 기능을 제공한다. InputStream을 이어주는 방법은 두 가지가 있다. 먼저 두 개의 inputStream을 이어주는 방법과 여러 개의 InputStream을 이어주는 방법이 있다.


SequenceInputStream 생성자 메서드

■ public SequenceInputStream(InputStream s1, InputStream s2) : 주어진 두 개의 InputStream을 하나의 InputStream처럼 순서대로 사용하는 SequenceInputStream 객체를 생성한다.

■ public SequenceInputStream(Enumeration e) : 주어진 Enumeration 객체가 포함하고 있는 모든 InputStream을 하나의 입력 스트림처럼 순서대로 사용하는 SequenceInputStream 객체를 생성한다.


SequenceInputStream 주요 메서드

■ public int available() : 현재의 스트림에 있는 바이트 수를 돌려준다.

■ public void close() : InputStream을 닫고, 관련된 모든 시스템 리소스를 해제한다.

■ public int read() : InputStream에서 데이터를 읽어 들인다.

1.3.11. PushBackInputStream

PushBackInputStream은 데이터의 읽기복구(unread) 기능을 제공하는 바이트 입력 필터이다. 즉, 방금 읽은 한  바이트를 읽기 전의 바이트 입력 스트림으로 되돌려 놓는 것을 가능하게 한다.

다음은 PushbackInputStream과 unread() 메서드를 사용하여 1byte를 읽기 전의 상태로 되돌리는 예제이다.


PushbackInputStreamTest.java (PushbackInputStream을 테스트하기 위한 예제)

import java.io.*;

class PushbackInputStreamTest {

   public static void main(String[] args) throws IOException {

       String s = "Java.";

       System.out.println(s);

       byte[] buffer = s.getBytes();

       ByteArrayInputStream in = new ByteArrayInputStream(buffer);

       PushbackInputStream f = new PushbackInputStream(in);

       int c;

       while ((c = f.read()) != -1) {

         switch(c) {

         case 'a':

                if ((c = f.read()) == 'v')

                      System.out.print("o");

                else {

                     System.out.print("b");

                      f.unread(c);

                }

                break;

        default:

                System.out.print((char)c);

                 break;

        }

      }

   }

}


C:\JavaExample\09>javac PushbackInputStreamTest.java

C:\JavaExample\09>java PushbackInputStreamTest

Java.

Job.


결과를 보면 “Java.“라는 문자열이 프로그램을 통해 “Job.“으로 바뀌었음을 알 수 있다. 이것은 위의 switch~case 문에서 문자 ‘a‘를 ‘o‘로 바꾸어 주고 문자 ‘a‘가 ‘b‘로 바뀌는 순간 unread()에 의해서 문자 ‘v‘가 스트림으로 되돌려 졌다. 따라서 ‘Job‘이란 문자열이 출력되었다.

소스를 보면, String을 바이트 배열로 변환하기 위해 getBytes() 메서드를 사용하여 문자열 “Java“를 byte 배열 buffer에 저장한다.

String s = "Java.";

Byte[] buffer = s.getBytes();

바이트 배열 buffer에 대한 ByteArrayInputStream 객체 in을 생성하고 다시, ByteArrayInputStream in에 대한 PushbackInputStream 객체 f를 생성한다.

ByteArrayInputStream in = new ByteArrayInputStream(buffer);

PushbackInputStream f = new PushbackInputStream(in);


바이트문자를 while 문으로 루프 돌면서 switch~case 문으로 검사해 ‘a‘를 만나서 다음 글자가 ‘v‘이면 ‘o‘를 출력하고, 그렇지 않으면 ‘b‘를 출력한 후 unread() 메서드로 한 바이트를 되돌린다(unread). 이 결과 “Java“가 “Job“으로 변하게 된다.

switch(c) {

  case 'a':

       if ((c = f.read()) == 'v')

              System.out.print("o");

       else {

              System.out.print("b");

              f.unread(c);

  }


PushbackInputStream 클래스는 문자를 읽고 나서 그것을 Stream에 반환하기 위해서 사용된다. 아무런 블로킹 없이 InputStream에서 나오는 것을 살짝 엿볼 수 있는 것이다. 하지만, 하나 이상의 단일 문자를 다시 push하려는 시도는 IOException을 발생시킴을 유의하여야 할 것이다.


PushbackInputStream 생성자 메서드

■ public PushbackInputStream(InputStream in) : InputStream에 in에 새로운 PushBackInputStream 객체를 생성한다.

■ public PushbackInputStream(InputStream in, int sz) : InputStream in에 정수형 sz 크기만큼의 새로운 PushbackInputStream 객체를 생성한다.

PushbackInputStream 주요 멤버 메서드

■ public void unread(byte[] b) : 주어진 바이트 배열에 있는 바이트들을 내부 버퍼의 앞에 복사함으로써, 읽지 않은 것처럼 한다.

■ public void unread(byte[] b, int off, int len) : 바이트배열 b를 내부 버퍼의 앞에 len길이만큼 푸시백 버퍼를 설정한다.

■ public void unread(int b) : 주어진 바이트를 내부 버퍼의 앞에 복사함으로써, 읽지 않은 것처럼 한다.

1.3.12. ByteArrayInputStream, ByteArrayOutputStream

ByteArrayInputStream&ByteArrayOutputStream는 바이트로 구성된 배열을 읽어 들인 후, 다시 그 배열을 출력하는 데 사용되는 클래스이다. 바이트 배열만 읽을 수 있기 때문에 사용하기 어려운 편은 아닌 클래스이다. 예제를 먼저 살펴보자.


ArrayStreamTest.java (ByteArrayInputStream,ByteArrayOutputStream을 테스트한 예제)

import java.io.*;

class ByteArrayStreamTest {

   public static void main(String[] args) throws IOException {

       int i;

       byte[] arr = {(byte)'j', (byte)'a', (byte)'b', (byte)'o', (byte)'o', (byte)'k'} ;

       ByteArrayInputStream in = new ByteArrayInputStream(arr);

       ByteArrayOutputStream out = new ByteArrayOutputStream();

       while((i = in.read()) != -1) {

         out.write(i);     

       }

       System.out.println(out.toString());

       in.close();

       out.close();

   }

}


C:\JavaExample\09>javac ByteArrayStreamTest.java

C:\JavaExample\09>java ByteArrayStreamTest

jabook


위의 예제는 바이트 배열을 읽어 들여서 그 값을 int형의 정수로 변환한 후 그 값을 다시 문자로 변환하여 출력하는 프로그램이다.

예제에서 먼저 바이트 배열을 생성하였다.

byte arr[] = { (byte)'j', (byte)'a', (byte)'b', (byte)'o', (byte)'o', (byte)'k' } ;


출력하려는 글을 전부 캐스팅하여 바이트 형으로 바꾸어 준 후, 배열 arr에 값을 집어넣어 배열을 생성하였다. 그 후에 바이트 배열을 ByteArrayInputStream 클래스를 사용하여 읽어 들이고, ByteArrayInputStream 클래스의 객체 in를 생성한다. 

ByteArrayInputStream in = new ByteArrayInputStream(arr);


생성된 객체를 사용하여 read() 메서드로 값을 입력받는다.

while((i=in.read()) != -1) { }


위의 구문에서 read() 메서드는 바이트를 읽어서 그 바이트의 값을 0부터 255 사이의 int형 정수로 반환하는 메서드이다. 만약 바이트 배열의 끝을 만나게 되면 -1의 값을 반환한다. 프로그램으로 돌아가서 설명하면 바이트 배열을 읽어서 그 값이 있으면 while 문을 실행하고, 배열의 끝을 만나면 while 문을 종료하라는 뜻이다.

그리고 ByteArrayOutputStream의 객체 out을 생성한다.

ByteArrayOutputStream out = new ByteArrayOutputStream();


생성된 객체 out을 사용하여 write(int b) 메서드를 호출하여 while문 안에서 사용하게 된다.

out.write(i);


write(int b) 메서드는 지정된 바이트를 바이트 배열 출력 스트림에 기록하는 역할을 한다. 그리고 toString() 메서드를 사용하여 버퍼의 내용을 문자열로 변환하고, 플랫폼의 기본 문자 인코딩에 따라 바이트를 문자로 변환하여 출력하였다.

System.out.println(out.toString());


ByteArrayInputStream&ByteArrayOutputStream 클래스는 바이트 배열을 읽어 들여서 그 값을 int형의 정수로 변환한 후 그 값을 다시 문자로 변환하여 출력하는 프로그램이다.


바이트 배열을 사용할 경우 사용한 다는 사실을 기억하면 된다.


ByteArrayInputStream 생성자 메서드

■ public ByteArrayInputStream(byte[] buf) : 지정된 바이트 배열에서 데이터를 읽는 새로운 바이트 배열 입력 스트림을 작성한다.

■ public ByteArrayInputStream(byte[] buf, int offset, int length) : 지정된 바이트 배열에서 데이터를 읽는 새로운 바이트 배열 입력 스트림을 작성한다.


ByteArrayInputStream 주요 메서드

■ public int available() : 실행을 중단하지 않고 입력 스트림에서 읽을 수 있는 바이트의 수를 반환한다.

■ public void mark(int readAHeadLimit) : 스트림의 현재 표시된 위치를 설정한다.

■ public boolean markSupported() : ByteArrayInputStream이 표시/재설정을 지원하는지 여부를 테스트한다.

■ public int read() : 입력 스트림에서 다음 데이터 바이트를 읽습니다.

■ public int read(byte[] b, int off, int len) : 입력 스트림에서 최대 len 바이트의 데이터를 배열로 읽습니다.

■ public void reset() : 버퍼를 표시된 위치로 재설정한다.

■ public void skip(long) : 입력 스트림에서 n 바이트의 입력을 건너뜁니다.

ByteArrayOutputStream 생성자 메서드

■ public ByteArrayOutputStream() : 새로운 바이트 배열 출력 스트림을 작성한다.

■ public ByteArrayOutputStream(int size) : 지정된 크기의 버퍼 용량(바이트 단위)으로 새로운 배열 출력 스트림을 작성한다.

ByteArrayOutputStream 주요 메서드

■ public void reset() : 출력 스트림 내의 현재 누적된 모든 출력이 버려지도록 바이트 배열 출력 스트림의 count 필드를 0으로 재설정한다.

■ public int size() : 버퍼의 현재 크기를 반환한다.

■ public byte [] toByteArray() : 새로 할당된 바이트 배열을 작성한다.

■ public String toString() : 버퍼의 내용을 문자열로 변환하고, 플랫폼의 기본 문자 인코딩에 따라 바이트를 문자로 변환한다.

■ public String toString(int hibyte) : 새로 할당된 문자열을 작성한다. Deprecated

■ public String toString(String enc) : 버퍼의 내용을 문자열로 변환하고, 지정된 문자 인코딩에 따라 바이트를 문자로 변환한다.

■ public void write(byte[] b, int off, int len) : len개의 바이트를 오프셋 off에서 시작하여 지정된 바이트 배열에서 바이트 배열 출력 스트림에 기록한다.

■ public void write(int b) : 지정된 바이트를 바이트 배열 출력 스트림에 기록한다.

■ public void writeTo(OutputStream out) : out.write(buf, 0, count)를 사용하여 출력 스트림의 write 메서드를 호출하는 것처럼 바이트 배열 출력 스트림의 내용 전체를 지정된 출력 스트림 인수에 기록한다.

Comments