Nous savons désormais écrire dans un fichier de nom name de façon
efficace en construisant un FileWriter
, puis un
BufferedWriter
.
Toutefois, écrire caractère par caractère (ou même par chaîne
avec
write(String str)
),
n’est pas toujours très pratique. La méthode print
de System.out
est bien plus commode.
Malheureusement, les PrintStream
de la bibliothèque sont
très inefficaces.
Par exemple,
la copie du fichier a dans le fichier b
par java Cat < a > b est environ vingt-cinq fois plus
lente que java Cp2 a b !
Mais la méthode print
est bien pratique,
voici par exemple une méthode simple qui affiche les nombres premiers
jusqu’à n directement dans System.out
.
L’application du crible d’Eratosthène est naïve, mais le programme est
inefficace d’abord à cause de ses affichages.
On s’en rend compte en modifiant sieve pour utiliser
un PrintWriter.
Les objets de la classe PrintWriter
sont des Writer
(flux de caractères) qui offrent les sorties
formatées, c’est-à-dire qu’ils possèdent une méthode print
(et
prinln
) surchargée qui allège un peu la programmation.
Par ailleurs, les PrintWriter
sont bufferisés par défaut.
La nouvelle méthode sieve écrit dans le PrintWriter
out dont l’initialisation est rendue pénible par les exceptions possibles.
Noter aussi que la sortie out est vidée
(et même fermée) par la méthode main qui l’a créée.
Il y a plusieurs constructeurs des PrintWriter
, qui
prennent divers arguments.
Par exemple,
si l’on avait voulu écrire dans la sortie standard, et non pas dans
le fichier primes.txt, on aurait très bien
pu « emballer » la sortie standard dans un PrintWriter
,
par new PrintWriter(System.out)
.
Pour ce qui est du temps d’exécution des essais rapides montrent
que le nouveau sieve (avec PrintWriter
) peut être
jusqu’à dix fois plus rapide que l’ancien (avec PrintStream
).
Nous insistons donc : System.out
est vraiment très
inefficace, il faut renoncer à son emploi dès qu’il
y a beaucoup de sorties.
Les PrintWriter
n’ont rien de magique,
et il est également possible d’employer un BufferedWriter
.
Noter surtout que l’affichage d’une ligne qui contient un entier est
pénible : conversion explicite (par String.valueOf
) et
retour à la ligne explicite (par out.newLine()
).
Les Reader
permettent de lire caractère par caractère,
les BufferedReader
permettent de lire ligne par ligne.
C’est déjà pas mal, mais c’est parfois insuffisant.
Par exemple, on peut imaginer vouloir relire le fichier primes.txt
de la section précédente.
Ce que l’on veut alors c’est lire une séquence d’entiers int
.
Pour ce faire nous pourrions reconstituer nous même
les int
à partir de la séquence de leurs chiffres,
mais ça ne serait pas très moderne.
Heureusement, il existe des objets qui savent faire ce
style de lecture pour nous : ceux de la classe Scanner du
package java.util. Les objets Scanner
sont des flux de
lexèmes (tokens). Les lexèmes sont simplement à un
langage informatique ce que les mots sont à un langage dit naturel.
Par défaut les lexèmes reconnus par un Scanner
sont les mots de
Java — un entier, un identificateur, une chaîne, etc.
Un Scanner
possède une méthode
next() qui renvoie
le lexème suivant du flux d’entrée (comme un String
)
et une méthode hasNext()
pour savoir si il existe un lexème suivant, ou si le flux est terminé.
Il possède aussi des méthodes spécialisées pour lire
des lexèmes particuliers ou savoir si le lexème suivant existe et
est un lexème particulier
(par exemple
nextInt()
et hasNextInt() pour
un int
).
On peut donc utiliser primes.txt pour décomposer
un int
en facteurs premiers ainsi (même si ce n’est pas une très
bonne idée algorithmiquement parlant).
On note que le Scanner
est construit à partir d’un Reader
.
Il y a bien entendu d’autres constructeurs.
On note aussi que l’entrée scan
est fermée explicitement
par scan.close()
, dès qu’elle n’est plus utile ; tandis
que la sortie n’est que vidée par out.flush()
.
En effet, il ne serait pas adéquat de fermer la sortie out
qui sert plusieurs fois, mais il faut s’assurer que les sorties du
programme sont bien envoyées au système d’exploitation.
Le contrôle fin du comportement
des Scanner
se fait à l’aide des expressions régulières
de Java (voir 6.3.3 et la classe de bibliothèque
Pattern)