0% ont trouvé ce document utile (0 vote)
3 vues17 pages

Java IP Sorties

Le document présente les nouvelles fonctionnalités des entrées-sorties en Java, introduites avec java.nio, qui permettent une gestion plus performante et fine de la mémoire. Il décrit les tampons mémoire (buffers), les canaux et les jeux de caractères, ainsi que les classes abstraites associées et leurs méthodes pour manipuler les données. Enfin, il aborde la création de tampons, l'accès aux éléments, et les différences entre tampons directs et non directs.

Transféré par

orimtaalba
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
3 vues17 pages

Java IP Sorties

Le document présente les nouvelles fonctionnalités des entrées-sorties en Java, introduites avec java.nio, qui permettent une gestion plus performante et fine de la mémoire. Il décrit les tampons mémoire (buffers), les canaux et les jeux de caractères, ainsi que les classes abstraites associées et leurs méthodes pour manipuler les données. Enfin, il aborde la création de tampons, l'accès aux éléments, et les différences entre tampons directs et non directs.

Transféré par

orimtaalba
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

Les nouvelles entrées-sorties en Java Les tampons mémoire (buffers)

 Depuis SDK 1.4, [Link].* NIO (New Input Output)  Utilisés par les primitives d'entrées-sorties de
– Gestion plus fine de la mémoire [Link]
– Gestion plus performante des entrées-sorties – Remplace les tableaux utilisés en [Link]
– Gestion simplifiée des différents jeux de caractères – Zone de mémoire contiguë, permettant de stocker
– Interaction plus fine avec le système de fichiers 
une quantité de données fixée,
– Utilisation d'entrées-sorties non bloquantes (plus  d'un type primitif donné
tard) – A priori pas prévus pour accès concurrent

Nouveaux concepts dans les entrées-sorties en 
il faudra les protéger en cas de besoin
Java 
Représentés par des classes abstraites
– Buffers (tampons mémoire) [Link].* – Permet de dédier l'implémentation native à la plate-
– Charsets (jeux de caractères) forme d'accueil
[Link].*
– Channels (canaux) [Link].*

Etienne Duris © Université de Marne la Vallée - Février 2007 Page 1 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 2

Classes abstraites des tampons Allocation et accès aux tampons


 Classe abstraite Buffer  Allocation de buffer d'un type primitif donné
– factorise les opérations indépendantes du type (prim):
primitif concerné – méthodes statiques dans les classes PrimBuffer

Classe abstraite ByteBuffer  PrimBuffer allocate(int capacity)
– fournit un ensemble de méthodes et d'opérations
plus riche que pour les autres types de buffer 
Deux manières d'accéder aux éléments d'un
 Classes abstraites dédiées à des types primitifs buffer
– CharBuffer, ShortBuffer, IntBuffer, LongBuffer, – Accès aléatoire (absolute)
FloatBuffer et DoubleBuffer  Relativement à un indice (comme dans un tableau)

– Accès séquentiel (relative)



Relativement à la position courante (comme un flot)
– La position courante représente l'indice du prochain
élément à lire ou à écrire

Etienne Duris © Université de Marne la Vallée - Février 2007 Page 3 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 4
Accès aléatoire vs séquentiel Les attributs et méthodes d'un tampon
 Si PrimBuffer est un buffer d'éléments de type  Capacité: nombre d'éléments qui peuvent être
prim : contenus
 Accès aléatoire (absolute) – fixée à la création du tampon
– prim get(int index) donne l'élément à la position – consultable par int capacity()
index  Limite: indice du premier élément ne devant pas
– primBuffer put(int index, prim value) ajoute value à être atteint
l'indice index, et renvoie le buffer modifié – par défaut, égale à la capacité.
– comme [Link]() – Fixée par Buffer limit(int newLimit)
– Peuvent lever IndexOutOfBoundsException – Connue par int limit()
 Accès séquentiel (relative) 
Position courante: indice du prochain élément
– Prim get() resp. primBuffer put(prim value) accessible

Donne la valeur (resp. place value) à la position – Consultable: int position()
courante – Modifiable: Buffer position(int newPosition)
– Peuvent lever des exceptions
BufferUnderflowException ou
BufferOverflowException
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 5 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 6

Les attributs et méthodes d'un tampon Les attributs et méthodes d'un tampon
 Marque (éventuelle): position dans le tampon  Quand la position courante vaut la limite
– Buffer mark() place la marque à la position courante – Un appel à get() provoque BufferUnderflowException
– Buffer reset() place la position à la marque – Un appel à put() provoque BufferOverflowException
 ou lève InvalidMarkException 
Pour éviter ça:
– La marque est toujours inférieure à la position. – int remaining() donne le nombre d'éléments entre la
 Si la position ou la limite deviennent plus petite que la position courante et la limite
marque, la marque est effacée – boolean hasRemaining() vaut vrai si la position est

Invariant: strictement inférieure à limite
0 <= marque <= position <= limite <=
capacité

 Buffer rewind()
– met la position à 0 et supprime la marque

Etienne Duris © Université de Marne la Vallée - Février 2007 Page 7 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 8
Les attributs et méthodes "en gros" Exemple de tampon aléatoire
 Les méthodes existent en version "en gros" (bulk) final static int SIZE=32;
– Manipulent un tableau au lieu d'une variable public static void main(String[] args) {

primBuffer get(prim[] dest, int offset, int length) et // Accès aléatoire (mode tableau)
primBuffer get(prim[] dest) IntBuffer iba = [Link](SIZE);
for (int i=0; i<SIZE; i++) {
– Tentent de lire le nombre d'éléments spécifié, ou
[Link](i,2*i); // Met la valeur 2*i à l'indice i
rien si pas assez de choses à lire }
(BufferUnderflowException)

primBuffer put(prim[] src, int offset, int length) et for (int i=0; i<SIZE; i++) {
primBuffer put(prim[] src) [Link]([Link](i));
– Tentent d'écrire le nombre d'éléments spécifié, } // affiche: 0 2 4 ... 62
ou rien si pas assez de place
[Link]([Link](2)); // affiche 4
(BufferOverflowException)
[Link]([Link](31)); // affiche 62
 primBuffer put(primBuffer src) [Link]([Link](32));
– Tente d'écrire le contenu de src // lève IndexOutOfBoundsException
(BufferOverflowException) }
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 9 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 10

Exemple de tampon séquentiel Exemple d'accès "en gros" (bulk)


char[] t1 = {'a', 'b', 'c', 'd', 'e', 'f'};
// Accès séquentiel (mode flot)
// Création tampon de caractères de 6 éléments
IntBuffer ibs = [Link](SIZE); CharBuffer cb1 = [Link]([Link]);
// capacity=32 limit=32 position=0 remaining=32 [Link](t1); // recopie en gros de t1 dans le tampon
for (int i=0; i<SIZE; i++) { [Link](2); // position de cb1 en 2 (sur 'c')
[Link](2*i);
}
CharBuffer cb2 = [Link]([Link]());
// capacity=32 limit=32 position=32 remaining=0 [Link](cb1); // recopie {c, d, e, f} de cb1 vers cb2
[Link]([Link]()); // limite de cb2 après 'f'
[Link](); // remet la position courante à 0 [Link](0); // et position en 0
for (int i=0; i<SIZE; i++) {
[Link]([Link]());
// Alloue un tableau du nbre d'élts à lire dans cb2
} // affiche: 0 2 4 ... 62 char[] t2 = new char[[Link]()];
[Link](t2); // lecture du tampon vers le tableau
[Link]().limit(2);
for (int i=0; i<[Link]; i++) {
for (int i=0; i<SIZE; i++) {
[Link](t2[i]); // Affiche c d e f
[Link]([Link]()); }
} // affiche: 0, 2, puis lève BufferUnderflowException
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 11 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 12
Création d'un tampon à partir d'un Création d'un tampon à partir d'un
tampon tampon
 duplicate() retourne un tampon partagé  asReadOnlyBuffer() retourne un nouveau
– toute modification de données de l'un est vue dans tampon en lecture seule
l'autre – Les méthodes comme put() lèvent
– les attributs (position, limite, marque) du nouveau ReadOnlyException
sont initialisés à partir de l'ancien, mais chaque – Peut être testé avec isReadOnly()
tampon possède ses propres attributs  Possibilité de créer des "vues" d'un tampon

slice() retourne un tampon partagé ne d'octet (ByteBuffer) comme s'il s'agissait d'un
permettant de "voir" que ce qui reste à lire dans tampon d'un autre type
le tampon de départ – asCharBuffer(), asShortBuffer(), asIntBuffer(),
– sa capacité est égale au "remaining()" du tampon de asLongBuffer(), asFloatBuffer(), asDoubleBuffer()
départ
– La position du nouveau est 0 et la marque n'est plus
définie

Etienne Duris © Université de Marne la Vallée - Février 2007 Page 13 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 14

Exemples de duplication et partage Exemples de duplication et partage

ByteBuffer bb1 = [Link](10); char[] t1 = {'a', 'b', 'c', 'd', 'e', 'f'};
ByteBuffer bb2 = [Link](); // 2 accès possibles CharBuffer cb1 = [Link]([Link]);
for (int i=0; i<[Link](); i++){ [Link](t1); // met le contenu de t1 dans cb1
[Link]((byte)i); // remplissage par bb1 [Link](2); // place cb1 prêt à lire 'c'
} [Link](5); // place limite de cb1 après 'e'
[Link]([Link]()); // affiche 10 [Link]([Link]()); // affiche 3
[Link]([Link]()); // affiche 0
[Link]((byte)3); // déplace la position de b2 en 1 // Crée un tampon partagé pour ce qui reste dans cb1
[Link]([Link](0)); // affiche 3 CharBuffer cb2 = [Link]();
[Link]([Link]()); // affiche 10 [Link]([Link]()); // affiche 3
[Link]('x'); // met 'x' en 0 dans cb2 (remplace 'c')
ByteBuffer bb3 = [Link](); for ([Link](); [Link](); )
[Link]([Link]()); // affiche 1 [Link]([Link]()); // affiche a b x d e
[Link](); // place la position de b3 en 0
[Link]([Link]()); // affiche 3 [Link](4,'y');// met 'y' en 4 dans cb1 (remplace 'e')
[Link]((byte)4); for ([Link](); [Link](); )
// throws [Link] [Link]([Link]()); // affiche x d y
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 15 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 16
Tampons directs et non directs Tampon comme enveloppe de tableau
 Par défaut, allocate() renvoit un tampon non-  Méthode statique wrap() dans chaque classe de
direct tampon (pour chaque type primitif) pour
– Allocation classique dans le tas "garbage collecté" envelopper un tableau

[Link]() renvoit un tampon – primBuffer wrap(prim[] tab, int offset, int length) ou
direct primBuffer wrap(prim[] tab)
– La mémoire associée peut être réservée en dehors – Enveloppe la totalité du tableau: capacité vaut
du tas classique (mémoire interne de la JVM) afin [Link]
d'optimiser les opérations de lecture et d'écriture – Position du tampon produit est mise à offset (sinon 0)
natives (éviter de recopier les buffers) – Limite est mise à offset+length (sinon [Link])
– Coûteux en allocation et déallocation – Si un tampon est une enveloppe de tableau
– À réserver pour les tampons d'entrées-sorties de 
hasArray() retourne true
taille et de durée de vie importantes 
array() retourne le tableau

Une "vue" d'un tampon d'octets direct est directe  arrayOffset() retourne le décalage du tampon par
– Peut être testé par isDirect() rapport au tableau

Etienne Duris © Université de Marne la Vallée - Février 2007 Page 17 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 18

Exemple de tampon enveloppe de Méthodes utilitaires


tableau sur les tampons

int[] it = new int[10]; it[2] = 22; it[4] = 44;


 compact()
– Place l'élément à la position
IntBuffer ib = [Link](it);
courante p à la position 0,
[Link]([Link]()); // affiche 10 l'élément p+1 à la position 1, etc.
[Link]([Link](2)); // affiche 22 La nouvelle position courante est
ib = [Link](it,4,5); placée après le dernier élément
[Link]([Link]()); // affiche 4 décalé. La limite est mise à la
[Link]([Link]()); // affiche 9 capacité et la marque effacée.
[Link]([Link]()); // affiche 10  flip()
[Link]([Link]()); // affiche 0 – limite <- position courante
[Link]([Link]()); // affiche 44 position <- 0. Marque indéfinie.
// et avance la position  rewind()
IntBuffer ib2 = [Link](); – position <- 0. Marque indéfinie
[Link]([Link]()); // affiche 0
[Link]([Link]()); // affiche 4

clear() N'efface pas le contenu!
[Link]([Link]()); // affiche 4
[Link]([Link]()); // affiche 5

Etienne Duris © Université de Marne la Vallée - Février 2007 Page 19 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 20
Comparaisons de tampons Types primitifs et représentation
 Les classes des tampons implémentent l'interface  Chaque classe de tampon de prim (type primitif)
Comparable<tamponDeMemeType> permet de lire (get) et d'écrire (put) des éléments de
– Comparable uniquement avec un tampon du même
type prim
type  La classe ByteBuffer sait en plus lire et écrire
– compareTo() compare les séquences d'éléments
restants (au sens de remaining()) de manière
n'importe quel type primitif avec getPrim() ou
lexicographique (le prochain, puis le suivant, etc.) putPrim()
– Nécessité de choisir l'ordre de représentation des types
 Deux tampons sont égaux au sens de equals() si
primitifs (ordre de stockage des octets)
– 1. ils ont le même type d'éléments,
– ByteOrder.BIG_ENDIAN (gros-boutiste): octet de poids
– 2. ils ont le même nombre d'éléments restants
fort stocké à l'indice le plus petit
et
– ByteOrder.LITTLE_ENDIAN (petit-boutiste): octet de
– 3. les deux séquences d'éléments restants,
poids faible stocké à l'indice le plus petit
considérées indépendament de leurs positions
de départ, sont égales élément par élément.

Etienne Duris © Université de Marne la Vallée - Février 2007 Page 21 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 22

Ordre de représentation des tampons Exemple sur les ordres des tampons
 [Link]() donne l'ordre de [Link]([Link]()); // LITTLE_ENDIAN
[Link]([Link](134480385));
stockage natif de la plateforme // affiche 00001000 00000100 00000010 00000001 ie {8,4,2,1}
 L'ordre d'un ByteBuffer peut être consulté ou fixé ByteBuffer bb = [Link](8);
[Link]([Link]()); // BIG_ENDIAN
par order() // On écrit les 4 octets en big endian (8 en 0, 4 en 1...)

Par défaut, tout tampon d'octet (ByteBuffer) [Link]((byte)8).put((byte)4).put((byte)2).put((byte)1);
// Puis les 4 octets dans l'ordre little endian
alloué est en big endian [Link]((byte)1).put((byte)2).put((byte)4).put((byte)8);
 Tous les autres tampons sont créés avec // On prend une vue entière big endian de ce tampon d'octet
IntBuffer ibBE = ((ByteBuffer)[Link]()).asIntBuffer();
l'ordre natif [Link]([Link]()); // affiche BIG_ENDIAN
 Les "vues" d'un tampon d'octet ont l'ordre du [Link]([Link]()); // affiche 134480385
tampon d'octet au moment de la création de la // On prend une vue entière en little endian du même tampon
IntBuffer ibLE = [Link](ByteOrder.LITTLE_ENDIAN).asIntBuffer();
vue. [Link]([Link]()); // affiche LITTLE_ENDIAN
[Link]([Link](1)); // affiche 134480385
[Link]([Link](0)); // affiche 16909320...
// ... ordre inverse

Etienne Duris © Université de Marne la Vallée - Février 2007 Page 23 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 24
Les tampons de caractères Appendable et Readable (jdk1.5)
 Les tampons de caractères CharBuffer implantent – CharBuffer implante ces deux interfaces
l'interface CharSequence 
Appendable: un truc auquel on peut ajouter des
– Permet d'utiliser les expressions régulières char
directement sur les tampons (Pattern: matcher(),  Soit un caractère tout seul: Appendable append(char c)
matches(), split())  Soit tout ou partie d'une CharSequence :
 Pour toute recherche, penser à revenir au début du Appendable append(CharSequence csq) et
tampon, par exemple avec flip(), car seuls les Appendable append(CharSequence csq, int start, int
caractères restants à lire sont pris en compte end)
– toString() retourne la chaîne entre la position  Exemples: BufferedWriter, CharBuffer, FileWriter,
courante et la limite OutputStreamWriter, PrintStream, PrintWriter,
StringBuffer, StringBuilder, StringWriter, Writer...
– wrap() peut accepter (en plus d'un char[]), n'importe
quel CharSequence : String, StringBuffer ou
 Readable: un truc dans lequel on peut lire des char
CharBuffer
 int read(CharBuffer cb)
 Dans ces derniers cas, le tampon est en lecture
 Retourne le nombre de caractères lus et placés dans le
seule. CharBuffer cb, ou -1 si le Readable n'a plus rien à lire

Etienne Duris © Université de Marne la Vallée - Février 2007 Page 25 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 26

Les jeux de caractères ([Link]) Classe Charset


 Charset : représente une association entre – Liste de jeux de caractères officiels gérée par IANA:
un jeu de caractères (sur un ou plusieurs octets)
 [Link]

et le codage Unicode "interne" à Java sur 2 octets – Jeux de caractères disponibles sur la plateforme
– Référencé par un nom (canonique, US-ASCII, ou alias  [Link]() retourne une SortedMap
ASCII) associant les noms aux Charset
 CharsetEncoder : encodeur 
La plateforme Java requiert au minimum:
– Transforme une séquence de caractères Unicode US-ASCII, ISO-8859-1, UTF-8, UTF-16BE, UTF-16LE, UTF-
codés sur 2 octets en une séquence d'octets 16
représentant ces caractères, mais utilisant un autre  [Link](String csName) vrai si la JVM
jeu de caractères. supporte le jeu de caractères dont le nom est passé en
argument
 CharsetDecoder : décodeur  [Link](String csName) retourne le Charset
– À partir d'une séquence d'octets représentant des 
Pour un Charset donné, name() donne le nom canonique
caractères dans un jeu de caractères donné, produit et aliases() donne les alias
une suite de caractères Unicode représentés sur  contains() teste si un jeu de caractères en contient un
deux octets. autre
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 27 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 28
L'objet encodeur (classe CharsetEncoder) Gestion des problèmes de codage
 Obtenu à partir d'un objet Charset par  Par défaut, MalformedInputException ou
newEncoder() Unmappable-CharacterException sont levées si
– Caractères d'entrée fournis par un CharBuffer problème
– Octets produits placée dans un ByteBuffer  On peut spécifier un comportement spécifique
 Méthode encode() la plus simple – onMalformedInput() ou onUnmappableCharacter()
– Accepte un CharBuffer et encode son contenu – Via une constante de type CodingErrorAction
(remaining) dans un ByteBuffer alloué pour l'occasion 
IGNORE permet d'ignorer simplement le problème

IllegalStateException si opération de codage déjà en 
REPLACE permet de remplacer le caractère non valide ou
cours non codable par une séquence d'octets (par défaut '?')
 Ou bien CharacterCodingException qui peut être: – byte[] replacement() permet de la consulter
– MalformedInputException si valeur d'entrée incorrecte – replaceWith(byte[]) permet d'en spécifier une nouvelle
– UnmappableCharacterException si caractère d'entrée  REPORT provoque la levée d'exception (par défaut)
n'a pas de codage dans le jeu de caractères de  Les méthodes malformedInputAction() et
destination unmappableCharacterAction() donnent la valeur actuelle

Racourci:
[Link]("ASCII").encode("texte à coder");
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 29 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 30

Exemple de codage vers ASCII Méthode encode() plus complète



CoderResult encode(CharBuffer in, ByteBuffer out, boolean
Charset ascii = [Link]("ASCII"); endOfInput)
CharsetEncoder versASCII = [Link](); – Encode au plus [Link]() caractères de in
CharBuffer cb = [Link]("accentués et J2€€");
// (a)
– Écrit au plus [Link]() octets dans out
// [Link](new byte[]{'$'}); – Fait évoluer les positions des deux buffers
// [Link]( – Retourne un objet CoderResult représentant le
// [Link]); résultat de l'opération d'encodage. Ce résultat peut
try { être testé:
ByteBuffer bb = [Link](cb);  isError() vrai si erreurt produite (malformed ou
// UnmappableCharacterException si (a) en commentaire unmappable)
while ([Link]()) {  isUnderflow() vrai si pas assez de caractères dans in
[Link](((char)[Link]()));  isOverflow() vrai si pas assez de place dans out
} // affiche "accentu$s et J2$$" en décommentant (a)
} catch(CharacterCodingException cce) {
 isMalformed() si un caractère mal formé a été
[Link](); rencontré
}  isUnmappable() si caractère pas codable dans le jeu
de sortie
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 31 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 32
Méthode encode() plus complète (2) Principe d'utilisation pour codage
 Le 3° argument booléen endOfInput – 1. Remettre à jour l'encodeur avec reset()
– S'il est à false, il indique que d'autres caractères 
Purge des états internes
doivent encore être décodés (tous appels sauf – 2. Appeler la méthode encode() zéro fois ou plus
dernier)  Tant que de nouvelles entrées peuvent être disponibles

L'état interne du codeur peut les attendre  En passant le troisième argument à false, en remplissant le
– Il doit être à true lors du dernier appel à cette buffer d'entrée et en vidant le buffer de sortie à chaque
fonction fois
 L'encodage est terminé lorsque

Cette méthode ne retourne que lorsqu'il n'y a plus rien à
lire, plus de place pour écrire ou qu'en cas de pbme de
– Le dernier appel à encode(), avec endOfInput à true,
codage
a renvoyé un CoderResult tel que isUnderflow() soit – On peut traiter ces problèmes éventuels
true (plus rien à lire)
– 3. Appeler la méthode encode() une dernière fois
– Il faut faire flush() pour terminer le codage (purge  En passant le troisième argument à true
des états internes)
– 4. Appeler la méthode flush()

Purger les états internes de l'encodeur dans le buffer de
sortie
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 33 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 34

Quelques méthodes utilitaires pour


Le décodage
encodage

Étant donné un jeu de caractères
 Principe semblable à celui du codage

Dimensionner les buffers d'octets/caractères
– Instance d'une sous-classe de la classe abstraite
– float averageBytesPerChar() : # moyen d'octet par char CharsetDecoder

Peut être récupérée par [Link]()
– float maxBytesPerChar() : pire des cas
– Méthodes decode()
 Assurer qu'une séquence de remplacement est 
Lit un tampon d'octets et produit un tampon de
correcte caractères
– boolean isLegalReplacement(byte[] repl)  Version complète avec ByteBuffer d'entrée,

Savoir si on est capable d'encoder un ou plusieurs CharBuffer de sortie et paramètre booléen endOfInput
char retournant un CoderResult
– En effet, certains caractères sont "couplés" (surrogate) – Les caractères à produire en cas de problème de
 boolean canEncode(char c) décodage sont fournis par replacement() et

boolean canEncode(CharSequence cs) replaceWith() qui manipulent des String au lieu de
– Attention, ces méthodes peuvent changer l'état interne byte[]
de l'encodeur (ne pas les appeler si encodage en cours) – maxCharsPerByte() et averageCharsPerByte()

Etienne Duris © Université de Marne la Vallée - Février 2007 Page 35 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 36
Classes/interfaces
Les canaux (channels) fondamentales
 Représentent des connexions ouvertes vers des
 [Link].*
– Channel
entités capables d'effectuer des opérations 
close(), isOpen()
d'entrées-sorties comme des fichiers, des sockets ou
– ReadableByteChannel
des tubes 
int read(ByteBuffer)
– Un canal est ouvert à sa création. 
Tente de lire au plus
– Il ne peut plus être utilisé une fois qu'il est fermé. remaining() octets.
– À la différence des flots, il peut être utilisé en mode – WritableByteChannel
bloquant ou non bloquant (on y reviendra plus tard).  int write(ByteBuffer)
 En mode non bloquant, toutes les lectures/écritures  Tente d'écrire au plus
retournent immédiatement, même si rien n'est lu ou remaining() octets
écrit. – ByteChannel
 On peut alors utiliser un sélecteur pour attendre en – Hérite des deux
même temps la possibilité d'effectuer des entrées-sorties
sur différents canaux.

Etienne Duris © Université de Marne la Vallée - Février 2007 Page 37 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 38

Canaux et flots Exemple de canaux associés à des flots


 Quand les canaux sont bloquants FileInputStream in = new FileInputStream(args[0]);
– read() et write() se comportent comme pour les flots ReadableByteChannel cin = [Link](in);

Tous les octets seront écrits au retour de write() FileOutputStream out = new FileOutputStream(args[1]);
 Au moins un octet lu au retour de read() ou alors WritableByteChannel cout = [Link](out);
retourne -1 ByteBuffer byteB = [Link](1000);

Deux lectures ou deux écritures concurrentes sur un int nb;
même canal ont toujours lieu l'une après l'autre try {
while ((nb=[Link](byteB))!=-1) {
– Sans interférer entre elles. Évite souvent de [Link](); // position=0, limite=fin des
synchroniser. // données lues
– En revanche l'interférence entre une lecture et une [Link](byteB);
écriture peut dépendre du type de canal. [Link](); // position=0, limite=capacité

La classe utilitaire Channels permet d'obtenir }
– Des canaux à partir de flots et des flots à partir de } finally {
canaux [Link](); // Ferme le canal et le flot !
[Link]();
 Ils sont liés:et la fermeture de l'un entraîne celle de
}
l'autre
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 39 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 40
Interfaces de canaux Canaux fermables et "interruptibles"
 ScatteringByteChannel extends  Interface InterruptibleChannel extends Channel
ReadableByteChannel 
Un canal qui implante InterruptibleChannel est un
– Ajoute les méthodes à multiple tampons – Canal fermable de manière asynchrone
("dispatcheur")  Si une thread t est bloquée par une opération
 long read(ByteBuffer[] dsts) et d'entrée/sortie sur un tel canal, et qu'une autre thread
long read(ByteBuffer[] dsts, int offset, int length) ferme le canal par close(), alors t reçoit une exception

Pratique pour lire des en-têtes de taille fixe (ex: AsynchronousCloseException
protocoles) – Canal interruptible de
 GatheringByteChannel extends 
Si une thread t est bloquée par une opération
WritableByteChannel d'entrée/sortie sur un tel canal, et qu'une autre thread
– Ajoute les méthodes ("collecteur") fait [Link](), alors le canal est fermé et t reçoit une
exception ClosedByInterruptException
 long write(ByteBuffer[] srcs) et – Dans ce cas, le statut d'interruption de t est positionné
long write(ByteBuffer[] srcs, int offset, int length)
 Si le statut d'interruption de t est positionné au

Pratique pour toujours débuter une écriture par une
suite d'en-têtes spécifiques moment de l'appel une opération d'entrée/sortie
bloquante, ça lève aussi ClosedByInterruptException
– Statut d'interruption reste positionné, le canal est fermé
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 41 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 42

Canaux à mode non bloquant Canaux vers les fichiers


 Héritent de la classe abstraite SelectableChannel  Classe abstraite FileChannel
– Lecture et écriture ne bloquent jamais – Instances obtenues par les méthodes getChannel() de
– Le nombre d'octets transférés peut être inférieur à 
FileInputStream, FileOutputStream ou RandomAccessFile
l'indication  Ne supporte que les méthodes correspondantes, sinon
 Éventuellement nul, en lecture comme en écriture NonWritableChannelException ou NonReadableChannelException

Tous les canaux standards peuvent être non
– Méthodes read() et write() conformes aux interfaces

read() retourne -1 quand la fin de fichier est atteinte
bloquants SAUF: 
Deux lectures ou deux écritures concurrentes ne
– Ceux obtenus par la classe utilitaire Channels
s'entrelassent pas
– Les canaux sur les fichiers – Utilisation de caches pour améliorer les performances

À leur création, les canaux sont en mode bloquant  Méthode force() pour écriture des données sur le fichier
– Changement de mode par configureBlocking(false) – Le flot et le canal sont liés et se reflètent leurs états
– Consultation du mode actuel par isBlocking() respectifs
 Ils sont les seuls à pouvoir être multiplexés avec un
sélecteur de la classe [Link]

Etienne Duris © Université de Marne la Vallée - Février 2007 Page 43 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 44
Fichiers mappés en mémoire Canaux sur les tubes
 Permet de voir un fichier comme un tampon d'octets  Comme les tubes des flots ([Link])
dans lequel on peut lire ou écrire – PipedReader,PipedWriter,PipedInputStream, PipedOutputStream
Opérations beaucoup plus rapides mais
– – Faire communiquer simplement deux processus légers
– Coût pour réaliser le mapping (alloué direct via malloc) – Ce qui est écrit un thread est lu dans l'ordre par l'autre
 MappedByteBuffer extends ByteBuffer 
À la différence des tubes des flots, ceux des canaux
– Obtenu par map() sur un FileChannel avec arguments: – [Link] : Pipe, [Link] et [Link]
 [Link] – Sont des objets qui ont une « réalité système »
– READ_ONLY, fichier ne peut pas être modifié via ce tampon 
Créés avec la méthode statique open() de type Pipe
– READ_WRITE, le fichier est modifié (avec un délai, cf. – Retourne un objet de type Pipe, sur lequel on peut faire
force()) 
[Link] sink() pour écrire des données
– PRIVATE crée une copie privée du fichier. Aucune (AbstractSelectableChannel,WritableByteChannel,GatheringByteChannel)
modification répercutée 
[Link] source() pour lire des données
 long position (AbstractSelectableChannel,ReadableByteChannel,ScatteringByteChannel)
 long taille  Les deux canaux supportent le passage en mode non

Le fichier mappé reste valide jusqu'à ce que le bloquant
MappedByteBuffer soit garbage-collecté
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 45 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 46

Accès à UDP via les canaux DatagramChannel


 Depuis jdk 1.4,  Par défaut, un canal dc récupéré par Datagram-
[Link] [Link]() est bloquant. Il peut être configuré non
 Canal vers une socket UDP bloquant.
– On peut créer une [Link] à partir 
[Link]() récupère alors la DatagramSocket correspondante
d'un DatagramChannel, mais pas le contraire 
Elle n'est pas attachée. On peut faire bind(SocketAddress) sur

Si une socket UDP su a été créée à partir d'un canal, cette socket
on peut récupérer ce canal par [Link]().  Les méthodes send() et receive() sont accessibles depuis
Sinon, cette méthode retourne null. le canal
 [Link]() crée et retourne un canal  Elles manipulent des ByteBuffer et receive() retourne un objet
associé à une socket UDP (non attachée) SocketAddress identifiant l'émetteur des données reçues
 DatagramChannel n'est pas une abstraction complète  On doit faire une pseudo-connexion pour pouvoir utiliser
des sockets UDP: pour les opérations précises (binding, les méthodes read() et write() avec des ByteBuffer, plus
etc...) on récupère l'objet DatagramSocket sous-jacent classiques sur les canaux (interlocuteur implicite pour ces
méthodes)

Etienne Duris © Université de Marne la Vallée - Février 2007 Page 47 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 48
Envoi sur un DatagramChannel Réception depuis un DatagramChannel
 int send(ByteBuffer src, SocketAddress target)  SocketAddress receive(ByteBuffer dst)
 Provoque l'envoi des données restantes du tampon  Par défaut, méthode bloquante tant que rien n'est reçu
src vers target par la socket
 Semblable à un write() du point de vue du canal  Au plus [Link]() octets peuvent être reçus
 Le reste est tronqué
 Si canal bloquant, la méthode retourne lorsque
 Retourne l'adresse de socket (IP+port) de l'émetteur des
tous les octets ont été émis (leur nombre est
données
retourné) 
Si canal non bloquant, soit tout est reçu, soit le
 Si canal non bloquant, la méthode émet tous les tampon n'est pas modifié et la méthode retourne null
octets ou aucun 
Bug: on ne peut pas limiter le temps d'attente en lecture,
 Si une autre écriture est en cours sur la socket par comme c'est le cas avec
une autre thread, l'invocation de cette méthode [Link]()
bloque jusqu'à ce que la première opération soit 
Dans ce cas, c'est ignoré par le flot de lecture sur le canal
terminée  Si besoin, il faut le faire « à la main »

Etienne Duris © Université de Marne la Vallée - Février 2007 Page 49 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 50

Exemple de client UDP avec canaux :


Exemple de client UDP avec canaux : envoi
réception

// Récupération de l'adresse IP et du port // zone de données pour réception


InetAddress server = [Link](args[0]); ByteBuffer bb = [Link](new byte[512]);
int port = [Link](args[1]); // Création d'un objet canal UDP non attaché
InetSocketAddress isa = new InetSocketAddress(server, port); DatagramChannel dc = [Link]();
// Création d'un objet canal UDP non attaché // La réception attache implicitement le canal UDP et retourne
DatagramChannel dc = [Link](); // la socket de l'émetteur
// Les données à envoyer doivent être dans un ByteBuffer SocketAddress sender = [Link](bb);
ByteBuffer bb = [Link]("Hello".getBytes()); [Link]();
// L'attachement de la socket UDP sous-jacente est implicite [Link]([Link]() +
[Link](bb,isa); " octets reçus de " + sender + ":");
[Link](); [Link](new String([Link](),0,[Link]()));
[Link]();

Etienne Duris © Université de Marne la Vallée - Février 2007 Page 51 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 52
Exemple de serveur UDP avec canaux Canaux vers les sockets TCP
 [Link] et SocketChannel
– Acceptation de connexion et représentation des connexions
// Création d'un objet canal UDP (non attaché)
DatagramChannel dc = [Link](); 
SocketChannel représente un canal sur une socket TCP
InetSocketAddress isa = new InetSocketAddress(port); – Objet canal de socket TCP (non attaché) retourné par open()
// attachement (explicite) de la socket sous-jacente – Attachement éventuel de la socket sous-jacente Socket
// au port d'écoute socket() Par un appel à bind() sur cet objet Socket
[Link]().bind(isa);
ByteBuffer bb = [Link](512);
 ServerSocketChannel
– Objet canal d'écoute de connexions entrantes retourné par
while (true) {
SocketAddress sender = [Link](bb); // réception open()
[Link](); // traitement éventuel – N'est que l'abstraction d'un canal sur une ServerSocket,
[Link](bb,sender); // réponse à l'émetteur récupérable par la méthode socket()
[Link](); – Il faut attacher cette ServerSocket par bind() avant de
} pouvoir accepter des connexions (sinon,
NotYetBoundException)

Etienne Duris © Université de Marne la Vallée - Février 2007 Page 53 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 54

ServerSocketChannel SocketChannel
 Appeler accept() sur le canal pour accepter des 
boolean connect(SocketAddress remote) sur le canal
 Si SocketChannel en mode bloquant, l'appel à connect() bloque
connexions (la server socket sous-jacente doit être et retourne true quand la connexion est établie, ou lève
attachée) IOException
– Si le canal est bloquant, l'appel bloque  Si SocketChannel en mode non bloquant, l'appel à connect()
 Retourne un SocketChannel quand la connexion est – Peut retourner true immédiatement (connexion locale, par exemple)
acceptée ou – Retourne false le plus souvent: il faudra plus tard appeler
finishConnect()
 Lève une IOException si une erreur d'entrée-sortie  Tant que le canal est non connecté, les opérations
arrive d'entrée/sortie lèvent NotYetConnectedException (peut être
– Si le canal est non bloquant, retourne testé par isConnected())
immédiatement  Un SocketChannel reste connecté jusqu'à ce qu'il soit fermé
– Possibilité de faire des half-closed et des fermetures asynchrones
 null s'il n'y a pas de connexion pendante – AsynchronousCloseException (si canal fermé alors qu'une opération
– Quel que soit le mode (bloquant / non bloquant) du d'écriture en cours était bloquée, ou pendant l'opération de
ServerSocketChannel le SocketChannel retourné est connection)
initialement en mode bloquant – ClosedByInterruptException (si autre thread interromp la thread
courante lorsqu'elle effectue une opération, IO, connection, etc.)

Etienne Duris © Université de Marne la Vallée - Février 2007 Page 55 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 56
Établissement de connexion Communication sur canal de socket TCP
– Méthode finishConnect() – Une fois la connexion établie, le canal de socket se
– Si connexion a échoué, lève IOException
comporte comme un canal en lecture et écriture
– Si pas de connexion initiée, lève
NoConnectionPendingException
 extends AbstractSelectableChannel et implements
ByteChannel, ScatteringByteChannel,
– Si connexion déjà établie, retourne immédiatement true
GatheringByteChannel
– Si la connexion n'est pas encore établie – Méthodes read() et write()

Si mode non bloquant, retourne false  En mode bloquant, write() assure que tous les octets

Si mode bloquant, l'appel bloque jusqu'au succès ou
à l'échec de la connexion (retourne true ou lève une
seront écrits mais read() n'assure pas que le tampon sera
exception) rempli (au moins un octet lu ou détection de fin de
– Si cette méthode est invoquée lorsque des opérations de connexion: retourne -1)
lecture ou d'écriture sur ce canal sont appelés  En mode non bloquant, lecture comme écriture peuvent
 Ces derniers sont bloqués jusqu'à ce que cette ne rien faire et retourner 0.
méthode retourne – Fermeture de socket par close() entraine la fermeture
– Si cette méthode lève une exception (la connexion du canal
échoue)
 Alors le canal est fermé

Etienne Duris © Université de Marne la Vallée - Février 2007 Page 57 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 58

Les sélecteurs Les sélecteurs (suite)

 Objets utilisés avec les canaux configurés en mode  Enregistrement d'un canal auprès d'un sélecteur
non bloquant qui héritent de SelectableChannel – Se fait par un appel, sur le canal à enregistrer, de:
– Ces canaux peuvent être enregistrés auprès d'un – SelectionKey register(Selector sel, int ops) ou
sélecteur après avoir été configurés non bloquant SelectionKey register(Selector sel, int ops, Object att)
(configureBlocking(false))  ops représente les opérations "intéressantes" pour ce
– [Link] : les instances sont créées par sélecteur
appel à la méthode statique open() et fermés par close()  SelectionKey retourné représente la clé de sélection de
ce canal, qui permet de connaître, outre le sélecteur:
– channel() renvoie le canal lui même
– interestOps() renvoie les opérations « intéressantes »
– attachment() renvoie l'objet att éventuellement attaché

Etienne Duris © Université de Marne la Vallée - Février 2007 Page 59 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 60
Autour des sélecteurs Utilisation des sélecteurs

– keys() appelé sur un sélecteur retourne toutes ses clés


 Appel à la méthode select()
– Entraîne l'attente passive d'événements intéressants pour les

SelectionKey keyFor(Selector sel) sur un canal donne la clé
canaux enregistrés
de sélection du sélecteur pour ce canal
– Dès qu'une de ces opérations peut être effectuée, la méthode
– On peut enregistrer plusieurs fois un même canal auprès retourne le nombre de canaux séléctionnés
d'un sélecteur (il met la clé à jour) – Elle ajoute également les clés de sélection de ces canaux à

Il est plus élégant de modifier sa clé de sélection l'ensemble retourné par selectedKeys() sur le selecteur
– En argument de interestOps() ou de attach() sur cette clé de  Il suffit de le parcourir avec un itérateur

sélection – C'est à l'utilisateur de retirer les clés de sélection correspondant


– Les opérations intéressantes sont exprimées par les bits aux canaux sélectionnés qu'il a « utilisé »
d'un entier (faire des OU binaires (|)) 
méthode remove() de l'ensemble ou de l'itérateur ou
– SelectionKey.OP_READ, SelectionKey.OP_WRITE,  méthode clear() de l'ensemble qui les retire toutes

SelectionKey.OP_CONNECT, SelectionKey.OP_ACCEPT  Si une clé est intéressée par plusieurs opérations


 Étant donné un canal, validOps() renvoit ses opérations – readyOps() donne celles qui sont prêtes
« valides » – raccourcis isAcceptable(), isConnectable(), isReadable() et
isWritable()

Etienne Duris © Université de Marne la Vallée - Février 2007 Page 61 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 62

Sélection bloquante ou non bloquante Fonctionnement des sélecteurs


– Un sélecteur maintient 3 ensembles de clés de sélection
 Les méthodes select() ou select(long timeout) sont
bloquantes

key set
– Elles ne retournent qu'après que
– Ensemble des clés de tous les canaux enregistrés dans ce
sélecteur.
 un canal soit sélectionné ou

 la méthode wakeup() soit appelée ou


– Méthode keys()
 la thread courante soit interrompue ou

selected-key set
 le timeout ait expiré
– Ensemble des clés dont les canaux ont été identifiés comme
prêts
 La méthode selectNow() est non bloquante
 pour au moins une des opérations spécifiées dans
 Retourne 0 si aucun canal n'est sélectionné
l'ensemble des opérations de cette clé
 La méthode wakeup() permet d'interrompre une opération  à l'occasion d'une opération de sélection
de sélection bloquante, depuis un autre processus léger précédente

L'annulation de l'enregistrement d'un canal auprès d'un – Méthode selectedKeys()
sélecteur peut se faire par cancel() sur la clé de sélection.  canceled-key set
Elle est aussi réalisée implicitement à la fermeture du
– Ensemble des clés qui ont été annulées mais dont les
canal.
canaux n'ont pas encore été désenregistrés de ce sélecteur
– Cet ensemble n'est pas directement accessible
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 63 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 64
Opération de sélection Sélection et concurrence

 1. Chaque clé de canceled-key set est supprimée – Les sélecteurs sont "thread-safe", mais pas leurs
– De chacun des ensembles dans lesquels elle est présente ensembles
– À la fin de 1. canceled-key set est vide – Une opération de sélection synchronise:

2. L'OS est interrogé – 1. sur le sélecteur
– Pour savoir quels canaux sont prêts à réaliser une – 2. sur le key-set
opération qui est décrite par sa clé comme "d'intérêt" au – 3. sur le selected-key set
début de l'opération de sélection
– Et sur le canceled-key set pendant les étapes 1. et 3. de la
– Pour chacun des canaux prêts à faire quelque chose sélection
 Soit sa clé n'était pas déjà dans selected-key set, alors

elle y est ajoutée avec un ensemble d'opérations


– Un thread bloqué sur select() ou select(long timeout)
prêtes reflétant l'état du canal peut être interrompu par un autre thread de trois

Soit sa clé était déjà dans selected-key set, alors son manières:
ensemble d'opérations prêtes est mis à jour par un OU – En appelant wakeup() sur le sélecteur (select() retourne)
binaire avec les anciennes – En appelant close() sur le sélecteur (select() retourne)
 3. Si, pendant l'étape 2, des clés ont été ajoutée dans – En appelant interrupt() sur le thread
canceled-key set, alors elles sont traitées comme dans 1. 
Pose le statut d'interruption et appelle la méthode
wakeup()
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 65 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 66

Exemples

– Serveur TCP de mise en majuscule utilisant des canaux


 Interrogeable avec netcat ou telnet
 Différentes versions
 Itératif avec canaux bloquants
 1 thread
 Concurrent avec canaux bloquants
 N threads

Acceptation de nouvelles connexions bloquante, mais
lecture non bloquante sur toutes les connexions établies
 2 threads
 Acceptation et lectures non bloquantes
 1 thread
 D'abord sans, puis avec mécanisme d'attachement de
gestionnaire
– Serveur Echo

Etienne Duris © Université de Marne la Vallée - Février 2007 Page 67

Vous aimerez peut-être aussi