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

Мне нужно преобразовать (возможно) массив байтов 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();
        }
    }
}

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

Wayne Bloss 27.09.2008 22:20
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
21
1
30 265
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

Ответ принят как подходящий

Есть ли причина не использовать конструктор 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 для соответствующей кодировки)?

Спасибо за ответ. Я не использовал String (sbyte *, int, int), потому что он не останавливается на первом найденном null, вместо этого он преобразует каждый null в пробел, как и Encoding.ASCII.GetString ().

Wayne Bloss 27.09.2008 22:29

О, также это не узкое место или что-то в этом роде. Я просто ботаник, которому на выходных нечего делать :)

Wayne Bloss 27.09.2008 22:30

Этот код привел к ошибке: «Невозможно получить адрес, получить размер или объявить указатель на управляемый тип 'byte []' (CS0208)». Чтобы исправить это, я удалил & из &buffer

user666412 04.03.2016 23:53

Это не означает, что он завершается после нулевого символа. Результирующая строка имеет длину всего буфера и содержит символ \0 и дополнительные байты.

Arek 25.11.2016 11:50

@Arek: Я предполагал, что OP будет делать это. Отредактирую, чтобы уточнить.

Jon Skeet 25.11.2016 11:54

@Arek: На самом деле, это еще не все ... сейчас ищу.

Jon Skeet 25.11.2016 11:55

while (offset < buffer.Length..., должен быть offset? или end.

Timeless 29.11.2017 06:52

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

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

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

string s = myEncoding.GetString(bytes.TakeWhile(b => !b.Equals(0)).ToArray());

Лучший ответ! Чтобы завершить ответ, не забудьте "using System.Linq;" и без myEncoding: "String s = Encoding.UTF8.GetString (rbuf.TakeWhile (b =>! b.Equals (0)). ToArray ());" где rbuf - это Byte [].

BoiseBaked 30.05.2019 22:05

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

string result = "";
for (int i = 0; i < data.Length && data[i] != 0; i++)
   result += (char)data[i];

Это происходит очень медленно, поскольку создается новый экземпляр строки для каждого символа. По совпадению, я делал этот точно такой же код раньше, и это оказалось моим узким местом (а строки были не более 255 символов в длину!) Это определенно нет, что OP хочет с точки зрения скорости.

Marlon 21.01.2012 12:54

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestProject1
{
    class Class1
    {
    static public string cstr_to_string( byte[] data, int code_page)
    {
        Encoding Enc = Encoding.GetEncoding(code_page);  
        int inx = Array.FindIndex(data, 0, (x) => x == 0);//search for 0
        if (inx >= 0)
          return (Enc.GetString(data, 0, inx));
        else 
          return (Enc.GetString(data)); 
    }

    }
}

Спасибо, именно то, что мне нужно. Я подозреваю, что для многих устаревших приложений, таких как мое, кодовая страница будет 1252, и это будет именно то, что им нужно.

eselk 01.06.2013 01:10

что произойдет, если нет нулевого завершения? Когда остановится Enc.GetString?

Rick 18.06.2015 21:54

@Rick его остановка в конце массива "data".

Vladimir Poslavskiy 06.07.2015 09:24

s = s.Substring(0, s.IndexOf((char) 0));

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

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

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

От байта [] к строке:

byte[] byteArray = new byte[] {0x41, 0x42, 0x09, 0x00, 0x255};
string s = System.Text.Encoding.ASCII.GetString(byteArray);

Это не обрабатывает нулевое завершение.

Jeff 19.12.2014 16:58

частный статический символ [] string2chars (строка S) {S + = '\ 0'; // Добавляем нулевой терминатор для строк C. byte [] bytes = System.Text.Encoding.UTF8.GetBytes (S); // Поскольку мы конвертируем в байты, '\ 0' имеет решающее значение, иначе он будет потерян char [] chars = System.Text.Encoding.UTF8.GetChars (bytes); // Можно использовать ASCII вместо return chars; }

DanielHsH 13.01.2015 20:48

Джефф - приведенный выше код устраняет проблему нулевого завершения

DanielHsH 13.01.2015 20:49

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

String MyString = Encoding.ASCII.GetString(MyByteBuffer).TrimEnd((Char)0);

Это работает, только если буфер содержит только одну строку, начиная с индекса 0 массива.

AaA 20.02.2015 13:57

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

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

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

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

Другие вопросы по теме