Vous aimez ce que vous lisez sur ce blog ?
Envie d'aller plus loin avec véritable formation d'expertise en Java ?
Venez suivre ma formation Masterclasse Expertise Java !

"Même un développeur experimenté a besoin de continuer à apprendre. Et dans cette formation... j'ai appris beaucoup !" - A.G., Java Champion

Prochaine sessions inter-entreprises : 13-16 février 2018
Sessions intra-entreprises sur demande : contact[at]mokatech.net.
Inscrivez-vous vite !

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 !


Commentaires

1. Le lundi 10 octobre 2011, 09:26 par Thibault Delor

Très bonne présentation de Guava que je ne connaissais que de nom.
Petite remarque, JUnit (>4.8 il me semble) fournit la classe TemporaryFolder et l'annotation @Rule pour créer un repertoire temporaire au début du test, ca évite de se soucier la creation/destruction de ce répertoire ;)

2. Le lundi 10 octobre 2011, 09:52 par Olivier Croisier

Merci pour l'astuce, que je ne connaissais pas. Je vais regarder ça !

3. Le lundi 10 octobre 2011, 11:43 par joseph

Sympa comme article, merci :)

En passant, je citerai aussi FileBackedOutputStream, qui permet de faire d'obtenir un input stream d'un outputstream, le tout avec écriture dans un fichier si besoin afin de ne pas trop monopoliser de mémoire. Vraiment utile et bien plus pratique qu'un PipedOutputStream.

++

4. Le mercredi 12 octobre 2011, 09:45 par fcamblor

Pas si "petite" librairie que ça ... le footprint du jar guava 10.0.1 fait tout de même 1,4Mo ... ce qui me paraît pas mal pour une simple librairie de classes utilitaires.
C'est vraiment le seul inconvénient que je trouve à guava : si on ne souhaite utiliser qu'une partie des classes utilitaires (juste les io, ou les collections) il faut tout de même se tirer le tout.

Au départ de guava, il y avait plusieurs artefacts de dispo, et ils ont fait le choix de tout rassembler dans un seul artefact.
J'aurais préféré un pom "a la" spring avec les artefacts bien modularisés et un artefact type "guava-all" qui tire toutes les deps guava pour éviter les incompat entre versions de modules, ainsi que les déclarations de 50 lignes dans le pom.

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.