Une exploration de l'API Java pour les entrées/sorties bloquantes...
Scanner pour commencer...
Classe Scanner
- Classe bien pratique pour découper un flot en lexèmes (morceau élémentaire)
- Lexème : types primitifs, mots séparés par des blancs ou validant une regex
Utilisation :
- Instantiation à partir d'un chemin de fichier + charset, encapsulation d'un InputStream
- boolean hasNextX(), hasNext(Pattern) pour savoir si un lexème suivant est disponible
- Si élément disponible, récupération avec X nextX(), String next(Pattern)
-
Si pas d'élément suivant avec appel à next... → NoSuchElementException
- Si élément suivant invalide → InputMismatchException
- Et bien sûr ne pas oublier de libérer les ressources avec close()
- Exemple : trouver tous les mots d'un texte et les trier par ordre lexicographique (en indiquant leur nombre d'occurrences)
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
- Terminologie : flot ou flux pour le francophones, stream pour les anglophones...
- Objets représentant des séquences d'octets ou de caractères
-
Fournis par le paquetage java.io en bloquant :
-
Séquence d'octets
- accessible en lecture : java.io.InputStream
- accessible en écriture : java.io.OutputStream
-
Séquence de caractères
- accessible en lecture : java.io.Reader
- accessible en écriture : java.io.Writer
-
Séquence d'octets
Remarques :
- Il s'agit de classes abstraites Java ne pouvant être instanciées directement.
- On utilise en pratique des classes dérivées pour la manipulation de fichiers ou de flots réseau.
- La plupart des méthodes des flots peuvent lever une java.io.IOException qui doit être gérée.
Deux paradigmes : flots bloquants et non-bloquants
-
Flots bloquants (java.io) : chaque demande d'E/S est bloquante
-
Nécessite l'usage de threads (fils d'exécution) pour le traitement de plusieurs flots
- lecture : retourne après avoir lu au moins un octet
- écriture : retourne après avoir écrit toutes les données
-
Nécessite l'usage de threads (fils d'exécution) pour le traitement de plusieurs flots
- Flots non-bloquants (java.nio) : chaque demande d'E/S est non-bloquante
Chemins de fichiers
Deux classes pour représenter un chemin de fichier dans le JDK :
- Classe historique File présente dès Java 1 intégrant des méthodes pour avoir des informations sur le fichier (length(), lastModified()...)
-
Classe Path introduite par Java 7
- Pas de méthodes directes sur les objets Path pour avoir des informations
- Méthodes statiques présentes dans la classe Files pour manipuler des instances de Path
Création de Path
Création d'une instance de Path en utilisant la méthode statique Paths.get(String first, String... more)
- Paths.get("foo", "bar", "bar2") représente le chemin relatif vers le fichier foo/bar/bar2 du répertoire courant
- Un chemin relatif peut être transformé en chemin absolu : path.toAbsolutePath()
- Paths.get("/foo", "bar", "bar2") représente le chemin absolu vers le fichier /foo/bar/bar2 (fonctionne uniquement sous un système Unix)
-
Paths.get construit un chemin depuis le FileSystem par défaut
- Il est possible d'avoir d'autres FileSystem par exemple pour stocker des fichiers sur des espaces de stockage en nuage
- On peut obtenir le parent d'un path : path.parent()
- On peut résoudre un path2 relativement à un path1 : path1.resolve(path2) (opération inverse avec relativize)
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
-
Copier : méthode long Path.copy(Path source, Path destination, CopyOption.. option)
- Il existe deux autres versions de la méthode copy avec le 1er argument remplacé par un InputStream et une autre avec le 2ème argument remplacé par un OutputStream
- Déplacer : méthode long Path.copy(Path source, Path destination, CopyOption.. option)
Options de copie ou de déplacement CopyOption :
- LinkOption.NOFOLLOW_LINKS : pas de suivi des liens symboliques (le lien symbolique est copié et pas ce qu'il pointe)
- StandardCopyOption.ATOMIC_MOVE : déplacement atomique
- StandardCopyOption.COPY_ATTRIBUTES : copie des attributs du fichier original (permissions, date de modification...)
- StandardCopyOption.REPLACE_EXISTING : écrasement d'un éventuel fichier existant en destination
Créer des fichiers ou répertoires
Pour créer des nouveaux fichiers vides :
- Path Files.createFile(Path path, FileAttribute<?>... attrs) : nouveau fichier
- Path Files.createTempFile(String prefix, String suffix, FileAttribute<?>... attrs) : nouveau fichier temporaire (retourne le chemin vers ce fichier)
Pour créer des liens (peut échouer si le système de fichier ne le supporte pas) :
- Path Files.createLink(Path link, Path existing) : nouveau lien dur
- Path Files.createSymbolicLink(Path link, Path target, FileAttribute<?>... attrs) : nouveau lien symbolique
Pour créer des nouveaux répertoires :
- Path Files.createDirectories(Path dir, FileAttribute<?>... attrs) : création de nouveaux répertoires (avec création des répertoires parents si nécessaire)
- Path Files.createTempDirectory(String prefix, FileAttribute<?>... attrs) : création d'un répertoire temporaire (retourne le chemin vers le répertoire créé)
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 :
- byte[] Files.readAllBytes(Path path) : lit tout le contenu d'un fichier binaire
- List<String> readAllLines(Path path, Charset cs) : lit toutes les lignes de texte d'un fichier texte
Pour écrire des données dans un fichier :
- Path Files.write(Path path, byte[] bytes, OpenOption... options) : écrit les octets d'un tableau dans un fichier
- Path Files.write(Path path, Iterable<? extends CharSequence> lines, Charset cs, OpenOption... options) : écrit les lignes de texte dans un fichier
En cas de non-connaissance du type de contenu du fichier :
- String Files.probeContentType(Path path) : retourne le type MIME présumé du fichier
Obtenir des informations sur des fichiers ou répertoires
Informations booléennes :
- boolean Files.exists(Path path, LinkOption... options) : y-a-t-il un élément au chemin indiqué ?
- boolean Files.isDirectory(Path path, LinkOption... options) : est-ce un répertoire ?
- boolean Files.isRegularFile(Path path, LinkOption... options) : est-ce un fichier normal ?
- boolean Files.isExecutable(Path path) : fichier exécutable ?
- boolean Files.isReadable(Path path) : a-t-on les droits en lecture ?
- boolean Files.isWritable(Path path) : a-t-on les droits en écriture ?
- boolean Files.isSameFile(Path path, Path path2) : est-ce que deux Paths designent-ils le même fichier/répertoire ?
- boolean Files.isSymbolicLink(Path path) : est-ce que le chemin désigne un lien symbolique ?
Attributs sur le fichier/répertoire :
- FileTime Files.getLastModifiedTime(Path path, LinkOption... options) : pour connaître la date de dernière modification du fichier (setter disponible)
- UserPrincipal Files.getOwner(Path path, LinkOption... options) : pour obtenir le propriétaire principal d'un fichier (setter disponible)
- Set<PosixFilePermission> Files.getPosixFilePermissions(Path path, LinkOption... options) : quelles sont les permissions sur le fichier ? (setter disponible)
- long Files.size(Path path) : quelle est la taille en octet du fichier ?
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
- Des fichiers peuvent être intégrés comme ressources d'une application Java
- Intérêt : les fichiers sont intégrés dans le paquetage JAR de distribution de l'application
- Avec Maven, les ressources doivent être placées sous le répertoire src/main/resources/ du projet
Comment ouvrir un InputStream correspondant à un fichier ressource ?
-
En utilisant getClass().getResourceAsStream(pathToTheResource) (dans un contexte statique NomDeLaClasse.class.getResourceAsStream(pathToTheResource)
- Si pathToTheResource a pour valeur /myFile.txt, le fichier doit être présent dans src/main/resources/myFile.txt
- Si pathToTheResource a pour valeur myFile.txt (sans le slash initial indiquant un chemin absolu), le fichier doit être présent dans src/main/resources/com/myorganization/mypackage/myFile.txt si la classe depuis laquelle on fait l'appel est dans le paquetage com.myorganization.mypackage
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
-
Les méthodes de lecture
- int read() : pour lire le prochain octet (sous forme d'int), retourne -1 si fin de flot
-
int read(byte[] data, int decalage, int longueur) : lit les prochains octets et les place dans data[decalage:decalage+longueur]
- retourne le nombre d'octets effectivement lus (entre 0 et longueur)
- ou -1 si la fin du flot est atteinte
- int read(byte[] data) : idem avec decalage = 0 et longueur = data.length
- long skip(long k) : saut d'au plus k octets dans le flot, retourne le nombre d'octets sautés
- int available() : retourne une sous-estimation du nombre d'octets lisibles sans bloquer
À propos de la lecture...
- Lecture bloquante d'au moins 1 octet avec les méthodes read si la fin du fichier n'est pas atteinte
- Seul cas (absurde) de lecture de 0 octet : passage d'un tableau de taille nulle
-
Remplir complètement un tableau d'octets peut nécessiter plusieurs appels à read
- Il faut donc boucler jusqu'à avoir rempli complétement le tableau ou alors attendre la fin du fichier
/** 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
- Flots conçus pour être lus séquentiellement : pas de retour en arrière possible
-
SAUF si le flot supporte les marques is.markSupported() :
- Pose d'une marque avec indication de k octets à mémoriser : is.mark(k)
-
Lecture (ou saut) jusqu'à k octets
- Possibilité de revenir en arrière sur la marque avec is.reset()
- Si is.reset() est appelée avec aucune marque préalable ou une marque trop éloignée : lancement de IOException
Flot en lecture depuis un fichier
-
java.io.FileInputStream
- Constructeur : new FileInputStream({String, File} cheminDuFichier)
- Construction depuis Files : Files.newInputStream(Path path)
-
java.io.BufferedInputStream
- Encapsule un InputStream et ajoute un niveau de bufferisation
- Supporte l'usage des marques pour un retour arrière
- Constructeur : BufferedInputStream(InputStream encapsule [, int tailleDuBuffer])
Fermeture des flots
-
Pour libérer les ressources système associées, un flot doit être fermé :
- avec la méthode void close()
- avec la syntaxe try-with-resources disponible depuis Java 1.7 (fonctionne avec toute classe implantant AutoCloseable)
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
-
Les méthodes d'écriture
- void write(int octet) : écrit un seul octet (obtenu par octet & 0xff)
- void write(byte[] data, int decalage, int longueur} : écrit dans le flot le contenu de data[decalage:decalage+longueur]
- void write(byte[] data) : idem que write(data, 0, data.length)
- void flush() : écrit effectivement les éventuelles données bufferisées (utile pour les flots réseau)
Flot en écriture vers un fichier
-
java.io.FileOutputStream
-
Constructeur : FileOutputStream(String cheminDuFichier, boolean ajout)
- Si !ajout, un éventuel fichier existant est écrasé.
- Si ajout, les données sont ajoutées en fin de fichier si le fichier existe déjà.
-
Constructeur : FileOutputStream(String cheminDuFichier, boolean ajout)
-
java.io.BufferedOutputStream
- Encapsule un OutputStream et ajoute un buffer en écriture
- Permet d'économiser les appels système en cas d'écriture de petites données
-
Constructeur : BufferedOutputStream(OutputStream encapsule, int tailleDuBuffer)
- flush() doit être appelé pour forcer la vidange du buffer dans le flot encapsulé
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
- Cette classe permet de lire et écrire aléatoirement sur un fichier
- Accès aléatoire : permet de se déplacer rapidement dans le fichier (sans consommer des données d'un flot)
- Usage possible : structures d'indexation, base de données avec lecture et écriture fréquentes
Utilisation :
-
Construction avec RandomAccessFile(String chemin, String mode)
- Modes possibles : "r" (lecture), "rw" (lecture et écriture), "rwd" (synchro données), "rws" (synchro données + métadonnées)
- void seek(long n) est utilisé pour se déplacer rapidement dans le fichier
-
Lectures et écritures similaires à un flot :
- int read(byte[] tab, int offset, int len) permet de lire des données
- void write(byte[] tab, int offset, int len) permet d'écrire des données
Flots utiles
Flots vers/depuis un tableau d'octets
- Depuis un tableau d'octets : new ByteArrayInputStream(byte[] data, int decalage, int longueur)
- Vers un tableau d'octets : new ByteArrayOutputStream() (toByteArray()} pour récupérer le 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
-
DataInputStream et DataOutputStream :
- encapsulent respectivement un InputStream et un OutputStream
- permettent d'y lire/écrire des types primitifs (boolean, byte, char, int, long) avec les méthodes X readX() et void writeX(X x)
- les données sont exprimées en grand-boutiste (octet de poids fort en tête)
Les flots de caractères
A propos de Reader et Writer
- L'utilisation de Reader et Writer est analogue à celle de InputStream et OutputStream
-
Seule différence : emploi de char à la place de byte
- char est le type primitif Java représentant un caractère UTF-16
- (certains caractères Unicode rares dits surrogates peuvent être représentés sur 2 chars)
- Writer offre la possibilité d'écrire directement des String avec void write(String str)
Classe Charset
Représentation d'un jeu de caractères. Méthodes statiques utiles :
- static Charset defaultCharset() : pour obtenir le charset par défaut du système (le plus souvent UTF-8)
- static Charset forName(String) : pour obtenir le charset d'un nom donné (un charset possède un nom canonique et possiblement des alias)
- static SortedMap<String,Charset> availableCharsets() : pour avoir un dictionnaire trié (clé : nom canonique) de tous les charsets supportés
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 :
- Une exception (UnsupportedEncodingException) peut être levée si le charset demandé est inconnu de la JVM
- L'encodage d'un caractère peut échouer si ce caractère n'existe pas dans le charset utilisé (par exemple le charset ASCII ne pourrait pas encoder la chaîne str). Le comportement de getBytes() dans cette situation n'est pas défini dans la spécification de l'API.
- Un charset peut ne pas réussir à décoder une séquence d'octets en caractère ; le comportement du constructeur de String remplace alors la séquence intraduisible en caractère par un caractère spécial de substitution.
Conversion de flot d'octets en flot de caractères
-
Convertisseurs
- D'un InputStream à un Reader : InputStreamReader
- D'un OutputStream à un Writer : OutputStreamWriter
-
Arguments des constructeurs
- Flot d'octets à convertir
- Jeu de caractères (UTF-8, ASCII, iso-8859-15...) à utiliser pour l'encodage/décodage
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
- Connaître le nombre d'octets moyen/maximal pour représenter un caractère
-
Choisir le comportement (CodingErrorAction) en cas d'erreur de codage/décodage (caractère non représentable dans le charset, séquence d'octets invalide inconvertible) :
- Remplacer par un caractère de substitution : REPLACE
- Ignorer l'erreur : IGNORE
- Lever une exception : REPLACE
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
-
java.io.BufferedReader
- Constructeur : BufferedReader(Reader in [, int tailleDuBuffer])
- Construction depuis Files : Files.newBufferedReader(Path path)
- Méthode String readLine() pour lire une ligne entière (sans caractère de fin de ligne), retourne null en fin de flot
-
java.io.BufferedWriter
- Constructeur : BufferedWriter(Writer out [, int tailleDuBuffer])
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
-
Quelquefois, blocage d'une thread sur une opération d'E/S → comment interrompre l'opération d'E/S et donc la thread ?
- En pratique, une opération d'E/S sur un flot de fichier ne bloque que rarement.
- Contrairement à une opération E/S sur un flot réseau qui peut bloquer.
-
Solutions
- Prévention contre le blocage en lecture avec available()
- Utilisation de timeout : setSoTimeout(long delayMs) sur une Socket ; provoque une SocketTimeoutException
- Fermeture "à la barbare" du flot avec close ; provoque une IOException de l'opération d'E/S en cours
- Utilisation d'un InterruptibleChannel (nio... qui ne sera pas abordé dans ce cours ;(