image/svg+xml $ $ ing$ ing$ ces$ ces$ Res Res ea ea Res->ea ou ou Res->ou r r ea->r ch ch ea->ch r->ces$ r->ch ch->$ ch->ing$ T T T->ea ou->r

Une exploration de l'API Java pour les entrées/sorties bloquantes...

Scanner pour commencer...

Classe Scanner

Utilisation :

package fr.upem.jacosa.io;
import java.util.*;
import java.io.*;

// @run: WordLister
public class WordLister
{
	public static void main(String[] args) throws IOException
	{
		Scanner s = new Scanner(System.in);
		s.useDelimiter("[\\p{Punct}\\p{Space}]+");
		Map<String, Integer> occurrences = new TreeMap<>();
		while (s.hasNext())
		{
			String word = s.next();
			Integer i = occurrences.get(word);
			if (i == null) i = 0;
			occurrences.put(word, i+1);
		}
		for (Map.Entry<String, Integer> entry: occurrences.entrySet())
			System.out.println(entry.getKey() + ": " + entry.getValue());
	}
}

Généralités sur les flots

A propos des flots

Remarques :

Deux paradigmes : flots bloquants et non-bloquants

Chemins de fichiers

Deux classes pour représenter un chemin de fichier dans le JDK :

Création de Path

Création d'une instance de Path en utilisant la méthode statique Paths.get(String first, String... more)

Quelques exemples :

package fr.upem.jacosa.io;
import java.nio.file.Path;
import java.nio.file.Paths;

public class PathHandling 
{
	public static void main(String[] args)
	{
		// we assume that the JVM is run from the working directory /home/user
		Path path1 = Paths.get("foo", "bar");
		System.out.println("path1=" + path1); // should print the relative path "foo/bar"
		System.out.println("absolute path of path1=" + path1.toAbsolutePath()); // should print the absolute path "/home/user/foo/bar"
		Path path2 = Paths.get("xyz");
		Path path3 = path1.resolve(path2); // path3 is foo/bar/xyz
		System.out.println("path3=" + path3); // should print "foo/bar/xyz"
		System.out.println("parent of path3=" + path3.getParent()); // should print "/foo/bar"
		System.out.println("path3.equals(path1)? " + path3.getParent().equals(path1)); // should be true
		Path path4 = path1.relativize(path3);
		System.out.println("path4=" + path4);
		System.out.println("path4.equals(path2)? " + path4.equals(path2)); // should be true
		
		// what is the filesystem used for the path?
		System.out.println("filesystem of path1=" + path1.getFileSystem());
	}
}

Informations et actions sur Path

Classe Files regroupant toutes les méthodes statiques pour avoir des informations et agir sur les Path

Copier ou déplacer un fichier

Options de copie ou de déplacement CopyOption :

Créer des fichiers ou répertoires

Pour créer des nouveaux fichiers vides :

Pour créer des liens (peut échouer si le système de fichier ne le supporte pas) :

Pour créer des nouveaux répertoires :

Ecrire ou lire dans des fichiers

Possibilité de lire ou écrire des données en une seule fois dans un fichier.
⚠ Utilisable uniquement pour des petits fichiers ; sinon il faut lire/écrire les données progressivement

Pour lire des données d'un fichier :

Pour écrire des données dans un fichier :

En cas de non-connaissance du type de contenu du fichier :

Obtenir des informations sur des fichiers ou répertoires

Informations booléennes :

Attributs sur le fichier/répertoire :

Supprimer des fichiers

Pour supprimer un fichier on appelle Files.delete(Path path). Si le fichier n'existe pas une exception est levée.
Si on est incertain de l'existence du fichier, on appelle boolean Files.deleteIfExists(Path path) qui ne lève pas d'exception et retourne true si le fichier existait et a été supprimé.
Ces méthodes fonctionnent sur les répertoires uniquement si ils sont vides.

Liste des fichiers d'un répertoire

Utilisation de la méthode DirectoryStream<Path> Files.newDirectoryStream(Path dir) retournant un Iterable<Path> pour lister les fichiers.

package fr.upem.jacosa.io;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.SortedSet;
import java.util.TreeSet;

// @run: FileLister

/** List all the files of a directory (sorted by name and modification date) */
public class FileLister
{
	public static void main(String[] args) throws IOException
	{
		if (args.length < 1)
		{
			System.err.println("No argument specified");
			System.exit(-1);
		}
		final Path dir = Paths.get(args[0]);
		if (! Files.exists(dir) || ! Files.isDirectory(dir))
		{
			System.err.println(dir + " does not exist or is not a directory");
			System.exit(-1);
		}
		
		// we add the files into a SortedSet (the natural ordering on files is based on the lexicographic order of their names)
		SortedSet<Path> sortedFiles = new TreeSet<>() ;
		for (Path p: Files.newDirectoryStream(dir)) sortedFiles.add(p) ;
		
		// we use an other SortedSet with an explicit order based on the modification date of the file
		SortedSet<Path> sortedFiles2 = new TreeSet<>( (p1, p2) -> { 
			try {
				return Files.getLastModifiedTime(p1).compareTo(Files.getLastModifiedTime(p2));
			} catch (IOException e)
			{
				return 0;
			}}
		);
			
		for (Path p: Files.newDirectoryStream(dir)) sortedFiles2.add(p) ;

		System.out.println("Files sorted by name: " + sortedFiles);
		System.out.println("Files sorted by modification date: " + sortedFiles2);
	}
}

Accès à des fichiers ressources

Comment ouvrir un InputStream correspondant à un fichier ressource ?

Une fois l'InputStream obtenu, on peut le manipuler comme un flot binaire classique et le transformer par exemple un flot textuel :

InputStream is = getClass().getResourceAsStream("/foobar.txt");
try (BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"))) {
	String line = br.readLine();
	...
}

Les flots binaires

InputStream

InputStream : flot d'octets en lecture

À propos de la lecture...

/** A method to completely fill (if possible) a byte array */
public static int fillArray(final InputStream is, final byte[] array)
{
	int read = 0; // Number of already read bytes
	int c = 0; // number of bytes read for the last call to is.read()
	while (read < array.length && (c = is.read(array, read, array.length - read)) > 0)
		read += c;
	return read; // smaller than array.length iff the end of file was reached
}

Retour en arrière

Flot en lecture depuis un fichier

Fermeture des flots

package fr.upem.jacosa.io;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/** Program determining the size of a fie by reading it */
public class FileSizer
{
	public static final int BUFFER_LEN = 1024;
	
	public static void main(String[] args) throws IOException
	{
		Path filepath = Paths.get(args[0]);
		System.out.println(String.format("Size of %s using getSize1: %l", filepath, getSize1(filepath)));
		System.out.println(String.format("Size of %s using getSize2: %l", filepath, getSize2(filepath)));
	}
	
	public static long getSize1(Path path) throws IOException
	{
		byte[] buffer = new byte[BUFFER_LEN];
		try (InputStream is = Files.newInputStream(path))
		{
			long size = 0;
			for (int i = is.read(buffer); i >= 0; i = is.read(buffer))
				size += i;
			return size;
		}
	}
	
	public static long getSize2(Path path) throws IOException
	{
		return Files.size(path);
	}
}

OutputStream

OutputStream : flot d'octets en écriture

Flot en écriture vers un fichier

Exemple 1 : une méthode de copie de fichier avec InputStream et OutputStream

package fr.upem.jacosa.io;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;

public class FileCopier1
{
	public static final int BUFF_LEN = 1024;
	
	public static void transfer(InputStream is, OutputStream os) throws IOException
	{
		byte[] data = new byte[BUFF_LEN];
		for (int i = is.read(data); i >= 0; i = is.read(data))
			os.write(data, 0, i);
	}

	public static void copyFile(String source, String destination) throws IOException
	{
		try (
			InputStream is = 
				new BufferedInputStream(Files.newInputStream(Paths.get(source)));
			OutputStream os = 
				new BufferedOutputStream(Files.newOutputStream(Paths.get(destination)))
			)
		{
			transfer(is, os);
		}
	}
	
	public static void main(String[] args) throws IOException
	{
		if (args.length < 2) 
		{
			System.err.println("Usage: java FileCopier1 source destination");
			System.exit(-1);
		}
		copyFile(args[0], args[1]);
	}
}

Exemple 1 : copie facile avec Files.copy

package fr.upem.jacosa.io;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;

public class FileCopier2
{
	public static void main(String[] args) throws IOException
	{
		if (args.length < 2) 
		{
			System.err.println("Usage: java FileCopier2 source destination");
			System.exit(-1);
		}
		Path path1 = Paths.get(args[0]);
		Path path2 = Paths.get(args[1]);
		// copy using the Files.copy static method
		Files.copy(path1, path2, StandardCopyOption.COPY_ATTRIBUTES);
	}
}

RandomAccessFile

Utilisation :

Flots utiles

Flots vers/depuis un tableau d'octets

Exemple : inversion de l'ordre des octets d'un InputStream

package fr.upem.jacosa.io;
import java.io.*;

public class FileInverter
{
	public static final int BUFFER_SIZE = 1024;
	
	public static InputStream invertOrder(InputStream is) throws IOException
	{
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		is.transferTo(baos);
		byte[] content = baos.toByteArray();
		// Reverse the array
		for (int k = 0; k < content.length/2; k++)
		{
			byte tmp = content[k];
			content[k] = content[content.length - 1 - k];
			content[content.length - 1 - k] = tmp;
		}
		return new ByteArrayInputStream(content);
	}
	
	/** Invert data from the standard input and print it to the standard output */
	public static void main(String[] args) throws IOException
	{
		InputStream reversed = invertOrder(System.in);
		byte[] tab = new byte[BUFFER_SIZE];
		int r = 0;
		while ((r = reversed.read(tab)) > 0)
			System.out.write(tab, 0, r);
	}
}

Flots de données binaires

Les flots de caractères

A propos de Reader et Writer

Classe Charset

Représentation d'un jeu de caractères. Méthodes statiques utiles :

Charsets supportés

Récupérons les charset supportés sur une JVM avec la méthode Charset.availableCharsets() :

[Big5, Big5-HKSCS, EUC-JP, EUC-KR, GB18030, GB2312, GBK, IBM-Thai, IBM00858, IBM01140, IBM01141, IBM01142, IBM01143, IBM01144, IBM01145, IBM01146, IBM01147, IBM01148, IBM01149, IBM037, IBM1026, IBM1047, IBM273, IBM277, IBM278, IBM280, IBM284, IBM285, IBM290, IBM297, IBM420, IBM424, IBM437, IBM500, IBM775, IBM850, IBM852, IBM855, IBM857, IBM860, IBM861, IBM862, IBM863, IBM864, IBM865, IBM866, IBM868, IBM869, IBM870, IBM871, IBM918, ISO-2022-CN, ISO-2022-JP, ISO-2022-JP-2, ISO-2022-KR, ISO-8859-1, ISO-8859-13, ISO-8859-15, ISO-8859-2, ISO-8859-3, ISO-8859-4, ISO-8859-5, ISO-8859-6, ISO-8859-7, ISO-8859-8, ISO-8859-9, JIS_X0201, JIS_X0212-1990, KOI8-R, KOI8-U, Shift_JIS, TIS-620, US-ASCII, UTF-16, UTF-16BE, UTF-16LE, UTF-32, UTF-32BE, UTF-32LE, UTF-8, windows-1250, windows-1251, windows-1252, windows-1253, windows-1254, windows-1255, windows-1256, windows-1257, windows-1258, windows-31j, x-Big5-HKSCS-2001, x-Big5-Solaris, x-COMPOUND_TEXT, x-euc-jp-linux, x-EUC-TW, x-eucJP-Open, x-IBM1006, x-IBM1025, x-IBM1046, x-IBM1097, x-IBM1098, x-IBM1112, x-IBM1122, x-IBM1123, x-IBM1124, x-IBM1364, x-IBM1381, x-IBM1383, x-IBM300, x-IBM33722, x-IBM737, x-IBM833, x-IBM834, x-IBM856, x-IBM874, x-IBM875, x-IBM921, x-IBM922, x-IBM930, x-IBM933, x-IBM935, x-IBM937, x-IBM939, x-IBM942, x-IBM942C, x-IBM943, x-IBM943C, x-IBM948, x-IBM949, x-IBM949C, x-IBM950, x-IBM964, x-IBM970, x-ISCII91, x-ISO-2022-CN-CNS, x-ISO-2022-CN-GB, x-iso-8859-11, x-JIS0208, x-JISAutoDetect, x-Johab, x-MacArabic, x-MacCentralEurope, x-MacCroatian, x-MacCyrillic, x-MacDingbat, x-MacGreek, x-MacHebrew, x-MacIceland, x-MacRoman, x-MacRomania, x-MacSymbol, x-MacThai, x-MacTurkish, x-MacUkraine, x-MS932_0213, x-MS950-HKSCS, x-MS950-HKSCS-XP, x-mswin-936, x-PCK, x-SJIS_0213, x-UTF-16LE-BOM, X-UTF-32BE-BOM, X-UTF-32LE-BOM, x-windows-50220, x-windows-50221, x-windows-874, x-windows-949, x-windows-950, x-windows-iso2022jp]

Conversion de String en byteLes entrées/sorties bloquantes en Java (et vice-versa)

// Encoding (String towards byte[])
String str = "étrange chaîne";
byte[] encodedStrWithUTF8 = str.getBytes("UTF-8");
byte[] encodedStrWithLatin1 = str.getBytes("latin1");

// Decoding (byte[] towards String)
String decodedStr = new String(encodedStrWithUTF8, 0, encodedStrWithUTF8.length, "UTF-8");
assert(decodedStr.equals(str));

Cas problématiques :

Conversion de flot d'octets en flot de caractères

Si problème d'encodage, pas d'erreur levée par InputStreamReader et OutputStreamWriter
Remplacement par un caractère de substitution

CharsetEncoder et CharsetDecoder

Usage : avoir plus de contrôle sur les opérations de codage et décodage

Application possible : détecter heuristiquement l'encodage d'une séquence d'octets en testant tous les charsets

Versions bufferisées de Reader et Writer

Exemple : longueur moyenne d'une ligne dans un fichier texte ?

package fr.upem.jacosa.io;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/** Compute the average line length of a text file */
public class LineAverager
{
	public static float getAverageLineLength(Path path, Charset charset) throws IOException
	{
		try (BufferedReader r = Files.newBufferedReader(path, charset))
		{
			int count = 0, len = 0;
			for (String line = r.readLine(); line != null; line = r.readLine())
			{
				count++;			
				len += line.length();
			}
			return (float)len / (float)count;
		}
	}
	
	public static void main(String[] args) throws IOException
	{
		if (args.length < 2)
		{
			System.err.println("Usage: java LineAverager filepath charset");
			System.exit(-1);
		}
		Path path = Paths.get(args[0]);
		Charset charset = Charset.forName(args[1]);
		System.out.println(String.format("Average line length for %s: %f",
			path, getAverageLineLength(path, charset)));
	}
}

Interruption d'une opération bloquante d'E/S