Blog Zenika

#CodeTheWorld

Java

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 !

Une réflexion sur “Guava par l'exemple (3/3) : I/O

  • Sébastien

    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é !

    Répondre

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

En savoir plus sur Blog Zenika

Abonnez-vous pour poursuivre la lecture et avoir accès à l’ensemble des archives.

Continue reading