I/O

컴퓨터의 5대 기능인 입력/출력/연산/저장/제어 중 입력(Input)과 출력(Ouput)을 줄여 I/O라고 말한다.



스트림 / 버퍼 / 채널 기반의 I/O

◾ 스트림

입출력을 도와주는 모듈로써 Stream이라는 단어 그대로 흐름을 의미하며, 한 방향으로만 진행하는 단방향통신이다.

◾ 버퍼

일종의 데이터 공간으로 메모리간, 컴퓨터와 사용자간의 속도차이로 생기는 병목현상을 줄이기 위한 공간

데이터를 쌓아두고 한번에 찾을 수 있는 데이터 공간으로 이를 이용하여 데이터를 입출력하는 방식으로 속도향상을 꾀할 수 있다.

◾ 채널

스트림과 다르게 양방향 통신이 가능한 방법으로 non-blocking/비동기 방식 모두 지원한다.

버퍼를 통해 데이터를 읽고 쓴다.

Selector

socket과 채널이 연결되어 클라이언트 연결시 스레드를 생성하는 대신 하나의 selector에 여러개의 채널을 생성하여 다수의 클라이언트를 대응할 수 있다.



InputStream과 OutputStream

Stream기반의 클래스의 최상위 클래스(추상 클래스) 이다.

바이트 스트림들이 갖는 메서드가 다음과 같이 존재한다.

◾ InputStream

메서드

메서드 기능
read() 입력 스트림으로부터 1바이트를 읽고 읽은 바이트를 리턴한다.
read(byte[] b) 입력 스트림으로부터 읽은 바이트들을 매개값으로 주어진 바이트 배열b에 저장하고 실제로 읽은 바이트 수를 리턴한다.
read(byte[] b, int off, int len) 입력 스트림으로부터 len개의 바이트만큼 읽고 매개값으로 주어진 바이트 배열 b[off]부터 len개까지 저장한다. 그리고 실제로 읽은 바이트 수인 len개를 리턴한다. 만약 len개를 모두 읽지 못하면 실제로 읽은 바이트 수를 리턴한다.
close() 사용한 시스템 자원을 반납하고 입력스트림을 닫는다.


◾ OutputStream

메서드

메서드 기능
write(int b) 출력 스트림으로 부터 1바이트를 보낸다.
write(byte[ ] b) 출력 스트림으로부터 주어진 바이트 배열 b의 모든 바이트를 보낸다.
write(byte[ ] b, int off, int len) 출력 스트림으로 주어진 바이트 배열 b[off]부터 len개까지의 바이트를 보낸다.
flush() 버퍼에 잔류하는 모든 바이트를 출력한다.
close() 사용한 시스템 자원을 반납하고 출력 스트림을 닫는다.



Byte와 Character 스트림

◾ Byte Stream

데이터를 Byte단위로 전송하며 바이트로 구성된 파일인 오디오,이미지,동영상 등을 읽고 쓰는데 적합한 스트림이다.

Input 종류

클래스 기능
AudioInputStream 오디오 포맷에 특화된 프레임 단위 스트림 입력
ByteArrayInputStream 바이트 배열을 바이트 스트림으로 변환 입력
BufferedInputStream 버퍼를 이용한 바이트 스트림 입력
FileInputStream 파일을 바이트 단위로 읽어들여 바이트 스트림 입력
FilterInputStream 버퍼와 같은 필터에 의한 바이트 스트림 입력
InputStream 바이트 스트림의 입력을 위한 추상 클래스
ObjectInputStream 자바 객체를 직렬화 시켜 읽어들여 스트림으로 변환
PipedInputStream 바이트 스트림을 읽어들여 연결된 PipedOutputStream으로 동시에 전달
SequenceInputStream 서로 다른 InpustStream을 순차적으로 입력하기 위한 클래스
StringBufferInputStream 문자열 스트림 입력을 위한 클래스, JDK 1.1 이후 StreamReader 클래스로 대체되었다.

Output 종류

클래스 기능
ByteArrayOutputStream 바이트 스트림을 바이트 배열로 출력
FileOutputStream 바이트 스트림을 바이트 파일로 출력
FilterOutputStream 버퍼와 같은 필터가 추가된 바이트 스트림 출력을 위한 추상 클래스
ObjectOutputStream 바이트 스트림을 직렬화된 객체 형식으로 출력
OutputStream 바이트 출력 스트림을 위한 추상 클래스
PipedOutputStream PipedInputStream의 입력 스트림을 출력
void byteArrayTest(){
        byte[] input = {0,1,2,3,4,5,6,7,8,9};
        byte[] output = null;

        byte[] temp = new byte[4];

        ByteArrayInputStream inputStream = new ByteArrayInputStream(input);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        int len;
        try {
            while ((len = inputStream.read(temp)) != -1){
                outputStream.write(temp,0,len); //read함수를 통해 읽은 데이터를 outputStream에 write
            }

            output = outputStream.toByteArray();  //OutputStream의 데이터를 output배열에 저장
            System.out.println("output : "+ Arrays.toString(output));

            inputStream.close();
            outputStream.close();
        }catch (IOException e){
            e.printStackTrace();
        }
    }

inputStream의 read함수가 더이상 데이터를 읽지 못하면 -1을 리턴하는 것을 이용해서 while문을 통해 inputStream을 끝까지 읽을 수 있으며, ByteArray~스트림을 이용하여 4바이트 단위로 데이터를 읽고 쓸 수도 있다.


◾ Char Stream

자바는 char형도 2바이트이기 때문에 바이트단위 스트림은 문자열(text)를 전송하는데 적합하지 않을 수 있기 때문에 문자 기반의 스트림을 제공한다.

클래스명도 뒤에 Stream이 아닌 Reader를 붙여 사용한다.

Input 종류 (Reader)

클래스 기능
BufferedReader 버퍼를 이용한 문자 스트림 입력
CharArrayReader 문자 배열의 입력
FileReader 파일을 문자 스트림으로 변환해 입력
FilterReader 버퍼와 같은 필터에 의한 문자 스트림 입력
InputStreamReader 바이트 스트림을 문자 스트림으로 변환
LineNumberReader 버퍼를 이용한 문자 스트림 입력, 라인번호 저장
PipedReader 문자 스트림을 읽어들여 연결된 PipedWriter로 동시에 전달
Reader 바이트 입력 스트림을 문자 스트림으로 변환하기 위한 추상 클래스
StringReader 문자열 데이터를 문자 스트림으로 입력

Output 종류 (Writer)

클래스 기능
BufferedWriter 문자 스트림을 버퍼를 이용해 문자열 단위로 출력
CharArrayWriter 문자 스트림을 문자 배열 단위로 출력
FilterWriter 버퍼와 같은 필터가 추가된 문자 스트림 출력을 위한 추상 클래스
OutputStreamWriter 문자 스트림을 바이트 스트림으로 변환 출력
PipedWriter PipedReader에서 전달받은 문자 스트림을 바로 출력
PrintWriter 형식이 있는 Writer 객체를 문자 스트림으로 출력
StringWriter 문자 스트림을 문자열 데이터로 출력



표준 스트림

표준 입출력 장치(콘솔)에 입출력을 위해 자바에서 제공하는 스트림이다.

java.lang패키지에 System 클래스를 통해 제공한다.

변수 입출력 기능
System.in Input 콘솔의 데이터를 입력받음
System.out Output 콘솔에 데이터를 출력
System.err Output 콘솔에 에러를 출력


◾ 키보드문자 입력 방법

Scanner 이용

Scanner scanner = new Scanner(System.in);

Scanner 클래스를 통해 표준입출력장치로 부터 데이터를 받아올 수 있으며 next(), nextLine(), nextByte(), nextDouble…과 같은 메서드를 통해 읽어 올 수 있다.


System.in.read() 이용

System클래스에서 제공하는 read함수를 이용하여 입력을 받을 수 있으며, 1byte씩만 데이터를 읽어 ascii코드에 해당하는 int형을 리턴하는 함수이다.


BufferedReader 이용

한문자씩 스트림에서 읽어오는 InputStreamReader를 버퍼를 사용해서 문자열 처리를 편하게 해주는 방법이 있다.

BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));

String str = bufferedReader.readLine();

bufferedReader.close();

Scanner처럼 데이터 타입에 맞게 파싱해주는 메소드를 따로 제공하지 않기 때문에, Intger.parseInt()와 같은 메서드를 이용해서 형변환을 해주어야 하고 Scanner보다는 빠른 입력처리가 가능하다.


◾ 데이터 출력 방법

모두가 잘 아는 System.out.println(), System.out.print() 등과 같이 System의 메서드를 이용하는 방법이 있는데, 이를 남발하면 시스템 성능 저하의 원인이 될 수 있다.

다른 방법으로는 BufferReader와 같은 방법으로 BufferedWriter를 이용한 방법이 있다.

BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(System.out));

bufferedWriter.write("Hello\n");

bufferedWriter.close();

println() 처럼 자동으로 줄바꿈을 해주는 메서드는 따로 존재하지 않기 때문에 개행문자 (\n)를 직접 입력해주어야 하고, IOException의 예외처리를 해주어야 한다.



파일 읽고 쓰기

◾ 바이트 기반 스트림 (FileStream)

FileInputStream, FileOutputStream을 이용한 방법

void FileOutputStreamTest(){
        FileOutputStream fileOutputStream = null;
        FileInputStream fileInputStream = null;
        try{
            fileOutputStream = new FileOutputStream("src/test/IoTest/outputFile");
            for(char ch='a'; ch <='z'; ch++){
                fileOutputStream.write(ch);
            }

            File file = new File("src/test/IoTest/outputFile");
            fileInputStream = new FileInputStream(file);

            int c;
            while((c = fileInputStream.read()) != -1){
                System.out.print((char) c);
            }

            fileInputStream.close();
            fileOutputStream.close();
        }catch (FileNotFoundException e){
            System.out.println("Not Found file");
        }catch (IOException e){
            System.out.println("IO Exception");
        }
    }


◾ 문자기반 스트림

FileReader와 FileWriter를 이용하여 사용하는 방법

    void fileWriterTest(){
        FileWriter fileWriter =null;
        FileReader fileReader = null;
        try{
            fileWriter = new FileWriter("src/test/IoTest/fileWriter.txt");

            for(int i=0; i < 10; i++){
                fileWriter.write(i);
            }
            fileWriter.close();

            fileReader = new FileReader("src/test/IoTest/fileWriter.txt");
            int c;
            while((c = fileReader.read()) != -1){
                System.out.print(c);
            }
            fileReader.close();

        }catch (IOException e){
            e.printStackTrace();
        }
    }


◾ 차이점

FileInputStream/FileOutputStreamInputStream/OutputStream을 상속받고 있고 FileReader/FileWriterReader/Writer를 상속받고 있다.

때문에, FileReader/Writer은 바이트를 문자로 변환하여 입출력을 처리하고 FileInput/OutputStream은 1바이트 이상인 한글등을 처리하기 위해서 버퍼를 사용해서 처리해야 한다.

파일 포인터의 시작 포인트가 다른데 byte방식은 outputStream을 close안해도 input에서 읽을 수 있고 char방식은 close를 안하면 reader로 읽을 수가 없다.

void FileOutputStreamTest(){
        FileOutputStream fileOutputStream = null;
        FileInputStream fileInputStream = null;
        try{
            fileOutputStream = new FileOutputStream("src/test/IoTest/outputFile.txt");
            for(int i=0; i< 10; i++){
                fileOutputStream.write(i);
            }

            fileInputStream = new FileInputStream("src/test/IoTest/outputFile.txt");
            int c;
            while((c = fileInputStream.read()) != -1){
                System.out.print(c);
            }

            fileInputStream.close();
            fileOutputStream.close();
        }catch (FileNotFoundException e){
            System.out.println("Not Found file");
        }catch (IOException e){
            System.out.println("IO Exception");
        }
    }

byte방식은 FileOutputStream으로 쓰고나서 close하지 않은 파일을 FileInputStream으로 처음부터 읽는게 가능하다.


void fileWriterTest(){
        FileWriter fileWriter =null;
        FileReader fileReader = null;
        try{
            fileWriter = new FileWriter("src/test/IoTest/fileWriter.txt");
            for(int i=0; i < 10; i++){
                fileWriter.write(i);
            }

            fileReader = new FileReader("src/test/IoTest/fileWriter.txt");
            int c;
            while((c = fileReader.read()) != -1){
                System.out.print(c);
            }
            fileWriter.close();
            fileReader.close();

        }catch (IOException e){
            e.printStackTrace();
        }
    }

char방식인 FileReader은 FileWriter로 작성후에 close하지 않은 파일을 읽으려고 하면 파일포인터가 끝을 가리키기 때문에 읽히지 않는다.



인코딩

byte단위로 입출력하는 FileInputStream/FileOutputStream에서 아스키코드가 아닌 그 외 문자들을 제대로 출력하기 위한 인코딩을 설정해주어야한다.

Reader의 클래스중 하나인 Input/OutputStreamReader를 이용할 수 있다.

◾ InputStreamReader / OutputStreamReader

파일의 인코딩 방식을 지정할 수 있고, 바이트입력 스트림에 연결되어 문자입력스트림인 Reader로 변환시킨다.

void encodingTest(){
        try {
            FileOutputStream fileOutputStream = new FileOutputStream("src/test/IoTest/encoding.txt");
            OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream,"utf-8");
            outputStreamWriter.write("안녕하세요");

            outputStreamWriter.close();
            fileOutputStream.close();

            FileInputStream fileInputStream = new FileInputStream("src/test/IoTest/encoding.txt");
            InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream,"utf-8");
            int c;
            while((c = inputStreamReader.read()) != -1){
                System.out.print((char) c);
            }

            inputStreamReader.close();
            fileInputStream.close();
        }catch (IOException e){
            e.printStackTrace();
        }
    }





Reference

https://velog.io/@godkimchichi/Java-7-IO-File

https://hyeonstorage.tistory.com/248