Guava par l'exemple (3/3) : I/O
Dans ce troisième et dernier article, je vous propose de découvrir les fonctionnalités de Guava relatives à la gestio des entrées/sorties (I/O).
Que ce soit pour réaliser des opérations systèmes (copie de fichier, déplacement…), ou pour travailler avec des fichiers textuels ou binaires, le package com.google.common.io
regorge de fonctionnalités pratiques.
Cet article fait partie d’une série :
Opérations systèmes
La classe utilitaire Files
fournit une API de haut niveau pour copier, déplacer, supprimer des fichiers et des répertoires (en attendant que Java 7 et son API Path
se démocratisent en entreprise) :
copy
et move
, qui acceptent des File
ou des streams
deleteDirectoryContents
et deleteRecursively
pour détruire des répertoires (même non vides).
Il est également possible de comparer deux fichiers bit à bit, et de calculer leur empreinte (CRC, MD5, SHA…) avec :
equal
, pour comparer deux fichiers de manière binaire
getChecksum
et getDigest
pour s’assurer de l’intégrité des fichiers
File tmpDir = new File(System.getProperty("java.io.tmpdir")+"/guava"); File sourceFile = new File(tmpDir, "source.txt"); File destFile = new File(tmpDir, "dest.txt"); File tmpSubDir = new File(tmpDir, "/subdir"); @Before public void prepareTest() throws IOException { tmpDir.mkdir(); tmpSubDir.mkdir(); sourceFile.delete(); assertTrue(sourceFile.createNewFile()); destFile.delete(); FileWriter fw = new FileWriter(sourceFile); fw.write("Hello World"); fw.close(); } @Test public void copyFile() throws IOException { assertTrue(sourceFile.exists()); assertFalse(destFile.exists()); Files.copy(sourceFile, destFile); assertTrue(sourceFile.exists()); assertTrue(destFile.exists()); } @Test public void moveFile() throws IOException { assertTrue(sourceFile.exists()); assertFalse(destFile.exists()); Files.move(sourceFile, destFile); assertFalse(sourceFile.exists()); assertTrue(destFile.exists()); } @Test public void deleteDirectories() throws IOException { Files.deleteDirectoryContents(tmpDir); assertFalse(sourceFile.exists()); assertFalse(destFile.exists()); assertTrue(tmpSubDir.exists()); Files.deleteRecursively(tmpDir); assertFalse(sourceFile.exists()); assertFalse(destFile.exists()); assertFalse(tmpSubDir.exists()); } @Test public void hashes() throws IOException, NoSuchAlgorithmException { long checksum = Files.getChecksum(sourceFile, new CRC32()); assertEquals(1243066710, checksum); byte[] digest = Files.getDigest(sourceFile, MessageDigest.getInstance("SHA")); assertArrayEquals( new byte[]{10, 77, 85, -88, -41, 120, -27, 2, 47, -85, 112, 25, 119, -59, -40, 64, -69, -60, -122, -48}, digest); } @Test public void equal() throws IOException { Files.copy(sourceFile, destFile); assertTrue(sourceFile.exists()); assertTrue(destFile.exists()); assertTrue(Files.equal(sourceFile, destFile)); }
I/O Texte
La lecture de fichiers textes a toujours été un peu pénible en Java. Guava fournit donc des méthodes très pratiques, qui simplifient ou masquent l’utilisation du BufferedReader
.
En écriture, nous avons donc :
append
pour ajouter immédiatement du texte à la fin d’un fichier.
newWriter
pour obtenir un BufferedWriter
permettant d’écrire dans le fichier. Attention toutefois, le _writer_ n’ajoute pas de texte au fichier, il écrase son contenu. Pour compléter un fichier existant, utilisez append
.
Et en lecture :
newReader
renvoie un BufferedReader
prêt à l’emploi.
readFirstLine
pour ne lire que la première ligne du fichier.
readLines
, qui propose deux versions : la première renvoyant une List<String>
représentant les lignes du fichier, et la seconde acceptant un LineProcessor
permettant de traiter les lignes au fur et à mesure de leur lecture.
toString
aspire tout le contenu du fichier dans un simple String
.
public static final Charset CHARSET = Charset.forName("UTF-8"); public static final String DALTONS = "Joe Dalton\nJack Danton\nWilliam Dalton\nAverell Dalton"; File tmpDir = new File(System.getProperty("java.io.tmpdir")+"/guava"); File file = new File(tmpDir, "test.txt"); @Before public void prepareTest() throws IOException { tmpDir.mkdir(); file.delete(); assertTrue(file.createNewFile()); FileWriter fw = new FileWriter(file); fw.write(DALTONS); fw.close(); } @Test public void append() throws IOException { assertTrue(file.exists()); Files.append("\nMa Dalton", file, CHARSET); BufferedReader reader = new BufferedReader(new FileReader(file)); String line = reader.readLine(); reader.close(); assertNotNull(line); assertEquals(DALTONS +"\nMa Dalton", line); } @Test public void newBufferedWriter() throws IOException { // Warning : the returned Writer does not append to the file, it overwrites. // To append, use Files.append() instead BufferedWriter writer = Files.newWriter(file, CHARSET); writer.write("Ma Dalton"); writer.close(); BufferedReader reader = new BufferedReader(new FileReader(file)); String line = reader.readLine(); assertNull(reader.readLine()); reader.close(); assertNotNull(line); assertEquals("Ma Dalton", line); } @Test public void newBufferedReader() throws IOException { BufferedReader reader = Files.newReader(file, CHARSET); String line = reader.readLine(); reader.close(); assertNotNull(line); assertEquals("Joe Dalton", line); } @Test public void readFirstLine() throws IOException { String line = Files.readFirstLine(file, CHARSET); assertNotNull(line); assertEquals("Joe Dalton", line); } @Test public void readLines() throws IOException { List<String> lines = Files.readLines(file, CHARSET); assertNotNull(lines); assertEquals(4, lines.size()); assertEquals("Joe Dalton", lines.get(0)); } @Test public void readLinesWithLineProcessor() throws IOException { LineProcessor<Integer> charCounterProcessor = new LineProcessor<Integer>() { private int nbchars = 0; public boolean processLine(String line) throws IOException { nbchars += line.length(); return true; // Continue processing } public Integer getResult() { return nbchars; } }; Integer totalNbChars = Files.readLines(file, CHARSET, charCounterProcessor); assertEquals(DALTONS.length(), totalNbChars.intValue()); } @Test public void fileToString() throws IOException { String message = Files.toString(file, CHARSET); assertEquals(DALTONS, message); }
I/O Binaire
Les fichiers binaires ne sont pas oubliés non plus, même si les méthodes leur étant dédiées sont moins nombreuses :
toByteArray
, qui “aspire” tout le contenu du fichier dans un byte
readBytes
, qui offre la possibilité de traiter les octets lus au fur et à mesure de leur lecture, via un ByteProcessor
.
private static final byte[] DATA = {42, 42, 42, 42, 42}; File tmpDir = new File(System.getProperty("java.io.tmpdir")+"/guava"); File file = new File(tmpDir, "test.bin"); @Before public void prepareTest() throws IOException { tmpDir.mkdir(); file.delete(); assertTrue(file.createNewFile()); FileOutputStream fos = new FileOutputStream(file); fos.write(DATA); fos.close(); } @Test public void toByteArray() throws IOException { byte[] bytes = Files.toByteArray(file); assertArrayEquals(DATA, bytes); } @Test public void readBytes() throws IOException { ByteProcessor<Integer> byteAdder = new ByteProcessor<Integer>() { int total = 0; public boolean processBytes(byte[] bytes, int off, int len) throws IOException { for (int index = off; index < off + len; index++) { total += bytes[index]; } return true; // Continue processing } public Integer getResult() { return total; } }; Integer sumOfBytes = Files.readBytes(file, byteAdder); assertNotNull(sumOfBytes); assertEquals(42 * DATA.length, sumOfBytes.intValue()); }
Conclusion
Guava fait partie des “petites” librairies très pratiques, bien conçues et pouvant rendre de grands services.
Au cours de ces trois articles, nous avons exploré une sélection de classes et méthodes couvrant différents domaines : code courant, manipulation des collections, gestion des input/outputs… Et Guava propose encore plein d’autres options intéressantes que je vous encourage à découvrir.
Merci d’avoir suivi cette série !
Merci Olivier pour ce panorama des fonctionnalités de Guava, très utile au quotidien !
Comme tu l’indiques, il reste de vraies perles à découvrir dans cette lib, comme la classe com.google.common.util.concurrent.Service, indispensable pour gérer facilement le cycle de vie d’un service. Et plus récemment (depuis la version 10) une gestion simple de cache et surtout de bus d’évènement (com.google.common.eventbus), un bonheur de simplicité et d’efficacité !