[JAVA] 객체 직렬화 ObjectInputStream / ObjectOutputStream
객체 직렬화 ObjectInputStream / ObjectOutputStream
객체 스트림은 프로그램 메모리상에 존재하는 객체를 직접 입출력해 줄 수 있는 스트림으로 현재 상태를 보존하기 위한 영속성을 지원할 수 있다.
자바에서 객체 안에 저장되어 있는 내용을 파일로 저장하거나 네트워크를 통하여 다른 곳으로 전송하려면 객체를 바이트 형태로 일일이 분해해야 한다. 이를 위하여 객체를 직접 입출력 할 수 있도록 해주는 객체 스트림이다.
1. 객체 전송의 단계
객체를 분해하여 전송하기 위해서는 직렬화(Serialization) 되어야 한다.
객체를 전송하기 위해서는 3가지 단계를 거친다.
(1) 직렬화된 객체를 바이트 단위로 분해한다. (marshalling)
(2) 직렬화 되어 분해된 데이터를 순서에 따라 전송한다.
(3) 전송 받은 데이터를 원래대로 복구한다. (unmarshalling)
2. 마샬링 (marshalling)
마샬링(marshalling)은 데이터를 바이트의 덩어리로 만들어 스트림에 보낼 수 있는 형태로 바꾸는 변환 작업을 뜻한다.
자바에서 마샬링을 적용할 수 있는 데이터는 원시 자료형(boolean, char, byte, short, int, long, float, double)와 객체 중에서 Serializable 인터페이스를 구현한 클래스로 만들어진 객체이다.
객체는 원시 자료형과 달리 일정한 크기를 가지지 않고 객체 내부의 멤버 변수가 다르기 때문에 크기가 천차만별로 달라진다. 이런 문제점을 처리할 수 있는게 ObjectOutputStream 클래스이다.
3. 직렬화 (Serializable)
마샬링으로 바이트로 분해된 객체는 스트림을 통해서 나갈 수 있는 준비가 되었다. 앞에서 언급한대로 객체를 마샬링하기 위해서는 Serializable 인터페이스를 구현한 클래스로 만들어진 객체에 한해서만 마샬링이 진행될 수 있다.
Serializable 인터페이스는 아무런 메소드가 없고 단순히 자바 버추얼 머신에게 정보를 전달하는 의미만을 가진다.
* 직렬화가 가능한 객체의 조건
(1) 기본형 타입(boolean, char, byte, short, int, long, float, double)은 직렬화가 가능
(2) Serializable 인터페이스를 구현한 객체여야 한다. (Vector 클래스는 Serializable 인터페이스구현)
(3) 해당 객체의 멤버들 중에 Serializable 인터페이스가 구현되지 않은게 존재하면 안된다.
(4) transient 가 사용된 멤버는 전송되지 않는다. (보안 변수 : null 전송)
4. 언마샬링 (unmarshalling)
언마샬링은 객체 스트림을 통해서 전달된 바이트 덩어리를 원래의 객체로 복구하는 작업이다. 이 작업을 제대로 수행하기 위해서는 반드시 어떤 객체 형태로 복구할지 형 변환을 정확하게 해주어야 한다.
Vector v = (Vector)ois.readObject();
// OutputInputStream의 객체를 읽어서 Vector 형으로 형변환 한다.
이때 ObjectInputStream을 사용하여 데이터를 복구한다.
* ObjectInputStream / ObjectOutputStream 생성자
생성자 | 설 명 |
ObjectInputStream(InputStream in) | in 으로부터의 unmarshalling을 위한 ObjectInputStream 객체를 생성한다. |
ObjectOutputStream(OutputStream out) | out을 marshalling 하기 위한 ObjectOutputStream 객체를 생성한다. |
* ObjectInputStream 의 메소드
ObjectInputStream 메소드 | 설 명 |
int available() | 객체에서 읽을 수 있는 바이트 값을 반환한다. |
void close() | 객체를 닫는다. |
void defaultReadObject() | 현재 Stream에서 static,transient가 아닌 객체를 읽는다. |
protected boolean enableResolveObject(boolean enable) | 현재 Stream에서 객체를 읽는 것 허용 여부를 설정한다. |
int read() | 데이터를 바이트 단위로 읽는다. |
int read(byte[] buf, int off, int len) | buf 바이트 배열에 off 부터 len까지 읽는다. |
boolean readBoolean() | 객체의 boolean 값을 읽는다. |
byte readByte() | 객체의 1 byte를 읽는다. (8 비트) |
char readChar() | 객체의 1 Char를 읽는다. (16 비트) |
protected ObjectStreamClass readClassDescriptor() | 직렬화된 스트림에서 Descriptor를 읽는다. |
double readDouble() | 객체에서 1 double을 읽는다. (64 비트) |
ObjectInputStream.GetField.readFileds() | 객체에서 영속성이 보장된 형의 이름을 가져온다. |
float readFloat() | 객체에서 1 float을 읽는다. (32 비트) |
void readFully(byte[] buf) | 객체에서 buf 만큼 바이트를 읽는다. |
void readFully(byte[] buf, int off, int len) | 객체에서 buf 만큼 off 부터 len만큼 읽는다. |
int readInt() | 객체에서 1 int를 읽는다. (32 비트) |
Long readLong() | 객체에서 1 Long을 읽는다. (64 비트) |
Object readObject() | 객체에서 Object를 읽는다. |
Short readShort() | 객체에서 1 Short를 읽는다. (16비트) |
protected void readStreamHeader() | 스트림의 헤더를 읽는다. |
Object readUnshared() | 스트림에서 "unshared" 객체를 읽는다. |
String readUTF() | String을 UTF-8 방식으로 읽는다. |
* ObjectOutputStream의 메소드
ObjectOutputStream 메소드 | 설 명 |
void close() | 객체를 닫는다. |
void defaultWriteObject() | 현재 Stream에 static,transient가 아닌 객체를 쓴다. |
protected void drain() | ObjectOutputStream의 버퍼에 있는 객체를 내보낸다. |
protected boolean enableReplaceObject(boolean enable) | 현재 Stream에서 객체를 쓰는것 허용 여부를 설정한다. |
void flush() | 스트림에 데이터를 내보낸다. |
protected Object replaceObject(Object obj) | 객체에 Object를 교체한다. |
void reset() | 스트림을 리셋한다. |
void useProtocolVersion(int version) | 스트림을 내보낼때 사용하는 프로토콜의 버전을 설정한다. |
void write(byte[] buf) | buf를 쓴다. |
void write(byte[] buf, int off, int len) | buf의 off부터 len 길이만큼을 스트림에 쓴다. |
void write(int val) | val 바이트만큼 스트림에 쓴다. |
void writeBoolean(boolean val) | val을 스트림에 쓴다. |
void writeByte(int val) | val을 byte로 스트림에 쓴다. (8 비트) |
void writeBytes(String str) | str을 sequence 바이트로 스트림에 쓴다. |
void writeChar(int val) | val을 Char로 스트림에 쓴다. (16 비트) |
void writeChars(String str) | str을 sequence char로 스트림에 쓴다. |
protected void writeClassDescriptor(ObjectStreamClass desc) | 스트림에 desc를 쓴다. |
void writeDouble(double val) | val을 Double로 스트림에 쓴다. (64 비트) |
void writeFilelds() | 스트림에 버퍼에 있는 필드를 쓴다. |
void writeFloat(float val) | val을 Float으로 스트림에 쓴다. (32 비트) |
void writeInt(int val) | val을 Int로 스트림에 쓴다.(32 비트) |
void writeLong(long val) | val을 long로 스트림에 쓴다. (64 비트) |
void writeObject(Object obj) | Obj 객체를 스트림에 쓴다. |
void writeShort(int val) | val을 Short로 스트림에 쓴다. |
protected void writeStreamHeader() | 스트림에 StreamHeader를 쓴다. |
void writeUnshared(Object obj) | "unshared" 객체를 스트림에 쓴다. |
void writeUTF(String str) | 객체의 문자의 인코딩을 UTF-8로 설정한다. |
* ObjectInputStream / ObjectOutputStream 사용 예제
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class ObjectStream { public static void main(String[] args){ // ObjectOutputStream 을 이용한 객체 파일 저장 FileOutputStream fos = null; ObjectOutputStream oos = null; // UserClass 에 이름과 나이를 입력하여 객체를 3개 생성한다. UserClass us1 = new UserClass("하이언", 30); UserClass us2 = new UserClass("스티브", 33); UserClass us3 = new UserClass("제이슨", 27); try{ // object.dat 파일의 객체 아웃풋스트림을 생성한다. fos = new FileOutputStream("object.dat"); oos = new ObjectOutputStream(fos); // 해당 파일에 3개의 객체를 순차적으로 쓴다 oos.writeObject(us1); oos.writeObject(us2); oos.writeObject(us3); // object.dat 파일에 3개의 객체 쓰기 완료. System.out.println("객체를 저장했습니다."); }catch(Exception e){ e.printStackTrace(); }finally{ // 스트림을 닫아준다. if(fos != null) try{fos.close();}catch(IOException e){} if(oos != null) try{oos.close();}catch(IOException e){} } // 파일로 부터 객체 데이터 읽어온다. FileInputStream fis = null; ObjectInputStream ois = null; try{ // object.dat 파일로 부터 객체를 읽어오는 스트림을 생성한다. fis = new FileInputStream("object.dat"); ois = new ObjectInputStream(fis); // ObjectInputStream으로 부터 객체 하나씩 읽어서 출력한다. // (UserClass) 로 형변환을 작성해야 한다. // System.out.println 으로 객체의 구현된 toString() 함수를 호출한다. System.out.println( (UserClass)ois.readObject()); System.out.println( (UserClass)ois.readObject()); System.out.println( (UserClass)ois.readObject()); }catch(Exception e){ e.printStackTrace(); }finally{ // 스트림을 닫아준다. if(fis != null) try{fis.close();}catch(IOException e){} if(ois != null) try{ois.close();}catch(IOException e){} } } } |
UserClass 보기
- UserClass 객체를 생성하여 ObjectOutputStream을 통해 object.dat 에 순차적으로 객체를 쓴다.
- UserClass 객체를 Stream에 쓰기위해서는 Serializable 인터페이스를 사용해야 직렬화 할 수 있다.
- Serializable 구현하지 않으면 NotSerializableException이 발생한다.
- ObjectInputStream을 통해 object.dat에 저장되어 있는 객체를 읽어온다.
- ObjectInputStream에서 readObject()로 읽을때는 정확한 형변환을 해주어야 정확하게 언마샬링을 할 수 있다.
출처: https://hyeonstorage.tistory.com/252?category=578560 [개발이 하고 싶어요]
'JAVA > Java IO' 카테고리의 다른 글
java.io 패키지 (try-with-resources, InputStream, OutputStream, Reader, Writer, Serialization) (0) | 2021.03.19 |
---|---|
[JAVA] Externalizable 인터페이스 (0) | 2019.08.07 |
[JAVA] Serializable 과 transient (0) | 2019.08.07 |
[JAVA] serialVersionUID 이란? Warning 해결하기 (0) | 2019.08.07 |
[JAVA] I/O Char 와 Encoding (0) | 2019.08.07 |
[JAVA] I/O 입출력 클래스의 종류 (0) | 2019.08.07 |
[JAVA] 문자 Stream : BufferedReader / BufferedWriter (파일 복사 예제) (0) | 2019.08.07 |
[JAVA] 문자 Stream : FileReader / FileWriter (0) | 2019.08.07 |