CodeGym /Cours /JAVA 25 SELF /Compression et profilage de la sérialisation

Compression et profilage de la sérialisation

JAVA 25 SELF
Niveau 45 , Leçon 4
Disponible

1. Introduction : pourquoi optimiser la sérialisation ?

Dans les applications modernes, la sérialisation est partout — des protocoles réseau aux caches distribués et aux échanges de données entre services.

La vitesse et la taille de la sérialisation sont cruciales. Si la sérialisation est lente, l’application commence à « ralentir » lors de l’enregistrement ou du chargement des données, et le réseau ou le disque restent sous‑utilisés. Si les objets deviennent trop volumineux, ils occupent beaucoup d’espace disque, mettent plus de temps à être transférés sur le réseau et exercent une charge supplémentaire sur la mémoire et la bande passante.

Les tâches typiques incluent la sauvegarde d’un grand graphe d’objets dans un fichier ou un cache, le transfert d’objets sur le réseau avec une latence minimale et une sérialisation/désérialisation rapide des données dans un système multithread.

La conclusion est simple : l’optimisation de la sérialisation n’est pas une « fonctionnalité premium », mais une pratique nécessaire pour des applications performantes et évolutives.

2. Optimisation de la taille des données sérialisées

Exclure les données inutiles : le mot‑clé transient

Par défaut, tous les champs de l’objet sont sérialisés, sauf ceux marqués transient. Si un champ n’a pas à être sauvegagé (par exemple, un cache, des données temporaires, des références à des services), marquez‑le transient :

public class User implements Serializable {
    private String name;
    private transient String sessionToken; // ne sera pas sérialisé
}

Avantages :

  • Taille de l’objet sérialisé plus petite.
  • Pas de données superflues/dangereuses dans le fichier ou sur le réseau.

Sérialisation manuelle : interface Externalizable

Si vous avez besoin d’un contrôle total sur ce qui est sérialisé et comment, implémentez l’interface Externalizable et décrivez explicitement la sérialisation (méthodes writeExternal/readExternal) :

public class Person implements Externalizable {
    private String name;
    private int age;
    private transient String secret;

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeUTF(name);
        out.writeInt(age);
        // secret n'est pas sérialisé
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException {
        name = in.readUTF();
        age = in.readInt();
    }
}

Avantages :

  • Seuls les champs nécessaires sont sérialisés.
  • On peut modifier le format de sérialisation sans perte de compatibilité.

Compression : compresser les données sérialisées

Les objets sérialisés occupent souvent beaucoup de place, surtout s’ils contiennent des chaînes répétitives et de grandes collections. On peut réduire la taille grâce à la compression.

Exemple avec GZIPOutputStream :

try (ObjectOutputStream out = new ObjectOutputStream(
         new GZIPOutputStream(new FileOutputStream("data.gz")))) {
    out.writeObject(bigObject);
}

Exemple avec ZipOutputStream :

try (ZipOutputStream zip = new ZipOutputStream(new FileOutputStream("data.zip"))) {
    zip.putNextEntry(new ZipEntry("object"));
    ObjectOutputStream out = new ObjectOutputStream(zip);
    out.writeObject(bigObject);
    out.flush();
    zip.closeEntry();
}

Avantages :

  • La taille du fichier peut diminuer fortement (surtout pour de grands graphes d’objets).
  • Moins de trafic lors du transfert sur le réseau.

Inconvénients :

  • La compression/décompression demande du temps supplémentaire (CPU).

3. Optimisation de la vitesse de sérialisation

Mise en mémoire tampon : pourquoi utiliser BufferedOutputStream et BufferedInputStream

Problème :
Sans mise en mémoire tampon, chaque appel à write() ou read() entraîne un accès système au disque ou au réseau — c’est très lent !

Solution :
Utilisez des flux tamponnés :

try (ObjectOutputStream out = new ObjectOutputStream(
         new BufferedOutputStream(new FileOutputStream("data.bin")))) {
    out.writeObject(bigObject);
}
try (ObjectInputStream in = new ObjectInputStream(
         new BufferedInputStream(new FileInputStream("data.bin")))) {
    Object obj = in.readObject();
}

Avantages :

  • Accélère considérablement l’écriture/la lecture de gros objets.
  • Réduit le nombre d’accès au disque/au réseau.

Comment cela fonctionne‑t‑il ?
Le tampon accumule les données en mémoire et les écrit par paquets, plutôt qu’octet par octet.

Copie rapide : FileChannel.transferTo

Si vous devez copier rapidement un gros fichier sérialisé, utilisez NIO et la méthode transferTo :

try (FileChannel src = new FileInputStream("data.bin").getChannel();
     FileChannel dest = new FileOutputStream("copy.bin").getChannel()) {
    src.transferTo(0, src.size(), dest);
}

Avantage :

  • La copie s’effectue au niveau du système d’exploitation, en évitant un buffering inutile dans Java — très rapide pour les gros fichiers.

4. Profilage de la sérialisation

Mesure simple du temps : System.nanoTime()

Pour une évaluation rapide des performances de la sérialisation, vous pouvez utiliser System.nanoTime() :

long start = System.nanoTime();
try (ObjectOutputStream out = new ObjectOutputStream(
         new BufferedOutputStream(new FileOutputStream("data.bin")))) {
    out.writeObject(bigObject);
}
long end = System.nanoTime();
System.out.println("Temps de sérialisation : " + (end - start) / 1_000_000 + " ms");

Avantages :

  • Simple et rapide.
  • Permet de comparer différentes variantes (avec tampon, sans tampon, avec compression, etc.).

Inconvénients :

  • Les résultats peuvent « varier » à cause du GC et des processus d’arrière‑plan.
  • Inadapté pour comparer précisément de très petites différences.

Profilage précis : JMH (Java Microbenchmark Harness)

Pour des mesures plus précises, utilisez JMH — une bibliothèque dédiée aux microbenchmarks.

Exemple de benchmark simple :

@Benchmark
public void serializeWithBuffer() throws Exception {
    try (ObjectOutputStream out = new ObjectOutputStream(
             new BufferedOutputStream(new FileOutputStream("data.bin")))) {
        out.writeObject(bigObject);
    }
}

Avantages :

  • Tient compte du warm‑up de la JVM, de l’influence du GC et du « bruit » du système d’exploitation.
  • Fournit des résultats fiables et reproductibles.

Inconvénients :

  • Nécessite une configuration et la compréhension de la méthodologie JMH.
  • Excessif pour de simples comparaisons « à vue ».

5. Pratique : comparaison du temps et de la taille de sérialisation

Faisons une mini‑expérience : sérialisons un grand graphe d’objets (par exemple, une liste de 100_000 objets avec des collections imbriquées) de différentes manières et comparons le temps et la taille du fichier.

Sérialisation sans mise en mémoire tampon ni compression

long start = System.nanoTime();
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data1.bin"))) {
    out.writeObject(bigList);
}
long end = System.nanoTime();
System.out.println("Sans tampon : " + (end - start) / 1_000_000 + " ms, taille : " +
    new File("data1.bin").length() + " octets");

Sérialisation avec mise en mémoire tampon

long start = System.nanoTime();
try (ObjectOutputStream out = new ObjectOutputStream(
         new BufferedOutputStream(new FileOutputStream("data2.bin")))) {
    out.writeObject(bigList);
}
long end = System.nanoTime();
System.out.println("Avec tampon : " + (end - start) / 1_000_000 + " ms, taille : " +
    new File("data2.bin").length() + " octets");

Sérialisation avec compression (GZIP)

long start = System.nanoTime();
try (ObjectOutputStream out = new ObjectOutputStream(
         new GZIPOutputStream(new FileOutputStream("data3.gz")))) {
    out.writeObject(bigList);
}
long end = System.nanoTime();
System.out.println("Avec compression : " + (end - start) / 1_000_000 + " ms, taille : " +
    new File("data3.gz").length() + " octets");

Analyse des résultats

Lors des tests de sérialisation, on voit à quel point la mise en mémoire tampon et la compression influencent les résultats. Les fichiers compressés deviennent généralement de 210 fois plus petits (le facteur exact dépend de la structure des données). Avec un tampon, la sérialisation est nettement plus rapide, tandis que la compression ralentit légèrement le processus, mais le gain d’espace en vaut souvent la peine.

Conclusion : pour de gros volumes de données, utilisez impérativement la mise en mémoire tampon, et si la taille est critique — ajoutez la compression.

6. Erreurs courantes lors de l’optimisation de la sérialisation

Erreur n° 1 : Ne pas utiliser de tampon — la sérialisation de gros objets devient plusieurs fois plus lente.

Erreur n° 2 : Sérialiser des données inutiles ou sensibles (par exemple, mots de passe, jetons temporaires) — utilisez toujours transient pour ces champs.

Erreur n° 3 : S’attendre à ce que la compression accélère toujours la sérialisation — en réalité, la compression réduit la taille mais peut légèrement ralentir le processus (surtout sur des CPU modestes).

Erreur n° 4 : Mesurer le temps sans tenir compte du warm‑up de la JVM et de l’influence du GC — pour des benchmarks précis, utilisez JMH.

Erreur n° 5 : Ne comparer que le temps ou que la taille — regardez toujours les deux paramètres afin de choisir l’équilibre optimal pour votre cas.

1
Mission
JAVA 25 SELF, niveau 45, leçon 4
Bloqué
Archiviste de données : compression maximale des informations répétitives
Archiviste de données : compression maximale des informations répétitives
1
Mission
JAVA 25 SELF, niveau 45, leçon 4
Bloqué
Développeur de jeu : évaluation des performances de sauvegarde de l'inventaire
Développeur de jeu : évaluation des performances de sauvegarde de l'inventaire
1
Étude/Quiz
Optimisation de la sérialisation binaire, niveau 45, leçon 4
Indisponible
Optimisation de la sérialisation binaire
Optimisation de la sérialisation binaire
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION