Как получить согласованное байтовое представление строк в C # без указания кодировки вручную? - PullRequest
2058 голосов
/ 23 января 2009

Как преобразовать string в byte[] в .NET (C #) без указания конкретной кодировки вручную?

Я собираюсь зашифровать строку. Я могу зашифровать его без конвертации, но мне все равно хотелось бы знать, почему здесь играет роль кодировка.

Кроме того, почему следует учитывать кодировку? Разве я не могу просто получить, в каких байтах хранится строка? Почему существует зависимость от кодировки символов?

Ответы [ 38 ]

6 голосов
/ 18 августа 2015

Наиболее близким подходом к вопросу ОП является вопрос Тома Блоджета, который фактически входит в объект и извлекает байты. Я говорю ближе всего, потому что это зависит от реализации объекта String.

"Can't I simply get what bytes the string has been stored in?"

Конечно, но здесь возникает фундаментальная ошибка в этом вопросе. String - это объект, который может иметь интересную структуру данных. Мы уже знаем, что делает, потому что позволяет хранить непарные суррогаты. Это может хранить длину. Он может содержать указатель на каждый из «парных» суррогатов, позволяющих быстро считать. И т.д. Все эти дополнительные байты не являются частью символьных данных.

То, что вы хотите, это байты каждого символа в массиве. И тут начинается кодировка. По умолчанию вы получите UTF-16LE. Если вам не нужны сами байты, за исключением передачи туда и обратно, вы можете выбрать любую кодировку, включая «default», и преобразовать ее позже (при условии, что будут те же параметры, что и кодировка по умолчанию, кодовые точки, исправления ошибок). разрешенные вещи, такие как непарные суррогаты и т. д.

Но зачем оставлять «кодирование» волшебным? Почему бы не указать кодировку, чтобы вы знали, какие байты вы собираетесь получить?

"Why is there a dependency on character encodings?"

Кодировка (в этом контексте) просто означает байты, которые представляют вашу строку. Не байты строкового объекта. Вы хотели, чтобы байты, в которых была сохранена строка - вопрос был задан наивно. Вы хотели получить байты строки в непрерывном массиве, представляющем строку, а не все другие двоичные данные, которые может содержать строковый объект.

То есть, как хранится строка, не имеет значения. Требуется строка «Encoded» в байты массива байтов.

Мне нравится ответ Тома Блога, потому что он направил вас к направлению «байты строкового объекта». Однако это зависит от реализации, и, поскольку он заглядывает во внутренние органы, может быть трудно восстановить копию строки.

Ответ Мехрдада неверен, потому что вводит в заблуждение на концептуальном уровне. У вас все еще есть список байтов, закодированный. Его конкретное решение позволяет сохранить непарные суррогаты - это зависит от реализации. Его конкретное решение не даст точные байты строки, если GetBytes вернет строку в UTF-8 по умолчанию.


Я передумал об этом (решение Мехрдада) - это не получение байтов строки; скорее это получение байтов символьного массива, который был создан из строки. Независимо от кодировки тип данных char в c # имеет фиксированный размер. Это позволяет создавать байтовый массив постоянной длины и воспроизводить массив символов в зависимости от размера байтового массива. Таким образом, если бы кодировкой было UTF-8, но каждый символ составлял 6 байтов для размещения наибольшего значения utf8, он все равно работал бы. Так что действительно - кодировка символа не имеет значения.

Но было использовано преобразование - каждый символ был помещен в поле фиксированного размера (тип символа c #). Однако, что это за представление, не имеет значения, что технически является ответом на ФП. Так что - если вы все равно собираетесь конвертировать ... Почему бы не "кодировать"?

6 голосов
/ 02 сентября 2013

Вы можете использовать следующий код для преобразования string в byte array в .NET

string s_unicode = "abcéabc";
byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(s_unicode);
4 голосов
/ 15 января 2013

Вот моя небезопасная реализация преобразования String в Byte[]:

public static unsafe Byte[] GetBytes(String s)
{
    Int32 length = s.Length * sizeof(Char);
    Byte[] bytes = new Byte[length];

    fixed (Char* pInput = s)
    fixed (Byte* pBytes = bytes)
    {
        Byte* source = (Byte*)pInput;
        Byte* destination = pBytes;

        if (length >= 16)
        {
            do
            {
                *((Int64*)destination) = *((Int64*)source);
                *((Int64*)(destination + 8)) = *((Int64*)(source + 8));

                source += 16;
                destination += 16;
            }
            while ((length -= 16) >= 16);
        }

        if (length > 0)
        {
            if ((length & 8) != 0)
            {
                *((Int64*)destination) = *((Int64*)source);

                source += 8;
                destination += 8;
            }

            if ((length & 4) != 0)
            {
                *((Int32*)destination) = *((Int32*)source);

                source += 4;
                destination += 4;
            }

            if ((length & 2) != 0)
            {
                *((Int16*)destination) = *((Int16*)source);

                source += 2;
                destination += 2;
            }

            if ((length & 1) != 0)
            {
                ++source;
                ++destination;

                destination[0] = source[0];
            }
        }
    }

    return bytes;
}

Это намного быстрее, чем принятый ответ, хотя и не так элегантно, как есть. Вот мои тесты секундомера за 10000000 итераций:

[Second String: Length 20]
Buffer.BlockCopy: 746ms
Unsafe: 557ms

[Second String: Length 50]
Buffer.BlockCopy: 861ms
Unsafe: 753ms

[Third String: Length 100]
Buffer.BlockCopy: 1250ms
Unsafe: 1063ms

Чтобы использовать его, вы должны поставить галочку «Разрешить небезопасный код» в свойствах сборки вашего проекта. В соответствии с .NET Framework 3.5 этот метод также можно использовать как расширение строки:

public static unsafe class StringExtensions
{
    public static Byte[] ToByteArray(this String s)
    {
        // Method Code
    }
}
3 голосов
/ 25 ноября 2014

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

[DllImport(
        "msvcrt.dll",
        EntryPoint = "memcpy",
        CallingConvention = CallingConvention.Cdecl,
        SetLastError = false)]
private static extern unsafe void* UnsafeMemoryCopy(
    void* destination,
    void* source,
    uint count);

public static byte[] GetUnderlyingBytes(string source)
{
    var length = source.Length * sizeof(char);
    var result = new byte[length];
    unsafe
    {
        fixed (char* firstSourceChar = source)
        fixed (byte* firstDestination = result)
        {
            var firstSource = (byte*)firstSourceChar;
            UnsafeMemoryCopy(
                firstDestination,
                firstSource,
                (uint)length);
        }
    }

    return result;
}

Эта функция довольно быстро даст вам копию байтов, лежащих в основе вашей строки. Вы получите эти байты любым способом, который они кодируют в вашей системе. Эта кодировка почти наверняка является UTF-16LE, но это деталь реализации, о которой вам не нужно беспокоиться.

Было бы безопаснее, проще и надежнее просто позвонить,

System.Text.Encoding.Unicode.GetBytes()

По всей вероятности, это даст тот же результат, его легче набирать, и байты всегда будут возвращаться туда и обратно при вызове

System.Text.Encoding.Unicode.GetString()
3 голосов
/ 30 июня 2015

Просто используйте это:

byte[] myByte= System.Text.ASCIIEncoding.Default.GetBytes(myString);
2 голосов
/ 11 июня 2014

Строка может быть преобразована в байтовый массив несколькими различными способами из-за следующего факта: .NET поддерживает Unicode, а Unicode стандартизирует несколько разностных кодировок, называемых UTF. Они имеют различную длину представления байтов, но эквивалентны в том смысле, что когда строка кодируется, она может быть закодирована обратно в строку, но если строка закодирована с одним UTF и декодирована в предположении другого UTF, если ее можно прикрутить до.

Кроме того, .NET поддерживает не-Unicode-кодировки, но они недопустимы в общем случае (будет действительным, только если ограниченный поднабор кодовой точки Unicode используется в фактической строке, такой как ASCII). Внутри .NET поддерживает UTF-16, но для потокового представления обычно используется UTF-8. Это также стандарт де-факто для Интернета.

Не удивительно, что сериализация строки в массив байтов и десериализация поддерживается классом System.Text.Encoding, который является абстрактным классом; его производные классы поддерживают конкретные кодировки: ASCIIEncoding и четыре UTF (System.Text.UnicodeEncoding поддерживает UTF-16)

Ссылка по этой ссылке.

Для сериализации в массив байтов используется System.Text.Encoding.GetBytes. Для обратной операции используйте System.Text.Encoding.GetChars. Эта функция возвращает массив символов, поэтому для получения строки используйте строковый конструктор System.String(char[]).
Ссылка на эту страницу.

* * Пример тысяча двадцать-один: * * 1 022
string myString = //... some string

System.Text.Encoding encoding = System.Text.Encoding.UTF8; //or some other, but prefer some UTF is Unicode is used
byte[] bytes = encoding.GetBytes(myString);

//next lines are written in response to a follow-up questions:

myString = new string(encoding.GetChars(bytes));
byte[] bytes = encoding.GetBytes(myString);
myString = new string(encoding.GetChars(bytes));
byte[] bytes = encoding.GetBytes(myString);

//how many times shall I repeat it to show there is a round-trip? :-)
2 голосов
/ 02 января 2012
bytes[] buffer = UnicodeEncoding.UTF8.GetBytes(string something); //for converting to UTF then get its bytes

bytes[] buffer = ASCIIEncoding.ASCII.GetBytes(string something); //for converting to ascii then get its bytes
2 голосов
/ 11 октября 2012

простой код с LINQ

string s = "abc"
byte[] b = s.Select(e => (byte)e).ToArray();

РЕДАКТИРОВАТЬ: как указано ниже, это не очень хороший способ.

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

string s = "abc"
byte[] b = s.Cast<byte>().ToArray();
2 голосов
/ 20 февраля 2009

Два способа:

public static byte[] StrToByteArray(this string s)
{
    List<byte> value = new List<byte>();
    foreach (char c in s.ToCharArray())
        value.Add(c.ToByte());
    return value.ToArray();
}

И

public static byte[] StrToByteArray(this string s)
{
    s = s.Replace(" ", string.Empty);
    byte[] buffer = new byte[s.Length / 2];
    for (int i = 0; i < s.Length; i += 2)
        buffer[i / 2] = (byte)Convert.ToByte(s.Substring(i, 2), 16);
    return buffer;
}

Я склонен использовать нижний чаще, чем верхний, не проверял их по скорости.

2 голосов
/ 08 ноября 2017

Это зависит от того, что вы хотите байты для

Это потому, что, как метко сказал Тайлер , сказал : «Строки не являются чистыми данными. Они также имеют информацию ». В этом случае информация представляет собой кодировку, которая была принята при создании строки.

Предполагая, что у вас есть двоичные данные (а не текст), хранящиеся в строке

Это основано на комментариях ОП к его собственному вопросу и является правильным вопросом, если я понимаю подсказки ОП на случай использования.

Хранение двоичных данных в строках, вероятно, является неправильным подходом из-за предполагаемой кодировки, упомянутой выше! Какая бы программа или библиотека не хранила эти двоичные данные в string (вместо массива byte[], который был бы более подходящим), уже проиграл битву до ее начала. Если они отправляют вам байты в виде запроса / ответа REST или чего-либо, что должно передать строки, Base64 будет правильным подходом.

Если у вас есть текстовая строка с неизвестной кодировкой

Все остальные ответили на этот неправильный вопрос неправильно.

Если строка выглядит хорошо, как есть, просто выберите кодировку (предпочтительно код, начинающийся с UTF), используйте соответствующую функцию System.Text.Encoding.???.GetBytes() и скажите всем, кому вы дадите байты для выбранной кодировки.

...