이것이 자바다 16일차 - Chapter18 데이터 입출력

Seo-Faper·2023년 1월 31일
0

이것이 자바다

목록 보기
17/20

입출력 스트림

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

자바는 입력 스트림과 출력 스트림을 통해 데이터를 입출력한다. 스트림은 단방향으로 데이터가 흐르는 것을 말하는데 키보드를 통해 입력한 데이터가 프로그램에 대입되고 다시 모니터를 통해 출력 되는 것을 구현하기 위해서 입력 스트림과 출력 스트림이 필요하다.

어떤 데이터를 입출력하느냐에 따라 스트림은 두 종류로 구분할 수 있다.

  • 바이트 스트림 : 그림, 멀티미디어, 문자 등 모든 종류의 데이터를 입출력 할 때 사용
  • 문자 스트림 : 문자만 입출력할 때 사용

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

바이트 출력 스트림

package ch18.sec02.exam01;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

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(IOException e){

        }

    }
}

이렇게 바이트를 하나씩 쓸 수도 있고

package ch18.sec02.exam02;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

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(IOException e){
            e.printStackTrace();
        }

    }
}

바이트 배열로 쓸 수도 있다.

package ch18.sec02.exam03;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

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(IOException e){
            e.printStackTrace();
        }

    }
}

범위 지정도 가능하다.

바이트 입력 스트림

package ch18.sec03.exam01;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

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

            while (true){
                int data = is.read();
                if(data == -1)break;
                System.out.println(data);
            }
            is.close();
        }catch(FileNotFoundException e){
            e.printStackTrace();
        }catch(IOException e) {
            e.printStackTrace();
        }
    }
}

이렇게 하면 일전에 만들어 둔 test1.db 파일을 읽어 10 20 30을 출력한다.

package ch18.sec03.exam03;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

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

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

        byte[] data = new byte[1024];
        while (true){
            int num = is.read(data);
            if (num == -1) break;

            os.write(data,0,num);

        }
        os.flush(); // 내부 버퍼 잔류 바이트를 출력하고 버퍼를 비움
        os.close();
        is.close();
        System.out.println("복사가 완료되었습니다.");
    }
}

기존의 파일을 복사할 수도 있다.

문자 입출력 스트림

파일에 문자를 쓰는 방법은 다음과 같다.

package ch18.sec04.exam01;

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class WriteExample {
    public static void main(String[] args) {
        try{
            Writer writer = new FileWriter("C:/Temp/test.txt");

            char a = 'A';
            writer.write(a);
            char b = 'B';
            writer.write(b);

            char[] arr = {'C','D','E'};
            writer.write(arr);

            writer.write("FGH");

            writer.flush();
            writer.close();

        }catch(IOException e){

        }
    }
}

Writer에서 write()는 char 배열과 문자열을 같은 것으로 취급한다.
저러고 나서 C:/Temp/test.txt 가 생성되는데, 열어보면 ABCDEFGH 가 적혀져 있다.

파일을 읽어서 출력하는 방법은 다음과 같다.

package ch18.sec04.exam02;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

public class ReadExample {
    public static void main(String[] args) {
        try{
            
            // 방법 1 
            Reader reader = null;
            reader = new FileReader("C:/Temp/test.txt");
            while (true){
                int data = reader.read();
                if(data == -1) break;
                System.out.print((char)data);
            }
            System.out.println();

            
            //방법 2 
            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();
        }catch (FileNotFoundException e){
            e.printStackTrace();
        }catch(IOException e){
            e.printStackTrace();
        }
    }
}

직접 한 글자씩 읽을 수도 있고 byte 배열을 대입해서 읽을 수도 있다.

보조 스트림

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

보조스트림 변수 = new 보조스트림(입출력스트림);

이렇게 입출력 스트림에 보조 스트림을 연결하려면 보조 스트림을 생성할 때 생성자 매개값으로 입출력 스트림을 제공하면 된다.

예를 들어 바이트 입력 스트림인 FileInputStream에 InputStreamReader 보조 스트림을 연결하는 코드는 다음과 같다.

InputStream is = new FileInputStream("...");
InputStreamReader reader = new InputStreamReader(is);

보조 스트림은 또 다른 보조 스트림과 연결되어 스트림 체인으로 구성할 수 있다.
예를 들어 문자 변환 보조 스트림인 InputStreamReader에 BufferedReader 보조 스트림을 연결하는 코드는 다음과 같다.

InputStream is = new FileInputStream("...");
InputStreamReader reader = new InputStreamReaer(is);
BufferedReader br = new BufferedReader(reader);

문자 변환 스트림

package ch18.sec06;

import java.io.*;

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();
        String str = new String(data,0,num);
        return str;
    }
}

이건 test.txt에 "문자 변환 스트림을 사용합니다."를 쓰고 그 다음 다시 test.txt를 읽어서 해당 문자열을 출력하는 예제이다. UTF-8 문자셋으로 파일에 문자를 저장하고 저장된 문자를 다시 읽는 것이다.

성능 향상 스트림

CPU와 메모리가 아무리 뛰어나도 하드 디스크의 입출력이 늦어지면 프로그램의 실행 성능은 하드디스크의 처리 속도에 맞춰진다. 향상된 버퍼 스트림을 사용하면 된다.

package ch18.sec07.exam01;

import java.io.*;

public class BufferExample {
    public static void main(String[] args) throws Exception {
        String originalFilePath1 =
                BufferExample.class.getResource("originalFile1.jpg").getPath();
        String targetFilePath1 = "C:/Temp/targetFile1.jpg";
        FileInputStream fis = new FileInputStream(originalFilePath1);
        FileOutputStream fos = new FileOutputStream(targetFilePath1);

        String originalFilePath2 =
                BufferExample.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 nonBufferTime = copy(fis,fos);
        System.out.println("버퍼 미사용: \t"+nonBufferTime+"ns");

        long bufferTime = copy(bis, bos);
        System.out.println("버퍼 사용: \t"+bufferTime+"ns");
        fis.close();
        fos.close();
        bis.close();
        bos.close();
    }
    public static long copy(InputStream is, OutputStream os) throws Exception{
        long start = System.currentTimeMillis();
        while (true){
            int data = is.read();
            if(data == -1 ) break;
            os.write(data);
        }
        os.flush();
        long end = System.currentTimeMillis();
        return (end- start);
    }
}

이렇게 파일을 2개 준비하고 실행해 보면 버퍼를 쓰는게 훨씬 빠른걸 볼 수 있다.

기본 타입 스트림

바이트 스트림에 DataInputStream과 DataOutputStream 보조 스트림을 연결하면 기본 타입인 boolean, char,short, int, long, float, double 값을 입출력 할 수 있다.

package ch18.sec08;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class DataInputOutputStreamExample {
    public static void main(String[] args) throws Exception {
        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(85.5);
        dos.writeInt(2);

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

        FileInputStream fis = new FileInputStream("C:/Temp/primitive.db");
        DataInputStream dis = new DataInputStream(fis);

        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();fis.close();
    }
}

굳이 쓰진 않을거 같다.

프린트 스트림

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

package ch18.sec09;

import java.io.FileOutputStream;
import java.io.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,"김자바","학생");
    }
}

그냥 System.out.println()을 쓰자.

객체 스트림

자바는 메모리에 생성된 객체를 파일 또는 네트워크로 출력할 수 있다.
객체를 출력하려면 필드값을 일렬로 늘어선 바이트로 변경해야 하는데, 이를 직렬화(serialization)이라고 한다.
반대로 직렬화된 바이트를 객체의 필드값으로 복원하는 것을 역직렬화(deserialization)라고 한다.

ObjectInputStream과 ObjectOutputStream을 통해 구현할 수 있다.

package ch18.sec10;

import java.io.Serializable;

public class Member implements Serializable {
    private static final long serialVersionUID = -622284561026719240L;
    private String id;
    private String name;

    public Member(String id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Member{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}
package ch18.sec10;

import java.io.Serializable;

public class Product implements Serializable {
    private static final long serialVersionUID = -621812868470078544L;
    private String name;
    private int price;

    public Product(String name, int price) {
        this.name = name;
        this.price = price;
    }

    @Override
    public String toString() {
        return "Product{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}

이렇게 객체를 만들고 Serializable을 구현하는 구현체를 만들면 된다.

package ch18.sec10;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Arrays;

public class ObjectInputOutputStreamExample {
    public static void main(String[] args) throws Exception{
        FileOutputStream fos = new FileOutputStream("C:/Temp/object.dat");
        ObjectOutputStream oos = new ObjectOutputStream(fos);

        Member m1 = new Member("fall","단풍이");
        Product p1 = new Product("노트북",150000);
        int[] arr1 = {1,2,3};

        oos.writeObject(m1);
        oos.writeObject(p1);
        oos.writeObject(arr1);
        oos.flush();oos.flush();oos.flush();

        //FileInputStream에 ObjectInputStream 보조 스트림 연결
        FileInputStream fis = new FileInputStream("C:/Temp/object.dat");
        ObjectInputStream ois = new ObjectInputStream(fis);

        //파일을 읽고 역직렬화
        Member m2 = (Member) ois.readObject();
        Product p2 = (Product) ois.readObject();
        int[] arr2 = (int[]) ois.readObject();

        ois.close(); fis.close();

        System.out.println(m2);
        System.out.println(p2);
        System.out.println(Arrays.toString(arr2));
    }
}

연습문제

1번

1번

3번

1번

3번

3번

new FileReader(filePath);
new BufferedReader(fr);

rowData = br.readLine();
if(rowData == null){
	break;
}
System.out.println(++rowNumber + ": "+rowData);

4번

2번

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Scanner;
public class Example {
public static void main(String[] args) {
	 try {
 		Scanner scanner = new Scanner(System.in);
 

		 System.out.print("원본 파일 경로: ");
      	 String originalFilePath = scanner.nextLine();
 
		 System.out.print("복사 파일 경로: ");
		 String targetFilePath = scanner.nextLine();

		 File originalFile = new File(originalFilePath);
		 if(!originalFile.exists()) {
		 System.out.println("원본 파일이 존재하지 않습니다.");
		 System.exit(0);
		 }
 
		 File targetFile = new File(targetFilePath);
 		File parentFile = targetFile.getParentFile();
		 if(!parentFile.exists()) {
		 parentFile.mkdirs();
		 }
 
		 BufferedInputStream bis = new BufferedInputStream(
		 new FileInputStream(originalFilePath));
		 BufferedOutputStream bos = new BufferedOutputStream(
		 new FileOutputStream(targetFilePath));
 
		 byte[] data = new byte[1024];
		 int num = -1;
		 while(true) {
		 num = bis.read(data);
		 if(num = = -1) break;
		 bos.write(data, 0, num);
		 }
         
		 System.out.println("복사가 성공되었습니다.");
 
	 	 
 		bis.close();
		 bos.close();
		 } catch(Exception e) {
 			e.printStackTrace();
		 }
	}
}
profile
gotta go fast

0개의 댓글