1.4. Structures de données de base en Java



Plan de ce chapitre:   ...........retour au plan général

1. La classe String

  2. Les tableaux Arrays, les matrices
    2.1 Les tableaux à une dimension
    2.2 Utiliser un tableau
    2.3 Les matrices


3. Les tableaux dynamiques
 

4. Les listes chaînées
 

5. Flux et fichiers

5.1 Les flux en Java
5.2 Les flux d'octets en entrée
5.3 Les flux d'octets en sortie
5.4 Les opérations d'entrée sortie standard dans une application
5.5 Les flux de caractères
5.6 Lecture et écriture dans un fichier de texte


 

1.La classe String

Le type de données String (chaîne de caractère) n'est pas un type élémentaires en Java, c'est une classe. Donc une chaîne de type string est un objet qui n'est utilisable qu'à travers les méthodes de la classe String.

Pour accéder à la classe String et à toutes ses méthodes, vous devez mettre avant la déclaration de votre classe l'instruction d'importation de package suivante :

import java.lang.String ;

Un littéral de chaîne est une suite de caractères entre guillemets : " abcdef " est un exemple de littéral de String.

Etant donné que cette classe est très utilisée les variables de type String bénéficient d'un statut d'utilisation aussi souple que celui des autres types élémentaires. On peut les considérer comme des listes de caractères numérotés de 0 à n-1 (si n figure le nombre de caractères de la chaîne).

Déclaration d'une variable String
String str1;
Déclaration d'une variable String avec initialisation 
String str1 = " abcdef ";
      Ou
String str1 = new String("abcdef ");
On accède à la longueur d'une chaîne par la méthode :
int length( )
String str1 = "abcdef";
int longueur;
longueur = str1.length( ); // ici longueur vaut 5

Toutefois les String de Java sont moins conviviales en utilisation que les string de pascal ou celles de C#, il appartient au programmeur d'écrire lui-même ses méthodes d'insertion, modification et suppression.

Toutes les autres manipulations sur des objets String nécessitent l'emploi de méthodes de la classe String. Nous donnons quelques exemples d'utilisation de méthode classique sur les String.

Le type String possède des méthodes classiques d'extraction, de concaténation, de changement de casse, etc.

Concaténation de deux chaînes
 Un opérateur ou une méthode
Opérateur : + sur les chaînes

       
ou

 Méthode : String concat(String s)
Les deux écritures ci-dessous sont donc équivalentes en Java :
str3 = str1+str2  str3 = str1.concat(str2)

String str1,str2,str3;
str1="bon";
str2="jour";
str3=str1+str2;


On accède à un caractère de rang fixé d'une chaîne par la méthode :

  char charAt(int rang)

Il est possible d'accéder en lecture seulement à chaque caractère d'une chaîne, mais qu'il est impossible de modifier un caractère directement dans une chaîne.

String ch1 = "abcdefghijk";



char car = ch1.charAt(4);
// ici la variable car contient la lettre 'e'

position d'une sous-chaîne à l'intérieur d'une chaîne donnée :

méthode
:

   int indexOf ( String ssch)

String ch1 = " abcdef " , ssch="cde";



int rang ;
rang = ch1.indexOf ( ssch );
// ici la variable rang vaut 2'

Les String Java ne peuvent pas être considérées comme des tableaux de caractères, il est nécessaire, si l'on souhaite se servir d'une String,  d'utiliser la méthode toCharArray pour convertir la chaîne en un tableau de caractères contenant tous les caractères de la chaîne.
Enfin, attention ces méthodes de manipulation d'une chaîne ne modifient pas la chaîne objet qui invoque la méthode mais renvoient un autre objet de chaîne différent. Ce nouvel objet est obtenu après action de la méthode sur l'objet initial.

Soient les quatre lignes de programme suivantes :

String str1 = "abcdef" ;
char [ ] tCarac ;
tCarac = str1.toCharArray( ) ;
tCarac = "abcdefghijk".toCharArray( );

Illustrons ci-dessous ce qui se passe relativement aux objets créés :



String
str1 = "abcdef" ;


str1 référence un objet de chaîne.


char
[ ] tCarac ;
tCarac = str1.toCharArray( ) ;



tCarac référence un objet de tableau à 6 éléments.

tCarac = "abcdefghijk".toCharArray( );


tCarac référence maintenant un nouvel objet de tableau à 11 éléments, l'objet précédent est perdu (éligible au Garbage collector)

L'exemple précédent sur la concaténation ne  permet pas de voir que l'opérateur + ou la méthode concat renvoie réellement un nouvel objet en particulier lors de l'écriture des  quatre lignes suivantes :

String str1,str2;
str1="bon";
str2="jour";
str1=str1+str2;

Illustrons ici aussi ce qui se passe relativement aux objets créés :

String str1,str2;
str1="bon";
str2="jour";


str1=str1+str2;

un nouvel objet de chaîne a été créé et str1 "pointe" maintenant vers lui.

Opérateurs d'égalité de String

String a , b ;
(a = = b ) renvoie true si les variables a et b référencent chacune le même objet de chaîne sinon il renvoie false.

String a , b ;
a.equals ( b ) renvoie true si les variables a et b ont la même valeur sinon il renvoie false.


En d'autres termes si nous avons deux variables de String ch1 et ch2, que nous ayons écrit ch1 = "abcdef"; et plus loin ch2 = "abcdef"; les variables ch1 et ch2 n'ont pas la même référence mais ont la même valeur (valeur = "abcdef").
 
Voici un morceau de programme qui permet de tester l'opérateur d'égalité = = et la méthode equals :

    String s1,s2,s3,ch;
    ch = "abcdef";
    s1 = ch;
    s2 = "abcdef";
    s3 = new String("abcdef".toCharArray( ));


    System.out.println("s1="+s1);
    System.out.println ("s2="+s2);
    System.out.println ("s3="+s3);
    System.out.println ("ch="+ch);
    if( s1 == ch ) System.out.println ("s1=ch");
      else System.out.println ("s1<>ch");
    if( s1 == s3 ) System.out.println ("s1=s3");
      else System.out.println ("s1<>s3");
    if( s1.equals(s2) ) System.out.println ("s1 même val. que s2");
      else System.out.println ("s1 différent de s2");
    if( s1.equals(s3) ) System.out.println ("s1 même val. que s3");
      else System.out.println ("s1 différent de s3");
   if( s1.equals(ch) ) System.out.println ("s1 même val. que ch");
      else System.out.println ("s1 différent de ch");

Après exécution on obtient :
 

ATTENTION

En fait, Java a un problème de cohérence avec les littéraux de String. Le morceau de programme ci-dessous montre cinq évaluations équivalentes de la String s2 qui contient après l'affectation la chaîne "abcdef", puis deux tests d'égalité utilisant l'opérateur = = . Nous avons mis en commentaire, après chacune des cinq affectations, le résultat des deux tests :
String ch;
ch = "abcdef" ;

String s2,s4="abc" ;
s2 = s4.concat("def") ; /* après tests : s2<>abcdef, s2<>ch */
s2 = "abc".concat("def"); /* après tests : s2<>abcdef, s2<>ch */
s2 = s4+"def"; /* après tests : s2<>abcdef, s2<>ch */
s2="abc"+"def"; /* après tests : s2 ==abcdef, s2 == ch */
s2="abcdef"; /* après tests : s2 == abcdef, s2 == ch */
//-- tests d'égalité avec l'opérateur = =
if( s2 == "abcdef" ) System.out.println ("s2==abcdef");
else System.out.println ("s2<>abcdef");
if( s2 == ch ) System.out.println ("s2==ch");
else System.out.println ("s2<>ch");


Nous remarquons que selon que l'on utilise ou non des littéraux les résultats du test ne sont pas les mêmes.

CONSEIL

Pour éviter des confusions et mémoriser des cas particuliers, il est conseillé d’utiliser la méthode equals pour tester la valeur d'égalité de deux chaînes.


Rapport entre String et char

Une chaîne String contient des éléments de base de type char comment passe-t-on de l'un à l'autre type.

1°) On ne peut pas considérer un char comme un cas particulier de String, le transtypage suivant est refusé :

char car = 'r';
String s;
s = (String)car;

Il faut utiliser la méthode de conversion valueOf des String :
s = String.valueOf(car);

2°) On peut concaténer avec l'opérateur +, des char à une chaîne String déjà existante et affecter le résultat à une String :

String s1 , s2 ="abc" ;
char c = 'e' ;
s1 = s2 + 'd' ;
s1 = s2 + c ;

L'écriture suivante sera refusée :
Ecriture correcte associée :
String s1 , s2 = "abc" ;
char c = 'e' ;
s1 =  'd'  +  c ; // types incompatibles
s1 =  'd'  +  'e'; // types incompatibles
String s1 , s2 = "abc" ;
char c = 'e' ;
s1 =  "d"  + String.valueOf (c) ;
s1 =  "d"  +  "e";


Pour plus d'information sur toutes les méthode de la classe String voici telle qu'elle est présentée par Sun dans la documentation du JDK 1.4.2 (http://java.sun.com), la liste des méthodes de cette classe.






 
 
 

2. Les tableaux Arrays, les matrices

Dès que l'on travaille avec de nombreuses données homogènes ( de même type) la première structure de base permettant le regroupement de ces données est le tableau. Java comme tous les langages algorithmiques propose  cette structure au programmeur. Comme pour les String, pour des raisons d'efficacité dans l'encombrement mémoire, les tableaux sont gérés par Java, comme des objets.
 


 

2.1 Les tableaux à une dimension

Déclaration d'une variable de tableau :
int [] table1;
char [] table2;
float [] table3;
...
String [] tableStr;

 

Déclaration d'une variable de tableau avec définition explicite de taille :
int [] table1 = new int [5];
char [] table2  = new char [12];
float [] table3  = new float [8];
...
String [] tableStr  = new String [9];

Le mot clef new correspond à la création d'un nouvel objet (un nouveau tableau) dont la taille est fixée par la valeur indiquée entre les crochets. Ici 4 tableaux sont créés et prêts à être utilisés : table1 contiendra 5 entiers 32 bits, table2 contiendra 12 caractères, table3 contiendra 8 réels en simple précision et tableStr contiendra 9 chaînes de type String.

On peut aussi déclarer un tableau sous la forme de deux instructions : une instruction de déclaration et une instruction de définition de taille avec le mot clef new, la seconde pouvant être mise n'importe où dans le corps d'instruction, mais elle doit être utilisée avant toute manipulation du tableau. Cette dernière instruction de définition peut être répétée plusieurs fois dans le programme, il s'agira alors à chaque fois de la création d'un nouvel objet (donc un nouveau tableau), l'ancien étant détruit et désalloué automatiquement par le ramasse-miettes (garbage collector) de Java.

int [] table1;
char [] table2;
float [] table3;
String [] tableStr;
....
table1 = new int [5];
table2  = new char [12];
table3  = new float [8];
tableStr  = new String [9];

Déclaration et initialisation d'un tableau avec définition implicite de taille :
int [] table1 = {17,-9,4,3,57};
char [] table2  = {'a','j','k','m','z'};
float [] table3  = {-15.7f,75,-22.03f,3,57};
String [] tableStr  = {"chat","chien","souris","rat","vache"};

Dans cette éventualité Java crée le tableau, calcule sa taille et l'initialise avec les valeurs fournies.
 

Il existe un attribut général de la classe des tableaux, qui contient la taille d'un tableau quelque soit son type, c'est l'attribut length.

Exemple :
int [ ] table1 = {17,-9,4,3,57};
int taille;
taille = table1.length;
// taille = 5
 

Il existe des classes permettant de manipuler les tableaux :
 


 

2.2 Utiliser un tableau

Un tableau en Java comme dans les autres langages algorithmiques s'utilise à travers une cellule de ce tableau repérée par un indice obligatoirement de type entier ou un char considéré comme un entier (byte, short, int, long ou char).

Le premier élément d'un tableau est numéroté 0, le dernier length-1.

On peut ranger des valeurs ou des expressions du type général du tableau dans une cellule du tableau.

Exemple avec un tableau de type int :

int [] table1 = new int [5];
// dans une instruction d'affectation:
table1[0] = -458;
table1[4] = 5891;
table1[5] = 72;  <--- est une erreur de dépassement de la taille ! (valeur entre 0 et 4)

// dans une instruction de boucle:
for (int i = 0 ; i<= table1.length-1;  i++)
 table1[i] = 3*i-1; // après la boucle: table1 = {-1,2,5,8,11}
 

Même exemple avec un tableau de type char :

char [] table2  = new char [7];

table2[0] = '?' ;
table2[4] = 'a' ;
table2[14] = '#' ;  <--- est une erreur de dépassement de la taille
for (int i = 0 ; i<= table2.length-1;  i++) 
      table2[i] =(char)('a'+i); 
// après la boucle: table2 = {'a', 'b', 'c' ,'d', 'e', 'f'}
 

Remarque :

Dans une classe exécutable la méthode main reçoit en paramètre un tableau de String nommé args qui correspond en fait aux éventuels paramètres de l'application elle-même:
static void main(String [] args) 

 
 

2.3 Les matrices

Les tableaux en Java peuvent être à deux dimensions auquel cas ils sont appelés des matrices, ce aussi des objets et ils se comportent comme les tableaux à une dimension tant au niveau des déclarations  qu'au niveau des utilisations. La déclaration s'effectue avec deux opérateurs crochets  [ ] [ ] .

Leur structuration n'est pas semblable à celle des tableaux pascal, en fait en java une matrice est composée de plusieurs tableaux unidimensionnels de même taille (pour fixer les idées nous les appellerons les lignes de la matrice) : dans une déclaration Java le premier crochet sert à indiquer le nombre de lignes (nombre de tableaux à une dimension), le second crochet sert à indiquer la taille de la ligne.
 
Un tel tableau à deux dimensions, peut être considéré comme un tableau unidimensionnel de pointeurs, où chaque pointeur référence un autre tableau unidimensionnel.
Voici une manière imagée de visualiser une matrice à n+1 lignes et à p+1 colonnes

int [ ][ ] table = new int [n+1][p+1];


Les tableaux multiples en Java sont utilisables comme des tableaux unidimensionnels. Si l'on garde bien présent à l'esprit le fait qu'une cellule contient une référence vers un autre tableau, on peut alors écrire en Java soient des instructions pascal like comme table[i,j] traduite en java par table[i][j], soient des instructions spécifiques à java n'ayant pas d'équivalent en pascal comme dans l'exemple ci-après :

table[0] = new int[p+1];

table[1] = new int[p+1];

Dans chacune de ces deux instructions nous créons un objet de tableau unidimensionnel qui est référencé par la cellule de rang 0, puis par celle de rang 1.


Ou encore, en illustrant ce qui se passe après chaque instruction  :



int [ ][ ] table = new int [n+1][p+1];

table[0] = new int[p+1];





int
[ ] table1 = new int [p+1];



table[1] = table1 ;

Rien n'oblige les tableaux référencés d'avoir la même dimension, ce type de tableau se dénomme tableaux en escalier ou tableaux déchiquetés en Java  :



int
[ ][ ] table = new int [3][ ];
table[0] = new int[2];
table[1] = new int[4];
table[2] = new int[3];


 

Si l'on souhaite réellement utiliser des matrices (dans lequel toutes les lignes ont la même dimension) on emploiera l'écriture pascal-like, comme dans l'exemple qui suit.

Exemple de matrice de type int :

int [][] table1 = new int [2][3];// deux lignes de dimension 3 chacunes
// dans une instruction d'affectation:
table1[0][0] = -458;
table1[2][5] = -3;  <--- est une erreur de dépassement ! (valeur entre 0 et 1)
table1[1][4] = 83;  <--- est une erreur de dépassement ! (valeur entre 0 et 4)

// dans une instruction de boucle:
for (int i = 0 ; i<= 2;  i++)
 table1[1][i] = 3*i-1; 

// dans une instruction de boucles imbriquées:
for (int i = 0 ; i<= 2;  i++)
 for (int k= 0 ; i<= 3;  i++) table1[i][k] = 100;
 


 

Le même attribut général length de la classe des tableaux, contient la taille du tableau :

Exemple : matrice à deux lignes de dimension 3 chacune
int [][] table1 = new int [2][3];
int taille;
taille = table1.length;
// taille = 2 (nombre de lignes)
taille = table1[0].length; // taille = 3 (nombre de colonnes)
taille = table1[1].length; // taille = 3 (nombre de colonnes)

Java initialise les tableaux par défaut à 0 pour les int, byte, ... et à null pour les objets.
 


 

3. Les tableaux dynamiques
 

Un tabeau array à une dimension, lorsque sa taille a été fixée soit par une définition explicite, soit par une définition implicite, ne peut plus changer de taille, c'est donc une structure statique.

char [] TableCar ;
TableCar = new char[8]; //définition de la taille et création d'un nouvel objet tableau à 8 cellules
TableCar[0] = 'a';
TableCar[1] = '#';
...
TableCar[7] = '?';

Si l'on rajoute l'instruction suivante aux précédentes

<TableCar= new char[10]; > il y a création d'un nouveau tableau de même nom et de taille 10, l'ancien tableau à 8 cellules est alors détruit. Nous ne redimensionnons pas le tableau, mais en fait nous créons un nouvel objet ayant la même reférence que le précédent :

Ce qui nous donne après exécution de la liste des instructions ci-dessous, un tableau TabCar ne contenant plus rien :
char [] TableCar ;
TableCar = new char[8];
TableCar[0] = 'a';
TableCar[1] = '#';
...
TableCar[7] = '?';
TableCar= new char[10];

Si l'on veut "agrandir" un tableau pendant l'exécution il faut en déclarer un nouveau plus grand et recopier l'ancien dans le nouveau.

Il est possible d'éviter cette façon de faire en utilisant un vecteur (tableau unidimensionnel dynamique) de la classe Vector, présent dans le package java.util.Vector. Ce sont en fait des listes dynamiques gérées comme des tableaux.

Un objet de classe Vector peut "grandir" automatiquement  d'un certain nombre de cellules pendant l'exécution, c'est le programmeur qui peut fixer la valeur d'augmentation du nombre de cellules supplémentaires dès que la capacité maximale en cours est dépassée. Dans le cas où la valeur d'augmentation n'est pas fixée, c'est la machine virtuelle Java qui procède à une augmentation par défaut (doublement dès que le maximum est atteint).

Vous pouvez utiliser le type Vector uniquement dans le cas d'objets et non d'éléments de type élémentaires (byte, short, int, long ou char ne sont pas autorisés), comme par exemple les String ou tout autre objet de Java ou que vous créez vous-même.

Les principales méthodes permettant de manipuler les éléments d'un Vector sont :
void addElement(Object obj) ajouter un élément à la fin du vecteur
void clear( ) effacer tous les éléments du vecteur
Object elementAt(int index) élément situé au rang = 'index'
int indexOf(Object elem) rang de l'élément 'elem'
Object remove(int index) efface l'élément situé au rang = 'index'
void setElementAt(Object obj, int index) remplace l'élément de rang 'index' par obj
int size( ) nombre d'éléments du vecteur

Voici un exemple simple de vecteur de chaînes utilisant quelques unes des méthodes précédentes :

static void afficheVector(Vector vect)
//affiche un vecteur de String
 {
   System.out.println( "Vector taille = " + vect.size( ) );
   for ( int i = 0; i<= vect.size( )-1; i++ )
        System.out.println( "Vector[" + i + "]=" + (String)vect.elementAt( i ) );
 }

static void VectorInitialiser( )
// initialisation du vecteur de String
{    Vector table = new Vector( );
      String str = "val:";
      for ( int i = 0; i<=5; i++ )
             table.addElement(str + String.valueOf( i ) );
      afficheVector(table);
}

Voici le résultat de l'exécution de la méthodeVectorInitialiser :
Vector taille = 6
Vector[0] = val:0
Vector[1] = val:1
Vector[2] = val:2
Vector[3] = val:3
Vector[4] = val:4
Vector[5] = val:5
 
 

4. Les listes chaînées

Rappellons qu'une liste linéaire (ou liste chaînée) est un ensemble ordonné d'éléments de même type (structure de donnée homogène) auxquels on accède séquentiellement. Les opérations minimales effectuées sur une liste chaînée sont l'insertion, la modification et la suppression d'un élément quelconque de la liste.

Les listes peuvent être uni-directionnelles, elles sont alors parcourues séquentiellement dans un seul sens :


 

ou bien bi-directionnelles dans lesquelles chaque élément possède deux liens de chaînage, l'un sur l'élément qui le suit l'autre sur l'élément qui le précède, le parcours s'effectuant en suivant l'un ou l'autre sens de chaînage :

 

La classe LinkedList présente dans le package java.util.LinkedList, est en Java une implémentation de la liste chaînée bi-directionnelle, comme la classe Vector, les éléments de la classe LinkedList ne peuvent être que des objets et non  de type élémentaires (byte, short, int, long ou char ne sont pas autorisés),

Quelques méthodes permettant de manipuler les éléments d'une LinkedList :
void addFirst(Object obj) ajouter un élément au début de la liste
void addLast(Object obj) ajouter un élément à la fin de la liste
void clear( ) effacer tous les éléments de la liste
Object get(int index) élément situé au rang = 'index'
int indexOf(Object elem) rang de l'élément 'elem'
Object remove(int index) efface l'élément situé au rang = 'index'
Object set( int index , Object obj) remplace l'élément de rang 'index' par obj
int size( ) nombre d'éléments de la liste

Reprenons l'exemple précédent sur une liste de type LinkedList d'éléments de type String :
import java.util.LinkedList;
class ApplicationLinkedList {

 static void afficheLinkedList (LinkedList  liste )
 //affiche une liste de chaînes
 {
   System.out.println("liste taille = "+liste.size());
   for ( int i = 0 ; i <= liste.size( )-1 ; i++ )
        System.out.println("liste(" + i + ")=" + (String)liste.get(i));
 }
 static void LinkedListInitialiser( )
 {    LinkedList liste = new LinkedList( );
     String str = "val:";
     for ( int i = 0 ; i <= 5 ; i++ )
     liste.addLast( str + String.valueOf( i ) );
     afficheLinkedList(liste);
 }
 static void main(String[] args) 
 {
   LinkedListInitialiser( );
 }
}

Voici le résultat de l'exécution de la méthode main de la classe ApplicationLinkedList :
liste taille = 6
liste(0) = val:0
liste(1) = val:1
liste(2) = val:2
liste(3) = val:3
liste(4) = val:4
liste(5) = val:5
 
 

5. Flux et fichiers
 

Un programme travaille avec ses données internes, mais habituellement il lui est très souvent nécessaire d'aller chercher en entrée, on dit lire, des nouvelles données (texte, image, son,...) en provenance de diverses sources (périphériques, autres applications...). Réciproquement, un programme peut après traitement, délivrer en sortie des résultats, on  dit écrire, dans un fichier ou vers une autre application.
 


 
 

5.1 Les flux en Java

En Java, toutes ces données sont échangées en entrée et en sortie à travers des flux (Stream).

Un flux est une sorte de tuyau de transport séquentiel de données.

 

Il existe un flux par type de données à transporter :

 

Un flux est unidirectionnel : il y a donc des flux d'entrée et des flux de sortie :


Afin de jouer un son stocké dans un fichier, l'application Java ouvre en entrée, un flux associé aux sons et lit ce flux séquentiellement afin ensuite de traiter ce son (modifier ou jouer le son).
 


La même application peut aussi traiter des images à partir d'un fichier d'images et renvoyer ces images dans le fichier après traitement. Java ouvre un flux en entrée sur le fichier image et un flux en sortie sur le même fichier, l'application lit séquentiellement le flux d'entrée (octet par octet par exemple) et écrit séquentiellement dans le flux de sortie.
 

Java met à notre disposition dans le package java.io.*, deux grandes catégories de flux :
( la notation "*" dans  package java.io.* indique que l'on utilise toutes les classes du package java.io)
 


 
  • Comme Java est un LOO (Langage Orienté Objet) les différents flux d'une famille sont des classes dont les méthodes sont adaptées au transfert et à la structuration des données selon la destination de celles-ci.
  • Lorsque vous voulez lire ou écrire des données binaires (sons, images,...) utilisez une des classes de la famille des flux d'octets.
  • Lorsque vous utilisez des données de type caractères préférez systématiquement l'un des classes de la famille des flux de caractères.

Etant donné l'utilité de tels flux nous donnons exhaustivement la liste et la fonction de chaque classe pour chacune des deux familles.
 
 

5.2 Les flux d'octets en entrée

Cette sous-famille de flux d'entrée contient 7 classes dérivant toutes de la classe abstraite InputStream.

Fonction des classes de flux d'octets en entrée
Classes utilisées pour la communication
FileInputStream lecture de fichiers octets par octets.
PipedInputStream récupère des données provenant d'un flux de sortie (cf. PipedOutputStream).
ByteArrayInputStream lit des données dans un tampon structuré sous forme d'un array.
Classes utilisées pour le traitement
SequenceInputStream concaténation d'une énumération de plusieurs flux d'entrée en un seul flux d'entrée.
StringBufferInputStream lecture d'une String (Sun déconseille son utilisation et préconise son remplacement par StringReader).
ObjectInputStream lecture d'objets Java.
FilterInputStream lit à partir d'un InputStream quelconque des données, en "filtrant" certaines données.

 

5.3 Les flux d'octets en sortie

Cette sous-famille de flux de sortie contient 5 classes dérivant toutes de la classe abstraite OutputStream.

Fonction des classes de flux d'octets en sortie
Classes utilisées pour la communication
FileOutputStream écriture de fichiers octets par octets.
PipedOutputStream envoie des données  vers un flux d'entrée (cf. PipedInputStream).
ByteArrayOutputStream écrit des données dans un tampon structuré sous forme d'un array.
Classes utilisées pour le traitement
ObjectOutputStream écriture d'objets Java lisibles avec ObjectInputStream.
FilterOutputStream écrit à partir d'un OutputStream quelconque des données, en "filtrant" certaines données.

 
 

5.4 Les opérations d'entrée sortie standard dans une application

Java met à votre disposition 3 flux spécifiques présents comme attributs dans la classe System du package java.lang.System :

Le flux d'entrée System.in est connecté à l'entrée standard qui est par défaut le clavier.
Le flux de sortie System.out est connecté à la sortie standard qui est par défaut l'écran.
Le flux de sortie System.err est connecté à la sortie standard qui est par défaut l'écran.

La classe PrintStream dérive de la classe FilterOutputStream. Elle ajoute de nouvelles fonctionnalités à un flux de sortie, en particulier le flux out possède ainsi une méthode println redéfinies avec plusieurs signatures ( plusieurs en-têtes différentes : byte, short, char, float,...) qui lui permet d'écrire des entiers de toute taille, des caractères, des réels...

Vous avez pu remarquer que depuis le début nous utilisions pour afficher nos résultats, l'instruction System.out.println(...);  qui en fait correspond à l'utilisation de la méthode println de la classe PrintStream.

Exemple d'utilisation simple des flux System.out et System.in :

public static void main(String[] args) throws IOException {
        System.out.println("Appuyez sur la touche <Entrée> :"); //message écran
        System.in.read( ); // attend la frappe clavier de la touche <Entrée>
 }

Dans Java, le flux System.in appartient à la classe InputStream et donc il est moins bien traité que le flux System.out et donc il n'y a pas en Java quelque chose qui ressemble à l'instruction readln du pascal par exemple. Le manque de souplesse semble provenir du fait qu'une méthode ne peut renvoyer son résultat de type élémentaire que par l'instruction return et il n'est pas possible de redéfinir une méthode uniquement par le type de son résultat.

Afin de pallier à cet inconvénient il vous est fourni une classe Readln avec une méthode de lecture au clavier pour chaque type élémentaire. En mettant le fichier Readln.class dans le même dossier que votre application vous pouvez vous servir de cette classe pour lire au clavier dans vos programmes n'importe quelles variables de type élémentaire.

Méthodes de lecture clavier dans la classe Readln
 String unstring( ) lecture clavier d'un chaîne de type String.
 byte unbyte( )  lecture clavier d'un entier de type byte.
 short unshort( ) lecture clavier d'un entier de type short.
 int unint( ) lecture clavier d'un entier de type int.
 long unlong( ) lecture clavier d'un entier de type long.
double undouble( ) lecture clavier d'un réel de type double.
float unfloat( )  lecture clavier d'un réel de type float.
char unchar( )  lecture clavier d'un caractère.

Voici un exemple d'utilisation de ces méthodes dans un programme :
 
class ApplicationLireClavier {

 public static void main(String [] argument)  {
 String Str;
 int i;  long L;  char k;
 short s;  byte b;  float f; double d;
  System.out.print("Entrez une chaîne : ");
  Str = Readln.unstring( );
  System.out.print("Entrez un int: ");
  i = Readln.unint( );
  System.out.print("Entrez un long : ");
  L = Readln.unlong( );
  System.out.print("Entrez un short : ");
  s = Readln.unshort( );
  System.out.print("Entrez un byte : ");
  b = Readln.unbyte( );
  System.out.print("Entrez un caractère : ");
  k = Readln.unchar( );
  System.out.print("Entrez un float : ");
  f = Readln.unfloat( )
  System.out.print("Entrez un double : ");
  f = Readln.unfloat( );
 }
}

Le source de la classe Readln est donné ci-dessous à titre indicatif et sans commentaires particulier car il s'agit simplement d'un outil.

import java.io.*;

public class Readln {

public static String unstring( ) // Lire un String
{
  String Strloc = new String();  //<=> Strloc ="";
  char Carlu='\0';
  try {
         while ((Carlu=(char) System.in.read()) !='\n')
          if (Carlu != '\r')  Strloc = Strloc+Carlu;
   }
  catch (IOException e) {
          System.out.println("Erreur de saisie");
          System.exit(0);
  }
  return Strloc;
} // fin de unstring()

 public static byte unbyte( )  // Lire un entier de type byte
 {
   byte b=0;
   try {
          b=Byte.parseByte(unstring());
   }
   catch (NumberFormatException e) {
          System.out.println("Entier byte incorrect");
          System.exit(0);
    }
   return b ;
 } // fin de unbyte()

 public static short unshort( )  // Lire un entier short
 {
   short s=0;
   try {
          s=Short.parseShort(unstring());
   }
   catch (NumberFormatException e) {
          System.out.println("Entier short incorrect");
          System.exit(0);
    }
   return s ;
 } // fin de unshort()

 public static int unint( )  // Lire un entier
 {
   int i=0;
   long loc=unlong();// un int est un long particulier
   i=(int)loc;
   return i ;
 } // fin de unint()

 public static long unlong( )  // Lire un entier long
 {
   long L=0;
   try {
    L=Integer.parseInt(unstring());
    }
   catch (NumberFormatException e) {
          System.out.println("Entier long incorrect");
          System.exit(0);
    }
   return L ;
 } // fin de unlong()

 public  static double undouble( )  // Lire un double
 {
   double D=0.0; // type réel par défaut de Java
   try {
    D=Double.valueOf(unstring()).doubleValue();
   }
   catch (NumberFormatException e) {
          System.out.println("Réel double incorrect");
          System.exit(0);
   }
   return D ;
 } // fin de undouble()

 public  static float unfloat( )  // Lire un float
 {
   float F=0.0f; // sinon double par défaut
   try {
   F=Double.valueOf(unstring()).floatValue();
   }
   catch (NumberFormatException e) {
          System.out.println("Format numérique incorrect");
          System.exit(0);
   }
   return F ;
 } // fin de unfloat()

 public  static char unchar( )  // Lire un caractere
 {
  String Strloc=unstring();// un caractère est un string particulier
  if (Strloc.length() = = 0)
     return '\n';
  else
    return Strloc.charAt(0);// on ne prend que le premier caractère
 } // fin de unchar()
}
 

5.5 Les flux de caractères

Cette sous-famille de flux de données sur 16 bits contient des classes dérivant toutes de la classe abstraite Reader pour les flux en entrée, et des classes relativement aux flux en sortie dérivant de la classe abstraite Writer.

Fonction des classes de flux de caractères en entrée
BufferedReader lecture de caractères dans un tampon.
CharArrayReader lit de caractères dans un tampon structuré sous forme d'un array.
FileReader lecture de caractères dans un fichier texte.
FilterReader lit à partir d'un Reader quelconque des caractères, en "filtrant" certaines caractères.
InputStreamReader conversion de flux d'octets en flux de caractères (8 bits en 16 bits)
LineNumberReader lecture de caractères dans un tampon (dérive de BufferedReader) avec comptage de lignes.
PipedReader récupère des données provenant d'un flux de caractères en sortie (cf. PipedWriter).
StringReader lecture de caractères à partir d'une chaîne de type String.

Fonction des classes de flux de caractères en sortie
BufferedWriter écriture de caractères dans un tampon.
CharArrayWriterWriter écrit des caractères dans un tampon structuré sous forme d'un array.
FileWriter écriture de caractères dans un fichier texte.
FilterWriter écrit à partir d'un Reader quelconque des caractères, en "filtrant" certaines caractères.
OutputStreamWriter conversion de flux d'octets en flux de caractères (8 bits en 16 bits)
PipedWriter envoie des données  vers un flux d'entrée (cf. PipedReader).
StringWriter écriture de caractères dans une chaîne de type String.

 
 

5.6 Lecture et écriture dans un fichier de texte
 

Il existe une classe dénommée File (dans java.io.File) qui est une représentation abstraite des fichiers, des répertoires et des chemins d'accès. Cette classe permet de créer un fichier, de l'effacer, de le renommer, de créer des répertoires etc...

Pour construire un fichier (texte ou non) il est nécessaire de le créer, puis d'y écrire des données à l'intérieur. Ces opérations passent obligatoirement en Java, par la connection du fichier après sa création, à un flux de sortie. Pour utiliser un fichier déjà créé (présent sur disque local ou télétransmis) il est nécessaire de se servir d'un flux d'entrée.

Conseil pratique : pour tous vos fichiers utilisez systématiquement les flux d'entrée et de sortie bufférisés (BufferedWriter et BufferedReader par exemple, pour vos fichiers de textes). Dans le cas d'un flux non bufférisé le programme lit ou écrit par exemple sur le disque dur, les données au fur et à mesure, alors que les accès disque sont excessivement coûteux en temps.

Exemple écriture non bufférisée de caractères dans un fichier texte :


 

Ci-dessous un exemple de méthode permettant de créer un fichier de caractères et d'écrire une suite de caractères terminée par le caractère '#', on utilise un flux de la classe FileWriter non bufférisée :
 
public static void fichierFileWriter(String nomFichier) {
  try {
    FileWriter out = new FileWriter(nomFichier);
    out.write("Ceci est une ligne FileWriter");
    out.write('#');
    out.close( );
  }
  catch (IOException err) {
    System.out.println( "Erreur : " + err );
  }
}

L'exécution de cette méthode produit le texte suivant :
Ceci est une ligne FileWriter#
 

Un flux bufférisé stocke les données dans un tampon (buffer, ou mémoire intermédiaire) en mémoire centrale, puis lorsque le tampon est plein, le flux transfert le paquet de données contenu dans le tampon vers le fichier (en sortie) ou en provenance du fichier en entrée. Dans le cas d'un disque dur, les temps d'accès au disque sont optimisés puisque celui-ci est moins fréquemment sollicité par l'écriture.

Exemple écriture bufférisée de caractères dans un fichier texte :


 

Ci-dessous un exemple de méthode permettant de créer un fichier de caractères et d'écrire une suite de caractères terminée par le caractère # , on utilise un flux de la classe BufferedWriter  bufférisée qui comporte la même méthode write, mais qui possède en plus la méthode newLine ajoutant un end of line (fin de ligne) à une suite de caractères, permettant le stockage simple de texte constitué de lignes:
 
public static void fichierBufferedWriter(String nomFichier) {
   try {
     fluxwrite = new FileWriter(nomFichier);
     BufferedWriter out = new BufferedWriter(fluxwrite);
     out.write("Ceci est une ligne FileBuffWriter");
     out.write('#');
     out.write("Ceci est la ligne FileBuffWriter n° 1");
     out.newLine( ); //écrit le eoln
     out.write("Ceci est la ligne FileBuffWriter n° 2");
     out.newLine( ); //écrit le eoln
     out.close( );
   }
  catch (IOException err) {
    System.out.println( "Erreur : " + err );
  }
}

L'exécution de cette méthode produit le texte suivant :
Ceci est une ligne FileBuffWriter#Ceci est la ligne FileBuffWriter n° 1
Ceci est la ligne FileBuffWriter n° 2

Nous avons utilisé la déclaration de flux bufférisée explicite complète :

fluxwrite = new FileWriter(nomFichier);
BufferedWriter out = new BufferedWriter(fluxwrite);

Java autorise une déclaration implicite raccourcie équivalente :

BufferedWriter out = new BufferedWriter(newFileWriter(nomFichier));