La classe Charset permet d'encoder et de
décoder les caractères de JAVA dans divers encodages.
Le concept de canal Channel a été introduit pour
la première fois dans le package nio : il permet un meilleur
contrôle des fluxs de fichier, de socket, de datagramme
et de pipe.
Enfin, le socket de serveur, qui ne faisait qu'attendre des demandes
de connexions pour créer un véritable socket "privé"
de connection entre un client et le serveur, devient un ServerSocketChannel.
Le Socket d'échange "privé" entre client
et serveur devient un SocketChannel.
Buffer et Channel
Source de BufferNio5.java
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
public class BufferNio5
{
static public void main(String args[]) throws Exception {
FileInputStream fin = new FileInputStream("alphabet.txt");
FileChannel canal = fin.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(10);
canal.read(buffer);
buffer.flip();
affiche(buffer);
buffer.clear();
canal.read(buffer);
buffer.flip();
affiche(buffer);
buffer.clear();
canal.read(buffer);
buffer.flip();
affiche(buffer);
fin.close();
}
static public void affiche(ByteBuffer buffer) {
int i=0;
while (buffer.remaining()>0)
System.out.print((char)buffer.get());
System.out.println();
}
}
|
EXECUTION
$java BufferNio5
abcdefghij
klmnopqrst
uvwxyz
|
Les lignes de la classe :
- Le programme ci-dessus réalise
3 lectures successives à partir d'un fichier texte vers
un tampon :
- le fichier contient les 26 lettres minuscules
en ASCII
- le tampon étant de taille 10 bytes,
il faut 3 lectures (10 puis 10 puis 6) pour lire tout le fichier.
- L'interface Channel et ses classes
d'implémentation définissent des canaux de communication
:
- il ne s'agit pas d'un nouveau type de
flot d'IO qui s'ajouterait à la longue liste existante
- le Channel permet la lecture et l'écriture
de données entre un flot (stream) et un Buffer
- chaque flot (stream du package java.io)
de fichier , de socket, de datagramme et de pipe fournit un objet
Channel par la méthode getChannel() ajoutée
dans la version 1.4 de Java
- La classe FileChannel implémente
les interfaces WritableByteChannel et ReadableByteChannel
- la méthode read(tampon)
lit des octets dans le canal et les stocke dans le tampon
- elle en lit un nombre au plus égal
à la place disponible dans le tampon
- elle les stocke à partir de "position",
donc en ajoute/append à d'autres s'il y en avait déjà
!
- elle retourne le nombre d'octets lus
- -1 si la fin de fichier est atteinte
- position est incrémenté
du nombre d'octes lus.
- en mode bloquant, read retourne quand
elle a écrit effectivement lu des octets (sauf fin de
flot)
- en mode non-bloquant, elle retourne le
nombre qu'elle a pu lire immédiatement
- est susceptible de lever plusieurs exceptions
dont ClosedChannelException quand le canal est fermé
- La méthode remaining() d'un
Buffer retourne le nombre d'éléments entre position
et limit
- le cycle d'utilisation du Buffer
est :
- clear() : remise des pointeurs à
"buffer vide"
- lecture du canal vers le buffer, donc
remplissage du buffer
- flip() : bascule de mode écriture
du buffer en mode lecture du buffer
- boucle d'extraction des données
du buffer par get()
Source de BufferNio6.java
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
public class BufferNio6
{
static public void main(String args[]) throws Exception {
FileInputStream fin = new FileInputStream("alphabet.txt");
FileChannel canal = fin.getChannel();
ByteBuffer buffer = ByteBuffer.allocate( 10 );
canal.read( buffer );
buffer.flip();
affiche(buffer);
canal.read( buffer );
buffer.flip();
affiche(buffer);
fin.close();
}
static public void affiche(ByteBuffer buffer) {
int i=0;
while (buffer.remaining()>0)
System.out.print( (char)buffer.get() );
System.out.println();
}
}
|
EXECUTION
$java BufferNio6
abcdefghij
abcdefghij // inchangé
|
Les lignes de la classe :
- l'instruction clear() a été
supprimée :
- une fois l'affichage du contenu du buffer,
celui-ci n'est pas remis à "vide"
- l'instruction read() tente de lire des
données du canal et de les ajouter au buffer, mais celui-ci
est plein.
copie de fichier avec
Buffer et Channel
Source de CopyFile.java
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
public class CopyFile
{
static public void main(String args[]) throws Exception {
if (args.length<2) {
System.err.println("Usage: java CopyFile source destination");
System.exit(1);
} else {
FileInputStream fIn = new FileInputStream(args[0]);
FileOutputStream fOut = new FileOutputStream(args[1]);
FileChannel canalIn = fIn.getChannel();
FileChannel canalOut = fOut.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int nombreLu = 0;
while (nombreLu != -1) {
buffer.clear();
nombreLu = canalIn.read(buffer);
if (nombreLu !=-1) {
buffer.flip();
canalOut.write(buffer);
}
}
canalIn.close();
canalOut.close();
fIn.close();
fOut.close();
}
}
}
|
EXECUTION
$ cat alphabet
abcdefghijklmnopqrstuvwxyz
$java CopyFile alphabet.txt alpha2
$ cat alpha2
abcdefghijklmnopqrstuvwxyz
|
Les lignes de la classe :
- la méthode write(tampon)
de l'interface WritableByteChannel écrit des octets
du tampon dans le canal :
- elle en écrit un nombre au plus
égal au nombre d'éléments disponible dans
le tampon, cad remaining()
- elle les écrit à partir
de "position"
- elle retourne le nombre d'octets écrit
- position est incrémenté
du nombre d'octets écrits
- en mode bloquant, write retourne quand
elle a écrit effectivement ses "remaining" éléments
- en mode non-bloquant, elle retourne le
nombre qu'elle a pu écrire
- est susceptible de lever plusieurs exceptions
dont ClosedChannelException quand le canal est fermé
- la méthode close() de l'interface
Channel ferme un canal
Charset : encodage
et décodage
Source de Charset1.java
import java.io.*;
import java.nio.*;
import java.nio.charset.*;
public class Charset1
{
static public void main( String args[] ) throws Exception {
CharBuffer carBuf = CharBuffer.wrap("abcd\u00E9");
System.out.println("CharBuffer = "+carBuf );
Charset charset = Charset.forName("ISO-8859-1");
System.out.println("Charset ISO-8859-1");
ByteBuffer byteBuf = charset.encode(carBuf);
System.out.println("ByteBuffer charset.encode(carBuf) : ");
while (byteBuf.remaining()>0)
System.out.print(UnicodeFormatter.byteToHex(byteBuf.get())
+ " " );
System.out.println();
byteBuf.flip();
CharBuffer carBuf2 = charset.decode(byteBuf);
System.out.println("CharBuffer charset.decode(byteBuf) : "
+carBuf2 );
charset = Charset.forName("UTF-8");
System.out.println("Charset UTF-8");
carBuf.flip();
byteBuf = charset.encode(carBuf);
System.out.println("ByteBuffer charset.encode(carBuf) : ");
while (byteBuf.remaining()>0) {
System.out.print( UnicodeFormatter.byteToHex(byteBuf.get()) + " " );
}
System.out.println();
byteBuf.flip();
carBuf2 = charset.decode(byteBuf);
System.out.println("CharBuffer charset.decode(byteBuf) : "+carBuf2 );
}
}
|
EXECUTION
$java Charset1
CharBuffer = abcdÚ
Charset ISO-8859-1
ByteBuffer charset.encode(carBuf) :
61 62 63 64 e9
CharBuffer charset.decode(byteBuf) : abcdÚ
Charset UTF-8
ByteBuffer charset.encode(carBuf) :
61 62 63 64 c3 a9
CharBuffer charset.decode(byteBuf) : abcdÚ
|
Les lignes de la classe :
- Le programme encode dans un ByteBuffer
puis décode les caractères de la chaine : abcdé
- d'abord en ISO-8859-1, notre fameux Latin-1
(8 bits)
- puis en UTF-8, l'unicode à longueur
d'encodage variable.
- le contenu du CharBuffer est encodé
et stocké dans un ByteBuffer puis le contenu du ByteBuffer
est décodé et stocké dans un CharBuffer
- il n'y a pas de problème pour encoder
l'Unicode JAVA en UTF-8
mais il pourrait y en avoir pour le Latin-1
- classe Charset
- un objet de cette classe est un jeu de
caractères (l'ensemble des caractères et son encodage)
- US-ASCII est l'alphabet américain
sur 7 bits
- ISO-8859-1, l'ISO-Latin-1 qui inclue sur
8 bits la plupard des caractères de l'Europe de l'Ouest
; surensemble de l'US-ASCII
- UTF-8, l'unicode à longueur d'encodage
variable sur une nombre entier d'octets, compatible avec l'US-ASCII
- UTF-16, l'unicode sur 16 bits
- la méthode statique forName(jeux_de_caractères)
récupère un tel objet par son nom
- la méthode encode(char_buffer)
encode les caractères Unicode JAVA 16 bits
- alloue un ByteBuffer dans lequel elle
stocke les bytes obtenus.
- retourne ce ByteBuffer
- si le byteBuffer est mal formé
ou si un carctère n'est pas encodable, il est remplacé
par un caractère par défaut
- la méthode decode(byte_buffer)
decode les octets en caractères Unicode JAVA 16 bits
- alloue un CharBuffer dans lequel elle
stocke les bytes obtenus.
- retourne ce CharBuffer
- il n'y a pas de problème pour encoder
l'Unicode JAVA en UTF-8
- mais il pourrait y en avoir pour le Latin-1
- si le byteBuffer est mal formé
ou si un carctère n'est pas encodable, il est remplacé
par un caractère par défaut
Source de UnicodeFormatter.java fourni par SUN
public class UnicodeFormatter {
static public String byteToHex(byte b) {
// Returns hex String representation of byte b
char hexDigit[] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
char[] array = { hexDigit[(b >> 4) & 0x0f], hexDigit[b & 0x0f] };
return new String(array);
}
static public String charToHex(char c) {
// Returns hex String representation of char c
byte hi = (byte) (c >>> 8);
byte lo = (byte) (c & 0xff);
return byteToHex(hi) + byteToHex(lo);
}
}
|
Charset et
Channel
Source de BufferCharset1.java
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
public class BufferCharset1
{
static public void main( String args[] ) throws Exception {
FileInputStream fin = new FileInputStream("alphabet.txt");
Charset charset = Charset.forName("ISO-8859-1");
CharsetDecoder decoder = charset.newDecoder();
FileChannel canal = fin.getChannel();
ByteBuffer byteBuf = ByteBuffer.allocate( 10 );
canal.read(byteBuf);
byteBuf.flip();
CharBuffer carBuf = decoder.decode(byteBuf);
System.out.println("lu : "+carBuf.toString());
byteBuf.clear();
canal.read(byteBuf);
byteBuf.flip();
carBuf = decoder.decode(byteBuf);
System.out.println("lu : "+carBuf.toString());
byteBuf.clear();
canal.read(byteBuf);
byteBuf.flip();
carBuf = decoder.decode(byteBuf);
System.out.println("lu : "+carBuf.toString());
fin.close();
}
}
|
EXECUTION
$java BufferCharset1
lu : abcdefghij
lu : klmnopqrst
lu : uvwxyz
|
Les lignes de la classe :
- Ce programme réalise la même
fonctionnalité que BufferNio5.java ci-dessus, mais sans astuce,
en utilisant le décodage de l'ASCII
- il réalise 3 lectures successives
à partir d'un fichier texte ASCII vers un tampon
- la méthode newDecoder()
de Charset permet d'obtenir un décodeur réutilisable
- elle retourne un objet CharsetDecoder
- la méthode decode(byte_buffer)
de la classe CharsetDecoder décode les octets en caractères
Unicode JAVA 16 bits
- est susceptible de lever :
- une MalformedInputException si le byteBuffer
est mal formé
- une UnmappableCharacterException si un
caractère n'est pas encodable
- mais il est possible de contrôler
ces comportements.
- de plus, il est souhaitable à certains
moments d'effectuer un reset() du decodeur ou un flush
.... Ceci nous emmènerait trop loin !
SocketChannel
Source de GetDayTime.java
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.net.*;
class GetDayTime {
public static void main(String args[]) throws Exception {
if (args.length != 2)
System.out.println("usage : java GetDayTime hote port");
else {
String hote = args[0];
int port = Integer.parseInt(args[1]);
InetSocketAddress hoteAdress = new InetSocketAddress(hote,port);
SocketChannel socket = SocketChannel.open(hoteAdress);
Charset charset = Charset.forName("US-ASCII");
CharsetDecoder decoder = charset.newDecoder();
ByteBuffer byteBuf = ByteBuffer.allocate(10);
while (socket.read(byteBuf) != -1) {
byteBuf.flip();
decoder.reset();
CharBuffer charBuf = decoder.decode(byteBuf);
System.out.print(charBuf.toString());
byteBuf.clear();
}
System.out.println();
socket.close();
}
}
}
|
EXECUTION
Récupérer et compiler SSCdayTime.java
sur une console lancer le serveur :
$java SSCdayTime 4444
sur une autre console :
$java GetDayTime localhost 4444
Wed Jan 11 18:26:09 CET 2006
|
Les lignes de la classe :
- le programme réalise une connection
sur le port de l'hote indiqué
- puis lit l'information envoyé par
le serveur jusquà ce qu'il ferme la connection.
- l'information est affichée en console.
- la classe SocketChannel
- implémente l'interface WritableByteChannel
et l'interface ReadableByteChannel
- permet la gestion de socket sous la forme
d'un canal
- donc d'écrire et de lire du socket
à un Buffer.
ServerSocketChannel
Source de SSCdayTime.java
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.net.*;
import java.util.*;
class SSCdayTime {
public static void main(String args[]) throws Exception {
if (args.length != 1)
System.out.println("usage : java SSCdayTime port");
else {
int port = Integer.parseInt(args[0]);
InetSocketAddress inetAdress = new InetSocketAddress(port);
ServerSocketChannel serverChannel = ServerSocketChannel.open();
ServerSocket serverSocket = serverChannel.socket();
serverSocket.bind(inetAdress);
Charset charset = Charset.forName("US-ASCII");
CharsetEncoder encoder = charset.newEncoder();
while (true) {
SocketChannel sockClient = serverChannel.accept();
Date maintenant = new Date();
CharBuffer reponse = CharBuffer.wrap(maintenant);
encoder.reset();
sockClient.write(encoder.encode(reponse));
sockClient.close();
}
}
}
}
|
EXECUTION
sur une console lancer le serveur :
$java SSCdayTime 4444
sur une autre console :
$ telnet localhost 4444
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Wed Jan 11 18:08:10 CET 2006
Connection closed by foreign host.
|
Les lignes de la classe :
- Ce serveur simule le service "daytime"
du port 13.
- La classe ServerSocketChannel
- modélise le socket de serveur
- la méthode socket() retourne
le SocketServer sous-jacent
- et c'est le SocketServer sous-jacent qui
est lié au port de l'hote par la méthode bind()
- la méthode accept() :
- attend des demandes de connexions
- crée et retourne un SocketChannel
de communication entre un client et le serveur.
exercices