Самый быстрый способ преобразовать байт [] ascii с нулевым завершением в строку?

Мне нужно преобразовать (возможно) массив байтов ascii с нулевым завершением в строку на C#, и самый быстрый способ, который я нашел для этого, — использовать мой метод UnsafeAsciiBytesToString, показанный ниже. Этот метод использует конструктор String.String(sbyte*), который содержит предупреждение в своих примечаниях:

«Предполагается, что параметр value указывает на массив, представляющий строку, закодированную с использованием кодовой страницы ANSI по умолчанию (то есть метода кодирования, указанного Encoding.Default).

Примечание: * Поскольку кодовая страница ANSI по умолчанию зависит от системы, строка, созданная этим конструктором из идентичных массивов байтов со знаком, может различаться в разных системах. * ...

* Если указанный массив не завершается нулем, поведение этого конструктора зависит от системы. Например, такая ситуация может вызвать нарушение прав доступа. * "

Теперь я уверен, что способ кодирования строки никогда не изменится... но кодовая страница по умолчанию в системе, в которой работает мое приложение, может измениться. Итак, есть ли причина, по которой я не должен кричать от использования String.String(sbyte*) для этой цели?

using System;
using System.Text;

namespace FastAsciiBytesToString
{
    static class StringEx
    {
        public static string AsciiBytesToString(this byte[] buffer, int offset, int maxLength)
        {
            int maxIndex = offset + maxLength;

            for( int i = offset; i < maxIndex; i++ )
            {
                /// Skip non-nulls.
                if( buffer[i] != 0 ) continue;
                /// First null we find, return the string.
                return Encoding.ASCII.GetString(buffer, offset, i - offset);
            }
            /// Terminating null not found. Convert the entire section from offset to maxLength.
            return Encoding.ASCII.GetString(buffer, offset, maxLength);
        }

        public static string UnsafeAsciiBytesToString(this byte[] buffer, int offset)
        {
            string result = null;

            unsafe
            {
                fixed( byte* pAscii = &buffer[offset] )
                { 
                    result = new String((sbyte*)pAscii);
                }
            }

            return result;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            byte[] asciiBytes = new byte[]{ 0, 0, 0, (byte)'a', (byte)'b', (byte)'c', 0, 0, 0 };

            string result = asciiBytes.AsciiBytesToString(3, 6);

            Console.WriteLine("AsciiBytesToString Result: \"{0}\"", result);

            result = asciiBytes.UnsafeAsciiBytesToString(3);

            Console.WriteLine("UnsafeAsciiBytesToString Result: \"{0}\"", result);

            /// Non-null terminated test.
            asciiBytes = new byte[]{ 0, 0, 0, (byte)'a', (byte)'b', (byte)'c' };

            result = asciiBytes.UnsafeAsciiBytesToString(3);

            Console.WriteLine("UnsafeAsciiBytesToString Result: \"{0}\"", result);

            Console.ReadLine();
        }
    }
}

person Wayne Bloss    schedule 27.09.2008    source источник
comment
Упс, только что кое-что понял... у меня нет возможности указать максимальную длину при использовании String.String(sbyte*), что в основном означает смерть использования конструктора для чтения из кольцевого буфера, поскольку он может хранить чтение за пределы максимальной длины в следующий сегмент!   -  person Wayne Bloss    schedule 27.09.2008


Ответы (8)


Есть ли причина не использовать конструктор String(sbyte*, int, int)? Если вы определились, какая часть буфера вам нужна, остальное должно быть простым:

public static string UnsafeAsciiBytesToString(byte[] buffer, int offset, int length)
{
    unsafe
    {
       fixed (byte* pAscii = buffer)
       { 
           return new String((sbyte*)pAscii, offset, length);
       }
    }
}

Если вам нужно сначала посмотреть:

public static string UnsafeAsciiBytesToString(byte[] buffer, int offset)
{
    int end = offset;
    while (end < buffer.Length && buffer[end] != 0)
    {
        end++;
    }
    unsafe
    {
       fixed (byte* pAscii = buffer)
       { 
           return new String((sbyte*)pAscii, offset, end - offset);
       }
    }
}

Если это действительно строка ASCII (т. е. все байты меньше 128), то проблема с кодовой страницей не должна быть проблемой, если у вас нет особенно странной кодовой страницы по умолчанию, которая не основана на ASCII. .

Из интереса, действительно ли вы профилировали свое приложение, чтобы убедиться, что это действительно узкое место? Вам определенно нужно самое быстрое преобразование, а не более читаемое (например, использование Encoding.GetString для соответствующей кодировки)?

person Jon Skeet    schedule 27.09.2008
comment
Спасибо за ваш ответ. Я не использовал String(sbyte*, int, int), потому что он не останавливается на первом найденном нулевом значении, вместо этого он преобразует каждый нуль в пробел, как Encoding.ASCII.GetString(). - person Wayne Bloss; 27.09.2008
comment
О, и это не узкое место или что-то в этом роде. Я просто ботаник, которому нечего делать на выходных :) - person Wayne Bloss; 27.09.2008
comment
Этот код привел к ошибке: невозможно получить адрес, получить размер или объявить указатель на управляемый тип «байт []» (CS0208). Чтобы исправить это, я удалил & из &buffer - person user666412; 04.03.2016
comment
Это не приводит к завершению после нулевого символа. Результирующая строка имеет длину всего буфера и содержит \0 символ и последующие байты. - person Arek; 25.11.2016
comment
@Arek: я предполагал, что ОП будет это делать. Отредактирую, чтобы уточнить. - person Jon Skeet; 25.11.2016
comment
@Arek: На самом деле, это еще не все... сейчас смотрю. - person Jon Skeet; 25.11.2016
comment
while (offset < buffer.Length..., должно быть offset? или end. - person Timeless; 29.11.2017

Oneliner (при условии, что буфер фактически содержит ОДНУ хорошо отформатированную строку с нулевым завершением):

String MyString = Encoding.ASCII.GetString(MyByteBuffer).TrimEnd((Char)0);
person user3042599    schedule 27.11.2013
comment
Это работает только в том случае, если буфер содержит только одну строку, начинающуюся с индекса 0 массива. - person AaA; 20.02.2015

Я не уверен в скорости, но мне было проще всего использовать LINQ для удаления нулей перед кодированием:

string s = myEncoding.GetString(bytes.TakeWhile(b => !b.Equals(0)).ToArray());
person Pat    schedule 02.12.2009
comment
Лучший ответ! Чтобы завершить ответ, не забудьте использовать System.Linq; и без myEncoding: String s = Encoding.UTF8.GetString(rbuf.TakeWhile(b =› !b.Equals(0)).ToArray()); где rbuf — это Byte[]. - person BoiseBaked; 30.05.2019

Один из возможных вариантов: убедитесь, что кодовая страница по умолчанию является приемлемой, и используйте эту информацию для выбора механизма преобразования во время выполнения.

Это также может учитывать, действительно ли строка завершается нулем, но как только вы это сделаете, конечно, скорость исчезнет.

person Jeffrey L Whitledge    schedule 27.09.2008

Простой/безопасный/быстрый способ преобразования объектов byte[] в строки, содержащие их ASCII-эквиваленты, и наоборот с использованием класса .NET System.Text.Encoding. Класс имеет статическую функцию, которая возвращает кодировщик ASCII:

Из строки в байт[]:

string s = "Hello World!"
byte[] b = System.Text.Encoding.ASCII.GetBytes(s);

Из byte[] в строку:

byte[] byteArray = new byte[] {0x41, 0x42, 0x09, 0x00, 0x255};
string s = System.Text.Encoding.ASCII.GetString(byteArray);
person Harald Coppoolse    schedule 11.07.2013
comment
Это не обрабатывает нулевое завершение. - person Jeff; 19.12.2014
comment
private static char[] string2chars(string S){ S += '\0'; // Добавляем нулевой терминатор для строк C. byte[] bytes = System.Text.Encoding.UTF8.GetBytes(S); // Так как мы конвертируем в байты, '\0' имеет решающее значение, иначе он будет потерян char[] chars = System.Text.Encoding.UTF8.GetChars(bytes); // Вместо этого можно использовать ASCII return chars; } - person DanielHsH; 13.01.2015
comment
Джефф - приведенный выше код исправляет проблему нулевого завершения - person DanielHsH; 13.01.2015

Просто для полноты вы также можете использовать встроенные методы .NET framework для этого:

var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
try
{
    return Marshal.PtrToStringAnsi(handle.AddrOfPinnedObject());
}
finally
{
    handle.Free();
}

Преимущества:

  • Он не требует небезопасного кода (т. е. вы также можете использовать этот метод для VB.NET) и
  • он также работает для «широких» (UTF-16) строк, если вместо этого вы используете Marshal.PtrToStringUni.
person Heinzi    schedule 17.09.2019

Это немного некрасиво, но вам не нужно использовать небезопасный код:

string result = "";
for (int i = 0; i < data.Length && data[i] != 0; i++)
   result += (char)data[i];
person Adam Pierce    schedule 17.03.2010
comment
Это очень медленно, так как создается новый экземпляр строки для каждого символа. По совпадению, я делал точно такой же код раньше, и это оказалось моим узким местом (и длина строк не превышала 255 символов!) Это определенно не то, чего хочет ОП с точки зрения скорости . - person Marlon; 21.01.2012

person    schedule
comment
Спасибо, как раз то, что мне было нужно. Я подозреваю, что для многих устаревших приложений, таких как мое, кодовая страница будет 1252, и это будет именно то, что им нужно. - person eselk; 01.06.2013
comment
что произойдет, если нет нулевого завершения? Когда Enc.GetString остановится? - person Rick; 18.06.2015
comment
@Rick, это остановка в конце данных массива. - person Vladimir Poslavskiy; 06.07.2015