Най-бързият начин за преобразуване на евентуално завършващ с нула ascii байт [] в низ?

Трябва да преобразувам (евентуално) нулев прекратен масив от ascii байтове в низ в C# и най-бързият начин, който открих, е да използвам моя метод UnsafeAsciiBytesToString, показан по-долу. Този метод използва конструктора String.String(sbyte*), който съдържа предупреждение в забележките си:

„Предполага се, че параметърът стойност сочи към масив, представляващ низ, кодиран с помощта на 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
Този код ми даде грешка: Не мога да взема адреса на, да получа размера или да декларирам указател към управляван тип „byte[]“ (CS0208). За да го поправя, премахнах & от &buffer - person user666412; 04.03.2016
comment
Това не го прави прекратен след нулев знак. Полученият низ има дължина на целия буфер и съдържа \0 символ и други байтове. - person Arek; 25.11.2016
comment
@Arek: Предполагах, че OP ще направи това. Ще редактирам, за да изясня. - 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 е байт []. - 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[] 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 низове. байт[] байтове = 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

В лек код на peusdo.

$r = query(SELECT * FROM TBL_WHATEVER ORDER BY TS ASC);
$last_year = null;

while($row as mysql_fetch_assoc($r)){
    $year = date('Y', $r['ts']);

   if($year != $last_year){
     echo $year;
     $last_year = $year;
   }else{
       $month = 1;
       while($month < 12){
           echo date('F', $r['ts']);
       }
    }

}

Има много публикации за това като тази:

PHP: Преминаване през всички месеци в даден период от време?

- person Marlon; 21.01.2012

person    schedule
comment

С помощта на JavaScript можете да постигнете това.

<script language="javascript">
var descval = document.getElementById('beskrivelse').innerHTML;
var paras = document.getElementsByTagName('meta');
for (i = 0; i < paras.length; i++) {
 var test = paras[i].getAttribute('property');
 if(test == "og:description")
 {
   paras[i].content = descval;
 }
}
</script>

Демонстрация на Fiddle

С помощта на Devloper Tool можете да проверите мета тага ново съдържание.

- person eselk; 01.06.2013
comment
какво се случва, ако няма нулево прекратяване? Кога ще спре Enc.GetString? - person Rick; 18.06.2015
comment
@Rick спира в края на данните от масива. - person Vladimir Poslavskiy; 06.07.2015