Как преобразовать string
в byte[]
в .NET (C #) без указания конкретной кодировки вручную?
Я собираюсь зашифровать строку. Я могу зашифровать его без преобразования, но я все же хотел бы знать, почему здесь применяется кодирование.
Кроме того, почему вообще следует учитывать кодировку? Разве я не могу просто узнать, в каких байтах была сохранена строка? Почему существует зависимость от кодировок символов?
30 ответов
Вопреки приведенным здесь ответам, вам НЕ нужно беспокоиться о кодировании если байты не нужно интерпретировать!
Как вы упомянули, ваша цель - просто «узнать, в каких байтах была сохранена строка» .
(И, конечно же, чтобы иметь возможность воссоздать строку из байтов.)
Для этих целей я, честно говоря, не понимаю, почему люди продолжают говорить вам, что вам нужны кодировки. Вам, конечно, не нужно беспокоиться о кодировках для этого.
Просто сделайте это вместо этого:
static byte[] GetBytes(string str)
{
byte[] bytes = new byte[str.Length * sizeof(char)];
System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
return bytes;
}
// Do NOT use on arbitrary bytes; only use on GetBytes's output on the SAME system
static string GetString(byte[] bytes)
{
char[] chars = new char[bytes.Length / sizeof(char)];
System.Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length);
return new string(chars);
}
Пока ваша программа (или другие программы) не пытается каким-то образом интерпретировать байты, о чем вы, очевидно, не упоминали, что собираетесь делать, то ничего неправильно с таким подходом! Беспокойство о кодировках просто усложняет вашу жизнь без реальной причины.
Дополнительное преимущество этого подхода: не имеет значения, содержит ли строка недопустимые символы, потому что вы все равно можете получить данные и восстановить исходную строку!
Он будет кодироваться и декодироваться точно так же, потому что вы просто смотрите на байты .
Однако, если бы вы использовали определенную кодировку, у вас возникли бы проблемы с кодированием / декодированием недопустимых символов.
GetString
и GetBytes
должны выполняться в системе с одинаковым порядком байтов для работы. Таким образом, вы не можете использовать это для получения байтов, которые хотите преобразовать в строку в другом месте. Так что мне трудно придумать ситуации, в которых я бы хотел это использовать.
Это зависит от кодировки вашей строки (ASCII, UTF-8, ...).
Например:
byte[] b1 = System.Text.Encoding.UTF8.GetBytes (myString);
byte[] b2 = System.Text.Encoding.ASCII.GetBytes (myString);
Небольшой пример того, почему кодирование имеет значение:
string pi = "\u03a0";
byte[] ascii = System.Text.Encoding.ASCII.GetBytes (pi);
byte[] utf8 = System.Text.Encoding.UTF8.GetBytes (pi);
Console.WriteLine (ascii.Length); //Will print 1
Console.WriteLine (utf8.Length); //Will print 2
Console.WriteLine (System.Text.Encoding.ASCII.GetString (ascii)); //Will print '?'
ASCII просто не приспособлен для работы со специальными символами.
Внутри .NET framework использует UTF-16 для представления строк, поэтому, если вы просто хотите чтобы получить точные байты, которые использует .NET, используйте System.Text.Encoding.Unicode.GetBytes (...)
.
См. Кодировка символов в .NET Framework (MSDN) для получения дополнительной информации.
Принятый ответ очень и очень сложен. Используйте для этого включенные классы .NET:
const string data = "A string with international characters: Norwegian: ÆØÅæøå, Chinese: 喂 谢谢";
var bytes = System.Text.Encoding.UTF8.GetBytes(data);
var decoded = System.Text.Encoding.UTF8.GetString(bytes);
Не изобретайте велосипед, если вам не нужно ...
System.Text.Encoding.Unicode
, чтобы быть эквивалентной ответу Мехрдада.
System.Text.Encoding.Unicode.GetBytes
, вероятно, будет более точным.
BinaryFormatter bf = new BinaryFormatter();
byte[] bytes;
MemoryStream ms = new MemoryStream();
string orig = "喂 Hello 谢谢 Thank You";
bf.Serialize(ms, orig);
ms.Seek(0, 0);
bytes = ms.ToArray();
MessageBox.Show("Original bytes Length: " + bytes.Length.ToString());
MessageBox.Show("Original string Length: " + orig.Length.ToString());
for (int i = 0; i < bytes.Length; ++i) bytes[i] ^= 168; // pseudo encrypt
for (int i = 0; i < bytes.Length; ++i) bytes[i] ^= 168; // pseudo decrypt
BinaryFormatter bfx = new BinaryFormatter();
MemoryStream msx = new MemoryStream();
msx.Write(bytes, 0, bytes.Length);
msx.Seek(0, 0);
string sx = (string)bfx.Deserialize(msx);
MessageBox.Show("Still intact :" + sx);
MessageBox.Show("Deserialize string Length(still intact): "
+ sx.Length.ToString());
BinaryFormatter bfy = new BinaryFormatter();
MemoryStream msy = new MemoryStream();
bfy.Serialize(msy, sx);
msy.Seek(0, 0);
byte[] bytesy = msy.ToArray();
MessageBox.Show("Deserialize bytes Length(still intact): "
+ bytesy.Length.ToString());
Это популярный вопрос. Важно понимать, что задает автор вопроса, и что это отличается от наиболее распространенной потребности. Чтобы предотвратить неправильное использование кода там, где он не нужен, я сначала ответил на последнее.
Общая потребность
Каждая строка имеет набор символов и кодировку. Когда вы конвертируете объект System.String
в массив System.Byte
, у вас все еще есть набор символов и кодировка. В большинстве случаев вы знаете, какой набор символов и кодировка вам нужны, а .NET упрощает «копирование с преобразованием». Просто выберите соответствующий класс Encoding
.
// using System.Text;
Encoding.UTF8.GetBytes(".NET String to byte array")
Преобразование может потребоваться для обработки случаев, когда целевой набор символов или кодировка не поддерживает символ, который находится в источнике. У вас есть выбор: исключение, замена или пропуск. Политика по умолчанию заключается в замене символа "?".
// using System.Text;
var text = Encoding.ASCII.GetString(Encoding.ASCII.GetBytes("You win €100"));
// -> "You win ?100"
Ясно, что конверсии не обязательно происходят без потерь!
Примечание. Для System.String
исходным набором символов является Unicode.
Единственное, что сбивает с толку, это то, что .NET использует имя набора символов для имени одной конкретной кодировки этого набора символов. Encoding.Unicode
следует называть Encoding.UTF16
.
Это для большинства случаев использования. Если это то, что вам нужно, прекратите читать здесь . Посмотрите забавную статью Джоэла Спольски если вы не понимаете, что такое кодировка.
Конкретная потребность
Теперь автор вопроса спрашивает: «Каждая строка хранится как массив байтов, верно? Почему я не могу просто иметь эти байты?»
Он не хочет никакого обращения.
Из спецификации C #:
При обработке символов и строк в C # используется кодировка Unicode. Тип char представляет собой кодовую единицу UTF-16, а строковый тип представляет последовательность кодовых единиц UTF-16.
Итак, мы знаем, что если мы запросим нулевое преобразование (то есть из UTF-16 в UTF-16), мы получим желаемый результат:
Encoding.Unicode.GetBytes(".NET String to byte array")
Но чтобы избежать упоминания кодировок, надо сделать это по-другому. Если допустим промежуточный тип данных, для этого есть концептуальный ярлык:
".NET String to byte array".ToCharArray()
Это не дает нам желаемого типа данных, но ответ Мердада показывает, как преобразовать этот массив Char в массив байтов с помощью BlockCopy. Однако это дважды копирует строку! И он также явно использует код, специфичный для кодировки: тип данных System.Char
.
Единственный способ добраться до фактических байтов, в которых хранится строка, - это использовать указатель. Оператор fixed
позволяет получить адрес значений. Из спецификации C #:
[Для] выражения типа строка, ... инициализатор вычисляет адрес первого символа в строке.
Для этого компилятор записывает код, пропускающий другие части строкового объекта с помощью RuntimeHelpers.OffsetToStringData
. Итак, чтобы получить необработанные байты, просто создайте указатель на строку и скопируйте необходимое количество байтов.
// using System.Runtime.InteropServices
unsafe byte[] GetRawBytes(String s)
{
if (s == null) return null;
var codeunitCount = s.Length;
/* We know that String is a sequence of UTF-16 codeunits
and such codeunits are 2 bytes */
var byteCount = codeunitCount * 2;
var bytes = new byte[byteCount];
fixed(void* pRaw = s)
{
Marshal.Copy((IntPtr)pRaw, bytes, 0, byteCount);
}
return bytes;
}
Как отметил @CodesInChaos, результат зависит от порядка байтов машины. Но автора вопроса это не волнует.
Length
[of String
]] возвращает количество объектов Char
в этом экземпляре, а не количество символов Юникода». Таким образом, ваш примерный код верен в том виде, в котором он написан.
new String(new []{'\uD800', '\u0030'})
Globalization.SortKey
, извлечь KeyData
и упаковать полученные байты из каждого в String
[два байта на символ, сначала MSB < / i>], вызов String.CompareOrdinal
для результирующих строк будет значительно быстрее, чем вызов SortKey.Compare
для экземпляров SortKey
или даже вызов memcmp
для этих экземпляров. Учитывая это, мне интересно, почему KeyData
возвращает Byte[]
, а не String
?
Вам необходимо принять во внимание кодировку, потому что 1 символ может быть представлен 1 или более байтами (примерно до 6), а разные кодировки будут обрабатывать эти байты по-разному.
У Джоэла есть сообщение об этом:
На первую часть вашего вопроса (как получить байты) уже ответили другие: посмотрите в пространство имен System.Text.Encoding
.
Я отвечу на ваш дополнительный вопрос: зачем вам выбирать кодировку? Почему вы не можете получить это из самого строкового класса?
Ответ состоит из двух частей.
Прежде всего, байты, используемые внутри строкового класса не имеют значения , и всякий раз, когда вы предполагаете, что это так, вы, вероятно, вносите ошибку.
Если ваша программа полностью находится в мире .Net, вам вообще не нужно беспокоиться о получении байтовых массивов для строк, даже если вы отправляете данные по сети. Вместо этого используйте .Net Serialization, чтобы беспокоиться о передаче данных. Вам больше не нужно беспокоиться о фактических байтах: форматтер сериализации сделает это за вас.
С другой стороны, что, если вы отправляете эти байты куда-то, что вы не можете гарантировать, что они получат данные из сериализованного потока .Net? В этом случае вам определенно нужно беспокоиться о кодировании, потому что, очевидно, эта внешняя система заботится. Итак, внутренние байты, используемые строкой, не имеют значения: вам нужно выбрать кодировку, чтобы вы могли явно указать эту кодировку на принимающей стороне, даже если это та же самая кодировка, которая используется внутри .Net.
Я понимаю, что в этом случае вы можете предпочесть использовать фактические байты, хранящиеся в строковой переменной в памяти, где это возможно, с мыслью, что это может сэкономить некоторую работу при создании вашего потока байтов. Однако я говорю вам, что это просто не важно по сравнению с тем, чтобы убедиться, что ваш вывод понят на другом конце, и чтобы гарантировать, что вы должны явно указывать свою кодировку. Кроме того, если вы действительно хотите согласовать свои внутренние байты, вы уже можете просто выбрать кодировку Unicode
и получить экономию производительности.
Это подводит меня ко второй части ... выбор кодировки Unicode
сообщает .Net использовать базовые байты. Вам действительно нужно выбрать эту кодировку, потому что, когда выйдет какой-нибудь новомодный Unicode-Plus, среда выполнения .Net должна быть свободной, чтобы использовать эту новую, лучшую модель кодирования, не нарушая вашу программу. Но на данный момент (и в обозримом будущем) простой выбор кодировки Unicode дает вам то, что вы хотите.
Также важно понимать, что ваша строка должна быть переписана в провод, и это включает в себя хотя бы некоторую трансляцию битового шаблона , даже если вы используете соответствующую кодировку . Компьютер должен учитывать такие вещи, как Big vs Little Endian, сетевой порядок байтов, пакетирование, информацию о сеансе и т. Д.
Чтобы продемонстрировать, что звуковой ответ Мехрдрада работает, его подход может даже сохранить непарные суррогатные символы (многие из которых высказывались против мой ответ, но в котором все одинаково виноваты, например System.Text.Encoding.UTF8.GetBytes
, System.Text.Encoding.Unicode.GetBytes
; эти методы кодирования не могут, например, сохранять высокие суррогатные символы d800
, а они просто заменяют высокие суррогатные символы со значением fffd
):
using System;
class Program
{
static void Main(string[] args)
{
string t = "爱虫";
string s = "Test\ud800Test";
byte[] dumpToBytes = GetBytes(s);
string getItBack = GetString(dumpToBytes);
foreach (char item in getItBack)
{
Console.WriteLine("{0} {1}", item, ((ushort)item).ToString("x"));
}
}
static byte[] GetBytes(string str)
{
byte[] bytes = new byte[str.Length * sizeof(char)];
System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
return bytes;
}
static string GetString(byte[] bytes)
{
char[] chars = new char[bytes.Length / sizeof(char)];
System.Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length);
return new string(chars);
}
}
Выход:
T 54
e 65
s 73
t 74
? d800
T 54
e 65
s 73
t 74
Попробуйте это с помощью System.Text.Encoding.UTF8.GetBytes или System.Text.Encoding.Unicode.GetBytes , они просто заменят высокие суррогатные символы значением fffd
Каждый раз, когда в этом вопросе происходит движение, я все еще думаю о сериализаторе (будь то от Microsoft или от стороннего компонента), который может сохранять строки, даже если он содержит непарные суррогатные символы; Я то и дело гуглил: сериализация непарного суррогатного символа .NET . Это не заставляет меня терять сон, но это немного раздражает, когда время от времени кто-то комментирует мой ответ, что он ошибочен, но их ответы одинаково ошибочны, когда речь идет о непарных суррогатных персонажах.
Черт, Microsoft должна была просто использовать System.Buffer.BlockCopy
в своем BinaryFormatter
ツ
谢谢!
System.Buffer.BlockCopy
внутри, все аргументы сторонников кодирования будут спорными
FFFD
для этого символа. Если вы хотите выполнять манипуляции со строкой вручную, используйте char [], как рекомендуется.
System.String
- неизменная последовательность Char
; .NET всегда позволял создавать объект String
из любого Char[]
и экспортировать его содержимое в Char[]
, содержащий те же значения, даже если исходный Char[]
содержит непарные суррогаты .
Попробуйте это, намного меньше кода:
System.Text.Encoding.UTF8.GetBytes("TEST String");
System.Text.Encoding.UTF8.GetBytes("Árvíztűrő tükörfúrógép);
и плачьте! Это будет работать, но System.Text.Encoding.UTF8.GetBytes("Árvíztűrő tükörfúrógép").Length != System.Text.Encoding.UTF8.GetBytes("Arvizturo tukorfurogep").Length
пока "Árvíztűrő tükörfúrógép".Length == "Arvizturo tukorfurogep".Length
Что ж, я прочитал все ответы, и они касались использования кодирования или сериализации, которая отбрасывает непарные суррогаты.
Плохо, когда строка, например, исходит из SQL Server, где она была построена из массива байтов. хранение, например, хэша пароля. Если мы что-нибудь из него отбросим, он сохранит недействительный хэш, а если мы хотим сохранить его в XML, мы хотим оставить его нетронутым (потому что модуль записи XML отбрасывает исключение для любого найденного непарного суррогата).
Поэтому в таких случаях я использую кодировку массивов байтов Base64, но в Интернете есть только одно решение этой проблемы на C #, и в нем есть ошибка, и это только один способ, поэтому я исправил ошибку и написал обратно процедуру. Вот вы, будущие гуглеры:
public static byte[] StringToBytes(string str)
{
byte[] data = new byte[str.Length * 2];
for (int i = 0; i < str.Length; ++i)
{
char ch = str[i];
data[i * 2] = (byte)(ch & 0xFF);
data[i * 2 + 1] = (byte)((ch & 0xFF00) >> 8);
}
return data;
}
public static string StringFromBytes(byte[] arr)
{
char[] ch = new char[arr.Length / 2];
for (int i = 0; i < ch.Length; ++i)
{
ch[i] = (char)((int)arr[i * 2] + (((int)arr[i * 2 + 1]) << 8));
}
return new String(ch);
}
Convert.ToBase64String(arr);
для преобразования base64 byte[] (data) <-> string (serialized data to store in XML file)
. Но чтобы получить начальный byte[] (data)
, мне нужно было что-то сделать с String
, содержащим двоичные данные (так MSSQL вернул их мне). ТАК, что приведенные выше функции предназначены для String (binary data) <-> byte[] (easy accessible binary data)
.
Также объясните, почему следует учитывать кодировку. Разве я не могу просто узнать, в каких байтах была сохранена строка? Откуда такая зависимость от кодировки? !!!
Потому что не существует такого понятия, как «байты строки».
Строка (или, в более общем смысле, текст) состоит из символов: букв, цифр и других символов. Это все. Однако компьютеры ничего не знают о персонажах; они могут обрабатывать только байты. Следовательно, если вы хотите сохранить или передать текст с помощью компьютера, вам необходимо преобразовать символы в байты. Как ты это делаешь? Вот где на сцену выходят кодировки.
Кодировка - это не что иное, как соглашение о переводе логических символов в физические байты. Самая простая и известная кодировка - это ASCII, и это все, что вам нужно, если вы пишете на английском языке. Для других языков вам понадобятся более полные кодировки, поскольку любой из вариантов Unicode является самым безопасным выбором в настоящее время.
Короче говоря, попытка «получить байты строки без использования кодировок» так же невозможна, как «написать текст без использования какого-либо языка».
Между прочим, я настоятельно рекомендую вам (и всем в этом отношении) прочитать эту небольшую мудрость: Абсолютный минимум, что каждый разработчик программного обеспечения должен абсолютно точно знать о Unicode и наборах символов (без оправданий!)
C # для преобразования string
в массив byte
:
public static byte[] StrToByteArray(string str)
{
System.Text.UTF8Encoding encoding=new System.Text.UTF8Encoding();
return encoding.GetBytes(str);
}
byte[] strToByteArray(string str)
{
System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
return enc.GetBytes(str);
}
Вы можете использовать следующий код для преобразования между строкой и байтовым массивом.
string s = "Hello World";
// String to Byte[]
byte[] byte1 = System.Text.Encoding.Default.GetBytes(s);
// OR
byte[] byte2 = System.Text.ASCIIEncoding.Default.GetBytes(s);
// Byte[] to string
string str = System.Text.Encoding.UTF8.GetString(byte1);
С появлением Span<T>
выпущенного с В C # 7.2 канонический метод захвата нижележащего представления строки в памяти в управляемый массив байтов:
byte[] bytes = "rubbish_\u9999_string".AsSpan().AsBytes().ToArray();
Преобразование обратно не должно начинаться, потому что это означает, что вы на самом деле каким-то образом интерпретируете данные, но для полноты:
string s;
unsafe
{
fixed (char* f = &bytes.AsSpan().NonPortableCast<byte, char>().DangerousGetPinnableReference())
{
s = new string(f);
}
}
Имена NonPortableCast
и DangerousGetPinnableReference
должны служить аргументом в пользу того, что вам, вероятно, не следует этого делать.
Обратите внимание, что для работы с Span<T>
требуется установить пакет NuGet System.Memory .
Тем не менее, актуальный исходный вопрос и последующие комментарии подразумевают, что базовая память не «интерпретируется» (что, как я полагаю, означает, что она не изменяется и не читается сверх необходимости записывать ее как есть), это указывает на то, что следует использовать некоторую реализацию класса Stream
вместо того, чтобы вообще рассматривать данные как строки.
new string(f)
неверно, вам, по крайней мере, нужно использовать перегрузку конструктора, которая принимает явную длину, если вы хотите надеяться на возврат всех строк.
Я не уверен, но я думаю, что строка хранит свою информацию как массив символов, что неэффективно с байтами. В частности, определение Char - «Представляет символ Юникода».
Возьмите этот пример образца:
String str = "asdf éß";
String str2 = "asdf gh";
EncodingInfo[] info = Encoding.GetEncodings();
foreach (EncodingInfo enc in info)
{
System.Console.WriteLine(enc.Name + " - "
+ enc.GetEncoding().GetByteCount(str)
+ enc.GetEncoding().GetByteCount(str2));
}
Обратите внимание, что ответ Unicode составляет 14 байтов в обоих случаях, тогда как ответ UTF-8 составляет всего 9 байтов для первого и только 7 для второго.
Поэтому, если вы хотите, чтобы строка использовалась просто байтами, просто используйте Encoding.Unicode
, но это будет неэффективно с пространством для хранения.
Ключевой проблемой является то, что глиф в строке занимает 32 бита (16 бит для символьного кода), но у байта остается только 8 бит. Однозначного сопоставления не существует, если вы не ограничиваете себя строками, содержащими только символы ASCII. System.Text.Encoding имеет множество способов сопоставить строку с byte [], вам нужно выбрать тот, который позволяет избежать потери информации и который будет легко использовать вашему клиенту, когда ему нужно сопоставить byte [] обратно со строкой. .
Utf8 - популярная кодировка, она компактна и не требует потерь.
Использование:
string text = "string";
byte[] array = System.Text.Encoding.UTF8.GetBytes(text);
Результат:
[0] = 115
[1] = 116
[2] = 114
[3] = 105
[4] = 110
[5] = 103
Самый быстрый способ
public static byte[] GetBytes(string text)
{
return System.Text.ASCIIEncoding.UTF8.GetBytes(text);
}
ИЗМЕНИТЬ как прокомментировал Макотосан, теперь это лучший способ:
Encoding.UTF8.GetBytes(text)
Самый близкий подход к вопросу OP - это Tom Blodget's, который фактически входит в объект и извлекает байты. Я говорю «ближайший», потому что это зависит от реализации объекта String.
"Can't I simply get what bytes the string has been stored in?"
Конечно, но здесь возникает основная ошибка в вопросе. String - это объект, который может иметь интересную структуру данных. Мы уже знаем, что это так, потому что это позволяет хранить непарные суррогаты. Он может хранить длину. Он может хранить указатель на каждый из «парных» суррогатов, позволяющий быстро подсчитывать. И т.д. Все эти дополнительные байты не являются частью символьных данных.
Вам нужны байты каждого символа в массиве. И здесь на помощь приходит «кодировка». По умолчанию вы получаете UTF-16LE. Если вас не интересуют сами байты, за исключением обратного пути, вы можете выбрать любую кодировку, включая `` по умолчанию '', и преобразовать ее позже (предполагая те же параметры, как кодировка по умолчанию, кодовые точки, исправления ошибок , разрешенные вещи, такие как непарные суррогаты и т. д.
Но зачем оставлять «кодировку» на волю магии? Почему бы не указать кодировку, чтобы знать, какие байты вы получите?
"Why is there a dependency on character encodings?"
Кодировка (в этом контексте) просто означает байты, представляющие вашу строку. Не байты строкового объекта. Вы хотели байты, в которых хранилась строка - здесь вопрос был задан наивно. Вам нужны байты строки в непрерывном массиве, представляющем строку, а не все другие двоичные данные, которые может содержать строковый объект.
Это означает, что то, как хранится строка, не имеет значения. Вам нужна строка, «закодированная» в байты в байтовом массиве.
Мне нравится ответ Тома Блогета, потому что он направил вас в сторону «байтов строкового объекта». Однако это зависит от реализации, и, поскольку он заглядывает во внутреннее устройство, может быть трудно восстановить копию строки.
Ответ Мердада неверен, потому что он вводит в заблуждение на концептуальном уровне. У вас все еще есть список закодированных байтов. Его конкретное решение позволяет сохранить непарные суррогаты - это зависит от реализации. Его конкретное решение не создало бы байты строки точно, если бы GetBytes
по умолчанию возвращал строку в UTF-8.
Я передумал по этому поводу (решение Мердада) - это не получение байтов строки; скорее он получает байты массива символов, который был создан из строки. Независимо от кодировки тип данных char в C # имеет фиксированный размер. Это позволяет создавать байтовый массив постоянной длины и воспроизводить массив символов на основе размера байтового массива. Таким образом, если бы кодировка была UTF-8, но каждый символ имел 6 байтов для размещения наибольшего значения utf8, он все равно работал бы. Так что действительно - кодировка символа не имеет значения.
Но использовалось преобразование - каждый символ помещался в поле фиксированного размера (тип символа C #). Однако не имеет значения, что это за представление, которое технически является ответом на OP. Итак - если вы все равно собираетесь конвертировать ... Почему бы не «кодировать»?
& (Char) 55906
& (Char) 55655
. Так что вы можете ошибаться, и ответ Мехрдада - безопасное преобразование без учета того, какие типы кодировок используются.
Как преобразовать строку в byte [] в .NET (C #) без указания конкретной кодировки вручную?
строка в .NET представляет текст как последовательность кодовых единиц UTF-16, поэтому байты уже закодированы в памяти в UTF-16.
Ответ Мехрдада
Вы можете использовать ответ Мердада, но на самом деле он использует кодировку, потому что символы - это UTF-16. Он вызывает ToCharArray, который при просмотре источника создает char[]
и копирует память напрямую в него. Затем он копирует данные в массив байтов, который также выделяется. Таким образом, под капотом он копирует базовые байты дважды и выделяет массив символов, который не используется после вызова.
Ответ Тома Блоджета
Ответ Тома Блоджет на 20-30% быстрее, чем Mehrdad, поскольку он пропускает промежуточный этап выделения массива символов и копирования байтов. к нему, но для этого требуется, чтобы вы скомпилировали его с опцией /unsafe
. Если вы абсолютно не хотите использовать кодировку, я думаю, что это выход. Если вы поместите свой логин для шифрования в блок fixed
, вам даже не потребуется выделять отдельный массив байтов и копировать в него байты.
Кроме того, почему следует учитывать кодировку? Разве я не могу просто узнать, в каких байтах была сохранена строка? Почему существует зависимость от кодировок символов?
Потому что это правильный способ сделать это. string
- это абстракция.
Использование кодировки может вызвать проблемы, если у вас есть «строки» с недопустимыми символами, но этого не должно происходить. Если вы вводите данные в свою строку с недопустимыми символами, вы делаете это неправильно. Вероятно, для начала вам следует использовать массив байтов или кодировку Base64.
Если вы используете System.Text.Encoding.Unicode
, ваш код будет более устойчивым. Вам не нужно беспокоиться о порядке байтов системы, в которой будет выполняться ваш код. Вам не нужно беспокоиться, если в следующей версии CLR будет использоваться другая внутренняя кодировка символов.
Я думаю, вопрос не в том, почему вы хотите беспокоиться о кодировке, а в том, почему вы хотите игнорировать ее и использовать что-то еще. Кодирование предназначено для представления абстракции строки в последовательности байтов. System.Text.Encoding.Unicode
предоставит вам кодировку порядка байтов с прямым порядком байтов и будет работать так же во всех системах, сейчас и в будущем.
Вы можете использовать следующий код для преобразования string
в byte array
в .NET.
string s_unicode = "abcéabc";
byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(s_unicode);
Если вам действительно нужна копия базовых байтов строки, вы можете использовать функцию, подобную следующей. Тем не менее, вам не следует читать дальше, чтобы узнать почему.
[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()
По всей вероятности, это даст тот же результат, его легче набрать, и байты будут передаваться туда и обратно, как и представление байтов в Unicode, с вызовом
System.Text.Encoding.Unicode.GetString()
Unicode.GetBytes()
/ Unicode.GetString()
НЕ выполняет двусторонний цикл для всех экземпляров .NET string
.
Marshal.Copy
отлично подойдет для копирования из указателя в массив байтов. stackoverflow.com/a/54453180/103167
Вот моя небезопасная реализация преобразования 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;
}
Это намного быстрее, чем принятый anwser, даже если он не такой элегантный, как он есть. Вот мои тесты секундомера за 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, этот метод также можно использовать как расширение String:
public static unsafe class StringExtensions
{
public static Byte[] ToByteArray(this String s)
{
// Method Code
}
}
RuntimeHelpers.OffsetToStringData
кратным 8 в версиях .NET для Itanium? Потому что в противном случае это не удастся из-за невыровненных чтений.
Когда вас спросят, что вы собираетесь делать с байтами, вы ответил:
Я собираюсь его зашифровать. Я могу зашифровать его без преобразования, но я все же хотел бы знать, почему здесь применяется кодирование. Просто дай мне байты, вот что я говорю.
Независимо от того, собираетесь ли вы отправлять эти зашифрованные данные по сети, загружать их обратно в память позже или передавать их другому процессу, вы явно намереваетесь в какой-то момент расшифровать их. В этом случае ответ заключается в том, что вы определяете протокол связи. Протокол связи не должен быть определен с точки зрения деталей реализации вашего языка программирования и связанной с ним среды выполнения. На это есть несколько причин:
- Возможно, вам потребуется связаться с процессом, реализованным на другом языке или во время выполнения. (Это может включать, например, сервер, работающий на другом компьютере, или отправку строки клиенту браузера JavaScript.)
- Программа может быть повторно реализована на другом языке или в другой среде выполнения в будущем.
- Реализация .NET может изменить внутреннее представление строк. Вы можете подумать, что это звучит неправдоподобно, но это на самом деле произошло в Java 9, чтобы уменьшить использование памяти. Нет причин, по которым .NET не мог последовать их примеру. Скит предполагает, что UTF-16, вероятно, сегодня не является оптимальным, поэтому возникают эмодзи и другие блоки Unicode, требующие большего, чем 2 байта для представления, что увеличивает вероятность того, что внутреннее представление может измениться в будущем.
Для связи (либо с полностью разрозненным процессом, либо с той же программой в будущем) вам необходимо определить свой протокол строго , чтобы свести к минимуму сложность работы с ним или случайного создания ошибок. В зависимости от внутреннего представления .NET не существует строгого, ясного или даже гарантированно непротиворечивого определения. Стандартная кодировка - это строгое определение, которое не подведет в будущем.
Другими словами, вы не можете удовлетворить свои требования к согласованности , не указав кодировку.
Вы можете определенно использовать UTF-16 напрямую, если обнаружите, что ваш процесс работает значительно лучше, поскольку .NET использует его для внутренних целей или по любой другой причине, но вам нужно явно выбрать эту кодировку и выполнить эти преобразования. явно в вашем коде, а не в зависимости от внутренней реализации .NET.
Так что выберите кодировку и используйте ее:
using System.Text;
// ...
Encoding.Unicode.GetBytes("abc"); # UTF-16 little endian
Encoding.UTF8.GetBytes("abc")
Как видите, на самом деле меньше кода просто использовать встроенные объекты кодирования, чем реализовывать собственные методы чтения / записи.
Строка может быть преобразована в массив байтов несколькими способами из-за следующего факта: .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[])
.
Ссылка на эту страницу.
Примере:
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? :-)
Это зависит от того, для чего вам нужны байты
Это потому, что, как очень точно сказал Тайлер, «Строки не являются чистыми данными. Они также содержат < a href = "http://www.diffen.com/difference/Data_vs_Information" rel = "nofollow noreferrer"> информация . " В этом случае информация представляет собой кодировку, которая была принята при создании строки.
Предполагая, что у вас есть двоичные данные (а не текст), хранящиеся в строке
Это основано на комментарии OP к его собственному вопросу, и это правильный вопрос, если я понимаю намеки OP на этот вариант использования.
Хранение двоичных данных в строках, вероятно, является неправильным подходом из-за предполагаемой кодировки, упомянутой выше! Какая бы программа или библиотека ни хранила эти двоичные данные в string
(вместо массива byte[]
, который был бы более подходящим), битва уже проиграна еще до того, как она началась. Если они отправляют вам байты в запросе / ответе REST или в чем-то, что должно передавать строки, Base64 < / a> было бы правильным подходом.
Если у вас есть текстовая строка с неизвестной кодировкой
Все остальные ответили на этот неправильный вопрос неправильно.
Если строка выглядит хорошо как есть, просто выберите кодировку (желательно начинающуюся с UTF), используйте соответствующую функцию System.Text.Encoding.???.GetBytes()
и сообщите тому, кому вы дадите байты, какую кодировку вы выбрали.
Если вы используете .NET Core или System.Memory для .NET Framework , существует очень эффективный механизм маршалинга, доступный через Span
Подводя итог сказанному другими:
- Хранение представления такого типа сериализации чувствительно к порядку байтов в системе, оптимизации компилятора и изменениям во внутреннем представлении строк в исполняющейся среде выполнения .NET.
- Избегайте длительного хранения.
- Избегайте десериализации или интерпретации строки в других средах. <ул>
- Сюда входят другие компьютеры, архитектуры процессоров, среды выполнения .NET, контейнеры и т. д.
- К ним относятся сравнения, форматирование, шифрование, обработка строк, локализация, преобразование символов и т. д.
- Не делайте предположений о кодировке символов. <ул>
- На практике кодировкой по умолчанию обычно является UTF-16LE, но компилятор/среда выполнения могут выбрать любое внутреннее представление
Реализация
public static class MarshalExtensions
{
public static ReadOnlySpan<byte> AsBytes(this string value) => MemoryMarshal.AsBytes(value.AsSpan());
public static string AsString(this ReadOnlySpan<byte> value) => new string(MemoryMarshal.Cast<byte, char>(value));
}
Примере
static void Main(string[] args)
{
string str1 = "你好,世界";
ReadOnlySpan<byte> span = str1.AsBytes();
string str2 = span.AsString();
byte[] bytes = span.ToArray();
Debug.Assert(bytes.Length > 0);
Debug.Assert(str1 == str2);
}
Furthur Insight
В C ++ это примерно эквивалентно reinterpret_cast , а в C это примерно эквивалентно приведению к системному типу слова ( char ).
В последних версиях .NET Core Runtime (CoreCLR) операции над промежутками эффективно вызывают встроенные функции компилятора и различные оптимизации, которые иногда могут исключать проверку границ, что приводит к исключительной производительности при сохранении безопасности памяти, предполагая, что ваша память был выделен средой CLR, и диапазоны не получены из указателей неуправляемого распределителя памяти.
Предостережения
При этом используется механизм, поддерживаемый CLR, который возвращает ReadOnlySpan
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
Просто используйте это:
byte[] myByte= System.Text.ASCIIEncoding.Default.GetBytes(myString);
System.Text.ASCIIEncoding.Default.GetBytes("Árvíztűrő tükörfúrógép.").ToString();
вернет "Árvizturo tukörfurogép."
информацию о потерях, которую невозможно восстановить. (И я еще не упомянул азиатские языки, где вы теряете все символы.)
Похожие вопросы
Связанные вопросы
Новые вопросы
c#
C# (произносится как «see Sharp») — это высокоуровневый мультипарадигменный язык программирования со статической типизацией, разработанный Microsoft. Код C# обычно нацелен на семейство инструментов и сред выполнения Microsoft .NET, которое включает в себя .NET, .NET Framework, .NET MAUI и Xamarin среди прочих. Используйте этот тег для ответов на вопросы о коде, написанном на C#, или о формальной спецификации C#.