[F-Lab 챌린지 9일차] TIL : 자바의신 27장 - Serializable & NIO

성수데브리·2023년 7월 7일
0

f-lab_java

목록 보기
7/73

학습 목표


  • Serializable
  • NIO

학습 결과 요약


  • 직렬화가 필요한 이유는 메모리상 존재하는 객체를 String 이나 byte 형태로 드라이브 또는 통신 회선이 전달 가능한 형태로 변환하는 작업이 필요하다.
  • NIO 는 스트림 대신 channel 과 buffer 로 데이터를 처리한다. IO 보다 속도가 빠르다.

학습 내용


Serializable

왜 필요한가?

Object → 데이터 스트림으로 변환하는 작업

Object 는 메모리 상에 존재하는 데이터인 반면 데이터 스트림은 드라이브 또는 통신 회선을 통해서 전달할 수 있기 때문에 변환 작업이 필요하다.

  • Serialization : 객체를 데이터 스트림로 변환하는 작업
  • Deserialization : 데이트 스트림을 객체로 변환하는 작업

Serializable

Serializable 인터페이스를 구현해야지만 직렬화, 역직렬화가 가능하다.

transient

직렬화되면 안 되는 값에 대해서 transient 를 사용할 수 있다.

public class UserInfo implements Serializable {
	String name;
	transient String password; // 직렬화 대상에서 제외된다.
}

ObjectOutputStream & ObjectInputStream

  • 기반 스트림을 필요로 하는 보조스트림이다. 그래서 객체를 생성할 때 입출력할 기반 스트림을 지정해주어야 한다.

  • 자바 데이터를 직렬화, 역직렬화를 해주는 클래스

  • 직렬화 예제

    public class SerialEx1 {
    
        public static void main(String[] args) throws FileNotFoundException {
            try {
                String fileName = "UserInfo.ser";
                FileOutputStream fos = new FileOutputStream(fileName);
                BufferedOutputStream bos = new BufferedOutputStream(fos);
                ObjectOutputStream out = new ObjectOutputStream(bos);
    
                UserInfo u1 = new UserInfo("JavaMan", "1234", 30);
                UserInfo u2 = new UserInfo("JavaWoman", "123456", 26);
    
                ArrayList<UserInfo> list = new ArrayList<>();
                list.add(u1);
                list.add(u2);
    
                out.writeObject(u1);
                out.writeObject(u2);
                out.writeObject(list);
                out.close();
    
                System.out.println("직렬화 잘 끝남");
    
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    직렬화는 객체에 정의된 모든 인스턴스 변수에 대한 참조를 찾아들어가기 때문에 상당히 복잡하고 시간이 걸리는 작업이 될 수 있다.

  • 역직렬화 예제

    public class SerialEx2 {
        public static void main(String[] args) {
    
            try{
                String fileName = "UserInfo.ser";
                FileInputStream fis = new FileInputStream(fileName);
                BufferedInputStream bis = new BufferedInputStream(fis);
    
                ObjectInputStream ois = new ObjectInputStream(bis);
    
                // 객체를 읽을 때는 출력한 순서와 일치해야 한다.
                UserInfo u1 = (UserInfo) ois.readObject();
                UserInfo u2 = (UserInfo) ois.readObject();
                ArrayList<UserInfo> list = (ArrayList) ois.readObject();
    
                System.out.println("u1 = " + u1);
                System.out.println("u2 = " + u2);
                System.out.println("list = " + list);
    
                ois.close();
            } catch (IOException | ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
    }

    주의할 점은 객체를 역직렬화 할 때는 직렬화할 때의 순서와 일치해야한다는 것이다.

serialVersionUID

객제의 버전을 명시하는 데 사용된다.

직렬화된 객체를 역직렬화할 때는 직렬화 했을 때와 같은 클래스를 사용해야한다.

그러나 클래스의 이름이 같더라도 클래스의 내용이 변경된 경우 역직렬화는 실패한다.

객체가 직렬화될 때 클래스에 정의된 멤버들의 정보를 이용해서 serialVersionUID 라는 클래스의 버전을 자동생성해서 직렬화 내용에 포함한다. 이 버전을 비교함으로써 클래스가 일치하는지 확인할 수 있다.

  • 왜 재정의하는가?

네크워크 간 객체를 주고 받을 때 양쪽 모두 같은 버전의 클래스를 갖고 있어야하는데 클래스가 조금만 변경되어도 재배포하는 것은 프로그램을 관리하기 어렵게 만든다.

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

public
class IOException extends Exception {
    static final long serialVersionUID = 7818375828146090155L;

궁금증

왜 Exception 자식 클래스들은 serialVersionUID 를 재정의 했을까?

최고 조상 클래스 Throwable 에서 Serializable 를 구현했다. 이 의미든 모든 자식 클래스는 역/직렬화 가능하다는 것이다.

public class Throwable implements Serializable {
	// 생략
}

커스텀 익셉션은 상태를 갖을 수 있다. serialVersionUID 재정의하지 않으면 상대 어플리케이션에서 다른 버전의 클래스로 인식하여 역/직렬화 에러가 발생할 수 있다.

  • 참고

Why my exception class needs to be serialized?

serialVersionUID Exception

자바 NIO란 ?

JDK 1.4 에서부터 NIO(New IO) 가 추가되었다.

NIO 가 생긴 이유는 단 하나다. 속도 때문이다.

NIO 는 지금까지 사용한 스트림을 사용하지 않고, 대신 채널(Channel)과 버퍼(Buffer)를 사용한다.

채널을 물건을 중간에서 처리하는 도매상이라 생각하면 되고,

버퍼는 도매상에서 물건을 사고, 소비자에게 물건을 파는 소매상으로 생각하면 된다.

  • 실습 코드
public class NioSample {

    public static void main(String[] args) {
        NioSample sample = new NioSample();
        sample.basicWriteAndRead();
    }

    private void basicWriteAndRead() {
        String fileName = "/Users/gutenlee/Library/Mobile Documents/com~apple~CloudDocs/f-lab/java/src/main/java/org/flab/chapter26/note.txt";

        try {
            writeFile(fileName, "My first NIO sample");
            readFile(fileName);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void writeFile(String fileName, String data) throws IOException {
        FileChannel channel = new FileOutputStream(fileName).getChannel();
        byte[] bytes = data.getBytes();
        ByteBuffer buffer = ByteBuffer.wrap(bytes);
        channel.write(buffer);
        channel.close();
    }

    private void readFile(String fileName) throws IOException {
        FileChannel channel = new FileInputStream(fileName).getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        channel.read(buffer);
        buffer.flip();
        while (buffer.hasRemaining()) {
            System.out.println((char)buffer.get());
        }
        channel.close();
    }
}

NIO 의 Buffer 클래스

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

*A container for data of a specific primitive type.
A buffer is a linear, finite sequence of elements of a specific primitive type. Aside from its content, the essential properties of a buffer are its capacity, limit, and position*

A buffer's capacity is the number of elements it contains. The capacity of a buffer is never negative and never changes.
A buffer's limit is the index of the first element that should not be read or written. A buffer's limit is never negative and is never greater than its capacity.
A buffer's position is the index of the next element to be read or written. A buffer's position is never negative and is never greater than its limit.

position 속성이 필요한 이유는 버퍼에 데이터를 담거나, 읽는 작업을 수행하면 현재의 위치가 이동한다. 그래야 그 다음 위치에 있는 것을 바로 쓰거나, 읽을 수 있기 때문이다.

궁금증

ByteBufferedInputStream 이 nio.Buffer 이랑 비슷한거같은데 왜 Buffer 가 더 빠른가?

A stream-oriented I/O 은 바이트 단위로 입출력하고 A block-oriented I/O ****은 메모리를 블럭 단위로 입출력 한다는거같다. 근데 아직 이해 안된다.

Getting started with new I/O (NIO), an article excerpt:

A stream-oriented I/O system deals with data one byte at a time. An input stream produces one byte of data, and an output stream consumes one byte of data. It is very easy to create filters for streamed data. It is also relatively simply to chain several filters together so that each one does its part in what amounts to a single, sophisticated processing mechanism. On the flip side, stream-oriented I/O is often rather slow.

block-oriented I/O system deals with data in blocks. Each operation produces or consumes a block of data in one step. Processing data by the block can be much faster than processing it by the (streamed) byte. But block-oriented I/O lacks some of the elegance and simplicity of stream-oriented I/O.

0개의 댓글