[이것이자바다] chapter 18. IO 기반 입출력 및 네트워킹

chapter18

IO 기반 입출력 및 네트워킹

IO 패키지 소개

프로그램에서는 데이터(data)를 외부에서 읽고 다시 외부로 출력하는 작업이 빈번히 일어난다. 데이터(data)는 사용자로부터 키보드를 통해 입력(in)될 수도 있고, 파일 또는 네트워크로부터 입력(in)될 수도 있다. 데이터(data)는 반대로 모니터로 출력(out)될 수도 있고, 파일로 출력(out)되어 저장될 수도 있으며 네트워크로 출력(out)되어 전송될 수도 있다.
자바에서 데이터는 스트림(stream)을 통해 입출력되므로 스트림의 특징을 잘 이해해야 한다. 스트림은 단일 방향으로(입력이면 입력, 출력이면 출력) 연속적으로 흘러가는 것을 말한다. 물이 높은 곳에서 낮은 곳으로 흐르듯이 데이터(data)는 출발지에서 나와 도착지로(입력이면 입력, 출력이면 출력) 들어간다는 개념이다.

입력 스트림과 출력 스트림

=> 프로그램이 네트워크상의 다른 프로그램과 데이터 교환을 하기 위해서는 양쪽 모두 입력 스트림과 출력스트림이 따로 필요하다. 스트림의 특성이 단방향이므로 하나의 스트림으로 입력과 출력을 모두 할 수 없기 때문이다.

구분 바이트 기반 스트림   문자 기반 스트림  
  입력 스트림 출력 스트림 입력 스트림 출력 스트림
최상위 클래스 InputStream OutputStream Reader Writer
하위 클래스 XXXInputStream XXXOutputStream XXXReader XXXWriter
(예) (FileInputStream) (FileOutputStream) (FileReader) (FileWriter)

=> 스트림 클래스는 크게 바이트(byte) 기반 스트림이고,문자(character) 기반 스트림 이렇게 2가지로 분류된다. 바이트 기반 스트림은 그림,멀티미디어, 문자 등 모든 종류의 데이터를 받고 보낼 수 있으나, 문자 기반 스트림은 오로지 문자만 받고 보낼 수 있도록 특화되어 있다.
=> InputStream은 바이트 기반 입력 스트림의 최상위 클래스이고, OutputStream은 바이트 기반 출력 스트림의 최상위 클래스이다.이 클래스들을 각각 상속받는 하위 클래스는 접미사로 InputStream 또는 OutputStream이 붙는다.
=> Reader는 문자 기반 입력 스트림의 최상위 클래스이고, Writer는 문자 기반 출력 스트림의 최상위 클래스이다. 이 클래스들을 각각 상속받는 하위 클래스는 접미사로 Reader 또는 Writer가 붙는다.
=> 예를 들어, 파일을 바이트 단위로 읽어들일 때에는 FileInputStream을 사용하고, 바이트 단위로 저장할 때에는 FileOuputStream을 사용한다. 텍스트 파일의 경우, 문자 단위로 읽어들일 때에는 FileReader를 사용하고, 문자 단위로 저장할 때에는 FileWriter을 사용한다.

InputStream

InputStream은 바이트 기반 입력 스트림의 최상위 클래스로 추상클래스이고, 모든 바이트 기반 입력 스트림은 이 클래스를 상속받아서 만들어진다.FileInputStream, BufferedInputStream, DataInputStream 클래스는 모두 InputStream 클래스를 상속하고 있다.

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

read()

public class ReadExample {
    public static void main(String[] args) throws IOException {
            InputStream is = new FileInputStream("/home/kimdo/hello.txt");
            int readByte;
            int i=0;
            while((readByte = is.read()) != -1){
                System.out.printf("%c", readByte);
            }
    }
}
//실행내용
//hello, my friends~

=> read() 메소드는 파일을 1 바이트씩 읽고 4바이트 int타입으로 리턴한다. 따라서 리턴된 4바이트 중 끝의 1바이트에만 데이터가 들어있다. 리턴한 값은 읽은 데이터의 크기이다. 예를 들어 문자를 읽었으면 문자의 아스키코드값이 리턴된다(즉 위와 같이 서식문자 %c이면 문자 출력).
=> 더 이상 입력 스트림으로부터 바이트를 읽을 수 없다면 read() 메소드는 -1을 리턴하는데 이를 이용하면 읽을 수 있는 마지막 바이트까지 루프를 돌며 한 바이트씩 읽을 수 있다.

InputStream is = new FileInputStream("/home/kimdo/hello.txt");
int readByte;
while((readByte = is.read()) != -1){
}

read(byte[] b)

public class ReadExample2 {
    public static void main(String[] args) throws IOException {
        InputStream is = new FileInputStream("/home/kimdo/car_data.csv");
        int readByteNo;
        byte[] readBytes = new byte[1000];
        while((readByteNo=is.read(readBytes))!=-1){

            for(int i=0; i<readByteNo; i++)
                System.out.printf("%c",readBytes[i]);
            System.out.println();
        }
    }
}
// readByteNo는 읽은 바이트의 수를 나타낸다. 문자를 입력받을 때 한 문자당 1바이트를 나타내므로 readByteNo가 1000이면, 1000개의 문자
// 즉 1000개의 바이트를 읽어낸 것이다. 또 read(byte[] b)는 매개값으로 준 배열의 길이보다 적은 바이트를 읽었으면 읽은 만큼만 리턴한다. 
// 예를 들어 매개값의 배열의 길이가 5이지만 읽은 바이트가 3이면 3을 출력한다. 그리고 매개값의 buf에 데이터가 입력되므로 데이터를 사용하는 것은 매개값의 buf로 데이터를 받고 이용하면 된다.
// 또, read(byte[] b) 역시 입력 스트림으로부터 더이상 읽을 데이터가 없다면 -1을 출력한다. 

InputStream is = new FileInputStream("/home/kimdo/car_data.csv");
int readByteNo;
byte[] readBytes = new byte[1000];
while((readByteNo = is.read(readBytes)) != -1){
}

read(byte[] b, int off, int len)

InputStream is = ...;
byte[] readBytes = new byte[100];
int readByteNo = is.read(readBytes, 0 ,100);
public class ReadExample3 {
    public static void main(String[] args) throws IOException {
        InputStream is = new FileInputStream("/home/kimdo/hello.txt");
        int readByteNo;
        byte[] readBytes = new byte[10];
        while((readByteNo = is.read(readBytes,0,5))!=-1){
            for(int i=0; i<readByteNo; i++) {
                System.out.printf("%c",readBytes[i]);
            }
            System.out.println();
        }
    }
}

close() 메소드

OutputStream

OutputStream은 바이트 기반 출력 스트림의 최상위 클래스로 추상 클래스이고, 모든 바이트 기반 출력 스트림은 이 클래스를 상속 받아서 만들어진다. FileOutputStream, BufferedOutputStream, DataOutputStream 클래스는 모두 OutputStream 클래스를 상속하고 있다.

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

write(int b)

public class WriteExample1 {

    public static void main(String[] args) throws IOException {
        OutputStream os = new FileOutputStream("/home/kimdo/test.txt");
        
        byte[] data = "ABC".getBytes();
        for (byte datum : data)
            os.write(datum);
            //write은 새로 써진다. 원래 있던 내용은 다 사라진다. 
    }
}
//test.txt의 내용 
// ABC

write(byte[] b)

public class WriteExample2 {
    public static void main(String[] args) throws IOException {
        OutputStream os = new FileOutputStream("/home/kimdo/test.txt");
        byte[] data = "ABCD".getBytes();
        os.write(data);
    }
}
//test.txt의 내용
// ABCD

write(byte[] b ,int off, int len)

public class WriteExample3 {
    public static void main(String[] args) throws IOException {
        OutputStream os = new FileOutputStream("/home/kimdo/test.txt");
        byte[] data = "ABC".getBytes();
        os.write(data,1,2);
        // index(off)가  1이므로 BC만 보내진다.
    }
}
//test.txt의 내용
// BC

flush()와 close()

OutputStream os = new FileOutputStream("/home/kimdo/test.txt");
byte[] data = "ABC".getBytes();
os.write(data);
os.flush(); // flush()가 언제 효과적인지 알아보자.
os.close();

리눅스 c에서의 버퍼링 정책

//   1. 풀 버퍼링 - 버퍼가 가득차면, 비운다.
//     => 일반 파일
//   2. 라인 버퍼링 - 엔터('\n')가 나오거나, 버퍼가 가득차면 비운다.
//     => stdout, stdin
//   3. 노 버퍼링  - 버퍼링을 하지 않는다.
//     => stderr

//   버퍼를 비우는 표준 라이브러리 함수
//    : fflush, fclose
//    -> 프로세스가 종료하면, 프로세스가 연 파일을 스스로 정리한다.

=> 따라서 풀 버퍼링의 경우엔 버퍼가 가득 차기 전에는 내용이 파일로 보내지지 않고 가득차야 보내진다. 또 printf와 같은 stdout(표준 출력 스트림)의 함수도 개행을 만나기 전이나 버퍼가 가득 차기 전에는 내용이 출력되지 않는다. 프로그램이 안전하게 종료되야지만 버퍼가 가득차지 않아도 파일로 데이터가 보내지는 것이다.(라인 버퍼링의 경우 개행을 만나지 않거나 버퍼가 차지 않아도 프로그램이 안전하게 종료되면 그때 모니터에 출력 된다. => 프로그램이 종료되지 않아도 출력이 되는데 이건 뭐지?? 물어봐야겠다. fflush()를 자체적으로 제공해주나??) 따라서 이러한 경우 프로그램이 비정상적으로 종료될때 아직 데이터가 버퍼에 남아있게 되고 결국 모니터나 파일로 출력이 되지않는다. 따라서 이러한 경우를 방지하기 위해 비정상 종료시에 fflush() 함수를 이용해 버퍼에 남아있는 데이터들을 파일로 전송해야 한다.(이것도 맞는지 물어보자)

물어볼 것

  1. C에서 printf가 개행을 만나지 않거나 버퍼가 채워지지않아도 모니터에 출력이 되던데 왜 그런가요?
  2. C에서 비정상 종료시 버퍼에 있는 데이터를 파일에 전송시키기 위해 if문을 이용해 fflush() 함수를 사용하나요??
  3. 자바에서의 버퍼링 정책은 무엇이고 자바의 flush() 메소드를 언제 사용해야 할까요?

Reader

메소드   설명
int read() 입력 스트림으로부터 한 개의 문자를 읽고 리턴한다.
int read(char[] cbuf) 입력 스트림으로부터 읽은 문자들을 매개값으로 주어진 문자 배열 cbuf에 저장하고 실제로 읽은 문자 수를 리턴한다.
int read(char[] cbuf, int off, int len) 입력 스트림으로부터 len개의 문자를 읽고 매개값으로 주어진 문자 배열 cbuf[off]부터 len개까지 저장한다. (즉 off =1 이라면 cbuf[0]엔 아무것도 저장되지 않는다.) 그리고 실제로 읽은 문자 수인 len개를 리턴한다.
void close() 사용한 시스템 자원을 반납하고 입력 스트림을 닫는다.

read()

=> read() 메소드는 입력 스트림으로부터 한 개의 문자(2바이트)를 읽고 4바이트 int 타입으로 리턴한다. 따라서 리턴된 4바이트 중 끝에 있는 2바이트에 문자가 들어있다. 예를 들어 입력 스트림에서 2개의 문자(총 4바이트)가 들어온다면 다음과 같이 read()메소드로 한 문자씩 두 번 읽을 수 있다.

char charData = (char)read(); //리턴 타입이 int 이므로 char타입으로 형변환하자.
//charData는 한 문자 씩 읽는다. read()자체가 한 문자씩 리턴하므로.
public class ReadEx1 {

    public static void main(String[] args) throws IOException {
        Reader reader = new FileReader("/home/kimdo/hello.txt");
        int readData;
        while((readData=reader.read())!=-1){
            char charData = (char)readData;
            System.out.print(charData);
        }
        System.out.println();
    }
}

read(char[] cbuf) ```java public class ReadEx1 {

public static void main(String[] args) throws IOException {
    Reader reader = new FileReader("/home/kimdo/hello.txt");
    int readDataNo;
    char[] buf = new char[3];

    while((readDataNo=reader.read(buf))!=-1){
       for(int i=0; i<readDataNo; i++)
           System.out.print(buf[i]);
        System.out.print(" ");
    }
} } ``` > read(char[] cbuf, int off, int len) ```java public class ReadEx1 {

public static void main(String[] args) throws IOException {
    Reader reader = new FileReader("/home/kimdo/hello.txt");
    int readDataNo;
    char[] buf = new char[3];

    while((readDataNo=reader.read(buf,0,3))!=-1){
       for(int i=0; i<readDataNo; i++)
           System.out.print(buf[i]);
        System.out.print(" ");
    }
} } ``` > close() ```java reader.close(); ```

Writer

메소드   설명
void write(int c) 출력 스트림으로 한 개의 문자를 보낸다.(c의 끝 2바이트)
void write(char[] cbuf) 출력 스트림으로 주어진 문자 배열 cbuf의 모든 문자를 보낸다.
void write(char[] cbuf, int off, int len) 출력 스트림으로 주어진 문자 배열 cbuf[off]부터 len개까지의 문자를 보낸다.
void write(String str) 출력 스트림으로 주어진 문자열을 모두 보낸다.
void write(String str, int off, int len) 출력 스트림으로 주어진 문자열 off순번부터 len개까지의 문자를 보낸다.
void flush() 버퍼에 잔류하는 모든 문자열을 출력한다.
void clear() 사용한 시스템 자원을 반납하고 출력 스트림을 닫는다.

write( int c)

  • write(int c) 메소드는 매개 변수로 주어진 int값에서 끝에 있는 2바이트(한 개의 문자)만 출력 스트림으로 보낸다. 매개변수가 int 타입이므로 4바이트 모두를 보내는 것으로 오해할 수 있는데 오해하지말자!
public class WriteEx1 {
    public static void main(String[] args) throws IOException {
        Writer writer = new FileWriter("/home/kimdo/hello.txt");
        char[] data = "김도형".toCharArray();
        for (char datum : data) {
            writer.write(datum);
        }
        writer.flush();
        //flush() 를 다 써야 하는 구나.
    }
}

write( char[] cbuf)

public class WriteEx1 {
    public static void main(String[] args) throws IOException {
        Writer writer = new FileWriter("/home/kimdo/hello.txt");
        char[] data = "김선무".toCharArray();
        writer.write(data);
        writer.flush();
        //flush() 를 다 써야 하는 구나.
        writer.close();
    }
}

write( char[] cbuf, int off, int len)

public class WriteEx1 {
    public static void main(String[] args) throws IOException {
        Writer writer = new FileWriter("/home/kimdo/hello.txt");
        char[] data = "김선무".toCharArray();
        writer.write(data,1,2); // 선무만 보내진다.
        writer.flush();
        //flush() 를 다 써야 하는 구나.
        writer.close();
    }
}

write( String str) 과 write(String str, int off, int len)

매개변수(String str)

public class WriteEx1 {
    public static void main(String[] args) throws IOException {
        Writer writer = new FileWriter("/home/kimdo/hello.txt");
        String data = "고병관";
        writer.write(data);
        writer.flush();
        //flush() 를 다 써야 하는 구나.
        writer.close();
    }
}

매개변수(String str, int off, int len)

public class WriteEx1 {
    public static void main(String[] args) throws IOException {
        Writer writer = new FileWriter("/home/kimdo/hello.txt");
        String data = "고병관";
        writer.write(data,1,2);//병관
        writer.flush();
        //flush() 를 다 써야 하는 구나.
        writer.close();
    }
}

flush(), close()