web-dev-qa-db-fra.com

Implémentation de la compression en mémoire pour les objets dans Java

Nous avons ce cas d'utilisation où nous aimerions compresser et stocker des objets (en mémoire) et les décompresser au fur et à mesure des besoins.

Les données que nous voulons compresser sont assez variées, des vecteurs flottants aux chaînes en passant par les dates.

Quelqu'un peut-il suggérer une bonne technique de compression pour ce faire?

Nous considérons la facilité de compression et la vitesse de décompression comme les facteurs les plus importants.

Merci.

35
Kichu

Si vous souhaitez compresser des instances de MyObject, vous pouvez le faire implémenter Serializable, puis diffuser les objets dans un tableau d'octets compressés, comme ceci:

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

Ensuite, pour décompresser votre byte[] retour aux objets:

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

Semblable aux réponses précédentes, sauf que je vous suggère d'utiliser DeflatorOutputStream et InflatorInputStream car elles sont plus simples/plus rapides/plus petites que les alternatives. La raison pour laquelle il est plus petit est qu'il ne fait que la compression alors que les alternatives ajoutent des extensions de format de fichier comme les vérifications CRC et les en-têtes.

Si la taille est importante, vous voudrez peut-être avoir une simple sérialisation de votre choix. Cela est dû au fait que ObjectOutputStream a une surcharge importante rendant les petits objets beaucoup plus grands. (Il s'améliore pour un objet plus grand, surtout lorsqu'il est compressé)

par exemple. un Integer prend 81 octets, et la compression n'aidera pas beaucoup pour un si petit nombre d'octets. Il est possible de réduire cela de manière significative.

9
Peter Lawrey

Une proposition pourrait consister à utiliser une combinaison des volets suivants:

7
Jonas Kongslund

La compression d'objets searilized dans Java n'est généralement pas bien ... pas si bien.

Tout d'abord, vous devez comprendre qu'un objet Java contient beaucoup d'informations supplémentaires non nécessaires. Si vous avez des millions d'objets, vous avez cette surcharge des millions de fois.

À titre d'exemple nous permet une double liste chaînée. Chaque élément a un pointeur précédent et un pointeur suivant + vous stockez une valeur longue (horodatage) + octet pour le type d'interaction et deux entiers pour les ID utilisateur. Puisque nous utilisons la compression du pointeur, nous sommes 6 octets * 2 + 8 + 4 * 2 = 28 octets. Java ajoute 8 octets + 12 octets pour le remplissage. Cela fait 48 octets par élément.

Maintenant, nous créons 10 millions de listes avec 20 éléments chacune (série chronologique des événements de clic des utilisateurs pour les trois dernières années (nous voulons trouver des modèles)).

Nous avons donc 200 millions * 48 octets d'éléments = 10 Go de mémoire (ok pas beaucoup).

Ok à côté de la collecte des ordures nous tue et les frais généraux à l'intérieur des gratte-ciel JDK, nous terminons avec 10 Go de mémoire.

Permet maintenant d'utiliser notre propre mémoire/stockage d'objets. Nous le stockons comme une table de données par colonne où chaque objet est en fait une seule ligne. Nous avons donc 200 millions de lignes dans une collection d'horodatage, précédente, suivante, userIdA et userIdB.

Les précédents et les suivants sont désormais des ID de ligne et deviennent 4 octets (ou 5 octets si nous dépassons 4 milliards d'entrées (peu probable)).

Nous avons donc 8 + 4 + 4 + 4 + 4 => 24 * 200 Mio = 4,8 Go + aucun problème de GC.

Étant donné que la colonne d'horodatage stocke les horodatages de manière min max et que nos horodatages sont tous dans les trois ans, nous n'avons besoin que de 5 octets pour stocker chacun des horodatages. Étant donné que le pointeur est désormais stocké relatif (+ et -) et que les séries de clics sont étroitement liées en temps opportun, nous n'avons besoin que de 2 octets en moyenne pour le précédent et le suivant et pour les identifiants utilisateur, nous utilisons un dictionnaire car les séries de clics concernent environ 500 000 utilisateurs nous n'avons besoin que de trois octets chacun.

Nous avons donc maintenant 5 + 2 + 2 + 3 + 3 => 15 * 200Mio => 3GB + Dictionnaire de 4 * 500k * 4 = 8MB = 3GB + 8MB. Sons différents de 10 Go à droite?

Mais nous n'avons pas encore fini. Puisque nous n'avons maintenant plus d'objets que des lignes et des données, nous stockons chaque série en tant que ligne de table et utilisons des colonnes spéciales comme des collections de tableaux qui stockent en fait 5 valeurs et un pointeur sur les cinq valeurs suivantes + un pointeur précédent.

Nous avons donc 10 listes de Mio avec 20 entrées chacune (puisque nous avons des frais généraux), nous avons par liste 20 * (5 + 3 + 3) + 4 * 6 (permet d'ajouter des frais généraux d'éléments partiellement remplis) => 20 * 11 + 5 * 6 => 250 * 10Mio => 2,5 Go + nous pouvons accéder aux tableaux plus rapidement que les éléments ambulants.

Mais bon, ce n'est pas encore fini ... les horodatages sont maintenant relativement stockés, ne nécessitant que 3 octets par entrée + 5 à la première entrée. -> nous économisons donc beaucoup plus 20 * 9 + 2 + 5 * 6 => 212 * 10Mio => 2,12 GB. Et maintenant, tout stocker en mémoire en utilisant gzip et nous obtenons 1 Go car nous pouvons tout stocker en premier en stockant linéairement la longueur du tableau, tous les horodatages, tous les identifiants utilisateur, ce qui fait qu'il y a beaucoup de motifs dans les bits à compresser . Puisque nous utilisons un dictionnaire, nous le trions simplement en fonction de la propension de chaque userId à faire partie d'une série.

Et comme tout est une table, vous pouvez tout désérialiser à une vitesse proche de la lecture, donc 1 Go sur un SSD moderne coûte 2 secondes à charger. Essayez ceci avec la sérialisation/désérialisation et vous pouvez entendre le cri de l'utilisateur interne.

Donc, avant de compresser des données sérialisées, stockez-les dans des tables, vérifiez chaque colonne/propriété si elle peut logiquement être compressée. Et enfin amusez-vous avec.

Et rappelez-vous que 1 To (ECC) coûte 10 000 aujourd'hui. Ce n'est rien. Et 1 To SSD 340 Euro. Alors ne perdez pas votre temps sur cette question, sauf si vous en avez vraiment besoin.

3
Martin Kersten

C'est un problème délicat:

Tout d'abord, utiliser ObjectOutputStream n'est probablement pas la réponse. Le format de flux comprend de nombreuses métadonnées liées au type. Si vous sérialisez de petits objets, les métadonnées obligatoires empêcheront l'algorithme de compression de "rentabiliser", même si vous implémentez des méthodes de sérialisation personnalisées.

L'utilisation de DataOutputStream avec des informations de type ajoutées minimales (ou aucune) donnera un meilleur résultat, mais les données mixtes ne sont généralement pas compressibles à l'aide d'algorithmes de compression à usage général.

Pour une meilleure compression, vous devrez peut-être examiner les propriétés des données que vous compressez. Par exemple:

  • Les objets Date peuvent être représentés par des valeurs int si vous savez que leur précision est de 1 jour.
  • Les séquences de valeurs int peuvent être encodées en longueur, ou encodées en delta si elles ont les bonnes propriétés.
  • etc.

Quelle que soit la façon dont vous le faites, vous devrez effectuer un travail sérieux pour obtenir une compression intéressante. OMI, une meilleure idée serait d'écrire les objets dans une base de données, un magasin de données ou un fichier et d'utiliser la mise en cache pour conserver en mémoire les objets fréquemment utilisés.

2
Stephen C

Il existe différents algorithmes de compression implémentés dans le JDK. Vérifiez la [Java.util.Zip](http://download.Oracle.com/javase/6/docs/api/Java/util/Zip/package-summary.html) pour tous les algorithmes implémentés. Cependant, il peut ne pas être judicieux de compresser toutes vos données. Par exemple, un tableau vide sérialisé peut avoir plusieurs dizaines d'octets tant que le nom de la classe sous-jacente se trouve dans le flux de données sérialisé. De plus, la plupart des algorithmes de compression sont conçus pour supprimer la redondance des gros blocs de données. Sur les petits à moyens Java Java, vous aurez probablement très peu ou pas de gain du tout.

2
gabuzo

La meilleure technologie de compression que je connaisse est Zip. Java supporte ZipStream. Tout ce dont vous avez besoin est de sérialiser votre objet dans un tableau d'octets puis de le compresser.

Conseils: utilisez ByteArrayOutputStream, DataStream, ZipOutputStream.

2
AlexR

Si vous devez compresser des objets arbitraires, une approche possible consiste à sérialiser l'objet en un tableau d'octets, puis à utiliser par exemple l'algorithme DÉGONFLER (celui utilisé par GZIP) pour le compresser. Lorsque vous avez besoin de l'objet, vous pouvez le décompresser et le désérialiser. Je ne sais pas à quel point cela serait efficace, mais ce sera complètement général.

1