2012-07-26 9 views
6

Normalmente, para leer caracteres de una secuencia de bytes, utiliza un StreamReader. En este ejemplo, estoy leyendo registros delimitados por '\ r' desde una secuencia infinita.Cómo leer caracteres UTF-8 de una secuencia de bytes infinita - C#

using(var reader = new StreamReader(stream, Encoding.UTF8)) 
{ 
    var messageBuilder = new StringBuilder(); 
    var nextChar = 'x'; 
    while (reader.Peek() >= 0) 
    { 
     nextChar = (char)reader.Read() 
     messageBuilder.Append(nextChar); 

     if (nextChar == '\r') 
     { 
      ProcessBuffer(messageBuilder.ToString()); 
      messageBuilder.Clear(); 
     } 
    } 
} 

El problema es que el StreamReader tiene un pequeño buffer interno, por lo que si el código de espera de un 'fin del registro' delimitador ('\ r' en este caso) tiene que esperar hasta que el buffer interno del StreamReader está enrojecido (generalmente porque han llegado más bytes).

Esta implementación alternativa funciona para caracteres UTF-8 de un solo byte, pero fallará en caracteres multibyte.

int byteAsInt = 0; 
var messageBuilder = new StringBuilder(); 
while ((byteAsInt = stream.ReadByte()) != -1) 
{ 
    var nextChar = Encoding.UTF8.GetChars(new[]{(byte) byteAsInt}); 
    Console.Write(nextChar[0]); 
    messageBuilder.Append(nextChar); 

    if (nextChar[0] == '\r') 
    { 
     ProcessBuffer(messageBuilder.ToString()); 
     messageBuilder.Clear(); 
    } 
} 

¿Cómo puedo modificar este código para que funcione con caracteres de varios bytes?

+0

no debe modificarse el título decir de varios bytes o caracteres en lugar de UTF-16 UTF-8? Parece engañoso. –

+1

@TimS. Los caracteres UTF-8 pueden ser más que un solo byte. – Iridium

+0

@TimS. ¿Qué quieres decir? Un carácter multibyte UTF-8 no se convierte automágicamente en un carácter UTF-16. [Wiki] (http://en.wikipedia.org/wiki/UTF-8#Description). – CodeCaster

Respuesta

9

En lugar de Encoding.UTF8.GetChars que está diseñado para convertir tampones completos, obtener una instancia de Decoder y repetidamente llamar a su método miembro GetChars esto hará que el uso de tampón interno la Decoder 's para manejar las secuencias de múltiples bytes parciales desde el extremo de uno llamar al siguiente

+0

Gracias Richard, eso funciona genial. Ver mi respuesta para mi implementación. –

5

Gracias a Richard, ahora tengo un lector de flujo infinito que funciona. Como explicó, el truco es usar una instancia de Decoder y llamar a su método GetChars. Lo probé con texto japonés de varios bytes y funciona bien.

int byteAsInt = 0; 
var messageBuilder = new StringBuilder(); 
var decoder = Encoding.UTF8.GetDecoder(); 
var nextChar = new char[1]; 

while ((byteAsInt = stream.ReadByte()) != -1) 
{ 
    var charCount = decoder.GetChars(new[] {(byte) byteAsInt}, 0, 1, nextChar, 0); 
    if(charCount == 0) continue; 

    Console.Write(nextChar[0]); 
    messageBuilder.Append(nextChar); 

    if (nextChar[0] == '\r') 
    { 
     ProcessBuffer(messageBuilder.ToString()); 
     messageBuilder.Clear(); 
    } 
} 
1

No entiendo por qué no está utilizando el método ReadLine del lector de flujo. Sin embargo, si hay una buena razón para no hacerlo, me parece que llamar repetidamente a GetChars en el decodificador es ineficiente. ¿Por qué no utilizar el hecho de que la representación de bytes de '\ r' no puede ser parte de una secuencia de múltiples bytes? (Bytes en una secuencia multi-byte debe ser mayor que 127;. Es decir, que tienen el mayor conjunto de bits)

var messageBuilder = new List<byte>(); 

int byteAsInt; 
while ((byteAsInt = stream.ReadByte()) != -1) 
{ 
    messageBuilder.Add((byte)byteAsInt); 

    if (byteAsInt == '\r') 
    { 
     var messageString = Encoding.UTF8.GetString(messageBuilder.ToArray()); 
     Console.Write(messageString); 
     ProcessBuffer(messageString); 
     messageBuilder.Clear(); 
    } 
} 
+0

Espera, ¿estás diciendo seriamente que llamar a 'GetChars' en el decodificador es ineficiente, mientras lees el flujo byte a byte, lo colocas dentro de una lista de bytes y luego construyes una matriz de bytes fuera de esa lista y llamas a 'Encoding.GetString' ? Parece que te has perdido el gran problema de rendimiento para el pequeño :) ... oh, veo que el OP hizo lo mismo. No importa. – Luaan

Cuestiones relacionadas