Я инженер-программист, и после обсуждения с некоторыми коллегами я понял, что не очень разбираюсь в сериализации концепции. Как я понимаю, сериализация - это процесс преобразования некоторого объекта, такого как объект в ООП, в последовательность байтов, так что указанный объект может быть сохранен или передан для последующего доступа (процесс «десериализации»).
Проблема, которая у меня есть: не все ли переменные (например, примитивы int
или составные объекты) уже представлены последовательностью байтов? (Конечно, потому что они хранятся в регистрах, памяти, на диске и т. Д.)
Так что делает сериализацию такой глубокой темой? Чтобы сериализовать переменную, мы не можем просто взять эти байты в памяти и записать их в файл? Какие тонкости я пропустил?
4 bytes
на моем PDP-11, а затем попытаюсь прочитать те же четыре байта в памяти на моем macbook, они не будут одинаковыми (из-за Endianes). Таким образом, вы должны нормализовать данные для представления, которое можно расшифровать (это сериализация). То, как вы сериализуете данные, также имеет компромисс между скоростью и гибкостью, понятными человеку / машине.Ответы:
Если у вас сложная структура данных, ее представление в памяти обычно может быть разбросано по всей памяти. (Например, подумайте о бинарном дереве.)
Напротив, когда вы хотите записать его на диск, вы, вероятно, захотите иметь представление в виде (возможно, короткой) последовательности непрерывных байтов. Это то, что сериализация делает для вас.
источник
Рассмотрим граф объектов в C с узлами, определенными так:
Во время выполнения весь
Node
граф объектов будет разбросан по пространству памяти, и на один и тот же узел можно будет указывать множество разных узлов.Вы не можете просто вывести память в файл / поток / диск и назвать ее сериализованной, потому что значения указателя (которые являются адресами памяти) не могут быть десериализованы (потому что эти области памяти могут быть уже заняты, когда вы загружаете дамп обратно в память). Другая проблема с простым дампом памяти заключается в том, что вы в конечном итоге будете хранить все виды неактуальных данных и неиспользуемого пространства - на x86 процесс имеет до 4 ГБ памяти, а ОС или MMU имеют только общее представление о том, какая память на самом деле имеет смысл или нет (в зависимости от страниц памяти, назначенных процессу), поэтому
Notepad.exe
выгрузка 4 ГБ необработанных байтов на мой диск всякий раз, когда я хочу сохранить текстовый файл, кажется немного расточительной.Другая проблема связана с версионированием: что происходит, если вы сериализуете свой
Node
график в 1-й день, затем в 2-й день вы добавляете другое полеNode
(например, другое значение указателя или примитивное значение), затем в 3-й день вы десериализуете свой файл из 1 день?Вы также должны учитывать другие вещи, такие как порядок байтов. Одна из основных причин, по которой файлы MacOS и IBM / Windows / PC были несовместимы друг с другом в 1980-х и 1990-х годах, несмотря на то, что якобы создавались одними и теми же программами (Word, Photoshop и т. Д.), Заключалась в том, что на x86 / PC многобайтовые целочисленные значения были сохранены в порядке с прямым порядком байтов, но с порядком байтов на Mac - и программное обеспечение не было создано с учетом межплатформенной переносимости. В настоящее время дела обстоят лучше благодаря лучшему обучению разработчиков и нашему все более разнородному миру вычислений.
источник
Хитрость есть на самом деле уже было описаны в самом слове: « серийной лизация».
Вопрос в основном: как я могу представить произвольно сложный взаимосвязанный циклически ориентированный граф произвольно сложных объектов в виде линейной последовательности байтов?
Подумайте об этом: линейная последовательность подобна вырожденному ориентированному графу, где каждая вершина имеет ровно одно входящее и исходящее ребро (кроме «первой вершины», у которой нет входящего ребра, и «последней вершины», у которой нет исходящего ребра) , И байт явно менее сложен, чем объект .
Таким образом, кажется разумным, что при переходе от произвольно сложного графа к гораздо более ограниченному «графу» (фактически просто к списку) и от произвольно сложных объектов к простым байтам информация будет потеряна, если мы сделаем это наивно и не будем t кодировать «постороннюю» информацию каким-либо образом. И это именно то, что делает сериализация: кодировать сложную информацию в простой линейный формат.
Если вы знакомы с YAML , вы можете взглянуть на функции привязки и псевдонима, которые позволяют вам представить идею, что «один и тот же объект может появляться в разных местах» в сериализации.
Например, если у вас есть следующий график:
Вы можете представить это как список линейных путей в YAML, например:
Вы также можете представить его в виде списка смежности, или матрицы смежности, или пары, первый элемент которой представляет собой набор узлов, а второй элемент представляет собой набор пар узлов, но во всех этих представлениях необходимо иметь способ обращения назад и вперед к существующим узлам, то есть указателям , которых у вас обычно нет в файле или сетевом потоке. Все, что у вас есть, в конце концов, это байты.
(Что, кстати, означает, что вышеуказанный текстовый файл YAML также должен быть «сериализован», для этого предназначены различные кодировки символов и форматы передачи Unicode… это не строго «сериализация», просто кодировка, потому что текстовый файл уже является последовательным). / линейный список кодовых точек, но вы можете увидеть некоторые сходства.)
источник
Другие ответы уже касаются сложных графов объектов, но стоит отметить, что сериализация примитивов также нетривиальна.
Используя имена примитивных типов C для конкретности, рассмотрим:
Я сериализую
long
. Некоторое время спустя я десериализовал его, но ... на другой платформе, а теперьlong
этоint64_t
не то, чтоint32_t
я хранил. Поэтому мне нужно либо очень внимательно следить за точным размером каждого типа, который я храню, либо хранить метаданные, описывающие тип и размер каждого поля.Обратите внимание, что это другая платформа может быть той же самой платформой после будущей перекомпиляции.
Я сериализирую
int32_t
. Некоторое время спустя я десериализовал его, но ... на другой платформе, и теперь значение повреждено. К сожалению, я сохранил значение на платформе с прямым порядком байтов и загрузил ее на плате с прямым порядком байтов. Теперь мне нужно установить соглашение для моего формата, или добавить больше метаданных, описывающих постоянство каждого файла / потока / чего угодно. И, конечно же, фактически выполнять соответствующие преобразования.char
и UTF-8, а другаяwchar_t
и UTF-16.Итак, я бы сказал, что сериализация с приемлемым качеством не тривиальна даже для примитивов в непрерывной памяти. Существует множество решений по кодированию, которые необходимо либо задокументировать, либо описать с помощью встроенных метаданных.
Графы объектов добавляют еще один уровень сложности поверх этого.
источник
Есть несколько аспектов:
Читаемость по той же программе
Ваша программа как-то хранит ваши данные в виде байтов в памяти. Но он может быть произвольно разбросан по разным регистрам, с указателями, перемещающимися назад и вперед между его меньшими частями. [Править: Как уже отмечалось, физически данные более вероятны в основной памяти, чем регистр данных, но это не снимает проблему с указателем] , Просто подумайте о связанном списке целых чисел. Каждый элемент списка может храниться в совершенно другом месте, и все, что содержит список, - это указатели от одного элемента к другому. Если бы вы взяли эти данные как есть и попытались скопировать их на другой компьютер с той же программой, вы столкнулись бы с проблемами:
Читаемость другой программой
Допустим, вам удается выделить только правильные адреса на другом компьютере, чтобы ваши данные вписывались. Если ваши данные обрабатываются отдельной программой на этом компьютере (на другом языке), эта программа может иметь совершенно другое базовое понимание данных. Скажем, у вас есть объекты C ++ с указателями, но ваш целевой язык даже не поддерживает указатели на этом уровне. Опять же, у вас нет чистого способа обработки этих данных во второй программе. В результате вы получаете в памяти некоторые двоичные данные, но затем вам нужно написать дополнительный код, который оборачивает данные и каким-то образом преобразует их во что-то, с чем может работать ваш целевой язык. Похоже на десериализацию, просто ваша отправная точка теперь странный объект, разбросанный по вашей основной памяти, который отличается для разных исходных языков, вместо файла с четко определенной структурой. Конечно, то же самое, если вы попытаетесь напрямую интерпретировать двоичный файл, содержащий указатели, - вам нужно писать синтаксические анализаторы для каждого возможного способа, которым другой язык может представлять данные в памяти.
Читаемость человеком
Два самых известных современных языка сериализации для веб-сериализации (xml, json) легко понятны человеку. Вместо двоичной кучи фактическая структура и содержание данных понятны даже без программы для чтения данных. Это имеет несколько преимуществ:
источник
В дополнение к тому, что сказали другие ответы:
Иногда вы хотите сериализовать вещи, которые не являются чистыми данными.
Например, подумайте о дескрипторе файла или соединении с сервером. Даже если дескриптор файла или сокета является
int
, это число не имеет смысла при следующем запуске программы. Чтобы правильно воссоздать объекты, которые содержат дескрипторы таких вещей, вам нужно заново открыть файлы и воссоздать соединения, и решить, что делать, если это не удастся.Многие языки в наши дни поддерживают хранение анонимных функций внутри объектов, например,
onBlah()
обработчик в Javascript. Это сложно, потому что такой код может содержать ссылки на дополнительные фрагменты данных, которые, в свою очередь, должны быть сериализованы. (И еще есть проблема сериализации кода кросс-платформенным способом, который, очевидно, проще для интерпретируемых языков.) Тем не менее, даже если поддерживается только подмножество языка, он все равно может оказаться весьма полезным. Не многие механизмы сериализации пытаются сериализовать код, но смотрите serialize-javascript .В тех случаях, когда вы хотите сериализовать объект, но он содержит что-то, что не поддерживается вашим механизмом сериализации, вам нужно переписать код так, чтобы обойти это. Например, вы можете использовать перечисления вместо анонимных функций, когда существует конечное число возможных функций.
Часто вы хотите, чтобы сериализованные данные были краткими.
Если вы отправляете данные по сети или даже храните их на диске, важно сохранить небольшой размер. Один из самых простых способов добиться этого - выбросить информацию, которую можно восстановить (например, отбросить кеши, хеш-таблицы и альтернативные представления тех же данных).
Конечно, программист должен вручную выбрать то, что должно быть сохранено и то, что должно быть отброшено, и убедиться, что вещи восстанавливаются при воссоздании объекта.
Подумайте об акте спасения игры. Объекты могут содержать множество указателей на графические данные, звуковые данные и другие объекты. Но большая часть этого материала может быть загружена из файлов данных игры и не нуждается в сохранении в файле сохранения. Отказ от этого может быть трудоемким, поэтому часто остаются мелочи. Я отредактировал некоторые файлы сохранения в свое время и обнаружил данные, которые явно были избыточными, например текстовые описания элементов.
Иногда пространство не важно, но удобочитаемость - в этом случае вы можете использовать формат ASCII (возможно, JSON или XML).
источник
Давайте определим, какова последовательность байтов на самом деле. Последовательность байтов состоит из неотрицательного целого числа, называемого длиной, и некоторой произвольной функции / соответствия, которая отображает любое целое число i , которое по крайней мере равно нулю и меньше длины на значение байта (целое число от 0 до 255).
Многие из объектов, с которыми вы работаете в типичной программе, не в этой форме, потому что объекты на самом деле состоят из множества различных выделений памяти, которые находятся в разных местах в ОЗУ, и могут быть отделены друг от друга миллионами байтов материала, который вы не волнует Подумайте только о базовом связанном списке: каждый узел в списке представляет собой последовательность байтов, да, но узлы находятся в разных местах памяти вашего компьютера, и они связаны с указателями. Или просто подумайте о простой структуре, которая имеет указатель на строку переменной длины.
Причина, по которой мы хотим сериализовать структуры данных в последовательность байтов, обычно заключается в том, что мы хотим сохранить их на диске или отправить их в другую систему (например, по сети). Если вы попытаетесь сохранить указатель на диске или отправить его в другую систему, это будет довольно бесполезно, поскольку программа, читающая этот указатель, будет иметь другой набор доступных областей памяти.
источник
int seq(int i) { if (0 <= i < length) return i+1; else return -1;}
это последовательность. Так как я собираюсь сохранить это на диске?sin
в таблицу поиска, которая представляет собой последовательность чисел? Знаете ли вы, что ваша функция такая же, как эта для входов, которые нас интересуют?int seq(n) { int a[] = [1, 2, 3, 4]; return a[n]; }
Почему именно вы говорите, что мой четырехбайтовый файл является неадекватным представлением?Сложности отражают сложности данных и самих объектов. Эти объекты могут быть объектами реального мира или объектами только компьютера. Ответ в названии. Сериализация - это линейное представление многомерных объектов. Есть много проблем, кроме фрагментированной оперативной памяти.
Если вы можете сгладить 12 пятимерных массивов и некоторый программный код, сериализация также позволяет переносить всю компьютерную программу (и данные) между компьютерами. Протоколы распределенных вычислений, такие как RMI / CORBA, широко используют сериализацию для передачи данных и программ.
Рассмотрим ваш телефонный счет. Это может быть один объект, состоящий из всех ваших вызовов (список строк), суммы оплаты (целое число) и страны. Или ваш телефонный счет может быть наизнанку из вышеперечисленного и состоять из отдельных телефонных звонков, связанных с вашим именем. Каждый расклад будет выглядеть по-разному, отражая то, как ваша телефонная компания написала эту версию своего программного обеспечения, и причину, по которой объектно-ориентированные базы данных никогда не взлетали.
Некоторые части структуры могут даже не быть в памяти вообще. Если вы используете отложенное кэширование, некоторые части объекта могут ссылаться только на файл диска и загружаться только при обращении к этой части этого конкретного объекта. Это часто встречается в серьезных системах персистентности. BLOB являются хорошим примером. Getty Images может хранить огромную мегабайтную фотографию Фиделя Кастро и некоторые метаданные, такие как имя изображения, стоимость аренды и само изображение. Возможно, вы не захотите каждый раз загружать 200 МБ изображение в память, если только вы не посмотрите на него. Сериализованный, весь файл потребует более 200 МБ памяти.
Некоторые объекты вообще не могут быть сериализованы. В области программирования Java вы можете иметь программный объект, представляющий графический экран или физический последовательный порт. Там нет реальной концепции сериализации любой из них. Как бы вы отправили свой порт кому-то еще по сети?
Некоторые вещи, такие как пароли / ключи шифрования, не должны храниться или передаваться. Они могут быть помечены как таковые (volatile / transient и т. Д.), И процесс сериализации пропустит их, но они могут жить в оперативной памяти. Пропуск этих тегов - это то, как ключи шифрования непреднамеренно отправляются / сохраняются в простом ASCII.
Этот и другие ответы, почему это сложно.
источник
Да, они. Проблема здесь заключается в расположении этих байтов. Простой
int
может быть длиной 2, 4 или 8 бит. Это может быть большой или маленький порядок байтов. Он может быть без знака, подписан с дополнением 1 или даже в каком-нибудь супер экзотическом битовом кодировании, таком как negabinary.Если вы просто
int
выгружаете двоичный файл из памяти и называете его «сериализованным», вам необходимо подключить практически весь компьютер, операционную систему и вашу программу, чтобы она была десериализуемой. Или, по крайней мере, точное описание их.Сериализация простого объекта в значительной степени записывает его в соответствии с некоторыми правилами. Этих правил много, и они не всегда очевидны. Например,
xs:integer
в XML написано в Base-10. Не Base-16, не Base-9, но 10. Это не скрытое предположение, это фактическое правило. И такие правила делают сериализацию сериализацией. Потому что, по большому счету, нет правил размещения битов вашей программы в памяти .Это была лишь верхушка айсберга. Давайте рассмотрим пример последовательности этих простейших примитивов: а C
struct
. Вы могли бы подумать, чтоимеет определенную структуру памяти на данном компьютере + ОС? Ну, это не так. В зависимости от текущей
#pragma pack
настройки компилятор будет заполнять поля. При настройках по умолчанию для 32-битной компиляции обаshorts
будут заполнены до 4 байтов, поэтомуstruct
в памяти будет фактически 3 поля по 4 байта. Так что теперь вам нужно не только указать, чтоshort
это 16-битная длина, это целое число, записанное в отрицательном дополнении 1 с большим или маленьким порядком байтов. Вы также должны записать настройку упаковки структуры, с которой была скомпилирована ваша программа.В этом и заключается суть сериализации: создание набора правил и их соблюдение.
Затем эти правила могут быть расширены для принятия еще более сложных структур (таких как списки переменной длины или нелинейные данные), добавлены такие функции, как удобочитаемость, управление версиями, обратная совместимость и исправление ошибок, и т. Д. Но даже запись одного
int
достаточно сложна, если только хочу убедиться, что вы сможете прочитать его обратно надежно.источник