Кодирование и строки с нулевым завершением

EDIT: я придумал решение, вот оно для всех, кому оно может понадобиться. Он может быть обновлен в будущем, если будет обнаружена ошибка или добавлены другие улучшения. Последнее обновление 18.07.2015.

    /// <summary>
    /// Decodes a string from the specified bytes in the specified encoding.
    /// </summary>
    /// <param name="Length">Specify -1 to read until null, otherwise, specify the amount of bytes that make up the string.</param>
    public static string GetString(byte[] Source, int Offset, int Length, Encoding Encoding)
    {
        if (Length == 0) return string.Empty;
        var sb = new StringBuilder();
        if (Length <= -1)
        {
            using (var sr = new StreamReader(new MemoryStream(Source, Offset, Source.Length - Offset), Encoding, false))
            {
                int ch;
                while (true)
                {
                    ch = sr.Read();
                    if (ch <= 0) break;
                    sb.Append((char)ch);
                }
                if (ch == -1) throw new Exception("End of stream reached; null terminator not found.");
                return sb.ToString();
            }
        }
        else return Encoding.GetString(Source, Offset, Length);
    }

Я обновляю внутреннюю строку/кодировку своего приложения и столкнулся с небольшой проблемой реализации.

По сути, я хотел сделать простой метод ReadNullTerminatedString. Поначалу сделать не составило большого труда. Я использовал Encoding.IsSingleByte для определения длины одного символа, считывал байты, проверял наличие 0 и прекращал чтение/продолжал на основе результата.

Вот где это становится сложно. UTF8 имеет кодировку переменной длины. Encoding.IsSingleByte возвращает false, но это не всегда правильно, так как это переменная кодировка, а символ может быть 1 байт, поэтому моя реализация, основанная на Encoding.IsSingleByte, не будет работать для UTF8.

В тот момент я не был уверен, что этот метод можно исправить, поэтому у меня возникла другая идея. Просто используйте метод кодировки GetString для байтов, используйте максимальную длину строки для параметра count, а затем обрежьте нули из возвращаемой строки.

Это тоже имеет оговорку. Я должен рассмотреть случаи, когда мои управляемые приложения будут взаимодействовать с массивами байтов, возвращаемыми из неуправляемого кода, случаи, когда, конечно, будет нулевой терминатор, но возможность наличия дополнительных ненужных символов после него. Например: "blah\0\0\oldstring"

ReadNullTerminatedString был бы идеальным решением в этом случае, но на данный момент это невозможно, если я хочу, чтобы он поддерживал UTF8. Второе решение тоже не сработает — обрежет 0, а барахло останется.

Есть идеи элегантного решения для С#?


person Eaton    schedule 17.07.2015    source источник
comment
Вы смотрели этот пост: stackoverflow.com/questions/ 11713878/   -  person David Tansey    schedule 17.07.2015
comment
Привет, да, но здесь это не очень применимо, так как мой метод предназначен для поддержки любой кодировки.   -  person Eaton    schedule 17.07.2015


Ответы (1)


Лучшее решение — использовать реализацию TextReader:

  • StreamReader, повторное чтение из потока
  • StringReader, если вы повторное чтение из строки

При этом вы можете прочитать исходный поток байтов в любой кодировке, которая вам нравится, и каждый "символ" вернется к вам как int:

int ch = reader.Read();

Внутренне магия выполняется через C# Decoder класс (который исходит из вашей кодировки):

var decoder = Encoding.UTF7.GetDecoder();

Классу Decoder нужен короткий буфер массива. К счастью, StreamReader знает, как сохранить заполненный буфер и все работает.

Псевдокод

Непроверенное, непроверенное и похожее только на C#:

String ReadNullTerminatedString(Stream stm, Encoding encoding)
{
   StringBuilder sb = new StringBuilder();

   TextReader rdr = new StreamReader(stm, encoding);
   int ch = rdr.Read(); 
   while (ch > 0) //returns -1 when we've hit the end, and 0 is null
   {
      sb.AppendChar(Char(ch));
      int ch = rdr.Read();
   }
   return sb.ToString();
}

Примечание. Любой код, опубликованный в открытом доступе. Атрибуция не требуется.

person Ian Boyd    schedule 17.07.2015
comment
Спасибо, Ян. Забыл, что могу использовать для этого StreamReader. Мне удалось найти свое идеальное решение. Я добавлю его в свой основной пост, чтобы другие могли сослаться на него, если это необходимо. - person Eaton; 18.07.2015