Реализация сжатия в памяти для объектов в Java - PullRequest
31 голосов
/ 09 мая 2011

У нас есть этот вариант использования, где мы хотели бы сжимать и хранить объекты (в памяти) и распаковывать их по мере необходимости.

Данные, которые мы хотим сжать, весьма разнообразны, от векторов с плавающей точкой до строк и дат.

Может кто-нибудь предложить какую-нибудь хорошую технику сжатия, чтобы сделать это?

Мы рассматриваем простоту сжатия и скорость распаковки как наиболее важные факторы.

Спасибо.

Ответы [ 8 ]

53 голосов
/ 09 мая 2011

Если вы хотите сжать экземпляры MyObject, вы могли бы реализовать Serializable, а затем передать объекты в сжатый байтовый массив, например, так:

ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream gzipOut = new GZIPOutputStream(baos);
ObjectOutputStream objectOut = new ObjectOutputStream(gzipOut);
objectOut.writeObject(myObj1);
objectOut.writeObject(myObj2);
objectOut.close();
byte[] bytes = baos.toByteArray();

Затем, чтобы распаковать ваш byte[] обратно в объекты:

ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
GZIPInputStream gzipIn = new GZIPInputStream(bais);
ObjectInputStream objectIn = new ObjectInputStream(gzipIn);
MyObject myObj1 = (MyObject) objectIn.readObject();
MyObject myObj2 = (MyObject) objectIn.readObject();
objectIn.close();
8 голосов
/ 09 мая 2011

Аналогично предыдущим ответам, за исключением того, что я предлагаю вам использовать DeflatorOutputStream и InflatorInputStream, так как они проще / быстрее / меньше альтернатив. Причина, по которой он меньше, заключается в том, что он просто выполняет сжатие, в то время как альтернативы добавляют расширения формата файлов, такие как проверки CRC и заголовки.

Если размер важен, вам может потребоваться выполнить собственную сериализацию. Это потому, что ObjectOutputStream имеет значительные накладные расходы, что делает небольшие объекты намного больше. (Улучшается для больших объектов, особенно при сжатии)

например. Integer занимает 81 байт, и сжатие мало поможет при таком небольшом количестве байтов. Это можно значительно сократить.

7 голосов
/ 09 мая 2011

Одним из предложений может быть использование комбинации следующих потоков:

3 голосов
/ 21 февраля 2015

Сжатие искомых объектов в Java обычно не очень хорошо ... не так хорошо.

Прежде всего вам нужно понять, что объект Java имеет много дополнительной информации, которая не нужна.Если у вас есть миллионы объектов, это накладные расходы миллионы раз.

В качестве примера приведем двойной связанный список.Каждый элемент имеет предыдущий и следующий указатель + вы сохраняете длинное значение (отметка времени) + байт для вида взаимодействия и два целых числа для идентификаторов пользователя.Так как мы используем сжатие указателей, мы 6Bytes * 2 + 8 + 4 * 2 = 28Bytes.Java добавляет 8 байт + 12 байт для заполнения.Это составляет 48 байт на элемент.

Теперь мы создаем 10 миллионов списков по 20 элементов в каждом (временные ряды событий кликов пользователей за последние три года (мы хотим найти шаблоны)).

Итак, мы имеем 200 миллионов * 48 байт элементов = 10 ГБ памяти (хорошо, не так много).

Хорошо, кроме того, что сборщик мусора убивает нас и накладные расходы в скалах JDK, мы заканчиваем с 10 ГБ памяти.

Теперь давайте используем нашу собственную память / хранилище объектов.Мы храним его как таблицу данных по столбцам, где каждый объект на самом деле представляет собой одну строку.Таким образом, у нас есть 200 миллионов строк в коллекции меток времени, предыдущего, следующего, userIdA и userIdB.

Предыдущий и следующий теперь указывают на идентификаторы строк и становятся 4 байтами (или 5 байтами, если мы превышаем 4 миллиарда записей (маловероятно)).

Итак, у нас 8 + 4 + 4 + 4 + 4 => 24 * 200 Mio = 4.8GB + проблем с GC нет.

Поскольку в столбце меток времени минимальные и максимальные значения времени хранятся, а все наши метки времени находятся в пределах трех лет, нам нужно всего 5 байтов для хранения каждой из меток времени.Так как указатель теперь хранится относительно (+ и -), и из-за того, что серии кликов своевременно тесно связаны, нам нужно в среднем только 2 байта для предыдущего и следующего, а для идентификаторов пользователей мы используем словарь, поскольку серии кликов рассчитаны примерно на 500 тыс. Пользователей.нам нужно всего три байта каждый.

Итак, теперь у нас есть 5 + 2 + 2 + 3 + 3 => 15 * 200Mio => 3 ГБ + словарь 4 * 500 К * 4 = 8 МБ = 3 ГБ + 8 МБ.Звучит иначе, чем 10ГБ, верно?

Но мы еще не закончили.Поскольку у нас теперь нет никаких объектов, кроме строк и данных, мы сохраняем каждую серию в виде строки таблицы и используем специальные столбцы, представляющие собой коллекции массивов, которые на самом деле хранят 5 значений и указатель на следующие пять значений + указатель на предыдущий.

Итак, у нас есть 10 миллионов списков с 20 записями в каждом (поскольку у нас есть накладные расходы), у нас есть в списке 20 * (5 + 3 + 3) + 4 * 6 (давайте добавим некоторые накладные расходы для частично заполненных элементов) => 20 * 11+ 5 * 6 => 250 * 10Mio => 2,5 ГБ + мы можем получить доступ к массивам быстрее, чем элементы ходьбы.

Но, эй, это еще не конец ... временные метки теперь относительно хранятся, требуя всего 3 байта на запись + 5 на первую запись.-> поэтому мы экономим намного больше 20 * 9 + 2 + 5 * 6 => 212 * 10Mio => 2,12 ГБ.А теперь мы сохраняем все это в памяти, используя gzip, и мы получаем 1 ГБ, так как мы можем хранить все это линейно, сначала сохраняя длину массива, все временные метки, все идентификаторы пользователей, что очень сильно делает наличие в битах шаблонов для сжатия,Поскольку мы используем словарь, мы просто сортируем его в соответствии с пригодностью каждого userId, чтобы быть частью серии.

И так как все является таблицей, вы можете десериализовать все практически со скоростью чтения, так что 1 ГБ на современном SSD стоит 2второй, чтобы загрузить.Попробуйте это с сериализацией / десериализацией, и вы услышите внутренний крик пользователя.

Поэтому, прежде чем когда-либо сжимать сериализованные данные, сохраните их в таблицах, проверьте каждый столбец / свойство, если оно может быть логически сжато.И, наконец, получайте от этого удовольствие.

И помните, что 1 ТБ (ECC) сегодня стоит 10 КБ.Ничего.И 1 ТБ SSD 340 евро.Так что не тратьте свое время на эту проблему, если вам действительно не нужно.

2 голосов
/ 09 мая 2011

Это сложная проблема:

Во-первых, использование ObjectOutputStream, вероятно, не ответ. Формат потока включает много метаданных, связанных с типом. Если вы сериализуете небольшие объекты, обязательные метаданные затруднят алгоритм безубыточности алгоритма сжатия, даже если вы реализуете собственные методы сериализации.

Использование DataOutputStream с минимальной (или отсутствующей) добавленной информацией о типе даст лучший результат, но смешанные данные обычно не сжимаются с использованием универсальных алгоритмов сжатия.

Для лучшего сжатия вам может понадобиться посмотреть свойства сжимаемых данных. Например:

  • Date объекты могут быть представлены как int значения, если вы знаете, что с точностью до 1 дня.
  • Последовательности значений int могут быть закодированы по длине прогона или дельта-кодированы, если они имеют правильные свойства.
  • и т. Д.

Как бы вы это ни делали, вам нужно будет проделать серьезную работу, чтобы получить достаточное количество сжатия. IMO, лучшая идея - записать объекты в базу данных, хранилище данных или файл и использовать кэширование для хранения часто используемых объектов в памяти.

2 голосов
/ 09 мая 2011

В JDK реализованы различные алгоритмы сжатия.Проверьте [java.util.zip](http://download.oracle.com/javase/6/docs/api/java/util/zip/package-summary.html) для всех реализованных алгоритмов.Однако может быть нехорошо сжимать все ваши данные.Например, сериализованный пустой массив может иметь длину несколько десятков байтов, если имя базового класса находится в потоке сериализованных данных.Также большинство алгоритмов сжатия предназначены для удаления избыточности из больших блоков данных.Для малых и средних объектов Java вы, вероятно, получите очень мало или вообще не получите никакого выигрыша.

2 голосов
/ 09 мая 2011

Лучшая технология сжатия, которую я знаю, это ZIP. Java поддерживает ZipStream. Все, что вам нужно, это сериализовать ваш объект в байтовый массив и затем сжать его.

Советы: используйте ByteArrayOutputStream, DataStream, ZipOutputStream.

1 голос
/ 09 мая 2011

Если вам нужно сжать произвольные объекты, возможный подход заключается в сериализации объекта в байтовый массив и последующем использовании, например. алгоритм DEFLATE (используемый GZIP) для его сжатия. Когда вам нужен объект, вы можете распаковать и десериализовать его. Не уверен в том, насколько это будет эффективно, но оно будет полностью общим.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...