
Pourquoi travailler avec des fichiers ?
Parce que c'est la façon la plus simple de stocker des informations à l'extérieur d'un programme. Ensuite, ces informations peuvent être utilisées n'importe quand (sans que le programme qui les a créées ne soit obligé de fonctionner) et par n'importe quel programme qui sait lire le fichier en question.
On a toujours besoin de fichiers ne serait-ce que pour enregistrer du texte.
Il existe un nombre infini de types de fichiers, et vous pouvez même créer les vôtres.
Un fichier possède un nom que se termine par un point suivi de quelques lettres (en général 2 à 4), comme ceci : "exemple.txt".
Ces quelques lettres représentent l'extension du fichier. Votre OS (système d'exploitation) reconnaît les extensions et sait quels programmes y sont associés.
Ces quelques lettres représentent l'extension du fichier. Votre OS (système d'exploitation) reconnaît les extensions et sait quels programmes y sont associés.
Ainsi, un fichier ".txt" est un fichier texte qui s'ouvrira automatiquement avec un éditeur de texte.
De même, un fichier ".png" représente une image et sera traité comme telle.
De même, un fichier ".png" représente une image et sera traité comme telle.
Tous les fichiers sont stockés en binaire : une succession de bits (0 ou 1) qui forment des octets (1 octet = 8 bits).
Ces octets sont en fait des nombres écrits en base 2. ils peuvent représenter tout et n'importe quoi : du texte, du son, une image, ...
Ces octets sont en fait des nombres écrits en base 2. ils peuvent représenter tout et n'importe quoi : du texte, du son, une image, ...
Nous avons l'habitude de travailler en base 10 : il y a dix chiffres (de 0 à 9) et le nombre abc en base 10(pas a*b*c !) représente a*102 + b*101 + c*100, ce qui fait 100*a + 10*b + c.
De même abc en base 2 représente a*22 + b*21 + c*20, ce qui fait 4*a + 2*b + c. Ainsi, 1001 en base 2correspond à 9 en base 10.
De même abc en base 2 représente a*22 + b*21 + c*20, ce qui fait 4*a + 2*b + c. Ainsi, 1001 en base 2correspond à 9 en base 10.
Un octet va de 0000 0000 jusqu'à 1111 1111 en base 2, soit de 0 à 255 (il y a 8 bits : nombre max = 28 - 1) en base 10.
Pour coder les caractères, le code ASCII a à l'origine été mis en place. Il y avait cependant un problème : les accents n'existent pas en anglais et n'ont pas été traduits en code ASCII. Ce code a donc évolué depuis et a été complété par d'autres.
Unicode est le code universel actuel. Vous avez d'ailleurs sûrement déjà entendu parler de l'encodage UTF-8, qui est un format de codage de caractères défini pour les caractères Unicode.
Unicode est le code universel actuel. Vous avez d'ailleurs sûrement déjà entendu parler de l'encodage UTF-8, qui est un format de codage de caractères défini pour les caractères Unicode.
Voici un fichier texte ouvert en mode binaire avec Textpad (ce n'est pas vraiment du binaire, c'est de l'héxadécimal) :
Fichier texte ouvert en mode binaire avec Textpad.
Un nombre hexadécimal (hexa = 6, décimal = 10) est écrit en base 16 :
base 2
|
base 10
|
base 16
|
---|---|---|
0000 0000
|
0
|
00
|
0000 0001
|
1
|
01
|
0000 0010
|
2
|
02
|
0000 0011
|
3
|
03
|
0000 0100
|
4
|
04
|
0000 0101
|
5
|
05
|
0000 0110
|
6
|
06
|
0000 0111
|
7
|
07
|
0000 1000
|
8
|
08
|
0000 1001
|
9
|
09
|
0000 1010
|
10
|
0A
|
0000 1011
|
11
|
0B
|
0000 1100
|
12
|
0C
|
0000 1101
|
13
|
0D
|
0000 1110
|
14
|
0E
|
0000 1111
|
15
|
0F
|
0001 0000
|
16
|
10
|
0001 0001
|
17
|
11
|
...
|
...
|
...
|
1111 1111
|
255
|
FF
|
Le code de la première lettre est 56 en hexadécimal (l'indice entre parenthèses est la base) :
56(16) = 5.161 + 6.160
56(16) = (4+1).16 + (4+2)
56(16) = (1.22 + 1.20).24 + (1.22 + 1.21)
56(16) = 1.22+4 + 1.20+4 + 1.22 + 1.21
56(16) = 1.26 + 1.24 + 1.22 + 1.21
56(16) = 0101 0110(2)
56(16) = (4+1).16 + (4+2)
56(16) = (1.22 + 1.20).24 + (1.22 + 1.21)
56(16) = 1.22+4 + 1.20+4 + 1.22 + 1.21
56(16) = 1.26 + 1.24 + 1.22 + 1.21
56(16) = 0101 0110(2)
Vous remarquez au passage que 5(16) = 0101(2) et 6(16) = 0110(2). C'est normal : 24 = 16 donc ce que l'on code avec 4 chiffres en base 2, on le code avec un seul en base 16 : 50(16) = 0101 0000(2).
Les quatre premiers bits (en partant de la droite) représentent un nombre compris entre 0 et 15 : en base 16 cela représente les unités ; les quatre derniers représentent un nombre entre 16 et 255 : en base 16 cela représente les "seizaines".
Dans la page du code ASCII sur Wikipédia (voir lien ci-dessus), vous voyez que cela correspond au caractère 'V' : l'éditeur de texte affiche donc un 'V'.
Si vous changez l'extension d'une image en ".txt", vous pourrez voir plein de caractères qui ne veulent rien dire, eh oui le code binaire décrit des informations concernant l'image, ce n'est pas du texte. L'éditeur affiche les caractères correspondant au code, mais cela ne représente rien pour nous autres humains.
Vous voyez donc que modifier l'extension d'une image en ".txt" va juste faire que le fichier sera affiché comme étant du texte et non comme étant une image ; les données en elles-mêmes ne sont pas affectées.
Lire
Nous allons travailler avec des objets de type
Stream
("stream" en anglais = "flux" en français), qui représentent des flux d'octets.
Qu'est-ce qu'un flux ?
Un flux est une connexion entre différents entités ; il transporte les données qu'elles s'échangent. On peut écrire des données dans le flux ou lire celles qui circulent déjà. Ici nous allons effectuer une connexion entre notre programme et l'espace de stockage de notre ordinateur.
Pour lire un fichier en utilisant les boîtes de dialogue, vous allez avoir besoin d'un objet de type
Pour en créer un, prenez-le dans la boîte à outils et mettez-le sur votre fenêtre. Ce n'est pas un objet qui s'affiche dans la fenêtre, il va donc se mettre en-dessous de votre fenêtre dans le mode Design.
OpenFileDialog
, ici nommé openFileDialog1
(ici le nom n'est pas fabuleux car il s'agit simplement d'un exemple ; dans un vrai programme, nommez-le en fonction de ce à quoi il sert).Pour en créer un, prenez-le dans la boîte à outils et mettez-le sur votre fenêtre. Ce n'est pas un objet qui s'affiche dans la fenêtre, il va donc se mettre en-dessous de votre fenêtre dans le mode Design.
Comme d'habitude, je vous donne d'abord le code (qui est à mettre dans une méthode) et ensuite je vous explique tout :
string fileName;
string fileContent;
// On interdit la sélection de plusieurs fichiers.
openFileDialog1.Multiselect = false;
// On supprime le nom de fichier, qui ici vaut "openFileDialog1" (avant sélection d'un fichier).
openFileDialog1.FileName = string.Empty;
// On met des filtres pour les types de fichiers : "Nom|*.extension|autreNom|*.autreExtension" (autant de filtres qu'on veut).
openFileDialog1.Filter = "Fichiers texte|*.txt|Tous les fichiers|*.*";
// Le filtre sélectionné : le 2e (là on ne commence pas à compter à 0).
openFileDialog1.FilterIndex = 2;
// On affiche le dernier dossier ouvert.
openFileDialog1.RestoreDirectory = true;
// Si l'utilisateur clique sur "Ouvrir"...
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
try
{
// On récupère le nom du fichier.
fileName = openFileDialog1.FileName;
// On lit le fichier.
fileContent = File.ReadAllText(fileName);
}
// En cas d'erreur...
catch (Exception ex)
{
MessageBox.Show("Une erreur est survenue lors de l'ouverture du fichier : {0}.", ex.Message);
}
}
Les quelques premières lignes ne devraient pas poser trop de problèmes : on modifie simplement quelques propriétés de la boîte de dialogue. C'est dans le
if
que ça devient intéressant. L'ordinateur évalue openFileDialog1.ShowDialog() == DialogResult.OK
pour obtenir un booléen. En fait, tout se passe comme si ==
était une méthode prenant deux arguments en entrée et retournant un booléen ; on pourrait avoir ==(openFileDialog1.ShowDialog(), DialogResult.OK)
. Je pense qu'on comprend mieux ce qui se passe avec cette écriture. Donc nous étions en train de dire que nous voulions évaluer cette expression. Pour cela, il faut commencer par évaluer les arguments.DialogResult.OK
est déjà une valeur, donc pas de soucis. Par contre ShowDialog
est une méthode : il faut donc l'appeler et exécuter le code qu'elle contient pour obtenir un résultat qui servira pour le ==
.
Que fait la méthode
ShowDialog
?ShowDialog
affiche une boîte de dialogue contenant un explorateur de fichiers et qui attend que vous en choisissiez un ou plusieurs (ici on a interdit d'en choisir plusieurs). Elle retourne un objet de typeDialogResult
(une énumération) qui donne des informations sur ce qu'a fait l'utilisateur. Cet objet peut valoir DialogResult.OK
si l'utilisateur a bien choisi un fichier et qu'il n'a, par exemple, pas annulé. C'est précisément ce que l'on teste dans le if
: on vérifie qu'un fichier a été choisi avant d'essayer d'y accéder.
Récapitulons : dans l'ordre chronologique on commence par afficher la boîte de dialogue et on attend que l'utilisateur fasse quelque chose : le programme est bloqué à la méthode
ShowDialog
. Ensuite on compare le résultat de cette méthode avec DialogResult.OK
à l'aide de l'opérateur ==
: si l'utilisateur a choisi un fichier alors on exécute le code du bloc if
.
Continuons l'analyse : on récupère d'abord le nom du fichier choisi puis on lit le fameux fichier en utilisant la méthode
File.ReadAllText
qui ouvre le fichier, le lit en entier, le ferme et retourne son contenu. En une seule ligne, on rassemble des actions que nous pouvons séparer.
Dans cet exemple j'ai utilisé la méthode
File.ReadAllText
. Comme son nom l'indique, on lit les données en faisant comme si c'était du texte ; à nous d'être sûrs que nous lisons du texte, sinon nous obtiendrons une suite de caractères sans aucun sens. Suivant ce que l'on veut faire avec le fichier, on peut utiliser d'autres méthodes ou classes. Très souvent, on utilise des objets de type Stream
(ou des types qui en dérivent). Cette classe est aussi située dans l'espace de noms System.IO
, il faut donc mettre using System.IO;
en début de fichier. Voici comment on peut faire avec cette nouvelle classe : je remplace juste la ligne fileContent = File.ReadAllText(fileName);
par :
Stream myStream = null;
StreamReader reader = null;
try
{
// Ouverture
myStream = openFileDialog1.OpenFile();
reader = new StreamReader(myStream, Encoding.UTF8);
// Lecture
fileContent = reader.ReadToEnd();
}
catch
{
Console.WriteLine("Something went wrong!");
}
finally
{
// Fermeture
if (reader != null)
{
reader.Dispose();
}
if (myStream != null)
{
myStream.Dispose();
}
}
Ici on a séparé ouverture, lecture et fermeture. Vous pouvez utiliser le mot-clef
using
pour appeler automatiquement à la fin du bloc correspond la méthode Dispose
de l'objet entre parenthèses :
try
{
// Ouverture
using (Stream myStream = openFileDialog1.OpenFile())
{
using (StreamReader reader = new StreamReader(myStream, Encoding.UTF8))
{
// Lecture
fileContent = reader.ReadToEnd();
}
// La méthode Dispose de reader vient d'être appelée.
}
// La méthode Dispose de myStream vient d'être appelée.
}
catch
{
Console.WriteLine("Something went wrong!");
}
Dans ce cas, la fermeture est implicite.
Il est très important de fermer un flux une fois que vous n'en avez plus besoin : cela libère de la mémoire et cela permet de ne pas monopoliser l'accès à la ressource en question (d'autres programmes en ont peut-être besoin).
Informations concernant la classe
Stream
(tirées des métadonnées).
Vous voyez que la classe
Stream
implémente bien l'interface IDsiposable
et propose bien une méthode Dispose
.
Voici le code complet de la deuxième version (je le répète : ce code est à mettre dans une méthode) :
string fileName;
string fileContent;
// On interdit la sélection de plusieurs fichiers.
openFileDialog1.Multiselect = false;
// On supprime le nom de fichier, qui ici vaut "openFileDialog1" (avant sélection d'un fichier).
openFileDialog1.FileName = string.Empty;
// On met des filtres pour les types de fichiers : "Nom|*.extension|autreNom|*.autreExtension" (autant de filtres qu'on veut).
openFileDialog1.Filter = "Fichiers texte|*.txt|Tous les fichiers|*.*";
// Le filtre sélectionné : le 2e (là on ne commence pas à compter à 0).
openFileDialog1.FilterIndex = 2;
// On affiche le dernier dossier ouvert.
openFileDialog1.RestoreDirectory = true;
// Si l'utilisateur clique sur "Ouvrir"...
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
try
{
// On récupère le nom du fichier.
fileName = openFileDialog1.FileName;
Stream myStream = openFileDialog1.OpenFile();
if (myStream != null)
{
using (myStream)
{
using (StreamReader reader = new StreamReader(myStream, Encoding.UTF8))
{
fileContent = reader.ReadToEnd();
}
}
}
}
// En cas d'erreur...
catch (Exception ex)
{
MessageBox.Show("Une erreur est survenue lors de l'ouverture du fichier : {0}.", ex.Message);
}
}
Si vous faites un clic-droit sur
StreamReader
et que vous cliquez ensuite sur "Go To Definition", vous aurez accès aux métadonnées, dans lesquelles vous pourrez lire :
// Summary:
// Implements a System.IO.TextReader that reads characters from a byte stream
// in a particular encoding.
[Serializable]
[ComVisible(true)]
public class StreamReader : TextReader
Vous voyez que le commentaire précise que l'on lit des caractères, autrement dit du texte. Si nous voulions lire des données brutes, nous n'aurions pas besoin de
StreamReader
et nous pourrions nous contenter de l'objet myStream
.Écrire
Pour écrire dans un fichier, le principe va être à peu près le même car nous allons encore utiliser une boîte de dialogue. La différence est qu'au lieu d'utiliser un contrôle de type
OpenFileDialog
, nous allons utiliser SaveFileDialog
.
Comme vous devez vous en douter, il vous suffit encore d'un simple glisser-déposer depuis la boîte à outils pour créer un objet de type
SaveFileDialog
; le mien gardera le piètre nomsaveFileDialog1
.
Voici maintenant comment écrire la valeur de
fileContent
dans un fichier (le code suivant va dans une méthode) :
saveFileDialog1.Filter = "Fichiers texte|*.txt|Tous les fichiers|*.*";
saveFileDialog1.FilterIndex = 2;
saveFileDialog1.RestoreDirectory = true;
if (saveFileDialog1.ShowDialog() == DialogResult.OK)
{
try
{
Stream myStream = saveFileDialog1.OpenFile();
if (myStream != null)
{
using (myStream)
{
using (StreamWriter writer = new StreamWriter(myStream, Encoding.UTF8))
{
writer.Write(fileContent);
}
}
}
}
catch (Exception ex)
{
MessageBox.Show("Une erreur est survenue lors de l'écriture du fichier: {0}.", ex.Message);
}
}
La seule différence notoire est que nous utilisons ici un
StreamWriter
au lieu d'un StreamReader
et la méthode Write
au lieu de ReadToEnd
.
Là encore, on s'intéresse à du texte et j'utilise l'encodage UTF-8 (vous pouvez en choisir un autre dans la classe
Encoding
).
J'ai porté l'accent sur le traitement de fichiers texte, car c'est le plus facile. Sachez que vous pouvez bien évidemment stocker tout ce que vous voulez, du son, des images, des vidéos, des données...