#java #serialization #clojure
#java #сериализация #clojure
Вопрос:
Я ищу способ эффективно сериализовать объекты Clojure в двоичный формат, то есть не просто выполнять классическую сериализацию печати и чтения текста.
т. е. я хочу сделать что-то вроде:
(def orig-data {:name "Data Object"
:data (get-big-java-array)
:other (get-clojure-data-stuff)})
(def binary (serialize orig-data))
;; here "binary" is a raw binary form, e.g. a Java byte array
;; so it can be persisted in key/value store or sent over network etc.
;; now check it works!
(def new-data (deserialize binary))
(= new-data orig-data)
=> true
Мотивация заключается в том, что у меня есть несколько больших структур данных, которые содержат значительный объем двоичных данных (в массивах Java), и я хочу избежать накладных расходов на преобразование всего этого в текст и обратно. Кроме того, я пытаюсь сохранить формат компактным, чтобы минимизировать использование пропускной способности сети.
Конкретные функции, которые я хотел бы иметь:
- Облегченная реализация на чистом Java
- Поддержка всех стандартных структур данных Clojure, а также всех примитивов Java, массивов и т. Д.
- Нет необходимости в дополнительных шагах сборки / файлах конфигурации — я бы предпочел, чтобы это просто работало «из коробки»
- Хорошая производительность как с точки зрения требуемого времени обработки
- Компактность с точки зрения представления в двоичном коде
Каков наилучший / стандартный подход к выполнению этого в Clojure?
Комментарии:
1. В зависимости от того, что вы кодируете и как вы его кодируете, текст может быть быстрее и компактнее, чем некоторые двоичные форматы. Часто накладными расходами при сериализации является использование отражения, а не преобразования, поэтому необходимость поддержки произвольных структур данных, скорее всего, будет вашей реальной проблемой. Это сравнивает сериализацию с консервированной и Java, vanillajava.blogspot.com/2011/08 /…
2. Должен ли он иметь возможность обрабатывать ссылки на объекты среды выполнения, такие как атомы и ссылки?
3. Я добился большого успеха, используя Kryo с Cascalog, ElephantDB и Storm. carbonite от revelytix имеет готовые сериализаторы для большинства структур данных Clojure.
Ответ №1:
Возможно, я что-то здесь упускаю, но что не так со стандартной сериализацией Java? Слишком медленный, слишком большой, что-то еще?
Оболочка Clojure для простой сериализации Java может быть примерно такой:
(defn serializable? [v]
(instance? java.io.Serializable v))
(defn serialize
"Serializes value, returns a byte array"
[v]
(let [buff (java.io.ByteArrayOutputStream. 1024)]
(with-open [dos (java.io.ObjectOutputStream. buff)]
(.writeObject dos v))
(.toByteArray buff)))
(defn deserialize
"Accepts a byte array, returns deserialized value"
[bytes]
(with-open [dis (java.io.ObjectInputStream.
(java.io.ByteArrayInputStream. bytes))]
(.readObject dis)))
user> (= (range 10) (deserialize (serialize (range 10))))
true
Существуют значения, которые не могут быть сериализованы, например, Java streams и Clojure atom / agent / future, но это должно работать для большинства простых значений, включая примитивы Java и массивы, а также функции, коллекции и записи Clojure.
Зависит от того, действительно ли вы что-то сохраняете. В моем ограниченном тестировании на небольших наборах данных сериализация в текст и двоичный файл занимает примерно одно и то же время и пространство.
Но для особого случая, когда основная часть данных представляет собой массивы примитивов Java, сериализация Java может быть на несколько порядков быстрее и сэкономить значительный объем пространства. (Быстрый тест на ноутбуке, 100 тыс. случайных байт: сериализация 0,9 мс, 100 КБ; текст 490 мс, 700 КБ.)
Обратите внимание, что (= new-data orig-data)
тест не работает для массивов (он делегирует Java equals
, который для массивов просто проверяет, является ли это одним и тем же объектом), поэтому вам может потребоваться / понадобиться написать собственную функцию равенства для проверки сериализации.
user> (def a (range 10))
user> (= a (range 10))
true
user> (= (into-array a) (into-array a))
false
user> (.equals (into-array a) (into-array a))
false
user> (java.util.Arrays/equals (into-array a) (into-array a))
true
Комментарии:
1. интересно — раньше у меня был плохой опыт с сериализацией Java (слишком медленный, раздутый размер сообщений), но из ваших тестов кажется, что это может действительно хорошо работать для больших массивов.
2. @mikera Я думаю, что основная проблема с сериализацией Java — это неожиданные зависимости, поэтому вы можете в конечном итоге сериализовать половину JVM. Но если вы придерживаетесь простых значений, это довольно хорошо. В этом блоге было обнаружено, что сериализация Java (немного) быстрее и меньше, чем Google protobufs для простого POJO.
Ответ №2:
Nippy — один из лучших вариантов imho: https://github.com/ptaoussanis/nippy
Ответ №3:
Рассматривали ли вы protobuf от Google? Возможно, вы захотите проверить репозиторий GitHub с интерфейсом для Clojure.
Комментарии:
1. интересно …. приятно видеть, что для этого есть оболочка Clojure! Однако, если я не ошибаюсь, это не позволяет сериализовать произвольные объекты Clojure, т. Е. Вы должны предварительно указать свою структуру в файле .proto?
2. Я рад, что вы нашли это полезным! К сожалению, я не понимаю, что вы подразумеваете под arbitraty , но в любом случае, насколько я понимаю,
.proto
файл находится именно там, где вы определяете свои структуры данных (например: схема), все остальное делается за вас API.3. Я имею в виду: я хочу иметь возможность сериализовать и десериализовать структуры Clojure без необходимости заранее определять структуры данных. Похоже, это должно быть возможно, поскольку все структуры данных Clojure — это просто карты, наборы, списки и т.д. с добавлением нескольких базовых объектов Java. Поскольку многие структуры данных динамически создаются во время выполнения в Clojure, практически невозможно указать их все заранее…….
Ответ №4:
Если у вас нет схемы заранее, сериализация в текст, вероятно, ваш лучший выбор. Чтобы сериализовать произвольные данные в целом, вам нужно проделать большую работу по сохранению графа объектов и выполнить рефлексию, чтобы увидеть, как сериализовать everything…at по крайней мере, принтер Clojure может выполнять статический поиск без отражения print-method
для каждого элемента.
И наоборот, если вам действительно нужен оптимизированный проводной формат, вам необходимо определить схему. Я использовал thrift из java и protobuf из clojure: ни то, ни другое не доставляет большого удовольствия, но это не ужасно обременительно, если вы планируете заранее.