18. 데이터 입출력

18.1 입출력 스트림

데이터는 키보드를 통해 입력될 수도 있고 파일또는 프로그램으로부터 입력될 수도 있다.
반대로 데이터는 모니터출력, 파일저장, 다른프로그램으로 전송될 수 있다.
이것을 총칭해서 데이터 입출력이라고 한다.

자바는 입력스트림과 출력스트림을 통해 데이터를 입출력한다.
스트림은 단방향으로 데이터가 흐르는 것을 말한다.

프로그램 기준으로 데이터가 들어오면 입력스트림, 데이터가 나가면 출력스트림이 된다.
프로그램이 다른 프로그램과 데이터를 교환하려면 양쪽 모두 입력스트림과 출력스트림이 필요하다.

어떤 데이터를 입출력하느냐에 따라 스트림은 두 종류로 구분된다.
바이트 스트림: 그림, 멀티미디어, 문자 등 모든 종류의 데이터를 입출력할때 사용
문자 스트림 : 문자만 입출력할때 사용

자바는 데이터 입출력 관련 라이브러리를 java.io패키지에서 제공하고 있다.

구분 | 바이트스트림 | 문자스트림
| 입력스트림 | 출력스트림 | 입력스트림 | 출력스트림
최상위 | InputStream | OutputStream | Reader | Writer
하위 | XXXInputStream | XXXOutputStream | XXXReader | XXXWriter
(FileInputStream)

바이트 입출력 스트림의 최상위 클래스는 InputStream과 OutputStream이다.
이클래스를 상속받는 자식클래스는 접미사로 위 클래스가 붙는다.
예를들어 이미지와 같은 바이너리파일은 FileInputStream , FileOutputStream가 있다.

18.2 바이트 출력 스트림

OutputStream은 바이트 출력스트림의 최상위 클래스로 추상클래스이다.
모든 바이트 출력 스트림은 이 OutputStream클래스를 상속받아서 만들어진다.
FileOutputStream / PrintStream / BufferedOutputStream / DataOutputStream이 있다.

OutputStream클래스에는 모든 바이트 출력 스트림이 기본적으로 가져야할 메소드가 정의되어 있다.
write(int b) | 1byte출력
write(byte[] b) | 매개값으로 주어진 배열 b의 모든 바이트를 출력
write(byte[] b, int off, int len) | 매개값으로 주어진 배열b[off]부터 len개의 바이트를 출력
flush() | 출력버퍼에 잔류하는 모든 바이트를 출력
close() | 출력스트림을 닫고 사용메모리 해제

18.2.1 바이트 출력 스트림

public class WriteExample {
    public static void main(String[] args) {
        try {
            OutputStream os = new FileOutputStream("C:Temp/test1.db");

            byte a = 10;
            byte b = 20;
            byte c = 30;

            os.write(a);
            os.write(b);
            os.write(c);

            os.flush();
            os.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

OutputStream은 내부에 작은 버퍼를 가지고 있다.
write()메소드가 호출되면 버퍼에 바이트를 우선 저장하고 버퍼가 차면 순서대로 바이트를 출력한다.
flush()메소드는 내부 버퍼에 잔류하는 모든 바이트를 출력하고 버퍼를 비우는 역할을 한다.
내부버퍼를 사용하는 이유는 출력성능을 향상하기 위해서이다.
출력스트림를 더이상 사용하지 않을때에는 close()메소드를 호출해서 출력스트림이 사용햇던 메모리를 해제하는 것이 좋다.

18.2.2 바이트 배열 출력

일반적으로 1바이트를 출력하는 경우는 드물다. 보통 바이트 배열을 통째로 출력하는 경우가 많다.
write(byte[] b) 메소드는 매개값으로 주어진 배열의 모든 바이틀 출력한다.

public class WriteExample {
    public static void main(String[] args) {
        try {
            OutputStream os = new FileOutputStream("C:/Temp/test2.db");

            byte[] array = { 10, 20, 30 };

            os.write(array);
            os.flush();
            os.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

만약 배열의 일부분만 출력하고 싶다면 write(byte[] b, int off, int len)메소드를 사용하면된다.
이 메소드는 b[off]부터 len개의 바이트를 출력한다.

public class WriteExample {
    public static void main(String[] args) {
        try {
            OutputStream os = new FileOutputStream("C:/Temp/test3.db");

            byte[] array = { 10, 20, 30, 40, 50 };

            os.write(array, 1, 3);
            os.flush();
            os.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

18.3 바이트 입력 스트림

InputStream은 바이트 입력 스트림의 최상위클래스로 추상클래스이다.
모든 바이트 입력 스트림은 InputStream을 상속받아 만들어진다.
FileInputStream / BufferdInputStream / DataInputStream

read() | 1byte를 읽은 후 읽은 바이트를 리턴
read(byte[] b) | 읽은 바이트를 매개값으로 주어진 배열에 저장 후 읽은 바이트 수를 리턴
close() | 입력 스트림을 닫고 사용 메모리 해제

18.3.1 1바이트 읽기

read()메소드는 입력 스트림으로부터 1byte를 읽고 int(4byre)타입으로 리턴한다.
따라서 리턴된 4byte 중 끝 1byte에만 데이터가 들어있다.
예를들어 5개의 바이트가 들어오면 1바이트씩 5번 읽어야한다.
더이상 읽을 수 없다면 read()메소드는 -1을 리턴한다.

public class ReadExample {
    public static void main(String[] args) throws Exception {
        InputStream is = new FileInputStream("C:Temp/test1.db");

        while (true) {
            int data = is.read();
            if(data == -1) break;
            System.out.println(data);
        }

        is.close();
    }
}

18.3.2 바이트 배열로 읽기

read(byte[] b)메소드는 입력스트림으로부터 주어진 배열의 길이 만큼 바이트를 읽고 배열에 저장한다음 읽은 바이트수를 리턴한다.
예를들어 입력 스트림에 5개의 바이트가 들어오면 3인배열로 두번 읽을 수 있다.

더이상읽을 수 없다면 -1을 리턴한다.

많은 양의 바이트를 읽을때는 이 메소드를 사용하는 것이 좋다.
입력스트림으로부터 100개의 바이트가 들어오면 read()메소드는 100번 반복해서 읽어야 하지만
read(byte[] b)메소드는 한번 읽을때 배열 길이만큼 읽어서 읽는 횟수가 줄어든다.

public class ReadExample {
    public static void main(String[] args) throws Exception {
        InputStream is = new FileInputStream("C:/Temp/test2.db");

        byte[] data = new byte[100];

        while (true) {
            int num = is.read(data); //최대 100바이트르 읽고 읽은바이트는 data저장 읽은 수는 리턴
            if ( num == -1) {
                break;
            }
            for (int i = 0; i < num; i++) {
                System.out.println(data[i]);
            }
        }
    }
}

읽어오는 바이트가 3이고 만약 data배열이 2칸이라면
첫 배열 1 2 읽어옴 -> 1 2출력함 -> 3읽고 2는 아직 2가있음 -> 그런데 읽어온 숫자가 1이니 3만출력

파일 복사 예제는 읽고 -> 쓰고를 반복하는 것이다.

public class CopyeExmaple {
    public static void main(String[] args) throws Exception {
        String orginalFileName = "C:/Temp/test.jpg";
        String targetFileName = "C:/Temp/test2.jpg";

        InputStream is = new FileInputStream(orginalFileName);
        OutputStream os = new FileOutputStream(targetFileName);

        byte[] data = new byte[1024]; //1024바이트 == 1키로바이튼

        while (true) {
            int num = is.read(data);
            if(num ==  -1) {
                break;
            }
            //마지막에 만약 2개만들어오면 2까지만읽기
            os.write(data, 0 , num); 
        }
        os.flush();
        is.close();
        os.close();

        System.out.println("복사 완료");
    }
}

자바 9부터는 좀 더 편리하게 입력 스트림에서 출력스트림으로 복사하는 transferTo()메소드가 InputStream에 추가디었다.

public class CopyExmaple {
    public static void main(String[] args) throws Exception {
        String orginalFileName = "C:/Temp/test.jpg";
        String targetFileName = "C:/Temp/test2.jpg";

        InputStream is = new FileInputStream(orginalFileName);
        OutputStream os = new FileOutputStream(targetFileName);

        is.transferTo(os);

        is.close();
        os.close();

        System.out.println("복사 완료");
    }
}

18.4 문자 입출력 스트림

바이트 입출력 스트림인 InputStream과 OutputStream에 대응하는 문자입출력 스트림으로
Reader와 Writer가 있다.
입출력 단위가 문자인것을 제외하고는 바이트 입출력 스트림과 사용방법은 동일하다.

18.4.1 문자출력

Writer는 문자 출력 스트림의 최상위 클래스로 추상클래스이다.
모든 문자 출력 스트림 클래스는 Writer클래스를 상속받아 만들어진다.
FileWriter BufferedWriter PrintWriter OutputStreamWriter

주요메소드는 다음과 같다.
write(int c) | 매개값으로 주어진 한 문자를 출력
write(char[] cbuf) | 매개값으로 주어진 배열의 모든문자를 출력
write(char[] cbuf, int off, int len) |매개값으로 주어진 배열에서 cubf[off]부터 len개까지의 문자를 출력
write(String str) |매개값으로 주어진 문자열을 출력
write(String str, int off, int len) | 매개값으로 주어진 문자열에서 off순번부터 len개까지의 문자를 출력
flush() | 버퍼에 잔류하는 모든 문자를 출력
close() | 출력스트림을 닫고 사용메모리를 해제

Write는 OutputStream과 사용방법이 동일하지만 출력 단위가 문자(char)이다.
그리고 문자열을 출력하는 write(String str)메소드를 추가로 제공한다.

public class WriteExample {
    public static void main(String[] args) throws IOException {
        // 문자 기반 출력 스트림 생성
        Writer writer = new FileWriter("C:/Temp/test.txt");

        // 1문자씩 출력
        char a = 'A';
        writer.write(a);
        char b = 'B';
        writer.write(b);

        // char배열 출력
        char[] arr = { 'C', 'D', 'E' };
        writer.write(arr);

        // 문자열 출력
        writer.write("FGH");

        // 버퍼에 잔류하고 있는 문자들을 출력하고 버퍼를 비움
        writer.flush();

        // 출력스트림 닫기
        writer.close();
    }
}

18.4.2 문자 읽기

Reader는 문자 입력 스트림의 최상위 클래스로 추상클래스이다.
모든 문자 입력 스트림 클래스는 Reader클래스를 상속받아 만들었다.
FileReader BufferedReader InputStreamReader

read() | 1개문자를 읽고리턴
read(char[] cbut) | 읽은 문자들을 매개값으로 주어진 문자배열에 저장하고 읽은 문자수를 리턴
close() | 입력스트림닫고 사용메모리 헤제

public class ReadExample {
    public static void main(String[] args) throws Exception {
        // 입력스트림 생성
        Reader reader = null;

        // 1문자씩 읽기
        reader = new FileReader("C:/Temp/test.txt");
        while (true) {
            int data = reader.read();
            if (data == -1) {
                break;
            }
            System.out.print((char) data);
        }
        reader.close();
        System.out.println();

        // 문자배열로 읽기
        reader = new FileReader("C:/Temp/test.txt");
        char[] data = new char[100];
        while (true) {
            int num = reader.read(data);
            if (num == -1) {
                break;
            }
            for (int i = 0; i < num; i++) {
                System.out.print(data[i]);
            }
        }
        reader.close();
    }
}

스트림을 할때마다 흐름을 열어야함.

18.5 보조스트림

보조스트림이란 다른 스트림과 연결되어 여러가지 편리한 기능을 제공해주는 스트림을 말한다.
보조스트림은 자체적으로 입출력을 수행할 수 없기때문에 입출력 소스로부터 직접 생성된 입출력 스트림에 연결해서 사용해야한다.

입력스트림 -> 보조스트림 -> 프로그램 -> 보조스트림 -> 출력스트림

입출력스트림에 보조스트림을 연결하려면 보조스트림을 생성할때 생성자 매개값으로 입출력 스트림을 제공하면된다.
보조스트림변수 = new 보조스트림(입출력 스트림);
보조스트림은 또 다른 보조스트림과 연결되어 스트림 체인으로 구성할 수도 있다.

자주 사용되는 보조스트림은 다음과 같다.
InputStreamReader | 바이트 스트림을 문자스트림으로 변환
BufferedInputStream/Reader BufferedOutputStream/Writer | 입출력 성능향상
DataInputStream, DataOutputStream | 기본타입 데이터 입출력
PrintStream, PrinterWriter | 줄 바꿈 처리 및 형식화 된 문자열 출력
ObjectInputStream, ObjectOutputStream | 객체 입출력

18.6 문자 변환스트림

바이트 스트림에서 입출력할 데이터가 문자라면 문자스트림 Reader와 Writer로 변환해서 사용하는 것이 좋다.
편리하고 문자셋의 종류를 지정할 수 잇기 때문이다.

18.6.1 InputStream을 Reader로 변환

InputStream is = new FileInputStream("test.txt");
Reader reader = new InputStreamReader(is);
InputStream을 Reader에 연결하면된다.

FileReader는 이 연결과정이 내부에 정의되어 있는 거라고 보면된다.

18.6.2 OutPutStream을 Writer로 변환
OutPutStream os = new FileOutPutStream("test.txt");
Writer writer = new OutputStreamWriter(os);

FileWriter역시 이 연결과정이 내장되어 있는 것이다.

->FileWirter/FileReader는 문자열 지정이 불가능하다. 다른 것들과 돌아가면서 사용해야하는 듯하다.

public class CharacterConvertStreamExample {
    public static void main(String[] args) throws Exception {
        write("문자 변환 스트림을 사용합니다.");
        String data = read();
        System.out.println(data);
    }

    public static void write(String str) throws Exception {
        OutputStream os = new FileOutputStream("C:/Temp/test.txt");
        Writer writer = new OutputStreamWriter(os, "UTF-8");
        writer.write(str);
        writer.flush();
        writer.close();
    }

    public static String read() throws Exception {
        InputStream is = new FileInputStream("C:/Temp/test.txt");
        Reader reader = new InputStreamReader(is, "UTF-8");
        char[] data = new char[100];
        int num = reader.read(data);
        reader.close();
        // char배열에서 읽은 문자 수만큼 문자열로 변환
        String str = new String(data, 0, num);
        return str;
    }
}

18.7 성능 향상 스트림

CPU와 메모리가 아무리 뛰어나도 하드디스크의 입출력이 늦어지면 프로그램의 실행성능은 하드디스크의 처리속도에 맞춰진다.
네트워크 역시 마찬가지이다.
이 문제에 대한 완전한 해결책은 될수 없지만 프로그램이 입출력 소스와 직접 작업하지 않고 중간에 메몸리 버퍼와 작업함으로서 실행성능을 향상시킬 수 있다.
출력 스트림의 경우 직접 하드디스크에 데이터를 보내지않고 메모리 버퍼에 데이터를 보냄으로써 출력 속도를 향상시킬수 있다.
버퍼는 데이터가 쌓이기를 기다렸다가 꽉차게 되면 데이터를 한꺼번에 하드디스크로 보냄으로써 출력횟수를 줄여준다.
입력스트림에서도 마찬가지이다.

public class BufferedExample {
    public static void main(String[] args) throws Exception {
        // 입출력 스트림생성
        String originalFilePath1 = BufferedExample.class.getResource("originalFile1.jpg").getPath();
        String targetFilePath1 = "C:/Temp/targetFile1.jpg";

        FileInputStream fis = new FileInputStream(originalFilePath1);
        FileOutputStream fos = new FileOutputStream(targetFilePath1);

        // 입출력 스트림 + 버퍼 스트림생성
        String originalFilePath2 = BufferedExample.class.getResource("originalFile2.jpg").getPath();
        String targetFilePath2 = "C:/Temp/targetFile2.jpg";

        FileInputStream fis2 = new FileInputStream(originalFilePath2);
        FileOutputStream fos2 = new FileOutputStream(targetFilePath2);
        BufferedInputStream bis = new BufferedInputStream(fis2);
        BufferedOutputStream bos = new BufferedOutputStream(fos2);

        // 버퍼를 사용하지 않고복사
        long nunBufferTime = copy(fis, fos);
        System.out.println("버퍼 미사용: " + nunBufferTime + " ns");
        //버퍼 미사용: 7560354800 ns

        // 버퍼사용
        long BufferedTime = copy(bis, bos);
        System.out.println("버퍼 사용: " + BufferedTime + " ns");
        //버퍼 사용: 94382700 ns

        fis.close();
        fos.close();
        bis.close();
        bos.close();
    }

    public static long copy(InputStream is, OutputStream os) throws Exception {
        // 시작시간저장
        long start = System.nanoTime();
        // 1바이트 읽고 1바이트 출력
        while (true) {
            int num = is.read();
            if (num == -1) {
                break;
            }
            os.write(num);
        }
        os.flush();
        long end = System.nanoTime();
        return end - start;
    }
}

시간의 차이를 보기 위해 일부러 한문자씩 읽었는데
공부를 위해 배열로 읽는 방법도 작성해보았다.

byte[] data = new byte[1024];
while (true) {
    int num = is.read(data);
    if (num == -1) {
        break;
    }
    os.write(data, 0, num);
}
os.flush();

18.7.2 성능 향상 스트림 -2

문자입력스트림 Reader에 BufferedReader를 연결하면 성능향상뿐만아니라 좋은 점이 한가지 더있느데
행 단위를 문자열로 읽는 매우 편리한 readLine()메소드를 제공한다.

public class ReadLineExample {
    public static void main(String[] args) throws Exception {
        Reader reader = new FileReader("src/Ch18/sec07/exam02/ReadLineExample.java");
        BufferedReader br = new BufferedReader(reader);    

        int lineNo = 1;
        while (true) {
            String str = br.readLine();
            if (str == null) {
                break;
            }
            System.out.println(lineNo + "\t" + str);
            lineNo++;
        }
        br.close();
    }
}

18.8 기본타입 스트림

바이트 스트림에 DataInputStream과 DataOutputStream 보조스트림을 연결하면 기본타입을 입출력할 수 있다.
read기본타입(); write기본타입();
String은 readUTF(); writeUTF();의 메소드를 가지고 있다.
한가지 주의할점은 데이터 타입의 크기가 모두 다르므로
다시 읽어올때는 출력한 순서에 맞게 입력을 받아야한다.

public class DataInputOutputStreamExample {
    public static void main(String[] args) throws Exception {
        // Data출력 생성
        FileOutputStream fos = new FileOutputStream("C:/Temp/primitive.db");
        DataOutputStream dos = new DataOutputStream(fos);

        // 기본타입 출력
        dos.writeUTF("홍길동");
        dos.writeDouble(95.5);
        dos.writeInt(1);

        dos.writeUTF("김자바");
        dos.writeDouble(90.3);
        dos.writeInt(2);

        dos.flush();
        dos.close();
        fos.close();

        // Data입력스트림
        DataInputStream dis = new DataInputStream(new FileInputStream("C:/Temp/primitive.db"));

        // 기본타입입력
        for (int i = 0; i < 2; i++) {
            String name = dis.readUTF();
            double score = dis.readDouble();
            int order = dis.readInt();
            System.out.println(name + ": " + score + " :" + order);
        }
        dis.close();
    }
}

18.9 프린트 스트림

PrintStream과 PrintWriter는 프린터와 유사하게 출력하는 print() println() printf()메소드를 가지고 있는 스트림이다.
우리는 콘솔에 출력하기 위해 sysout을 했는데 그 이유는 out필드가 PrintStream타입이기 때문이다.

public class PrintStreamExample {
    public static void main(String[] args) throws Exception {
        FileOutputStream fos = new FileOutputStream("C:/Temp/printStream.txt");
        PrintStream ps = new PrintStream(fos);

        ps.print("마치 ");
        ps.println("프린터가 출력하는 것 처럼");
        ps.println("데이터를 출력합니다.");
        ps.printf("| %6d | %-10s | %10s | \n", 1 , "홍길동", "도적");
        ps.printf("| %6d | %-10s | %10s | \n", 2 , "김자바", "학생");

        ps.flush();
        ps.close();
    }
}

'기초단계 > JAVA' 카테고리의 다른 글

2023.03.09 Java 복습  (0) 2023.03.10
2023.03.07 Java 복습  (0) 2023.03.07
2023.02.28 -2 Java 복습  (0) 2023.02.28
2023.02.28 - 1 Java복습  (0) 2023.02.28
2023.02.27 -2 Java복습  (0) 2023.02.27

+ Recent posts