Guide de BufferedReader

1. Vue d'ensemble

BufferedReader est une classe qui simplifie la lecture de texte à partir d'un flux d'entrée de caractères. Il met les caractères en mémoire tampon afin de permettre une lecture efficace des données textuelles.

Dans ce didacticiel, nous allons voir comment utiliser la classe BufferedReader .

2. Quand utiliser BufferedReader

En général, BufferedReader est pratique si nous voulons lire du texte à partir de n'importe quel type de source d'entrée, que ce soit des fichiers, des sockets ou autre chose.

En termes simples, cela nous permet de minimiser le nombre d'opérations d'E / S en lisant des morceaux de caractères et en les stockant dans un tampon interne. Alors que le tampon contient des données, le lecteur lira à partir de celui-ci au lieu de directement à partir du flux sous-jacent.

2.1. Mise en mémoire tampon d'un autre lecteur

Comme la plupart des classes d'E / S Java, BufferedReader implémente le modèle Decorator, ce qui signifie qu'il attend un Reader dans son constructeur. De cette manière, cela nous permet d'étendre de manière flexible une instance d'une implémentation de Reader avec une fonctionnalité de mise en mémoire tampon:

BufferedReader reader = new BufferedReader(new FileReader("src/main/resources/input.txt"));

Mais, si la mise en mémoire tampon ne compte pas pour nous, nous pourrions simplement utiliser un FileReader directement:

FileReader reader = new FileReader("src/main/resources/input.txt");

En plus de la mise en mémoire tampon, BufferedReader fournit également de jolies fonctions d'aide pour la lecture des fichiers ligne par ligne . Ainsi, même s'il peut sembler plus simple d'utiliser FileReader directement, BufferedReader peut être d'une grande aide.

2.2. Mise en mémoire tampon d'un flux

En général, nous pouvons configurer BufferedReader pour prendre tout type de flux d'entréecomme source sous-jacente . Nous pouvons le faire en utilisant InputStreamReader et en l'enveloppant dans le constructeur:

BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

Dans l'exemple ci-dessus, nous lisons à partir de System.in qui correspond généralement à l'entrée du clavier. De même, nous pourrions transmettre un flux d'entrée pour la lecture à partir d'une socket, d'un fichier ou de tout type imaginable d'entrée textuelle. La seule condition préalable est qu'il existe une implémentation InputStream appropriée .

2.3. BufferedReader vs Scanner

Comme alternative, nous pourrions utiliser la classe Scanner pour obtenir les mêmes fonctionnalités qu'avec BufferedReader.

Cependant, il existe des différences significatives entre ces deux classes qui peuvent les rendre plus ou moins pratiques pour nous, selon notre cas d'utilisation:

  • BufferedReader est synchronisé (thread-safe) alors que Scanner n'est pas
  • Le scanner peut analyser les types primitifs et les chaînes à l'aide d'expressions régulières
  • BufferedReader permet de changer la taille du tampon tandis que le scanner a une taille de tampon fixe
  • BufferedReader a une taille de tampon par défaut plus grande
  • Le scanner cache IOException , tandis que BufferedReader nous oblige à le gérer
  • BufferedReader est généralement plus rapide que Scanner car il ne lit que les données sans les analyser

Dans cet esprit, si nous analysons des jetons individuels dans un fichier, Scanner se sentira un peu plus naturel que BufferedReader. Mais, juste lire une ligne à la fois est où BufferedReader brille.

Si nécessaire, nous avons également un guide sur le scanner .

3. Lecture de texte avec BufferedReader

Passons en revue tout le processus de construction, d'utilisation et de destruction d'un BufferReader correctement pour le lire à partir d'un fichier texte.

3.1. Initialisation d'un BufferedReader

Tout d' abord, nous allons créer un BufferedReader utilisant son BufferedReader (Reader) constructeur :

BufferedReader reader = new BufferedReader(new FileReader("src/main/resources/input.txt"));

Emballer le FileReader comme celui-ci est un bon moyen d'ajouter une mise en mémoire tampon en tant qu'aspect à d'autres lecteurs.

Par défaut, cela utilisera un tampon de 8 Ko. Cependant, si nous voulons mettre en mémoire tampon des blocs plus petits ou plus grands, nous pouvons utiliser le constructeur BufferedReader (Reader, int) :

BufferedReader reader = new BufferedReader(new FileReader("src/main/resources/input.txt")), 16384);

Cela définira la taille de la mémoire tampon à 16384 octets (16 Ko).

La taille optimale de la mémoire tampon dépend de facteurs tels que le type de flux d'entrée et le matériel sur lequel le code s'exécute. Pour cette raison, pour atteindre la taille de tampon idéale, nous devons la trouver nous-mêmes en expérimentant.

Il est préférable d'utiliser des puissances de 2 comme taille de tampon car la plupart des périphériques matériels ont une puissance de 2 comme taille de bloc.

Enfin, il existe un autre moyen pratique de créer un BufferedReader à l' aide de la classe d'assistance Files de l' API java.nio :

BufferedReader reader = Files.newBufferedReader(Paths.get("src/main/resources/input.txt"))

Créercomme ceci est une bonne façon de mettre en mémoire tampon si nous voulons lire un fichier car nous n'avons pas à créer manuellement un FileReader d' abord, puis à l'envelopper.

3.2. Lecture ligne par ligne

Ensuite, lisons le contenu du fichier en utilisant la méthode readLine :

public String readAllLines(BufferedReader reader) throws IOException { StringBuilder content = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { content.append(line); content.append(System.lineSeparator()); } return content.toString(); }

Nous pouvons faire la même chose que ci-dessus en utilisant la méthode lines introduite dans Java 8 un peu plus simplement:

public String readAllLinesWithStream(BufferedReader reader) { return reader.lines() .collect(Collectors.joining(System.lineSeparator())); }

3.3. Fermer le flux

Après avoir utilisé BufferedReader , nous devons appeler sa méthode close () pour libérer toutes les ressources système qui lui sont associées. Cela se fait automatiquement si nous utilisons un bloc try-with-resources :

try (BufferedReader reader = new BufferedReader(new FileReader("src/main/resources/input.txt"))) { return readAllLines(reader); }

4. Autres méthodes utiles

Concentrons-nous maintenant sur diverses méthodes utiles disponibles dans BufferedReader.

4.1. Lire un seul caractère

Nous pouvons utiliser la méthode read () pour lire un seul caractère. Lisons tout le contenu caractère par caractère jusqu'à la fin du flux:

public String readAllCharsOneByOne(BufferedReader reader) throws IOException { StringBuilder content = new StringBuilder(); int value; while ((value = reader.read()) != -1) { content.append((char) value); } return content.toString(); }

This will read the characters (returned as ASCII values), cast them to char and append them to the result. We repeat this until the end of the stream, which is indicated by the response value -1 from the read() method.

4.2. Reading Multiple Characters

If we want to read multiple characters at once, we can use the method read(char[] cbuf, int off, int len):

public String readMultipleChars(BufferedReader reader) throws IOException { int length; char[] chars = new char[length]; int charsRead = reader.read(chars, 0, length); String result; if (charsRead != -1) { result = new String(chars, 0, charsRead); } else { result = ""; } return result; }

In the above code example, we'll read up to 5 characters into a char array and construct a string from it. In the case that no characters were read in our read attempt (i.e. we've reached the end of the stream), we'll simply return an empty string.

4.3. Skipping Characters

We can also skip a given number of characters by calling the skip(long n) method:

@Test public void givenBufferedReader_whensSkipChars_thenOk() throws IOException { StringBuilder result = new StringBuilder(); try (BufferedReader reader = new BufferedReader(new StringReader("1__2__3__4__5"))) { int value; while ((value = reader.read()) != -1) { result.append((char) value); reader.skip(2L); } } assertEquals("12345", result); }

In the above example, we read from an input string which contains numbers separated by two underscores. In order to construct a string containing only the numbers, we are skipping the underscores by calling the skip method.

4.4. mark and reset

We can use the mark(int readAheadLimit) and reset() methods to mark some position in the stream and return to it later. As a somewhat contrived example, let's use mark() and reset() to ignore all whitespaces at the beginning of a stream:

@Test public void givenBufferedReader_whenSkipsWhitespacesAtBeginning_thenOk() throws IOException { String result; try (BufferedReader reader = new BufferedReader(new StringReader(" Lorem ipsum dolor sit amet."))) { do { reader.mark(1); } while(Character.isWhitespace(reader.read())) reader.reset(); result = reader.readLine(); } assertEquals("Lorem ipsum dolor sit amet.", result); }

In the above example, we use the mark() method to mark the position we just read. Giving it a value of 1 means only the code will remember the mark for one character forward. It's handy here because, once we see our first non-whitespace character, we can go back and re-read that character without needing to reprocess the whole stream. Without having a mark, we'd lose the L in our final string.

Note that because mark() can throw an UnsupportedOperationException, it's pretty common to associate markSupported() with code that invokes mark(). Though, we don't actually need it here. That's because markSupported() always returns true for BufferedReader.

Of course, we might be able to do the above a bit more elegantly in other ways, and indeed mark and reset aren't very typical methods. They certainly come in handy, though, when there is a need to look ahead.

5. Conclusion

Dans ce tutoriel rapide, nous avons appris à lire les flux d'entrée de caractères sur un exemple pratique à l'aide de BufferedReader .

Enfin, le code source des exemples est disponible à l'adresse over sur Github.