Pourquoi l'impression de "B" est-elle plus lente que "#" en Java ? Analyse avec exemples

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Pourquoi "B" ralentit et "#" vole ? Le mystère de la question culte Java

\n\n

Si vous avez déjà écrit de simples boucles en Java pour afficher des caractères dans la console, vous avez peut-être remarqué une bizarrerie : l'affichage de la lettre minuscule 'B' peut être nettement plus lent que celui du dièse '#'. Cet effet a un jour fait exploser Stack Overflow — la question a recueilli des milliers de vues et de votes. Aujourd'hui, nous allons comprendre ce qui se cache derrière ce phénomène et comment il est lié aux mécanismes internes de Java, des entrées-sorties console et de la mise en mémoire tampon.

\n\n

Mise en place de l'expérience

\n

Imaginez que nous exécutions deux boucles qui impriment 100 000 caractères chacune dans la console : une boucle affiche '#', l'autre 'B'. À première vue, l'opération est identique. Cependant, sur de nombreux systèmes (notamment Windows), la première boucle se termine en ~100 ms, tandis que la seconde prend plusieurs secondes. Pourquoi ?

\n

Reproduisons cela en Java.

\n\n

Code pour le test

\n
public class SlowPrintTest {\n    public static void main(String[] args) {\n        long start = System.nanoTime();\n        for (int i = 0; i < 100_000; i++) {\n            System.out.print("#");\n        }\n        long end = System.nanoTime();\n        System.out.println("\\nTemps pour '#': " + (end - start) / 1_000_000 + " ms");\n\n        start = System.nanoTime();\n        for (int i = 0; i < 100_000; i++) {\n            System.out.print("B");\n        }\n        end = System.nanoTime();\n        System.out.println("\\nTemps pour 'B': " + (end - start) / 1_000_000 + " ms");\n    }\n}
\n\n

Exécutez ce code dans un terminal, pas dans un IDE (par exemple, via java SlowPrintTest). Le résultat vous surprendra. Sur Windows avec CMD, la différence peut être de 10 à 50 fois.

\n\n

Pourquoi cela se produit-il ? La racine du mal : le rendu des polices

\n

Le problème ne vient pas de System.out.print() lui-même ni de la JVM. La raison principale réside dans la façon dont le système d'exploitation et le terminal traitent l'affichage des caractères. System.out en Java est une enveloppe autour du flux de sortie standard (stdout). Les données sont transmises à la console, mais la vitesse d'affichage à l'écran dépend de :

\n
    \n
  • La largeur du caractère — de nombreuses anciennes consoles (par exemple, CMD sous Windows) utilisent des polices à chasse fixe, mais le caractère 'B' peut nécessiter un rendu plus complexe que '#'.
  • \n
  • La mise à jour de l'écran — la console peut effectuer un réaffichage complet de la ligne lors de l'affichage de chaque caractère, plutôt que de simplement l'ajouter.
  • \n
  • La mise en mémoire tampon du terminal — certains terminaux mettent en mémoire tampon la sortie par blocs, mais si un caractère nécessite le calcul de la largeur du glyphe, cela ralentit le processus.
  • \n
\n\n

L'effet est particulièrement marqué dans l'invite de commandes Windows (CMD) avec des polices bitmap. Lorsque vous imprimez 'B', la console tente d'afficher le caractère en mode graphique, tandis que pour '#', elle utilise une approche simplifiée. Dans les terminaux modernes (Windows Terminal, iTerm2, Alacritty), la différence peut être moins perceptible, voire inexistante.

\n\n

Rôle de la mise en mémoire tampon en Java

\n

Un aspect important de l'optimisation de la sortie est l'utilisation de flux mis en mémoire tampon. System.out n'est pas mis en mémoire tampon par défaut (plus précisément, il l'est au niveau de la JVM, mais avec une petite taille). Chaque appel à print() peut déclencher un appel système write(), ce qui est très coûteux. Si nous ajoutons une mise en mémoire tampon via BufferedWriter, les performances augmentent, mais la différence entre les caractères persiste — elle se situe du côté du terminal.

\n\n
// Exemple avec mise en mémoire tampon\nBufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));\nfor (int i = 0; i < 100_000; i++) {\n    bw.write("#");\n}\nbw.flush();
\n\n

Même avec la mise en mémoire tampon, si le terminal affiche lentement 'B', le temps total augmentera. Un

Blogs

Book Recommendations