web-dev-qa-db-ja.com

Javaのオブジェクトにメモリ内圧縮を実装する

このユースケースでは、オブジェクト(メモリ内)を圧縮して保存し、必要に応じてオブジェクトを解凍します。

浮動小数点ベクトルから文字列、日付まで、圧縮するデータは非常に多様です。

誰かがこれを行うための良い圧縮技術を提案できますか?

最も重要な要素として、圧縮のしやすさと解凍の速度を検討しています。

ありがとう。

35
Kichu

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();
55
WhiteFang34

以前の回答と同様ですが、DeflatorOutputStreamとInflatorInputStreamを使用することをお勧めします。それが小さい理由は、圧縮を行うだけであるのに対し、代替案はCRCチェックやヘッダーなどのファイル形式の拡張子を追加するためです。

サイズが重要な場合は、独自のシリアル化を使用することをお勧めします。これは、ObjectOutputStreamに大きなオーバーヘッドがあり、小さなオブジェクトがはるかに大きくなるためです。 (特に、圧縮されたときに、より大きなオブジェクトで改善されます)

例えばIntegerは81バイトを必要とし、圧縮はそのような少数のバイトに対してはあまり役に立ちません。これを大幅にカットすることが可能です。

9
Peter Lawrey

1つの提案は、以下のストリームの組み合わせを使用することです。

7
Jonas Kongslund

Javaでの密封されたオブジェクトの圧縮は、通常はうまくいきません...あまり良くありません。

まず、Javaオブジェクトには、不要な多くの追加情報があることを理解する必要があります。数百万のオブジェクトがある場合、このオーバーヘッドが数百万回発生します。

例として、二重リンクリストを示します。各要素には、前のポインタと次のポインタがあります+相互作用の種類の長い値(タイムスタンプ)+バイトとユーザーIDの2つの整数を格納します。ポインター圧縮を使用しているため、6バイト* 2 + 8 + 4 * 2 = 28バイトになります。 Javaは、8バイト+ 12バイトのパディングを追加します。これにより、要素あたり48バイトになります。

ここで、それぞれ20要素の1000万リストを作成します(過去3年間のユーザーの一連のクリックイベント(パターンを見つけたい))。

したがって、200Million * 48 Bytesの要素= 10GBのメモリがあります(あまり多くありません).

ガベージコレクションの横にある[OK]をクリックすると、JDKスカイロック内部のオーバーヘッドがなくなり、10GBのメモリで終わります。

次に、独自のメモリ/オブジェクトストレージを使用します。各オブジェクトが実際には単一の行である列ごとのデータテーブルとして格納します。したがって、タイムスタンプの前、次、userIdAおよびuserIdBコレクションに2億行あります。

Previousとnextは行IDを指し、4バイトになります(または、40億エントリを超える場合は5バイトになります)。

したがって、8 + 4 + 4 + 4 + 4 => 24 * 200 Mio = 4.8GB + GCの問題はありません。

タイムスタンプ列はタイムスタンプを最小最大形式で保存し、すべてのタイムスタンプは3年以内であるため、各タイムスタンプを保存するのに必要なのは5バイトだけです。ポインターは相対(+と-)に格納され、クリックシリーズはタイムリーに密接に関連しているため、クリックシリーズは約50万人のユーザー向けであるため、前と次の両方に平均2バイト、ユーザーIDには辞書のみを使用します。各3バイトのみ必要です。

したがって、5 + 2 + 2 + 3 + 3 => 15 * 200Mio => 3GB + 4 * 500k * 4 = 8MB = 3MB + 8MBのディクショナリになります。 10GBとは違うサウンドですね?

しかし、まだ終わっていません。行とデータ以外のオブジェクトがないため、各系列をテーブル行として格納し、実際に5つの値と次の5つの値へのポインター+前のポインターを格納している配列のコレクションである特別な列を使用します。

したがって、それぞれに20のエントリがある10Mioリストがあり(オーバーヘッドがあるため)、リストごとに20 *(5 + 3 + 3)+ 4 * 6(部分的に満たされた要素のオーバーヘッドが追加されます)=> 20 * 11 + 5 * 6 => 250 * 10Mio => 2,5GB +ウォーキング要素よりも速く配列にアクセスできます。

しかし、まだ終わりではありません...タイムスタンプは現在、比較的格納されており、エントリごとに3バイト+最初のエントリで5バイトを必要とします。 ->より多くの20 * 9 + 2 + 5 * 6 => 212 * 10Mio => 2,12 GBを節約します。そして今、すべてをgzip itを使用してメモリに格納します。最初に配列の長さ、すべてのタイムスタンプ、すべてのユーザーIDを格納してすべて直線的に格納できるため、1GBになります。圧縮可能なビットのパターンがあることを非常に高くしています。 。辞書を使用するので、各userIdがシリーズの一部になる可能性に従って辞書を並べ替えるだけです。

そして、すべてがテーブルであるため、ほぼ読み取り速度ですべてをデシリアライズできるため、最新のSSDで1GBをロードするには2秒かかります。これをシリアライゼーション/デシリアライゼーションで試してみてください。そうすれば、ユーザーの内部からの叫びが聞こえてきます。

したがって、シリアル化されたデータを圧縮する前に、テーブルに格納し、論理的に圧縮できるかどうか各列/プロパティを確認してください。そして最後にそれを楽しんでください。

そして、1TB(ECC)のコストは今日1万です。何でもありません。そして1TB SSD 340ユーロ。だから本当に必要がない限り、その問題に時間を無駄にしないでください。

3
Martin Kersten

これはトリッキーな問題です:

まず、ObjectOutputStreamを使用するのはおそらく答えではありません。ストリーム形式には、タイプに関連する多くのメタデータが含まれています。小さなオブジェクトをシリアル化する場合、必須のメタデータにより、カスタムのシリアル化メソッドを実装した場合でも、圧縮アルゴリズムが「均等に」実行されにくくなります。

最小限の(またはまったくない)タイプ情報を追加してDataOutputStreamを使用すると、より良い結果が得られますが、混合データは一般に、汎用圧縮アルゴリズムを使用して圧縮することができません。

圧縮率を上げるには、圧縮するデータのプロパティを確認する必要があります。例えば:

  • Dateオブジェクトは、精度が1日であることがわかっている場合、int値として表すことができます。
  • int値のシーケンスは、ランレングスでエンコードすることも、適切なプロパティがある場合はデルタでエンコードすることもできます。
  • 等々。

ただし、その方法にかかわらず、相当量の圧縮を行うには、かなりの量の作業を行う必要があります。 IMO、より良いアイデアは、オブジェクトをデータベース、データストア、またはファイルに書き込み、キャッシュを使用して頻繁に使用されるオブジェクトをメモリに保持することです。

2
Stephen C

JDKにはさまざまな圧縮アルゴリズムが実装されています。実装されているすべてのアルゴリズムについて[Java.util.Zip](http://download.Oracle.com/javase/6/docs/api/Java/util/Zip/package-summary.html)を確認してください。ただし、すべてのデータを圧縮することは良いことではありません。たとえば、基になるクラスの名前がシリアル化されたデータストリームにある場合、シリアル化された空の配列は数十バイトの長さになることがあります。また、ほとんどの圧縮アルゴリズムは、大きなデータブロックから冗長性を取り除くように設計されています。中小規模のJavaオブジェクトでは、おそらくほとんどまたはまったく利益が得られません。

2
gabuzo

私が知っている最高の圧縮技術はZipです。 JavaはZipStreamをサポートしています。必要なのは、オブジェクトをバイト配列にシリアル化してからZipすることだけです。

ヒント:ByteArrayOutputStream、DataStream、ZipOutputStreamを使用します。

2
AlexR

任意のオブジェクトを圧縮する必要がある場合、可能なアプローチはオブジェクトをバイト配列にシリアル化し、次に使用します。 [〜#〜] deflate [〜#〜] アルゴリズム(GZIPで使用されるアルゴリズム)で圧縮します。オブジェクトが必要な場合は、解凍して逆シリアル化できます。これがどれほど効率的かはわかりませんが、完全に一般的です。

1