Language/Java

I/O - 다양한 IO객체들, 객체 직렬화

kimjingyu 2023. 9. 18. 21:27
728x90

DataInputStream, DataOutputStream

DataInputStream과 DataOutputStream은 기본형 타입과 문자열을 읽고 쓸 수 있는 객체이다.

 

다음은 다양한 타입을 파일에 저장하고, 읽어들이는 코드이다.

package theory.io;

import java.io.DataOutputStream;
import java.io.FileOutputStream;

public class DataInputExam {
    public static void main(String[] args) throws Exception {
        // /tmp/score.dat 파일에 저장한다.
        String name = "kim";
        int kor = 90;
        int eng = 60;
        int math = 70;
        double total = kor + eng + math;
        double avg = total / 3.0;

        // 다양한 타입을 저장할 때 사용하는 객체 -> DataOutputStream
        DataOutputStream out = new DataOutputStream(new FileOutputStream("/tmp/score.dat"));
        out.writeUTF(name);
        out.writeInt(kor);
        out.writeInt(eng);
        out.writeInt(math);
        out.writeDouble(total);
        out.writeDouble(avg);
        out.close();
    }
}
package theory.io;

import java.io.DataInputStream;
import java.io.FileInputStream;

public class DataInputExam {
    public static void main(String[] args) throws Exception {
        DataInputStream in = new DataInputStream(new FileInputStream("/tmp/score.dat"));
        String name = in.readUTF();
        int kor = in.readInt();
        int eng = in.readInt();
        int math = in.readInt();
        double total = in.readDouble();
        double avg = in.readDouble();
        in.close();

        System.out.println("name = " + name);
        System.out.println("kor = " + kor);
        System.out.println("eng = " + eng);
        System.out.println("math = " + math);
        System.out.println("total = " + total);
        System.out.println("avg = " + avg);
    }
}

 

ByteArrayInputStream, ByteArrayOutputStream

byte[]에 데이터를 읽고 쓰는 클래스이다.

 

ByteArrayOutputStream에 데이터를 저장하면 메모리 상에 저장된다. 그리고 toByteArray를 호출하면 메모리 상에 저장된 바이트 배열을 반환해준다.

package theory.io;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class ByteArrayOutputExam {
    public static void main(String[] args) throws IOException {
        int data1 = 1;
        int data2 = 2;

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        out.write(data1);
        out.write(data2);
        out.close();

        byte[] array = out.toByteArray();
        System.out.println("array.length = " + array.length);
        System.out.println("array[0] = " + array[0]);
        System.out.println("array[1] = " + array[1]);
    }
}
package theory.io;

import java.io.ByteArrayInputStream;

public class ByteArrayInputExam {
    public static void main(String[] args) throws Exception {
        byte[] array = new byte[2];
        array[0] = (byte) 1;
        array[1] = (byte) 2;

        ByteArrayInputStream in = new ByteArrayInputStream(array);
        int read1 = in.read(); // 첫번째 바이트 값
        int read2 = in.read(); // 두번째 바이트 값
        int read3 = in.read(); // EOF
        in.close();
    }
}

 

즉, 간혹가다가 메모리 상에 무언가를 써주고 싶을때가 있는데 그때는 ByteArrayOutputStream을 사용하면 된다.

 

CharArrayReader, CharArrayWriter

char[]에 데이터를 읽고 쓴다.

 

StringReader, StringWriter

문자열을 읽고 쓴다.

// 생성자 아무것도 안 받아들이는 IO 객체는 메모리에 쓴다.
StringWriter out = new StringWriter();
out.write("hello");
out.write("world");
out.close();

String str = out.toString();
System.out.println("str = " + str);

결과

str = helloworld

StringReader in = new StringReader("helloworld");
int ch = -1;

while ((ch = in.read()) != -1) {
    System.out.print((char) ch);
}
in.close();

 

ObjectInputStream, ObjectOutputStream

아무 객체나 읽고 쓸 수 있는 것이 아니라 직렬화 가능한 객체만 읽고 쓸 수 있다. 직렬화 가능한 대상은 기본형 타입 혹은 java.io.Serializable 인터페이스를 구현하고 있는 객체이다. 이때, Serializable 인터페이스는 구현할 메서드가 없는 mark Interface라고 한다.

 

다음 코드는 파일을 통한 직렬화, 역직렬화 예제이다.

User user = new User("hong@example.com", "홍길동", 2020);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("/tmp/user.dat"));
out.writeObject(user); // user를 객체 직렬화시켜서 파일에 저장
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("/tmp/user.dat"));
User user = (User) in.readObject(); // 파일에 있는 데이터를 읽어들여서 역직렬화
in.close();
System.out.println("user = " + user);

 

그리고 다음은 ArrayList 자체도 직렬화 대상임을 보여주는 예제이다.

User user1 = new User("hong@example.com", "홍길동", 2020);
User user2 = new User("kim@example.com", "김철수", 1990);
User user3 = new User("lee@example.com", "이영희", 2000);
ArrayList<User> list = new ArrayList<>(); // ArrayList 자체도 직렬화 가능
list.add(user1);
list.add(user2);
list.add(user3);

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("/tmp/userlist.dat"));
out.writeObject(list); // user를 객체 직렬화시켜서 파일에 저장
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("/tmp/userlist.dat"));
ArrayList<User> list = (ArrayList) in.readObject(); // 파일에 있는 데이터를 읽어들여서 역직렬화
in.close();

for (User user : list) {
    System.out.println("user = " + user);
}
user = User{email='hong@example.com', name='홍길동', birthYear=2020}
user = User{email='kim@example.com', name='김철수', birthYear=1990}
user = User{email='lee@example.com', name='이영희', birthYear=2000}

 

얕은 복사를 피하기위한 객체 직렬화

아래 코드를 보면 list를 writeObject 객체 직렬화 메서드에 넣어 직렬화시켰다. 그러면 list가 참조하고 있는 직렬화가능한 모든 것이 byte의 흐름으로 변해서 저장이 된다. 즉, 메모리상의 byte[]로 형태로 바뀐 것이다. 그리고 readObject 하는 순간 바이트배열에 있는 내용이 인스턴스가 새로 만들어지고, list2가 참조하게 된다.

 

따라서 메모리에 상에 그 내용이 그대로 복사된 내용이 만들어지고, 이를 깊은 복사라고 한다. 자바에서 이 기술을 이용해서 어떤 객체를 임시적으로 디스크에 저장 후 읽어들이거나, 어떤 객체들을 메모리 상에 복제를 한다던지가 가능해진다.

package theory.serialization;

import java.io.*;
import java.util.ArrayList;

public class ObjectIOExam {
    public static void main(String[] args) throws Exception {
        User user1 = new User("hong@example.com", "홍길동", 2020);
        User user2 = new User("kim@example.com", "김철수", 1990);
        User user3 = new User("lee@example.com", "이영희", 2000);
        ArrayList<User> list = new ArrayList<>(); // ArrayList 자체도 직렬화 가능
        list.add(user1);
        list.add(user2);
        list.add(user3);

        // 기존 list를 받아들여서 ArrayList로 copy하는 메서드
        ArrayList<User> list2 = copy(list);

        for (User user : list2) {
            System.out.println("user = " + user);
        }
    }

    private static ArrayList<User> copy(ArrayList<User> list) throws IOException, ClassNotFoundException {
        // Object output stream 에 쓴 것이 바이트 배열 메모리에 저장된다.
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(bout);
        out.writeObject(list);
        out.close();
        bout.close();

        byte[] array = bout.toByteArray(); // list가 직렬화되어서 byte 배열이 됨

        // 역직렬화
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(array));
        ArrayList<User> list2 = (ArrayList) in.readObject();
        in.close();
        return list2;
    }
}

 

인용

https://www.youtube.com/watch?v=Nhad1kpMvl8&t=74s&ab_channel=%EB%B6%80%EB%B6%80%EA%B0%9C%EB%B0%9C%EB%8B%A8-%EC%A6%90%EA%B2%81%EA%B2%8C%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EB%B0%B0%EC%9A%B0%EA%B8%B0 

 

728x90