직렬화

내위키

Serialization.

컴퓨터의 데이터 객체를 저장 매체에 저장할 수 있는 형식, 또는 네트워크를 통해 전송할 수 있는 것으로 변환하는 것을 뜻한다. 그 반대로 변환하는 것은 De-serialization(역직렬화). 직렬화와 역직렬화는 세트로 항상 같이 가는 개념이다. 즉 직렬화된 데이터는 역직렬화를 통해 원래의 데이터로 복구할 수 있어야 한다. 다만 직렬화 → 역직렬화를 한 데이터가 원본과 완벽하게 똑같지는 않을 가능성은 있다. 버그가 없다는 가정으로, 이러한 차이는 직렬화를 한 플랫폼과 역직렬화를 한 플랫폼에 하드웨어 또는 운영체제나 프로그래밍 언어와 같은 소프트웨어에 차이가 있어서일 수도 있고, 직렬화 형식에 한계가 있어서일 수도 있다. 예를 들어 직렬화를 한 플랫폼은 소수를 지원하지만 역직렬화를 하는 플랫폼은 정수만 지원한다면 소수 데이터의 소숫점 이하 수치는 잃어버릴 것이다.

저장장치에 저장되는 데이터, 또는 네트워크를 타고 다니는 데이터는 결국은 0과 1로 이루어진 디지털 데이터의 연속이며, 이는 한 줄로 이어진 길다란 줄과도 같다. 마치 건전지를 직렬로 연결하듯이, 데이터 역시 0과 1이 직렬로 연결되어 긴 줄을 이루는 셈이다. 따라서 프로그래밍 언어에서 사용하는 변수, 구조체, 객체와 같은 데이터들을 저장장치에 저장하거나, 네트워크로 전송할 때에는 여기에 적합한 형식으로 만들어 줘야 한다.

날짜 입금액 출금액 잔액
2021-11-10 10,000 10,000
2021-11-20 50,000 60,000
2021-12-05 20,000 40,000
2021-12-20 12,345 52,345

우리가 보기에는 표로 정리되어 있지만 이것을 파일로 저장하거나 네트워크로 내보내려면 직렬화를 해야 한다.

컴퓨터의 '파일'은 직렬화의 대표적인 예다. 워드프로세서로 작업한 문서라든가 엑셀로 작업한 스프레드시트, 그리고 사진, 음악과 같은 다양한 디지털 데이터들은 결국은 저장장치 또는 네트워크로 보내야 한다. 따라서 '파일'의 형태로 변환해야 하는데 이게 직렬화다. 파일을 불러와서 프로그램 위에 문서, 시트, 사진, 음악, 영상과 같은 모습으로 띄우는 것은 역직렬화인 셈.

직렬화의 방식은 크게 두 가지로, 사람은 직접 읽고 이해하기 힘들지만 기계는 쉽게 이해할 수 있는 0과 1의 디지털 형식으로 된 이진(binary) 방식이 있고, 사람이 읽을 수 읽는(human-readable) 형태로 직렬화하는 텍스트(text) 방식이 있다. 프로그래머라면 친숙한 XML, JSON, YAML 같은 형식은 텍스트 형식 직렬화이고, 아래아한글의 HWP 파일, 워드의 DOC 혹은 DOCX 파일, 이미지를 위한 JPG, PNG, GIF 같은 형식들, 오디오를 위한 WAV, MP3, OGG, AAC 같은 형식들, 영상을 위한 AVI, MOV, MP4와 같은 형식들은 이진 방식 직렬화다.

앞에서 살펴본 표를 JSON 형식으로 직렬화 한다면 다음과 같이 될 것이다. 단, 이것은 어디까지나 한 가지의 예시이지, 같은 표라도 직렬화/역직렬화 규칙을 어떻게 작성하느냐에 따라 JSON 형식으로 나타내는 방식은 여러 가지가 있다.

[
  {
    "날짜": "2021-11-10",
    "입금액": 10000,
    "출금액": 0,
    "잔액": 10000
  },
  {
    "날짜": "2021-11-20",
    "입금액": 50000,
    "출금액": 0,
    "잔액": 60000
  },
  {
    "날짜": "2021-12-05",
    "입금액": 0,
    "출금액": 20000,
    "잔액": 40000
  },
  {
    "날짜": "2021-12-25",
    "입금액": 12345,
    "출금액": 0,
    "잔액": 52345
  }
]

스프레드시트를 텍스트 방식으로 저장하는 형식인 CSV 형식으로는 다음과 같다.

날짜,입금액,출금액,잔액,
2021-11-10,"10,000",,"10,000",
2021-11-20,"50,000",,"60,000",
2021-12-05,,"20,000","40,000",
2021-12-20,"12,345",,"52,345",

이것도 나름 사람이 보기 좋게 여러 줄로 되어 있지만, 실제 데이터는 줄바꿈은 특수문자 하나에 불과하다. CSV는 어디에서 한 줄이 끝나는지를 표시하기 위해 줄바꿈 문자를 쓰지만 JSON은 필요 없기 때문에 실제 오가는 데이터에는 줄바꿈 문자가 들어가 있지 않다. 사람이 JSON 데이터를 보는 뷰어는 알아서 보기 편한 형태로 줄바꿈을 해줄 뿐이다.

텍스트 방식의 장점은 물론 사람이 보고서 읽고 이해할 수 있다는 것. 사람이 직접 작성하거나 내용을 수정할 수 있다. 반면 이진 형식은 사람이 봐서는 뭐가 뭔지 알기 힘들다. 외계인급의 괴수라면 직접 만들거나 고치는 게 불가능하지는 않겠지만 보통 사람은 직렬화 상태에 있는 데이터는 거의 알아볼 수 없다. 반면 컴퓨터가 이해하기에는 텍스트보다는 이진 방식이 훨씬 낫다. 컴퓨터는 텍스트 형태의 데이터를 바로 이해할 수 없으며, 파싱을 거쳐야 한다. 반면 이진 형식의 데이터는 파싱이 필요 없다. 똑같은 같은 양의 데이터라고 한다면 이진 방식이 크기가 작아서 효율적이며, 데이터의 양이 많을수록 차이가 점점 벌어진다.

예를 들어 이미지의 경우, 각 픽셀의 값을 사람이 알아볼 수 있는 텍스트 값으로 한다면 최대한 효율적으로 해봐야 웹에서 사용하는 16진수 값의 연속 형태로 해야 하며[1], 이 경우 RGB 정보를 저장하려면 최소 6바이트를 필요로 한다. 반면 이진 방식이라면 3바이트면 충분하다. 또한 6바이트 값을 줄줄줄 그냥 늘어놓기만 하면 사람이 전혀 알아볼 수 없으므로 웹 컬러처럼 해시(#)를 앞에 달아주든지 띄어쓰기를 해야 하고 중간 중간 줄바꿈도 넣어줘야 그나마 알아라도 볼 수 있으니 더욱 많은 용량을 필요로 한다.[2] 사실 이렇게 해 봐야 텍스트 형태의 16진수 값으로만 뒤덮인 파일을 사람이 열어봐야 이게 무슨 이미지인지 알 수가 없다. 기계가 이해하려면 이걸 또 파싱해야 하기 때문에 이진 방식에 비해 처리 속도는 하세월로 간다. 따라서 사람이 직접 읽고 이해하거나 편집할 필요가 있으며, 비교적 용량이 작은 데이터를 다룰 때에는 텍스트 방식도 괜찮지만 그렇지 않으면 이진 방식으로 직렬화하고 사람은 이를 역직렬화해 주는 응용프로그램을 통해서 데이터를 보고 편집하는 게 낫다.

직렬화가 불가능한 데이터들도 있다. 정확히는 불가능이 아니라 무의미한 데이터다. 대표적인 게 포인터. 포인터는 어떤 데이터가 메모리 공간에서 차지하는 주소의 시작값을 가리키는 것이라서 저장장치나 네트워크로 데이터가 갈 때에는 의미가 없어진다. 예를 들어, 어떤 컴퓨터에서 A라는 메모리 주소에 저장된 변수 데이터가 네트워크를 타고 B 컴퓨터에 갔을 때에는 전혀 다른 주소에 저장될 것이다. 따라서 포인터 값을 직렬화하는 것은 아무런 의미가 없다. 필요하다면 포인터가 가리키는 메모리의 데이터를 직렬화해야 할 것이며, 실제 직렬화를 수행하는 프로그램은 그렇게 한다. 예를 들어, 클래스 A의 객체가 다른 클래스 B의 객체를 변수로 가지고 있다면, 메모리 상에는 B 객체가 포인터로 저장되어 있지만 클래스 A의 객체를 직렬화할 때에는 B 객체의 실제 데이터를 집어 넣어서 직렬화할 것이다.

public class Author {
    public String name;
    public String email;
    public String phone;

    public Author(String name, String email, String phone) {
        this.name = name;
        this.email = email;
        this.phone = phone;
    }
}

public class Book {
    public String title;
    public String publisher;
    public Author author;

    Book(String title, String publisher, Author author) {
        this.title = title;
        this.publisher = publisher;
        this.author = author;
    }
}

public class Main {

    public static void main(String[] args) {
        Author john = new Author("John Doe", "john_doe@example.com", "010-1234-5678");
        Book newBook = new Book("New Book", "Big Publishing Co.", john);

        System.out.println(newBook.author);
    }
}

위 코드를 실행해 보면, 아마 화면에 'Author@2a139a55' 같은 식으로 'Author@' 뒤에 john 객체의 해시 코드가 출력될 것이다. 즉, 컴퓨터 메모리에는 newBook.author는 john 객체의 포인터 형태로 저장되어 있다. 이 데이터를 직렬화해 봐야 아무 소용 없을 것이다. 만약 newBook 객체를 JSON으로 직렬화 한다면,

{
  "title": "New Book",
  "publisher": "Big Publishing Co.",
  "author": {
    "name": "John Doe",
    "email": "john_doe@example.com",
    "phone": "010-1234-5678"
  }
}

이렇게 포인터가 가리키는 john 객체의 실제 데이터가 포함될 것이다.

각주

  1. 예를 들어 웹 컬러로 보라색은 #EE82EE로 표기하며, 이는 각 0~255의 값을 가지는 RGB 색깔 값을 연속해서 붙여 놓은 것이므로 빨간색 EE(238), 녹색 82(130), 파란색 EE(238)를 뜻한다. RGB(238, 130, 238)로 표기하면 더 알아보기 쉽지만 그러면 픽셀 하나가 잡아먹는 용량이 훨씬 커진다.
  2. 여기에 이진 파일은 각종 압축 테크닉을 발휘해서 용량을 더 줄일 수 있지만 텍스트는 압축을 하면 이진 형식이 되어버리므로 알아볼 수 없게 되어 버린다.