ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Java 입출력 - 1, 스트림(I/O Stream)
    Java 2024. 2. 27. 13:49
    728x90
    반응형

    I/O란 Input, Output의 약자로 입력과 출력, 간단하게 입출력

    입출력은 컴퓨터 내부 또는 외부의 장치와 프로그램간의 데이터를 주고받는 것

    예를 들면 키보드로부터 데이터를 입력받는다든가, 
    System.out.println()을 이용해서 화면에 출력한다던가 하는 것이 가장 기본적인 입출력이다.

    1. 스트림(Stream)

    입출력을 수행하려면, 즉 어느 한 쪽에서 다른 쪽으로 데이터를 전달하려면, 
    두 대상을 연결하고 데이터를 전송할 수 있는 무언가가 필요한데 이것을 스트림(stream)이라고 한다.

    람다와 스트림의 스트림 API와 다른 개념이다.

    스트림은 데이터를 운반하는데 사용되는 연결통로이다.

    스트림은 단방향통신만 가능하기 때문에 하나의 스트림으로 입력과 출력을 동시에 할 수 없다.

    그래서 입력과 출력을 동시에 하려면, 
    입력을 위한 입력 스트림(input stream), 출력을 위한 출력 스트림(output stream),
    모두 2개의 스트림이 필요하다.

    스트림은 먼저 보낸 데이터를 먼저 받게 되어 있으며 중간에 건너뜀 없이 연속적으로 데이터를 주고 받는다.
    큐(Queue)와 같은 FIFO(First In First Out) 구조로 되어있다고 생각하면 된다.



    1 - 1. 바이트기반 스트림 - InputStream, OutputStream

        스트림은 바이트단위로 데이터를 전송한다.

        대상에 따라 스트림이 있다

        파일, 메모리(byte 배열), 프로세스(프로세스간의 통신), 오디오장치

        입력스트림은 InputStream, 출력스트림은 OutputStream의 자손들로 구성되어 있으며,
        각각 읽고 쓰는데 필요한 추상메서드를 자신에 맞게 구현해놓았다.

        자바는 java.io패키지를 통해 많은 입출력 관련 클래스들을 제공하고 있으며,
        입출력을 처리할 수 있는 표준화된 방법을 제공함으로써, 
        입출력의 대상이 달라져도 동일한 방법으로 입출력이 가능하기 때문에 편리하다.

    InputStream
    abstract int read()
    int read(byte[] b)
    int read(byte[] b, int off, int len)
    
    OutputStream
    abstract void write(int b)
    void write(byte[] b)
    void write(byte[] b, int off, int len)


    참고            read()의 반환타입이 byte가 아니라 int인 이유는 read()의 반환값의 범위가 0~255와 -1이기 때문이다.

    InputStream의 read()와 OutputStream의 write(int b)는 입출력 대상에 따라 읽고 쓰는 방법이 다를 것이기 때문에,
    각 상황에 맞게 구현하라는 의미에서 추상메서드로 정의되어 있다.

    read()와 write(int b)를 제외한 나머지 메서드들은 추상메서드가 아니니까,
    그냥 사용하면 될 것이라 생각할 수 있겠지만,
    read()와 write(int b)를 이용해서 구현한 것들이기 때문에,
    read()와 write(int b)가 구현되어 있지 않으면 의미가 없다.


    1 - 2. 보조 스트림

        스트림의 기능을 보완하기 위한 보조스트림

        실제 데이터를 주고받는 스트림이 아니기 때문에 데이터를 입출력할 수 있는 기능은 없지만,
        스트림의 기능을 향상시키거나 새로운 기능을 추가할 수 있다.

        그래서 보조스트림만으로는 입출력을 처리할 수 없고, 
        스트림을 먼저 생성한 다음에 이를 이용해서 보조스트림을 생성해야한다.

        test.txt 파일을 읽기 위한 FileInputStream,
        입력 성능 향상을 위해 버퍼를 사용하는 보조스트림 BufferedInputStream

    FileInputStream fis = new FileInputStream("test.txt");
    
    BufferedInputStream bis = new BufferedInputStream(fis);
    
    bis.read();


        코드 상으로는 보조스트림인 BufferedInputStream이 입력기능을 수행하는 것처럼 보이지만,
        실제 입력기능은 BufferedInputStream과 연결된 FileInputStream이 수행하고,
        보조스트림인 BufferedInputStream은 버퍼만을 제공한다.

        버퍼를 사용한 입출력과 사용하지 않은 입출력간의 성능 차이가 상당하기 때문에,
        대부분의 경우에 버퍼를 이용한 보조스트림을 사용한다.

        보조스트림들은 모두 FileInputStream의 자손들이고, 
        FileInputStream이 InputStream의 자손이라서, 
        결국 모든 보조스트림은 InputStream과 OutputStream의 자손들이므로 입출력 방법이 같다.


    1 - 3. 문자기반 스트림 - Reader, Writer

    바이트기반은 입출력의 단위가 1byte라는 뜻이다.

    C언어와 달리 java는 한 문자를 의미하는 char형이 1byte가 아니라 2byte기 때문에,
    바이트기반 스트림으로 2byte인 문자를 처리하는 데는 어려움이 있다.

    이 점을 보완하기 위해 문자기반의 스트림이 제공된다.
    문자데이터를 입출력할 때는 바이트기반 스트림 대신 문자기반 스트림을 사용하자.

    InputStream >> Reader
    OutputStream >> Writer

    문자기반 스트림의 이름은 바이트기반 스트림의 이름에서, 
    InputStream은 Reader로, OutputStream은 Writer로 바꾸기만 하면 된다.    
    단, ByteArrayInputStream에 대응하는 문자 기반 스트림은 char배열을 사용하는 CharArrayReader이다.

    Reader
    int read()
    int read(char[] cbuf)
    abstract int read(char[] cbuf, int off, int len)
    
    Writer
    void write(int c)
    void write(char[] cbuf)
    abstract void write(char[] cbuf, int off, int len)
    void write(String str)
    void write(String str, int off, int len)


    바이트기반 스트림과 비교하면,
    byte배열 대신 char배열을 사용한다는 것과, 추상메서드가 달라졌다.

    Reader와 Writer에서도 역시 추상메서드가 아닌 메서드들은 추상메서드를 이용해서 작성되었으며,
    프로그래밍적 관점에서 볼 때 read()보다 int read(char[] cbuf, int off, int len)를 추상메서드로 하는 것이 더 바람직하다.

    보조스트림 역시 문자기반 보조스트림이 존재하며, 
    사용목적과 방식은 바이트기반 보조스트림과 같다.

    2. 바이트기반 스트림

    2 - 1. InputStream

            스트림의 종류에 따라 mark()와 reset()을 통해 이미 읽은 데이터를 되돌려서 다시 읽을 수 있다.
            이 기능을 지원하는 스트림인지 확인하는 markSuppoprted()를 통해 알 수 있다.

        

    2 - 2. OutputStream

            flush()는 버퍼가 있는 출력스트림의 경우만 의미가 있고, 
            OutputStream에 정의된 flush는 아무 일도 하지 않는다.

            프로그램이 종료될 때, 사용하고 닫지 않은 스트림을 JVM이 자동적으로 닫아주기는 하지만,
            스트림을 사용해서 모든 작업을 마치고 난 후 에는 close()를 호출해서 반드시 닫아줘야 한다.

            그러나 ByteArrayInputStream 같이 메모리를 사용하는 스트림과, 
            System.in, System.out 같은 표준 입출력 스트림은 닫아주지 않아도 된다.
        


        

    2 - 3. ByteArrayInputStream, BufferedOutputStream

    메모리, 즉 바이트배열에 데이터를 입출력하는데 사용되는 스트림

    주로 다른 곳에 입출력하기 전에 데이터를 임시로 바이트배열에 담아서 변환하는 등의 작업을 하는데 사용된다.

    import java.io.*;
        import java.util.Arrays;
    
    class IOEx{
        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();
    
            Sysout(Arrays.toString(inSrc));
    
            try{
                while(input.available() > 0){
                    input.read(temp);           // 배열의 길이만큼 읽고, 배열을 채우고 읽은 길이를 반환, 지금은 4, 4, 2 반환. 지금은 리턴값을 받을 변수가 없기 때문에 의미 X
                    output.write(temp);         // 배열에 저장된 모든 내용을 출력소스에 쓴다.
    
                    outSrc = output.toByteArray();
                    printArrays(temp, outSrc);
                }
            } catch(IOException e){}
        }
    
        static void printArrays(byte[] temp, byte[] outSrc){
            Sysout(Arrays.toString(temp));
            Sysout(Arrays.toString(outSrc));
        }
    }
    결과
            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
        [0, 1, 2, 3]
        [0, 1, 2, 3]
        [4, 5, 6, 7]
        [0, 1, 2, 3, 4, 5, 6, 7]
        [8, 9, 6, 7]
        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 6, 7]


    read()나 write()가 예외를 발생시킬 수 있기 때문에 try-catch문을 감싸주었다.

    available()은 블락킹(blocking)없이 읽어올 수 있는 바이트의 수를 반환

    예상과 다른 결과가 나왔을 텐데, 
    그 이유는 마지막에 읽은 배열의 9번째와 10번째 요소 값인 8, 9만 출력해야 하는데 temp에 남아있던 6, 7까지 출력했기 때문이다.

    보다 나은 성능을 위해 temp에 담긴 내용을 지우고 쓰는 것이 아니라 그냥 기존의 내용 위에 덮어 쓴다.
    그래서 [4, 5, 6, 7]이 8, 9를 읽으면 [8, 9, 6, 7]이 된다.

    원하는 결과를 얻으려면 코드를 수정해야 한다.

    input.read(temp);
    output.write(temp);
        >>
    int len = input.read(temp);
    output.write(temp, 0, len);


                
    참고            블락킹이란 데이터를 읽어올 때 데이터를 기다리기 위해 멈춰있는 것을 뜻한다.
                       예를 들어 사용자가 데이터를 입력하기 전까지 기다리고 있을 때 블락킹 상태에 있다고 한다.

     

    class IOEx{
        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();
    
            Sysout(Arrays.toString(inSrc));
    
            try{
                while(input.available() > 0){
                    int len = input.read(temp);
                    output.write(temp, 0, len);
    
                    printArrays(temp, outSrc);
                }
            } catch(IOException e){}
    
            outSrc = output.toByteArray();
    
            Sysout(Arrays.toString(inSrc));
            Sysout(Arrays.toString(temp));
            Sysout(Arrays.toString(outSrc));
    
        }
    }
    결과
        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
        [8, 9, 6, 7]
        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]


    temp에 저장된 모든 내용을 출력하는 대신 값을 읽어온만큼만 출력하도록 바꿔서 해결했다.
                
        

    2 - 4. FileInputStream, FileOutputStream

    파일에 입출력을 하기 위한 스트림

    import java.io.*;
    
    class FileViewer{
        public static void main(String args[]){
            FileInputStream fis = new FileInputStream(args[0]);
            int data = 0;
    
            while((data = fis.read()) != -1){
                char c = (char)data;
                Sysout(c);
            }
        }
    }


    커맨드라인으로부터 입력받은 파일의 내용을 읽어서 그대로 화면에 출력하는 간단한 예제이다.

    read()의 반환값이 int(4 byte)지만, 
    더 이상 입력값이 없음을 알리는 -1을 제외하고는 0~255(1 byte)범위의 정수값이기 때문에, 
    char형(2 byte)로 변환한다 해도 손실되는 값은 없다

    read()가 한번에 1byte씩 파일로부터 데이터를 읽어들이긴 하지만,
    데이터의 범위가 십진수로 0~255(16진수로는 0x00~0xff)범위의 정수값이고,
    읽을 수 있는 입력값이 더 이상 없음을 알릴 수 있는 값(-1)도 필요하다
            
    그래서 다소 크긴 하지만 정수형 중에 연산이 가장 효율적이고 빠른 int형 값을 반환하도록 한 것

     

     

    import java.io.*;
    
    class FileCopy{
        public static void main(String args[]){
            try{
                FileInputStream fis = new FileInputStream(args[0]);
                FileOutputStream fos = new FileOutputStream(args[1]);
    
                int data = 0;
                while((data = fis.read()) != -1){
                    fos.write(data);                // void write(int b)
                }
    
                fis.close();
                fos.close();
            } catch(IOException e){
                e.printStackTrace();
            }
        }
    }


    FileInputStream, FileOutputStream을 사용해서 FileCopy.java 파일의 내용을 그대로 FileCopy.bak로 복사하는 일을 한다.
    단순히 FileCopy.java의 내용을 read로 읽어서, write(int b)로 FileCopy.bak에 출력한다.

    이처럼 텍스트파일을 다루는 경우에는 문자기반의 스트림인 FileReader, FileWriter를 사용하는 것이 더 좋다.

    참고            기존의 파일에 새로운 내용을 추가하려면, FileOutputStream fos = new FileOutputStream(args[1], true)같이                       생성자의 두번째 매개변수 값을 true로 해야한다.


    3. 바이트기반의 보조스트림

    3 - 1. FilterInputStream, FilterOutputStream

    InputStream/OutputStream의 자손이면서 모든 보조스트림의 조상
    보조스트림은 자체적으로 입출력을 수행할 수 없기 때문에 기반스트림을 필요로 한다.

    protected   FilterInputStream(InputStream in)
    public      FilterOutputStream(OutputStream out)


    FilterInputStream/FilterOutputStream의 모든 메서드는 단순히 기반스트림의 메서드를 그대로 호출할 뿐이다.
    FilterInputStream/FilterOutputStream 자체로는 아무 일도 안한다.
    FilterInputStream/FilterOutputStream은 상속을 통해 원하는 작업을 수행하도록 읽고 쓰는 메서드를 오버라이딩해야 한다.

    public class FilterInputStream extends InputStream{
        protected volatile InputStream in;
        protected FilterInputStream(InputStream in){
            this.in = in;
        }
    
        public int read() throws IOException{
            return in.read();
        }
            ...
    }


    생성자의 접근자가 protected이기 때문에, 
    FilterInputStream의 인스턴스를 생성해서 사용할 수 없고 상속을 통해 오버라이딩되어야 한다.

    3 - 2. BufferedInputStream, BufferedOutputStream

    스트림의 입출력 효율을 높이기 위해 버퍼를 사용하는 스트림

    BufferedInputStream의 버퍼크기는 입력소스로부터 한 번에 가져올 수 있는 데이터의 크기로 지정하면 좋다.

    보통 입력소스가 파일인 경우 8192(8K) 정도의 크기로 하는 것이 보통이고,
    버퍼의 크기를 변경해가면서 테스트하면 최적의 버퍼크기를 알아낼 수 있다.

    프로그램에서 입력소스로부터 데이터를 읽기 위해 처음으로 read를 호출하면,
    BufferedInputStream은 입력소스로부터 버퍼 크기만큼의 데이터를 읽어다 자신의 내부 버퍼에 저장한다.

    이제 프로그램에서는 BufferedInputStream의 버퍼에 저장된 데이터를 읽으면 되는 것이다.

    외부의 입력소스로부터 읽는 것보다 내부의 버퍼로부터 읽는 것이 훨씬 빠르기 때문에 그만큼 작업 효율이 높아진다.

    프로그램에서 버퍼에 저장된 모든 데이터를 다 읽고 그 다음 데이터를 읽기 위해 read 메서드가 호출되면,
    BufferedInputStream은 입력소스로부터 다시 버퍼크기만큼의 데이터를 읽어다 버퍼에 저장해놓는다.
    이와 같은 작업이 계속해서 반복된다.

    BufferedOutputStream 역시 버퍼를 이용해서 출력소스와 작업을 하게 되는데,
    버퍼가 가득 찼을 때만 출력소스에 출력하기 때문에,
    마지막 출력부분이 출력소스에 쓰이지 못하고, 
    BufferedOutputStream의 버퍼에 남아있는 채로 프로그램이 종료될 수 있다는 점을 주의해야한다.

    그래서 프로그램에서 모든 출력작업을 마친 후 BufferedOutputStream에 close()나 flush()를 호출해서,
    마지막에 버퍼에 있는 모든 내용이 출력소스에 출력되도록 해야 한다.

    참고            BufferedOutputStream의 close()는 flush를 호출하여 버퍼의 내용을 출력스트림에 쓰도록 한 후,
                        BufferedOutputStream인스턴스의 참조변수에 null을 지정함으로써 사용하던 자원들이 반환되게 한다.

    import java.io.*;
    
    class BufferedOutputStreamEx{
        public static void main(String args[]){
            try{
                FileOutputStream fos = new FileOutputStream("123.txt");
    
                BufferedOutputStream bos = new BufferedOutputStream(fos, 5);        // 버퍼의 크기를 5로 지정
    
                for(int i = '1'; i <= '9'; i++){
                    bos.write(i);
                }
                fos.close();
            } catch(IOException e){
                e.printStackTrace();
            }
        }
    }
    결과
    12345


    크기가 5인 BufferedOutputStream을 이용해서 파일 123.txt에 1~9를 출력하는 예제인데 결과를 보면 5까지만 출력되었다.

     

    버퍼에 남은 데이터가 출력되지 못한 상태로 프로그램이 종료되었기 때문이다.

    fos.close()를 호출해서 스트림을 닫아주기는 했지만,
    이렇게 해서는 BufferedOutputStream의 버퍼에 있는 내용이 출력되지 않는다.

    bos.close();로 BufferedOutputStream의 close()를 호출해야 버퍼에 남은 모든 내용이 출력된다.

    BufferedOutputStream의 close()는 기반스트림인 FileOutputStream의 close()를 호출하기 때문에,
    FileOutputStream의 close는 따로 호출하지 않아도 된다.

    이처럼 보조스트림을 사용한 경우, 기반스트림의 close나 flush를 호출할 필요 없이,
    단순히 보조스트림의 close만 호출하면 된다.
        

    3 - 3. DataInputStream, DataOutputStream

    각각 FilterInputStream/FilterOutputStream의 자손이며,
    DataInputStream은 DataInput 인터페이스를, DataOutputStream은 DataOutput 인터페이스를 구현했기 때문에,
    데이터를 읽고 쓰는데 있어서 byte단위가 아닌, 8가지 기본 자료형의 단위를 읽고 쓸 수 있다는 장점이 있다.

    DataOutputStream이 출력하는 형식은 각 기본자료형 값을 16진수로 표현하여 저장한다.
    예를 들어 int값을 출력한다면, 4byte의 16진수로 출력된다.

    각 자료형의 크기가 다르므로, 출력한 데이터를 다시 읽어올 때는 출력했을 때의 순서를 염두에 두어야 한다.

    FileOutputStream을 기반으로 하는 DataOutputStream을 생성한 후,
    DataOutputStream을의 메서드들을 이용해서 sample.dat파일에 값들을 출력했다.

    이 때 출력한 값들은 이진 데이터(binary data)로 저장된다.

    문자 데이터(text data)가 아니므로 문서 편집기로 sample.dat를 열어봐도 알 수 없는 글자들로 이루어져 있을 것이다.

    파일을 16진수 코드로 볼 수 있는 UltraEdit과 같은 프로그램이나 ByteArrayOutputStream을 사용하면 이진데이터를 확인할 수 있다.

    import java.io.*;
    
    class DataOutputStreamEx1{
        public static void main(String args[]){
            FileOutputStream fos = null;
            DataOutputStream dos = null;
    
            byte[] result = null;
    
            try{
                fos = new FileOutputStream("sample.dat");
                dos = new DataOutputStream(fos);
    
                dos.writeInt(10);
                dos.writeFloat(20.0f);
                dos.writeBoolean(true);
    
                dos.close();
            } catch(IOException e){
                e.printStackTrace();
            }
        }
    }


    FileOutputStream 대신 ByteArrayOutputStream을 사용

    import java.io.*;
    
    class DataOutputStreamEx2{
        public static void main(String args[]){
            FileOutputStream fos = null;
            DataOutputStream dos = null;
    
            byte[] result = null;
    
            try{
                fos = new ByteArrayOutputStream("sample.dat");
                dos = new DataOutputStream(fos);
    
                dos.writeInt(10);
                dos.writeFloat(20.0f);
                dos.writeBoolean(true);
    
                result = bos.toByteArray();
    
                String[] hex = new String[result.length];
    
                for(int i = 0; i < result.length; i++){
                    hex[i] = String.format("%02x", result[i] + 256);
                } else{
                    hex[i] = String.format("%02x", result[i]);
                }
    
                sysout("10진수 :" + Arrays.toString(result));
                sysout("16진수 :" + Arrays.toString(hex));
    
                dos.close();
            } catch(IOException e){
                e.printStackTrace();
            }
        }
    }

     

    결과
    10진수 : [0, 0, 0, 10, 65, -96, 0, 0, 1]
    16진수 : [00, 00, 00, 0a, 41, a0, 00, 00, 01]


    이처럼 ByteArrayOutputStream을 사용하면 byte단위의 데이터 변환 및 조작이 가능하다.

    사실 DataOutputStream에 의해서 어떻게 저장되는지 몰라도,
    DataOutputStream의 write메서드들로 기록한 데이터는 DataInputStream의 read메서드들로 읽기만 하면 된다.

    주의할 점은 여러 가지 종류의 자료형으로 출력한 경우, 읽을 때는 반드시 쓰인 순서대로 읽어야 한다.

    import java.io.*;
    
    class DataInputStreamEx1{
        public static void main(String args[]){
            FileOutputStream fos = null;
            DataInputStream dos = null;
    
            byte[] result = null;
    
            try{
                fis = new FileInputStream("sample.dat");
                dis = new DataInputStream(fis);
    
                sysout(dis.readInt());
                sysout(dis.readFloat());
                sysout(dis.readBoolean());
    
                dos.close();
            } catch(IOException e){
                e.printStackTrace();
            }
        }
    }
    결과
    10
    20.0
    true


    파일로부터 데이터를 읽어 올 때,
    아무런 변환이나 자릿수를 셀 필요없이,
    단순히 readInt와 같이 읽어 올 데이터의 타입에 맞는 메서드를 사용하기만 하면 된다.

    문자로 데이터를 저장하면, 다시 데이터를 읽어올 때 문자들을 실제 값으로 변환하는,
    예를 들면 문자열 "100"을 숫자 100으로 변환하는 과정을 거치고,
    읽어야할 데이터의 개수를 결정해야 하는 번거로움이 있다.

    하지만 이처럼 DataInputStream과 DataOutputStream을 사용하면,
    데이터를 변환할 필요도 없고, 
    자리수를 세어서 따지지 않아도 되므로 편리하고 빠르게 데이터를 저장하고 읽을 수 있다.

    int형 배열 score의 값들을 DataOutputStream을 이용해서 score.dat파일에 출력하는 예제

    import java.io.*;
    
    class DataOutputStreamEx3{
        public static void main(String args[]){
            int[] score = {100, 90, 95, 85, 50};
    
            try{
                FileOutputStream fos = new FileOutputStream("score.dat");
                DataOutputStream dos = new DataOutputStream("fos");
    
                for(int i = 0; i < score.length; i++){
                    dos.writeInt(score[i]);
                }
            }
            dos.close();
        } catch (IOException e){
            e.printStackTrace();
        }
    }


    실행 결과

    C:\jdk1.8\work\ch15>java DataOutputStreamEx3
    
    C:\jdk1.8\work\ch15type score.dat
    d   Z   _   U   2

              
    type 명령으로 score.dat score.dat의 내용을 보면 숫자가 아니라 문자들이 나타나는데,
    type 명령이 파일의 내용을 문자로 변환해서 보여주기 때문이다.
    파일에 실제 저장된 내용은 다음과 같다.
    아래 숫자는 10진수로 변환한 결과이다.

    00 00 00 64     00 00 00 5A     00 00 00 5F     00 00 00 55     00 00 00 32
    100             90              95              85              50


    int 크기가 4byte이므로 모두 20byte의 데이터가 저장되어 있다.

    참고로 16진수 두자리가 1byte다.

    이 파일을 읽어서 데이터의 총합을 구할 것이다.

    import java.io.*;
    
    class DataInputStreamEx2{
        public static void main(String args[]){
            int sum = 0;
            int score = 0;
    
            FileInputStream fis = null;
            DataInputStream dis = null;
    
            try{
                fis = new FileInputStream("score.dat");
                dis = new DataInputStream(fis);
    
                while(true){
                    score = dis.read();
                    sysout(score);
                    sum += score;
                }
            } catch(EOFException e){
                sysout(sum);
            } catch(IOException e){
                e.printStackTrace();
            } finally{
                try{
                    if(dis != null)         // dis가 null이 아니면
                        dis.close();        // 스트림을 닫는다.
                } catch(IOException e){
                    e.printStackTrace();
                }
            }
        }
    }


    DataInputStream의 readInt와 같이 데이터를 읽는 메서드는 
    더 이상 읽을 데이터가 없으면 EOFException를 발생시킨다.

    그래서 다른 입력스트림들과 달리 무한반복문과, 
    EOFException를 처리하는 catch문을 이용해서 데이터를 읽는다.

    원래 while문에서 작업을 마친 후에 스트림을 닫아줘야 하지만,
    while문이 무한 반복문이라 finally블럭에서 닫았다.

    dis가 null일 때 close()를 호출하면 NullPointerException이 발생하므로,
    if문을 사용해서 dis가 null인지 체크한 후에 close()를 호출해야 한다.

    그리고 close()는 IOException을 발생시킬 수 있으므로 try-catch블럭으로 감싸주었다.

    사실 프로그램이 종료될 때,
    가비지 컬렉터가 사용하던 자원들을 모두 해제해주기 때문에,
    이렇게 간단한 예제는 스트림을 닫지 않아도 별 문제가 없다.
    그래도 가능하면 스트림을 사용한 직후에 바로 닫아서 자원을 반환하는 것이 좋다.

    jdk1.7부터는 try-with-resources문을 이용해서 close()를 직접 호출하지 않아도 자동호출되도록 할 수 있다.
    훨씬 간단해진다.

    import java.io.*;
    
    class DataInputStreamEx2{
        public static void main(String args[]){
            int sum = 0;
            int score = 0;
    
            try(FileInputStream fis = new FileInputStream("score.dat");
                DataInputStream dis = new DataInputStream(fis)){
    
                while(true){
                    score = dis.read();
                    sysout(score);
                    sum += score;
                }
            } catch(EOFException e){
                sysout(sum);
            } catch(IOException e){
                e.printStackTrace();
            }
        }
    }


        

    3 - 4. SequenceInputStream

    여러 개의 입력스트림을 연속적으로 연결해서, 
    하나의 스트림으로부터 데이터를 읽는 것과 같이 처리할 수 있도록 도와준다.

    생성자를 제외하고 나머지 작업은 다른 입력스트림과 다르지 않다.

    큰 파일을 여러 개의 작은 파일로 나누었다가, 
    하나의 파일로 합치는 것과 같은 작업을 수행할 때 사용하면 좋을 것이다.

    SequenceInputStream(Enumeration e)                          // Enumeration에 저장된 순서대로 입력스트림을 하나의 스트림으로 연결한다.
    SequenceInputStream(InputStream s1, InputStream s2)         // 두 입력스트림을 하나로 연결한다.

    Vector에 연결할 입력스트림들을 저장한 다음 Vector의 Enumeration elements()를 호출해서 생성자의 매개변수로 사용

    예시1

    Vector files = new Vector();
    files.add(new FileInputStream("file.001"));
    files.add(new FileInputStream("file.002"));
    SequenceInputStream in = new SequenceInputStream(files.elements());
    
    System.out.println(getMax(1, 2));
    System.out.println(getMax(1, 1));
    System.out.println(getMax(2, 1));


                
    예시2

    FileInputStream file1 = new FileInputStream("file.001");
    FileInputStream file2 = new FileInputStream("file.002");
    SequenceInputStream in = new SequenceInputStream(file1, file2);

                
    Vector에 저장된 순서대로 SequenceInputStream에 입력되므로 순서에 주의하자

        

    3 - 5. PrintStream

    데이터를 기반스트림에 다양한 형태로 출력할 수 있는 print, println, printf같은 메서드를 오버로딩하여 제공

    데이터를 적절한 문자로 출력하는 것이기 때문에 문자기반 스트림의 역할 수행

    jdk1.1부터 향상된 기능의 문자기반 스트림 PrintWriter가 추가되었으나,
    그 동안 자주 사용되던 System.out이 PrintStream이다 보니 둘 다 사용할 수밖에 없게 되었다.

    PrintStream과 PrintWriter는 거의 같은 기능이지만,
    PrintWriter가 다양한 언어의 문자를 처리하는데 적합하기 때문에,
    가능하면 PrintWriter를 사용하는 것이 좋다.

    참고            우리가 자주 쓰던 System클래스의 static멤버인 out과 err, 즉 System.out, System.err이 PrintStream이다.

    print()나 println()을 이용해서 출력하는 중에, 
    PrintStream의 기반스트림에서 IOException이 발생하면 checkError()을 통해 알 수 있다.

    print나 println은 매우 자주 사용되기 때문에, 예외를 던지지 않고 내부에서 처리한다.
    그러지 않으면 println을 사용하는 모든 곳에 try-catch문을 사용해야 할 것이다.

    printf는 jdk1.5부터 추가된 것으로,
    C언어와 같이 편리한 형식화된 출력을 지원하게 되었다.

    printf에 쓸 수 있는 옵션은 다양한데 그 중 자주 쓰는 옵션들

    정수
    %d      10진수
    %o      8진수
    %x      16진수
    %c      문자
    %s      문자열
    %5d     5자리 숫자. 빈자리는 공백으로 채움
    %-5d    5자리 숫자. 빈자리는 공백으로 채움. 왼쪽 정렬
    %05d    5자리 숫자. 빈자리는 0으로 채움


    문자열
    %s      문자열(string)
    %5s     5자리 문자열. 빈자리는 공백
    %-5s    5자리 문자열. 빈자리는 공백. 왼쪽 정렬

    실수
    %e      지수형태표현(exponent)
    %f      10진수(decimal float)
    %3.1f   출력될 자리수를 최소 3자리(소수점 포함), 소수점 이하 1자리(2번째 자리에서 반올림)
    %08.1f  소수점이상 최소 6자리, 소수점 이하 1자리, 출력될 자리수를 최소 8자리(소수점 포함)를 확보한다. 빈자리는 0으로 채운다.
    %-8.1f  소수점이상 최소 6자리, 소수점 이하 1자리, 출력될 자리수를 최소 8자리(소수점 포함)를 확보한다. 빈자리는 공백으로 채운다. 왼쪽 정렬


    특수문자
    %t      탭(tab)
    %n      개행 문자(new line)
    %%      %


    날짜와 시간
    %tR             시분(24시간)
    %tH:%tM         똑같음

    %tT             시분초(24시간)
    %tH:%tM:%tS     똑같음

    %tD             월일년
    %tm/%td%ty      똑같음

    %tF             년월일
    %tY-%tm-%td     똑같음

    형식화된 문자열에 사용된 옵션의 개수와 매개변수의 개수가 일치하도록 주의


    4. 문자기반 스트림

        문자데이터를 다루는데 사용된다는 점을 빼면 바이트기반 스트림과 사용방법이 거의 같다.

        

    4 - 1. Reader, Writer

            바이트기반 스트림의 조상이 InputStream, OutputStream인 것과 같이,
            문자기반 스트림은 Reader/Writer가 조상이다.

            byte배열 대신 char배열을 사용한다는 것 외에는 InputStream/OutputStream의 메서드와 같다

            문자기반 스트림이 단순히 2byte로 스트림을 처리하는 것만을 의미하지는 않는다.
            문자데이터를 다루는데 필요한 또 하나의 정보는 인코딩(encoding)이다.

            문자기반 스트림 Reader/Writer와 그 자손들은
            여러 종류의 인코딩과 자바에서 사용하는 유니코드(UTF-16)간의 변환을 자동적으로 처리해준다.

            Reader는 특정 인코딩을 읽어서 유니코드로 변환하고,
            Writer는 유니코드를 특정 인코딩으로 변환하여 저장한다.
        
        

    4 - 2. FileReader/FileWriter

            파일로부터 텍스트데이터를 읽고, 파일에 쓰는데 사용
            사용법은 FileInputStream/FileOutputStream과 다르지 않다
        
        

    4 - 3. PipedReader/PipedWriter

            쓰레드 간 데이터를 주고받을 때 사용
            다른 스트림과 달리 입력과 출력스트림을 하나의 스트림으로 연결(connect)해서 데이터를 주고받는다

            스트림을 생성한 다음 어느 한쪽 쓰레드에서 connect를 호출해서 입력스트림과 출력스트림을 연결한다.

            입출력을 마친 후 어느 한쪽 스트림만 닫아도 나머지 스트림은 자동으로 닫힌다.

            나머지는 일반 입출력방법과 다르지 않다.
        
        

    4 - 4. StringReader/StringWriter

            CharArrayReader/CharArrayWriter와 같이 입출력 대상이 메모리인 스트림

            StringWriter에 출력되는 데이터는 내부의 StringBuffer에 저장되며,
            StringWriter의 메서드를 이용해서 저장된 데이터를 얻을 수 있다.

    StringBuffer getBuffer()        StringWriter에 출력한 데이터가 저장된 StringBuffer를 반환한다.
    String toString()               StringWriter에 출력된 (StringBuffer에 저장된) 문자열을 반환.


            근본적으로 String도 char배열이지만, 
            아무래도 char배열보단 String으로 처리하는 것이 여러모로 편리하다.


    5. 문자기반의 보조스트림

        

    5 - 1. BufferReader/BufferWriter

            버퍼를 이용해서 입출력의 효율을 높일 수 있도록 해주는 역할
            
            버퍼를 이용하면 입출력의 효율이 비교할 수 없을 정도로 좋아지기 때문에 사용하는 것이 좋다.

            BufferReader의 readLine()을 사용하면 데이터를 라인단위로 읽을 수 있고

            BufferWriter는 newLine()이라는 줄바꿈 해주는 메서드를 가지고 있다.
        
        

    5 - 2. InputStreamReader/OutputStreamWriter

    바이트기반 스트림을 문자기반 스트림으로 연결시켜주는 역할

    바이트기반 스트림의 데이터를 지정된 인코딩의 문자데이터로 변환하는 작업 수행

    참고            InputStreamReader/OutputStreamWriter는 Reader/Writer의 자손이다.          

    InputStreamReader(InputStream in)                       OS에서 사용하는 기본 인코딩의 문자로 변환하는 InputStreamReader 생성
    InputStreamReader(InputStream in, String encoding)      지정된 인코딩을 사용하는 InputStreamReader 생성
    String getEncoding()                                    InputStreamReader의 인코딩을 알려준다
    
    OutputStreamWriter(OutputStream out)                        OS에서 사용하는 기본 인코딩의 문자로 변환하는 OutputStreamWriter 생성
    OutputStreamWriter(OutputStream out, String encoding)       지정된 인코딩을 사용하는 OutputStreamWriter 생성
    String getEncoding()                                        OutputStreamWiter의 인코딩을 알려준다


            한글윈도우에서 중국어로 작성된 파일을 읽을 때, 
            인코딩이 중국어로 되어 있다는 것을 지정해주어야 파일의 내용이 깨지지 않는다.

            InputStreamReader(InputStream in, String encoding)를 이용해서,
            인코딩을 지정해주지 않는다면 OS에서 사용하는 인코딩을 사용해서 파일을 해석해서 보여주기 때문에,
            원래 작성된 대로 볼 수 없다.

            마찬가지로 OutputStreamWriter(OutputStream out, String encoding)를 이용하지 않으면, 
            OS에서 사용하는 인코딩으로 데이터를 저장할 것이다.

            참고            시스템 속성에서 sun.jnu.encoding의 값을 보면 OS에서 사용하는 인코딩의 종류를 알 수 있다.
                            Properties pop = System.getProperties();
                            System.out.println(prop.get("sun.jnu.encoding"));

    728x90
    반응형

    'Java' 카테고리의 다른 글

    Java 네트워킹  (1) 2024.02.27
    Java 입출력 - 2, 표준 입출력과 파일  (1) 2024.02.27
    Java 스트림(Stream)  (1) 2024.02.27
    Java 람다, 람다식  (3) 2024.02.26
    Java 쓰레드(Thread)  (1) 2024.02.26
Designed by Tistory.