URL d'origine : http://www.blogwind.com/Wuvist/42999.shtml
Dans le framework .Net, l'encodage utilisé par StreamReader doit être spécifié dans le constructeur et ne peut pas être modifié du tout à mi-chemin.
Dans des circonstances normales, cela ne posera aucun problème. Généralement, si un fichier est lu à partir d'un disque dur, l'encodage au sein d'un seul fichier est généralement uniforme. Même si vous trouvez une erreur de lecture, vous pouvez fermer StreamReader et relancer la lecture en utilisant le nouvel encodage.
J'ai récemment rencontré le besoin de modifier l'encodage et mon programme n'a pas désactivé la possibilité de relire. Étant donné que le BaseStream du StreamReader que j'utilise est un flux réseau, je ne peux pas le fermer... mais les éléments transmis par le flux réseau peuvent contenir des encodages différents... GB2312, Big5, UTF8, ISO-8859-1, etc. .. Bien que les informations d'encodage soient d'abord obtenues, puis que le contenu spécifique soit lu. Cependant, une fois que l'encodage Stream Reader utilisé au début est erroné, le contenu lu ne pourra jamais être récupéré... les mots seront perdus...
Je Je ne peux pas l'obtenir à nouveau... Après avoir codé les informations, recréez un nouveau Stream Reader, car le contenu spécifique a été mis en mémoire tampon par le Stream Reader d'origine...
La seule solution est d'implémenter un Stream Reader qui peut modifier le Attribut CurrentEncoding...
Tout écrire à partir de zéro C'était très peu pratique au début. J'ai d'abord obtenu le code source de mono et apporté des modifications à partir du code d'implémentation de Stream Reader de mono.
Stream Reader est en fait très simple. Il contient deux tampons, l'un est le tampon d'entrée et l'autre est le tampon décodé. Le premier est utilisé pour mettre en cache les données originales lues à partir du flux de base, et le second est utilisé pour mettre en cache les éléments. décodé selon les données originales... ...Tant que vous comprenez la méthode ReadBuffer dans l'implémentation de mono, il n'est pas trop difficile de modifier dynamiquement CurrentEncoding...
Le protocole réseau que je dois gérer est un protocole de ligne ...J'ai seulement appelé la méthode Readline de StreamReader dans le programme, mais complètement Les deux méthodes de Read ne sont pas utilisées, ce qui me permet également de modifier beaucoup plus facilement dynamiquement l'encodage...
Ce que je fais, c'est qu'à chaque fois que je j'appelle Readline, je déplace non seulement le curseur (pos) du tampon décodé, mais je déplace également un nouveau curseur dans le tampon d'entrée (pos_input), la méthode est très simple. La méthode Readline doit appeler FindNextEOL pour déplacer le curseur. trouver le symbole de nouvelle ligne... J'ajoute une ligne supplémentaire à la méthode FindNextEOL :
int FindNextEOL()
{
FindNextInputEOL();
....
La nouvelle fonction FindNextInputEOL est une réplique complète de FindNextEOL, sauf que la première traite le tampon d'entrée, tandis que la seconde traite le tampon décodé...
De cette façon, je peux savoir qu'après chaque Readline, le tampon d'entrée a n'a pas été décodé par la couche supérieure. Quelles sont les données originales lues...
Ensuite, ajoutez la méthode Set à l'attribut CurrentEncoding :
ensemble
{
encodage=valeur ;
décodeur = encodage.GetDecoder();
decoded_count = pos + decoder.GetChars (input_buffer, pos_input, cbEncoded, pos_input, decoded_buffer, pos) ;
}
Lors de la définition d'un nouvel encodage, le programme re-décode les données originales qui n'ont pas été lues en fonction du curseur du tampon d'entrée (pos_input), et remplace le contenu dans le tampon décodé.
Ensuite, le tour est joué... Vous n'avez même pas besoin d'apporter de modifications à la méthode Readline... Sauf de mettre la variable cbEncoded dans le global...
Cependant, cette modification rend les deux méthodes de Read complètement inutilisables ... Une fois appelé... les deux curseurs dans le tampon d'entrée et le tampon décodé seront désynchronisés... Le code complet est joint ci-dessous. J'espère que quelqu'un pourra m'aider à comprendre les deux méthodes de lecture. ... Merci d'avance …
/
// System.IO.StreamReader.cs
//
// Auteur:
// Dietmar Maurer ( [email protected] )
//Miguel de Icaza ( [email protected] )
//
// (C) Ximian, Inc. http://www.ximian.com
// Copyright (C) 2004 Novell ( http://www.novell.com )
//
//
// Copyright (C) 2004 Novell, Inc ( http://www.novell.com )
//
// L'autorisation est accordée gratuitement à toute personne obtenant
// une copie de ce logiciel et des fichiers de documentation associés (le
// "Logiciel"), pour commercialiser le Logiciel sans restriction, y compris
// sans limitation les droits d'utilisation, de copie, de modification, de fusion, de publication,
// distribuer, accorder des sous-licences et/ou vendre des copies du Logiciel, et
// permettre aux personnes à qui le Logiciel est fourni de le faire, sous réserve de
// les conditions suivantes :
//
// L'avis de droit d'auteur ci-dessus et cet avis d'autorisation doivent être
// inclus dans toutes les copies ou parties substantielles du Logiciel.
//
// LE LOGICIEL EST FOURNI "EN L'ETAT", SANS GARANTIE D'AUCUNE SORTE,
// EXPRESS OU IMPLICITE, Y COMPRIS MAIS SANS LIMITATION LES GARANTIES DE
// QUALITÉ MARCHANDE, ADÉQUATION À UN USAGE PARTICULIER ET
// NON-VIOLATION. EN AUCUN CAS LES AUTEURS OU LES TITULAIRES DES DROITS D'AUTEUR NE SERONT EN AUCUN CAS.
// RESPONSABLE DE TOUTE RÉCLAMATION, DOMMAGES OU AUTRE RESPONSABILITÉ, QUE CE SOIT DANS LE CADRE D'UNE ACTION
// D'UN CONTRAT, D'UN DÉLIT OU AUTRE, DÉCOULANT DE, OU EN RELATION AVEC
// AVEC LE LOGICIEL OU L'UTILISATION OU AUTRES TRANSACTIONS DANS LE LOGICIEL.
//
utilisant le système ;
en utilisant System.Text ;
en utilisant System.Runtime.InteropServices,
l'espace de noms System.IO ;
{
[Sérialisable]
classe publique DynamicStreamReader : TextReader
{
const int DefaultBufferSize = 1024 ;
const int DefaultFileBufferSize = 4096 ;
const int MinimumBufferSize = 128
;
//Le tampon d'entrée
//
octet [] input_buffer;
//
// Le tampon décodé du tampon d'entrée ci-dessus
//
char [] decoded_buffer;
//
// Octets décodés dans decoded_buffer.
//
int décodé_count;
//
// Position actuelle dans le decoded_buffer
//
position int
//
// Position actuelle dans le input_buffer
//
int pos_entrée;
//
// La taille du tampon que nous utilisons
//
int taille_tampon ;
int do_checks ;
Encodage encodage ;
Décodeur décodeur ;
Flux base_stream ;
bool mayBlock ;
StringBuilder line_builder ;
classe privée NullStreamReader : DynamicStreamReader
{
remplacement public int Peek ()
{
renvoie -1 ;
}
remplacement public int Lecture ()
{
renvoie -1 ;
}
public override int Read (tampon [In, Out] char[], index int, nombre int)
{
renvoie 0 ;
}
chaîne de remplacement publique ReadLine ()
{
renvoie null ;
}
chaîne de remplacement publique ReadToEnd ()
{
return String.Empty;
}
remplacement public Stream BaseStream
{
obtenir { return Stream.Null }
}
remplacement public Encodage CurrentEncoding
{
get { return Encodage.Unicode }
}
}
public nouveau statique en lecture seule DynamicStreamReader Null = (DynamicStreamReader)(new NullStreamReader());
DynamicStreamReader() interne {}
public DynamicStreamReader (flux de flux)
: this (stream, Encoding.UTF8, true, DefaultBufferSize) { }
public DynamicStreamReader (Stream stream, bool detector_encoding_from_bytemarks)
: this (stream, Encoding.UTF8, detector_encoding_from_bytemarks, DefaultBufferSize) { }
public DynamicStreamReader (Stream stream, Encoding encoding)
: this (stream, encoding, true, DefaultBufferSize) { }
public DynamicStreamReader (Stream stream, Encoding encoding, bool detect_encoding_from_bytemarks)
: this (flux, encodage, detect_encoding_from_bytemarks, DefaultBufferSize) { }
public DynamicStreamReader (flux de flux, encodage, bool detect_encoding_from_bytemarks, int buffer_size)
{
Initialiser (flux, encodage, detect_encoding_from_bytemarks, buffer_size) ;
}
public DynamicStreamReader (chemin de chaîne)
: this (chemin, Encoding.UTF8, true, DefaultFileBufferSize) { }
public DynamicStreamReader (chemin de chaîne, bool detector_encoding_from_bytemarks)
: this (chemin, Encoding.UTF8, detect_encoding_from_bytemarks, DefaultFileBufferSize) { }
public DynamicStreamReader (chemin de chaîne, encodage d'encodage)
: this (chemin, encodage, vrai, DefaultFileBufferSize) { }
public DynamicStreamReader (chemin de chaîne, encodage d'encodage, bool detect_encoding_from_bytemarks)
: this (chemin, encodage, detect_encoding_from_bytemarks, DefaultFileBufferSize) { }
public DynamicStreamReader (chemin de chaîne, encodage, bool detect_encoding_from_bytemarks, int buffer_size)
{
si (null == chemin)
lancer un nouveau ArgumentNullException("chemin");
if (String.Empty == chemin)
throw new ArgumentException("Chemin vide non autorisé");
if (path.IndexOfAny (Path.InvalidPathChars) != -1)
throw new ArgumentException("le chemin contient des caractères invalides");
if (null == encodage)
lancer une nouvelle ArgumentNullException ("encodage");
si (buffer_size <= 0)
throw new ArgumentOutOfRangeException ("buffer_size", "La taille minimale du tampon doit être positive");
string DirName = Path.GetDirectoryName(path);
if (DirName != String.Empty && !Directory.Exists(DirName))
lancer une nouvelle DirectoryNotFoundException ("Répertoire '" + DirName + "' introuvable.");
si (!File.Exists(chemin))
throw new FileNotFoundException("Fichier introuvable.", chemin);
Stream stream = (Stream) File.OpenRead (chemin);
Initialiser (flux, encodage, detect_encoding_from_bytemarks, buffer_size) ;
}
internal void Initialize (Stream stream, Encoding encoding, bool detect_encoding_from_bytemarks, int buffer_size)
{
si (null == flux)
lancer une nouvelle ArgumentNullException ("stream");
if (null == encodage)
lancer une nouvelle ArgumentNullException ("encodage");
si (!stream.CanRead)
lancer une nouvelle ArgumentException ("Impossible de lire le flux");
si (buffer_size <= 0)
throw new ArgumentOutOfRangeException ("buffer_size", "La taille minimale du tampon doit être positive");
if (buffer_size < MinimumBufferSize) ;
buffer_size = MinimumBufferSize ;
base_stream = flux ;
input_buffer = nouvel octet [buffer_size] ;
this.buffer_size = buffer_size;
this.encoding = encodage ;
décodeur = encodage.GetDecoder ();
octet [] préambule = encodage.GetPreamble ();
do_checks = detect_encoding_from_bytemarks ?
do_checks += (préambule.Length == 0) ?
decoded_buffer = nouveau char [encoding.GetMaxCharCount (buffer_size)];
nombre_décodé = 0 ;
pos = 0 ;
pos_input =0 ;
}
Stream virtuel public BaseStream
{
obtenir
{
retourner base_stream ;
}
}
Encodage virtuel public CurrentEncoding
{
obtenir
{
si (encodage == null)
lancer une nouvelle exception ();
codage de retour ;
}
ensemble
{
encodage=valeur ;
décodeur = encodage.GetDecoder();
decoded_count = pos + decoder.GetChars (input_buffer, pos_input, cbEncoded - pos_input, decoded_buffer, pos) ;
//DiscardBufferedData();
}
}
remplacement public void Close ()
{
Disposer(vrai);
}
remplacement protégé void Dispose (bool disposant)
{
if (élimination de && base_stream != null)
base_stream.Close ();
input_buffer = nul ;
decoded_buffer = nul ;
encodage = nul ;
décodeur = nul ;
base_stream = nul ;
base.Dispose (élimination);
}
//
// Fournit une détection automatique de l'encodage, ainsi que le saut
// marques d'octets au début d'un flux.
//
int DoChecks (int nombre)
{
si ((do_checks & 2) == 2)
{
octet [] préambule = encoding.GetPreamble ();
int c = préambule.Longueur;
si (compte >= c)
{
int je;
pour (je = 0; je < c; je++)
if (input_buffer [i] != préambule [i])
pause;
si (i == c)
je reviens;
}
}
si ((do_checks & 1) == 1)
{
si (compte < 2)
renvoie 0 ;
si (input_buffer [0] == 0xfe && input_buffer [1] == 0xff)
{
this.encoding = Encoding.BigEndianUnicode ;
retourner 2 ;
}
si (input_buffer [0] == 0xff && input_buffer [1] == 0xfe)
{
this.encoding = Encodage.Unicode ;
retourner 2 ;
}
si (compte < 3)
renvoie 0 ;
si (input_buffer [0] == 0xef && input_buffer [1] == 0xbb && input_buffer [2] == 0xbf)
{
this.encoding = Encodage.UTF8 ;
retourner 3 ;
}
}
renvoie 0 ;
}
public void DiscardBufferedData ()
{
pos = décodé_count = 0 ;
mayBlock = faux ;
// Supprime également l'état interne du décodeur.
décodeur = encodage.GetDecoder ();
}
int cbEncoded ;
int parse_start;
// le tampon est vide, remplis-le à nouveau
privé int ReadBuffer ()
{
pos = 0 ;
pos_input = 0 ;
cbEncoded = 0;
// continue la boucle jusqu'à ce que le décodeur nous donne quelques caractères
nombre_décodé = 0 ;
analyser_start = 0 ;
faire
{
cbEncoded = base_stream.Read (input_buffer, 0, buffer_size) ;
si (cbEncoded == 0)
return 0 ;
mayBlock = (cbEncoded < buffer_size );
si (do_checks > 0)
{
Encodage ancien = encodage ;
parse_start = DoChecks (cbEncoded);
if (ancien != encodage)
{
décodeur = encodage.GetDecoder ();
}
faire_checks = 0 ;
cbEncoded -= parse_start;
}
decoded_count += decoder.GetChars (input_buffer, parse_start, cbEncoded, decoded_buffer, 0) ;
analyser_start = 0 ;
} while (decoded_count == 0);
return decoded_count ;
}
remplacement public int Peek ()
{
si (base_stream == null)
lancer une nouvelle ObjectDisposedException ("StreamReader", "Impossible de lire à partir d'un StreamReader fermé");
if (pos >= decoded_count && (mayBlock || ReadBuffer () == 0))
renvoie -1 ;
renvoie le tampon_décodé [pos] ;
}
remplacement public int Lecture ()
{
throw new Exception("Le lecteur dynamique n'a pas pu lire !");
}
public override int Read ([In, Out] char[] dest_buffer, int index, int count)
{
throw new Exception("Le lecteur dynamique n'a pas pu lire !");
}
booléen foundCR_input ;
int FindNextInputEOL()
{
char c = '