도서/자바의 신

[도서/자바의 신] #27 Serialiable과 NIO도 살펴봅시다

yulee_to 2023. 1. 1. 05:18

자바의 신

✔️이 글은 [자바의 신 - 이상민 지음] 도서를 바탕으로 정리한 글입니다. 


Serialiable에 대해서 좀 살펴보자

Serializable 인터페이스에는 선언된 변수나 메소드가 없다. 

개발을 하다보면, 생성한 객체를 파일로 저장하거나, 저장한 객체를 읽거나, 객체를 다른 서버로 보내거나, 다른 서버에서 생성한 객체를 받는 일이 생길 수도 있다. 그럴 때 필요한 것이 Serializable이다.

내가 만든 클래스가 파일에 읽고 쓰거나, 다른 서버로 보내거나 받을 수 있도록 하기 위해선 Serializable 인터페이스를 구현해야만 한다. 그럼 JVM에서 해당 객체는 저장하거나 다른 서버로 전송할 수 있도록 해준다. 

 

Serializable을 구현한 후에는 다음 변수를 선언하는 것이 좋다. 선언해주지 않으면 자바 컴파일시 자동으로 생성된다.

static final long serialVersionUID = 1L;

이 변수는 해당 객체의 버전을 명시해주는 데 사용한다. 두 서버 간 객체를 주고 받을 때 서로에게 해당하는 클래스가 존재해야 하고, 그 객체의 내용이 같아야 한다. 이때 두 객체가 같은지 다른지 서버가 쉽게 확인할 수 있도록 serialVersionUID로 확인을 해준다. 이름이 같아도 UID가 다르면 다른 객체고, UID가 같더라도 변수의 개수나 타입이 다르면 다른 클래스로 인식한다. 

객체를 저장해보자

ObjectOutputStream 클래스는 객체를 저장할 수 있고, 반대로 ObjectInputStream 클래스는 저장해놓은 객체를 읽을 수 있다. 

 

ObjectOutputStream에는 write() 메소드로 int값을 저장하고, writeByte() 메소드로 바이트 값을 저장, writeObject()로 객체를 저장한다. writeObject로 객체를 저장할 때는 그 객체의 클래스가 Serializable을 구현해야만 한다. 그렇지 않으면 NotSerializableException 예외가 발생한다. 저장한 객체는 바이너리로 저장되어있다. 

객체를 읽어보자

객체를 읽을 때는 ObjectInputStream를 사용하면 되고 ObjectOutputStream에서 사용한 것들의 이름에서 output은 input으로 write는 read로만 변경된 것들을 사용하면 된다. 

객체를 읽어 올 때는 readObject() 메소드를 사용하고 이 메소드의 리턴 타입이 Object이므로 받은 후에 원하는 객체 타입으로 형변환을 해주면 된다. 

 

저장된 객체를 읽어올 때 그 객체의 클래스에 있는 변수의 개수가 변경되면 serialVersionUID가 다르다는 InvalidClassException 예외 메시지가 출력된다. 변수가 추가되는 등 Serializable 객체의 형태가 변경되면 컴파일시 serialVersionUID가 다시 생성되기 때문에 이와 같은 문제가 발생한다. 

이런 상황에서 예외가 발생되지 않게 하려면 다루는 객체의 클래스에 serialVersionUID를 명시해줘서 다시 저장하고 읽어오면 된다. 

serialVersionUID를 지정해둔 상태에서 저장되어 있는 객체와 읽는 객체가 다르면 변경된 변수는 null로 처리해준다. 

이렇게 serialVersionUID를 지정하면 변수가 변경되더라도 예외가 발생하지 않기 때문에 데이터가 바뀌면 serialVersionUID 값도 변경해주는 것이 좋다. 요즘은 개발툴에서 자동으로 값을 생성하는 기능을 제공한다. 

transient라는 예약어는 Serialiable과 떨어질 수 없는 관계다

Serializable을 구현한 클래스의 변수 선언문 앞에 transient라는 예약어를 추가하면 그 변수는 저장대상에서 제외된다.

해당 객체를 생성한 JVM에서는 변수를 사용하는데 문제가 없다. 

예시로 비밀번호를 저장하는 객체를 다른 서버로 넘겨줄 때 그 값은 빼고 넘겨주게 할 때 transient를 사용한다. 

자바 NIO란?

JDK 1.4부터 더 빠른 I/O 처리를 위해 NIO가 나왔다. NIO는 스트림이 아닌 채널과 버퍼를 사용해 I/O를 처리한다.

NIO에서 데이터 주고 받을 땐 버퍼를 통해 처리하고 파일을 일고 쓰기, 파일 복사, 네트워크로 데이터 주고 받을 때 사용된다. 

 

파일 데이터를 다룰 때에는 ByteBuffer라는 버퍼와 FileChannel이라는 채널을 사용하면 된다. 

Channel의 경우 간단하게 객체만 생성하여 read()나 write() 메소드만 불러주면 된다. Buffer는 조금 복잡하니 다음 절에서 알아보자.

NIO의 Buffer 클래스

NIO에서 제공하는 Buffer는 java.nio.Buffer 클래스를 확장하여 사용한다. 

CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer, ByteBuffer 등이 존재한다.

 

Buffer의 상태 및 속성 확인 메소드

  • capacity() : 버퍼에 담을 수 있는 크기 리턴
  • limit() : 버퍼에서 읽거나 쓸 수 없는 첫 위치 리턴
  • position() : 현재 버퍼의 위치 리턴

버퍼에 데이터를 담거나, 읽는 작업을 수행하면 현재의 "위치"가 이동한다.

버퍼의 position, limit, capacity의 관계는 아래와 같다.

0 <= position <= limit <= capacity 

 

Buffer의 위치 변경 메소드

  • flip() : limit값을 현재 position으로 지정한 후, position을 0(맨 앞)으로 이동
  • mark() : 현재 position을 mark
  • reset() : 버퍼의 position을 mark한 곳으로 이동
  • rewind() : 현재 버퍼 position 0으로 이동
  • remaining() : limit-position 계산 결과 리턴
  • hasRemaining() : position과 limit 값에 차이가 있으면 true
  • clear() : 버퍼를 지우고 현재 position을 0으로 이동, limit 값을 버퍼의 크기로 변경

정리해 봅시다

1. java.io.Serializable을 import하는 이유는?

생성한 객체를 파일로 저장하거나, 저장한 객체를 읽거나, 객체를 다른 서버로 전송하거나, 다른 서버에게서 객체를 받을 일이 있을 때 Serializable 인터페이스를 구현해야 하기 때문이다. 

2. java.io.Serialiable의 serialVersionUID를 지정하는 이유는?

객체의 버전을 명시해 객체가 같은지 다른지 확인하기 위해서이다.

3. 자바에서 객체를 파일로 읽거나 쓸 때 사용하는 Stream 클래스 이름은?

FileInputStreame, FileOutputStream

4. transient 예약어의 용도는?

예약어를 사용한 변수를 저장대상에서 제외하기 위해서이다. 

5. NIO가 생긴 이유는?

채널(Channel)과 버퍼(Buffer)를 사용해서 스트림 기반의 I/O 처리의 속도를 향상시키기 위해서이다. 

6. NIO에서 Channel의 용도는?

데이터를 중간에서 처리

7. NIO에서 Buffer의 용도는?

데이터를 담음

8. NIO에서 Buffer의 상태를 확인하기 위한 메소드에는 어떤 것들이 있나요?

position(), capacity(), limit()

9. NIO에서 Buffer의 position을 변경하기 위한 메소드들에는 어떤 것들이 있나요?

flip() mark() reset() rewind() remaining() hasRemaining() clear()

728x90