Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Tags
more
Archives
Today
Total
관리 메뉴

forDevLife

[자바의정석]Chapter 15. 입출력 I/O - 2 본문

Java

[자바의정석]Chapter 15. 입출력 I/O - 2

JH_Lucid 2021. 4. 14. 14:43

6.3 RandomAccessFile

- 추가로, writeInt() 등으로 value를 저장했을 경우, readInt()를 통해 정상적인 값을 읽어올 수 있다.

   readShort()등 다른 메서드로 읽게 되면, 파일 포인터의 위치는 Short(2 byte)씩 변하긴 하지만 아무런 값도 읽어오지 못한다.

 

 

6.4 File

- 파일 포인터는 추후 정리. 자주 쓰이는 것이므로, 모를때마다 찾아봅시다.

 

 

7. 직렬화 (Serialization)

- 객체를 컴퓨터에 저장 / 불러오기 및 네트웍을 통해 컴퓨터 간 객체를 주고받는 방법은 없을까에서 만들어진게 직렬화이다.

- 직렬화란 객체를 데이터 스트림으로 만드는 것을 뜻한다. 

- 객체에 저장된 데이터를 스트림에 쓰기(Write)위해 연속적인 데이터로 변환하는 것을 말한다. 

- 반대로 스트림으로부터 데이터를 읽어서 객체를 만드는 것을 역직렬화(deserialization)라고 한다.

 

- 객체에 대해 다시 한번 생각해보면, 클래스에 정의된 인스턴스 변수의 집합이다. 객체에는 클래스 변수나 메서드가 포함되지 않으며, 오직 인스턴스 변수로만 구성되어 있다. 

- 인스턴스 변수는 인스턴스마다 다른 값을 가질수 있어야 하기 때문에 별도의 메모리 공간이 필요하지만, 메서드는 변하는 것이 아니기 때문에 메모리를 낭비해 가면서 인스턴스 마다 같은 내용의 코드(메서드)를 포함시킬 필요가 없다. 

 

- 따라서 객체를 저장한다는 것은, 객체의 모든 인스턴스 변수의 값을 저장한다는 것과 같은 의미이다.

   -> 저장 시, 현재 객체의 모든 인스턴스 변수 값을 저장한다.

   -> 다시 생성 시, 객체를 생성한 후에 저장했던 값을 읽어서 생성한 객체의 인스턴스 변수에 저장하면 된다.

 

- ObjectInputStream / ObjectOutputStream을 통해, 객체 직렬화 / 역 직렬화를 할 수 있다!

 

7.2 ObjectInputStream / ObjectOutputStream

직렬화 : 스트림에 객체를 출력 > ObjectOutputStream

역직렬화 : 스트림으로부터 객체를 입력 > ObjectInputStream

 

- 직렬화의 예

FileOutputStream fos = new FileOutputStream("objectfile.ser");
ObjectOutputStream out = new ObjectOutputStream(fos);

out.writeObject(new UserInfo());

직렬 / 역직렬 스트림은 보조 스트림으로, 기반 스트림을 필요로 하기 때문에 위와 같이 생성한다.

파일에 객체를 저장(직렬화) 하고 싶다면 위와 같이 작성하면 된다.

 

위의 코드는 objectfile.ser이라는 파일에 UserInfo객체를 직렬화 하여 저장한다. (writeObject 메소드 사용)

 

 

-  역직렬화의 예

FileInputStream fis = new FileInputStream("objectfile.ser");
ObjectInputStream in = new ObjectInputStream(fis);

UserInfo info = (UserInfo)in.readObject();

역직렬화 역시 간단하다. 직렬과는 달리 입력스트림을 사용한다. 다만 readObject()의 반환 타입이 Object이기 때문에 형변환이 필요하다.

 

 

+ read / writeObject() 이외에도 여러 가지 타입의 값을 입출력할 수 있는 메서드를 제공한다.

+ 객체를 직/역직렬화 하는 작업은 객체의 모든 인스턴스 변수가 참조하고 있는 모든 객체에 대한 것이기 때문에 상당히 복잡 / 오래 걸린다.

+ read / writeObject()와 같은 자동 직렬화가 편하기는 하나, 시간을 단축시키기 위해 직렬화하고자 하는 객체의 클래스에 다음 2개의 메서드를 직접 구현할 필요가 있다. : writeObject / readObject > 나중에 예제로 배운다.

 

 

7.3 직렬화가 가능한 클래스 만들기 - Serializable, transient

직렬화하고자 하는 클래스가 java.io.Serializable 인터페이스를 구현하도록 하면 된다.

- 이 인터페이스는 아무런 내용도 없는 빈 인터페이스이지만, 직렬화를 고려하여 작성한 클래스인지 판단하는 기준이 된다.

- 조상이 Serializable 인터페이스를 구현했으면, 자식도 역시 직렬화가 가능해진다.

- 또한, 자식의 객체를 직렬화하면, 조상에 정의된 인스턴수 변수도 함께 직렬화된다.

- 하지만, 조상에는 구현 x, 자손에만 구현되어있는 경우, 조상의 인스턴스 변수는 직렬화 대상에서 제외된다!

- 모든 클래스의 최고 조상인 Object는 Serializable을 구현하지 않았기 때문에 직렬화 불가능이다. 

   > 따라서 직렬화하고자 하는 클래스 내에 Object 객체를 참조하는 인스턴스 변수가 있을 경우, 직렬화에 실패한다.

   > 하지만, Object obj에 실제로 저장된 객체가 직렬화가 가능한 인스턴스일 경우에는 직렬화가 가능하다.

   > 즉, 인스턴스 변수의 타입이 아닌, 실제로 연결된 객체의 종류에 따라서 직렬화 가능 여부가 결정된다!

 

- 직렬화가 불가능한 값, 또는 보안 상 직렬화를 원하지 않는 값에는 제어자 transient를 붙인다.(직렬화 대상에서 제외)

   > 해당 제어자가 붙은 인스턴수 변수의 값은 그 타입의 기본 값으로 직렬화가 된다. (예를 들어, String, Object는 Null 값으로 직렬화 됨)

 

- 객체 직렬화 - 역직렬화 시, 순서에 유의한다. 예를 들어 객체 u1, u2, u3의 순서로 직렬화 했다면, 역직렬화 시에도 해당 순서로 진행한다.

- 따라서 직렬화가 많을 때는 ArrayList와 같은 컬렉션에 저장하여 직렬화 하는 것이 좋다.

 

- 앞에서 언급되었던 직렬화 되지 않는 조상으로부터 상속받은 인스턴스 변수에 대한 직렬화

  > 직렬화 - 역직렬화 시, writeObject / readObject 메소드를 정의해야 한다.

  > 이 메소드들은 직렬화/역직렬화 작업 시 자동적으로 호출된다.

  > 하나라도 직렬화되지 않는 value가 있다면 이러한 식으로 정의가 필요하다.

  > 메소드 안에서 직렬화 처리하지 않은 instance 변수는 default value가 들어간다.

 

 

7.4 직렬화가능한 클래스의 버전 관리

직렬화된 객체를 역직렬화할 때는 직렬화 했을 때와 같은 클래스를 사용해야 한다는 것은 자명하다. 그러나 클래스의 이름이 같더라도 클래스의 내용이 변경된 경우, 역직렬화는 실패하며 예외가 발생한다. (예외 : local class incompatible: ~ )

해당 예외 내용은, 직렬화 / 역직렬화 클래스의 버전이 같아야 하는데 다르다는 것이다. 

-> 객체 직렬화 시, 클래스에 정의된 멤버들의 정보를 이용해서 serialVersionUID라는 클래스의 버전을 자동 생성, 직렬화 내용에 포함된다.

-> 이를 이용, 역직렬화 시 클래스의 버전을 비교함으로써 직렬화 때의 클래스 버전과 일치하는지 확인한다.

 

+ 그러나 static 변수 / 상수 / transient가 붙은 인스턴스 변수가 추가되는 경우에는, 직렬화에 영향을 미치지 않으므로 상관 없다.

 

네트워크로 객체를 직렬화하여 전송하는 경우, 보내는 쪽 / 받는 쪽 모두 같은 버전의 클래스를 가지고 있어야 한다. 

이러한 부분으로 인해 클래스를 재배포함에 있어서 프로그램 관리를 어렵게 만든다.

따라서 클래스의 버전을 수동으로 관리해줄 필요가 있다.

 

class MyData implements java.io.Serializable{

	static final long serialVersionUID = 203949859450393L;
    int value1;
}

위에서 serialVersionUID를 통해, 클래스의 버전을 수동으로 관리할 수 있다. 

이렇게 정의해주면, 클래스의 내용이 바뀌어도 클래스의 버전이 자동생성된 값으로 변경되지 않는다. 

 

즉, serialVersionUID는 내가 직접 정의해주지 않으면, 클래스 내용이 변경될때마다 임의로 변경된다. 이를 정의해줌으로써 클래스의 uid를 유지시켜준다.

 

serialver -classpath /Users/maegbug/eclipse-workspace/test_java/bin i_o_study.UserInfo2

 

위처럼, 다음과 같이 class path 써주고 패키지.클래스 이름으로 실행할 수 있다.

맨 처음 실행시에는 serialVersionUID를 지정해주지 않아, 마이너스 어쩌고가 나온다.

두번째 실행 결과에서는 내가 직접 지정한 값이 출력된다.

Comments