Кодиране и низове с нулев край

РЕДАКТИРАНЕ: Намерих решение, ето го за всеки друг, който може да го иска. Може да бъде актуализиран в бъдеще, ако се открие грешка или се добавят други подобрения. Последна актуализация на 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, за да определя дължината на един символ, щях да прочета байтовете, да проверя за 0s и да спра четенето/продължа въз основа на резултата.

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

В този момент не бях сигурен дали този метод може да бъде коригиран, така че имах друга идея. Просто използвайте метода GetString на кодирането върху байтовете, използвайте максималната дължина, която низът може да бъде за параметъра за броене, и след това отрежете нулите от върнатия низ.

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

ReadNullTerminatedString би било идеалното решение в този случай, но в момента не може да бъде, ако искам да поддържа UTF8. Второто решение също няма да работи - ще изреже 0-те, но боклуците ще останат.

Някакви идеи за елегантно решение за C#?


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