기초단계/JAVA

2022.12.07 JAVA 데이터 입출력

춘핑이 2022. 12. 7. 17:32

18. 데이터 입출력

18.6 문자변환 스트림

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

18.6.1 InputStream 을 Reader로 변환

InputStream을 Reader로 변환하려면 InputStreamReader을 보조스트림연결하면된다.


InputStream is = new FileInputStream("파일")
Reader reader = new InputStreamReader(is);
바이트를 문자스트림으로 변환시켜줌.
FileReader의 원리
FileInputStream 에 InputStreamReader를 연결하지 않고 FileReader를 직접생성할수있다.
FileReader는 InputStreamReader의 자식클래스로 이것은 FileReader가 FileInputStream에 InputStreamReader보조스트림연결한것이라고 볼수있다.

18.6.2 OutStream을 Writer로 변환

OutputStream을 writer로 변환하려면 OutStreamWriter을 보조스트림연결하면된다.
바이트배열을 얻어서 getbyte를 얻어서 사용해도되는데 당장 출력하고자하면 보조스트림을 달면된다.
OutputStream os = new FileOutputStream("파일")
Writer writer = new OutputStreamWriter(is);

package ch18.sec06;

import java.io.*;

public class CharacterConvertStreamExample2 {
public static void main(String[] args) {

    try {
        OutputStream os = new FileOutputStream("C:/Temp/test.txt");
        Writer writer = new OutputStreamWriter(os, "UTF-8");

        String str = "문자 변환 스트림을 사용합니다.";
        //바이트배열만들기
        //byte[] data = str.getBytes("UTF-8");
        //os.write(data); 
        //이렇게 하면 바이트배열을 만들고 변환하는 과정이 필요하기때문에 너무 복잡하다. 
        //os.flush();
        //os.close();

        writer.write(str);
        writer.flush();
        writer.close();

        InputStream is = new FileInputStream("C:/Temp/test.txt"); 
        //txt라 FileReader하는게 빠르지만 몰라서 인풋스트림사용햇다면
        Reader reader = new InputStreamReader(is, "UTF-8");

        //바이트배열만들고 읽기
        //byte[] data = new byte[100];
        //int num = is.read(data);
        //받은 바이트배열을 스트링으로 디코딩하기
        //String str2 = new String(data, "UTF-8");
        //System.out.println(str2);
        //이러면 가독성이 너무떨어짐

        char[] data = new char[20]; //바이트수가 아닌 문자 수 최대 20자까지
        int num = reader.read(data); //실제로 읽은 문자 수 리턴
        String str2 = new String(data, 0 , num); //인덱스0부터 읽은 수까지 리턴
        System.out.println(str2);

    } catch (Exception e) {
        e.printStackTrace();
    }}
//책에서는 메소드로 바꿈
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"); //txt라 FileReader하는게 빠르지만 몰라서 인풋스트림사용햇다면
    Reader reader = new InputStreamReader(is, "UTF-8");
    char[] data = new char[20]; //바이트수가 아닌 문자 수 최대 20자까지
    int num = reader.read(data); //실제로 읽은 문자 수 리턴
    String str2 = new String(data, 0 , num); //인덱스0부터 읽은 수까지 리턴
    return str2; //리턴값이 필요하니 print 대신 출력
}}

 write("문자 변환 스트림을 사용합니다.");
String str = read();
System.out.println(str);

18.7 성능향상 스트림

하드웨어가 좋으면 성능이 좋아지지만 이것을 좀더 좋게 만들기 위해 프로그램에서 조절할 수 있다.
cpu와 메모리가 아무리 뛰어나도 하드 디스크의 입출력이 늦어지면 프로그램의 실행성능은 하드디스크의 처리속도에 맞춰진다.
네트워크로 데이터를 전송할때도 느린 네트워크 환경이면 컴퓨터 사양이 좋아도 메신저와 게임의 속도는 느릴수밖에없다.

이문제에 대한 완전한 해결책은 될수 없지만 프로그램 이 입출력소스와 직접작업하지 않고 중간에 메모리 버퍼와 작업함으로써 실행성능을 향상할수있다.
출력 스트림의 경우 직접 하드 디스크에 데이터를 보내지 앟고 메모리 버퍼에 데이터를 보냄으로써 출력속도를 향상시킬수있다.
버퍼는 데이터가 쌓이기를 가다렷다가 꽉차게 되면 데이터를 한꺼번에 하드디스크로 보냄으로써 출력 횟수를 줄여준다.


입력스트림에서도 버퍼를 사용하면 읽기 성능이 좋아진다.
BufferdInputStream bis = new BufferdInputStream(바이트 입력스트림);
BufferdOutputStream bos = new BufferdOutputStream(바이트 출력스트림);

BufferedReader br = new BufferedReader(문자 입력스트림);
BufferedWriter bw = new BufferedWriter(문자 입력스트림);
보조스트림에도 사용가능하다.

package ch18.sec07.exam01;

import java.io.*;

public class BufferExample2 {
    public static void main(String[] args)  {

        try {
            //버퍼없는 스트링 생성
            //절대경로에 대한 문자열 얻기

            String originalFilePath1 = BufferExample.class.getResource("originalFile1.jpg").getPath();
            String targetFilePath1 = "C:/Temp/targetFile1.jpg";    //얻은 파일을 복사

            FileInputStream fis1 = new FileInputStream(originalFilePath1);
            FileOutputStream fos1 = new FileOutputStream(targetFilePath1);

            //버퍼있는 스트링생성
            String originalFilePath2 = BufferExample.class.getResource("originalFile2.jpg").getPath();
            String targetFilePath2 = "C:/Temp/targetFile2.jpg";    //얻은 파일을 복사

            FileInputStream fis2 = new FileInputStream(originalFilePath2);
            BufferedInputStream bis = new BufferedInputStream(fis2);

            FileOutputStream fos2 = new FileOutputStream(targetFilePath2);
            BufferedOutputStream bos = new BufferedOutputStream(fos2);

            //복사시간측정
            long nonBufferTime = copy(fis1, fos1);
            System.out.println("버퍼 미사용:\t" + nonBufferTime + "ns");

            long BufferTime = copy(bis, bos);
            System.out.println("버퍼  사용:\t" + BufferTime + "ns");

            fis1.close();
            fos1.close();
            //보조스트림이 닫히면 메인스트림이 닫혀서 fis2와 fos2를 닫을필요는 없다.
            bis.close();
            bos.close();
        } catch (Exception e) {}
    } 

    public static long copy(InputStream is, OutputStream os) throws Exception {
        //시작 시간 저장
        long start = System.nanoTime();
        //1 바이트를 읽고 1 바이트를 출력 일부러 느리게하도록
        while(true) {
            int data = is.read();
            if(data == -1) break;
            os.write(data);
        }
        os.flush();
        //끝 시간 저장
        long end = System.nanoTime();
        //복사 시간 리턴
        return (end-start);
    }}    

BufferedReader를 연결하면 성능향상 뿐만아니라 좋은점이 한가지 더있는데 행단위로 문자열을 읽는 매우 편린한 readLine()메소드를 제공한다.
파일에서 한행씩 읽기
BufferedReader br = new BufferedReader(new FileReader("");
while (true){
String str = br.readLine();
if(str== null) break; }
이 보조스트림은 Reader와만 결합이 가능하다.
이전장에서 밑의 행위를 한것은 Reader와만 결합이 가능하니가 해준거임.
InputStream is = new FileInputStream("C:/Temp/test.txt"); //txt라 FileReader하는게 빠르지만 몰라서 인풋스트림사용햇다면
Reader reader = new InputStreamReader(is, "UTF-8");
BufferedReader br = new BufferedReader(reader);

package ch18.sec07.exam02;

import java.io.*;

public class ReadLineExample {
public static void main(String[] args) throws Exception {
    BufferedReader br = new BufferedReader(
        new FileReader("src/ch18/sec07/exam02/ReadLineExample.java") 
        //앞에는 이프로젝트 기준이니 ㄱㅊ음. 좋은방법은 아님. 간단한게 사용하기 위해 일단 넣음.
        //소스코드를 읽고 뱉어내는 예제임.
    );

    int lineNo = 1;
    while(true) {
        String str = br.readLine();
        //더이상읽을게 없으면 null을 뱉어냄.
        if(str == null) break;
        System.out.println(lineNo + "\t" + str);
        lineNo++;
    }

    br.close();
}}

18.8 기본타입 스트림

바이트 스트림에 DataInputStream과 DataOutputStream 보조스트림을 연결하면 기본타입인 boolean char short int long float double값을 입출력할 수잇다.
정수값하나를 입출력하는데 바이트로 바구고 다시 복원하는게 귀찮으니 이걸 위해 사용한다.

DataInputStream dis = new DataInputStream(바이트 입력 스트림);
DataOutputStream dos = new DataOutputStream(바이트 출력 스트림);
다음은 DataInputStream과 DataOutputStream 이 제공하는 메소드이다.

주의할점은 데이터 타입의 크기가 모두 다르므로 출력한 데이터를 다시 읽어올때는 출력한 순서와 동일한 순서로 읽어야한다는 것이다.

package ch18.sec08;

import java.io.*;

public class DatatInputOutputStreamExample  {
public static void main(String[] args) {
    // DatatOutputStream 생성
    try {
        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(); 필요 x

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

18.9 프린트 스트림

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

생성자에 File file도가능한데 이것을 주 스트림으로도 사용가능하다는 뜻임.
PrintStream ps = new PrintStream( 바이트 출력스트림);
PrintWriter pw = new PrintWriter(바이트, 문자 출력 스트림);
주의! 얘는 바이트든 문자든 다 결합가능함.
선택기준은 주 스트림에 따라 선택하기

둘다 메소드를 같게 가지고 있다.


printf()메소드는 형식화된 문자열출력하는데 2장 12절에서 이미 배웠다.
다음예제는 printf()메소드도 사용해본다.

package ch18.sec09;

import java.io.*;

public class PrintStreamExample {
public static void main(String[] args) {
    try {
        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, "홍길동", "도적"); //%-10s좌측정렬 10자리문자
        ps.printf("| %6d | %-10s | %10s | \n", 2, "김자바", "학생"); //%10s우측정렬 10자리문자

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

18.10 객체 스트림

객체를 입출력하는 스트림. 지금까지 문자열 숫자 등 만 입출력했었다. 객체 그자체도 입출력대상이 될 수 있다.
객체를 바이트로 바꿔야 파일 또는 네트워크로 출력할 수 있다.
객체를 출력하려면 필드값을 일렬로 늘어선 바이트로 변경해야하는데 이것을 '직렬화'라고 한다.
반대로 직렬화된 바이트를 객체의 필드값으로 복원하는 것을 '역직렬화'이다.
ObjectInputStream과 ObjectOutputStream은 객체를 입출력할 수 있는 보조스트림이다.
ObjectOutputStream은 바이트 출력스트림과 연결되어 객체를 직렬화하고 ObjectInputStream는 바이트입력스트림과 연결되어 객체로 복원하는 역직렬화를 한다.

ObjectOutputStream로 객체를 직렬화하기 위해서는 writeObject(객체); 메소드를 사용한다.
반대로 readObject()메소드는 읽은 바이트를 역질렬화 해서 객체로 생성한다.
readObject()메소드의 리턴타입은 Object 이므로 구체적인 타입으로 강제타입변환해야한다.
객체타입 변수 = (객체타입) ois.readObject();

그렇다면 모든 객체를 출력할수 있을까? 불가능함. 객체를 프로그램내부에서 사용한다면 다르다. 다른쪽으로 보낸다는 것은 보안이 중요하다.
어떤객체든 출력하면 문제가 있다. 클래스를 설계할때 출력해도 좋다 라는 것을 나타내는 것이 있다.
public class 클래스이름 implements Serializable

package ch18.sec10;
import java.io.Serializable;
public class Member implements Serializable {
private static final long serialVersionUID = 6237230879659378976L;
private String id;
private String name;
public Member(String id, String name) {
    this.id = id;
    this.name = name;}
@Override
public String toString() { return id + ": " + name; }}

package ch18.sec10;
import java.io.Serializable;
public class Product implements Serializable {
private static final long serialVersionUID = 2093895522785110099L;
private String name;
private int price;
public Product(String name, int price) {
    this.name = name;
    this.price = price;}
@Override
public String toString() { return name + ": " + price; }}

 package ch18.sec10;

import java.io.*;
import java.util.Arrays;

public class ObjectOutputStreamExample {
public static void main(String[] args) {
    // FileOutputStream에 ObjectOutputStream 보조 스트림 연결
    try {
        FileOutputStream fos = new FileOutputStream("C:/Temp/object.dat");
        ObjectOutputStream oos = new ObjectOutputStream(fos);

        //객체생성 이 3가지 객체를 다 넣겠다.
        Member m1 = new Member("fall", "단풍이");
        Product p1 = new Product("노트북", 1500000);
        int[] arr1 = {1, 2, 3};

        //객체를 직렬화해서 파일에 저장
        oos.writeObject(m1);
        oos.writeObject(p1);
        oos.writeObject(arr1);

        oos.flush();
        oos.close();
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }}}

   package ch18.sec10;

import java.io.*;
import java.util.Arrays;

public class ObjectInputStreamExample {
public static void main(String[] args) {
    // FileOutputStream에 ObjectOutputStream 보조 스트림 연결
    try {
        //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();

        //복원된 객체 내용 확인
        System.out.println(m2);
        System.out.println(p2);
        System.out.println(Arrays.toString(arr2)); //Arrays.toString하면 배열의 항목을 문자열로 만들어서 출력
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }}}

18.10.2 Serializable 인터페이스

Serializable가 무조건 있어야한다. 아무것도 없는데 어떤 역할을 하나?
안하면 java.io.NotSerializableException: 예외발생
비어있는 인터페이스 이고 그냥 객체를 직렬화 할 수 있다고 표시하는 역할을 한다.
데이터부분이 바이트배열로 만들어니다.
인스턴스 필드값은 직렬화 대상이지만 정적필드값과 transient로 선언된 필드값은 직렬화에서 제외된다.
String, Data 클래스타입도 직렬화 가능하다.

18.10.3 serialVesionUID필드

직렬화할때의 클래스와 역직렬화할때 클래스가 기본적으로 동일한 클래스여야하는데 같지 않는 경우가 발생하면 역직렬화에 실패한다.
완전하게 동일하지 않는 경우도 발생한다.


클래스내용이 다르다할지라도 직렬화된 필드를 공통으로 포함하고 있다면 역직렬화할수있는 방법이 있다.
두 클래스가 동일한 serialVesionUID 상수값을 가지고 있으면 된다.
serialVesionUID의 값은 개발자가 임의로 줄 수 있지만 가능하다면 클래스마다 다른 유일한 값을 갖도록하는 것이 좋다.
이클립스는 serialVesionUID필드를 자동생성하는 기능을 제공한다.
implements Serializable를 붙인다음 마우스를 클래스 이름에 갖다대면 Add generated serial Vesion ID 링크가 나온다.

2022.12.07 리뷰

DataInputStream cannot be resolved to a type 컴파일에러
BufferdOutputStream bos = new BufferdOutputStream 컴파일에러
JRE System Library를 제대로 인식하지 못해 발생한 오류임.
https://coding-factory.tistory.com/6 제거하고 다시 추가하니 해결됨.

중요한것은 꺾이지 않는 마음