Jak uzyskać spójną reprezentację bajtów ciągów w C # bez ręcznego określania kodowania?


2189

Jak przekonwertować stringna byte[].NET (C #) bez ręcznego określania konkretnego kodowania?

Mam zamiar zaszyfrować ciąg. Mogę go zaszyfrować bez konwersji, ale nadal chciałbym wiedzieć, dlaczego tutaj kodowanie się pojawia.

Dlaczego warto nawet brać pod uwagę kodowanie? Czy nie mogę po prostu pobrać bajtów, w których zapisano ciąg? Dlaczego istnieje zależność od kodowania znaków?


23
Każdy ciąg jest przechowywany jako tablica bajtów, prawda? Dlaczego nie mogę po prostu mieć tych bajtów?
Agnel Kurian

135
Kodowanie jest co odwzorowuje znaki na bajty. Na przykład w ASCII litera „A” odwzorowuje na liczbę 65. W innym kodowaniu może nie być taka sama. Podejście wysokiego poziomu do łańcuchów podjętych w środowisku .NET sprawia, że ​​jest to w dużej mierze nieistotne (z wyjątkiem tego przypadku).
Lucas Jones

20
Aby zagrać w adwokata diabła: Jeśli chcesz pobrać bajty ciągu w pamięci (ponieważ .NET ich używa) i jakoś nimi manipulować (tj. CRC32), a NIGDY NIE chciałeś nigdy dekodować go z powrotem do oryginalnego ciągu ... to nie jest oczywiste, dlaczego zależy Ci na kodowaniu lub w jaki sposób wybierasz, którego użyć.
Greg

78
Zaskoczony, nikt jeszcze nie podał tego linku: joelonsoftware.com/articles/Unicode.html
Bevan

28
Znak nie jest bajtem, a bajt nie jest znakiem. Znak jest zarówno kluczem do tabeli czcionek, jak i tradycji leksykalnej. Ciąg to ciąg znaków. (Słowa, akapity, zdania i tytuły również mają swoje własne tradycje leksykalne, które uzasadniają ich własne definicje typów - ale dygresję). Podobnie jak liczby całkowite, liczby zmiennoprzecinkowe i wszystko inne, znaki są zakodowane w bajtach. Był czas, kiedy kodowanie było proste jeden do jednego: ASCII. Jednak, aby pomieścić całą ludzką symbolikę, 256 permutacji bajtu było niewystarczające i opracowano kodowania umożliwiające selektywne wykorzystanie większej liczby bajtów.
George

Odpowiedzi:


1855

W przeciwieństwie do odpowiedzi tutaj, nie musisz się martwić kodowaniem, jeśli bajty nie muszą być interpretowane!

Jak już wspomniałeś, Twoim celem jest po prostu „zdobycie bajtów, w których łańcuch został zapisany” .
(I oczywiście, aby móc zrekonstruować ciąg z bajtów.)

Jeśli chodzi o te cele, szczerze nie rozumiem, dlaczego ludzie wciąż mówią ci, że potrzebujesz kodowania. Z pewnością NIE musisz się tym martwić o kodowanie.

Po prostu zrób to zamiast tego:

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);
}

Tak długo, jak twój program (lub inne programy) nie próbuje w jakiś sposób interpretować bajtów, o czym oczywiście nie wspomniałeś, że zamierzasz to zrobić, to nie ma nic złego w tym podejściu! Martwienie się o kodowanie tylko komplikuje życie bez prawdziwego powodu.

Dodatkowe korzyści tego podejścia:

Nie ma znaczenia, czy ciąg zawiera nieprawidłowe znaki, ponieważ nadal możesz uzyskać dane i zrekonstruować oryginalny ciąg!

Zostanie zakodowany i odkodowany tak samo, ponieważ patrzysz tylko na bajty .

Jeśli jednak użyjesz określonego kodowania, sprawiłoby ci to problemy z kodowaniem / dekodowaniem nieprawidłowych znaków.


247
Brzydkie w tym jest to, że GetStringi GetBytestrzeba je wykonać w systemie z taką samą endianią działania. Więc nie możesz tego użyć, aby uzyskać bajty, które chcesz zmienić w ciąg znaków w innym miejscu. Trudno mi więc wymyślić sytuacje, w których chciałbym tego użyć.
CodesInChaos

72
@CodeInChaos: Tak jak powiedziałem, chodzi o to, że chcesz używać go w tym samym systemie z tym samym zestawem funkcji. Jeśli nie, to nie powinieneś go używać.
user541686

193
-1 Gwarantuję, że ktoś (kto nie rozumie bajtów vs. znaków) będzie chciał przekonwertować swój ciąg znaków na tablicę bajtów, przejrzy google i przeczyta tę odpowiedź, i zrobi coś złego, ponieważ prawie we wszystkich przypadki, kodowanie JEST istotne.
artbristol

401
@artbristol: Jeśli nie mogą sobie pozwolić na przeczytanie odpowiedzi (lub innych odpowiedzi ...), przepraszam, to nie ma lepszego sposobu na komunikację się z nimi. Generalnie wolę odpowiedzieć na OP, niż próbować zgadywać, co inni mogą zrobić z moją odpowiedzią - OP ma prawo wiedzieć, a to, że ktoś może nadużywać noża, nie oznacza, że ​​musimy ukryć wszystkie noże na świecie dla nas. Ale jeśli się nie zgadzasz, to też jest w porządku.
user541686,

185
Ta odpowiedź jest błędna na tak wielu poziomach, ale przede wszystkim z powodu deklinacji „NIE musisz martwić się o kodowanie!”. Te dwie metody, GetBytes i GetString, są zbędne, ponieważ są jedynie ponownymi implementacjami tego, co już robią Encoding.Unicode.GetBytes () i Encoding.Unicode.GetString (). Stwierdzenie „Tak długo, jak twój program (lub inne programy) nie próbuje interpretować bajtów” również jest zasadniczo wadliwe, ponieważ domyślnie oznaczają, że bajty powinny być interpretowane jako Unicode.
David

1108

To zależy od kodowania twojego łańcucha ( ASCII , UTF-8 , ...).

Na przykład:

byte[] b1 = System.Text.Encoding.UTF8.GetBytes (myString);
byte[] b2 = System.Text.Encoding.ASCII.GetBytes (myString);

Mała próbka, dlaczego kodowanie ma znaczenie:

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 po prostu nie jest przystosowany do radzenia sobie ze znakami specjalnymi.

Wewnętrznie .NET Framework używa UTF-16 do reprezentowania ciągów, więc jeśli chcesz uzyskać dokładnie te bajty, których używa .NET, użyj System.Text.Encoding.Unicode.GetBytes (...).

Aby uzyskać więcej informacji, zobacz Kodowanie znaków w .NET Framework (MSDN).


14
Ale dlaczego warto brać pod uwagę kodowanie? Dlaczego nie mogę po prostu pobrać bajtów bez konieczności sprawdzania, jakie kodowanie jest używane? Nawet gdyby był wymagany, czy sam obiekt String nie powinien wiedzieć, jakie kodowanie jest używane, i po prostu zrzucić to, co jest w pamięci?
Agnel Kurian

57
Ciągi .NET są zawsze kodowane jako Unicode. Więc użyj System.Text.Encoding.Unicode.GetBytes (); aby uzyskać zestaw bajtów, których .NET użyłby do przedstawienia znaków. Dlaczego jednak tego chcesz? Polecam UTF-8 szczególnie, gdy większość postaci znajduje się w zachodnim zestawie łacińskim.
AnthonyWJones

8
Ponadto: dokładne bajty użyte wewnętrznie w ciągu nie mają znaczenia, czy system, który je pobiera, nie obsługuje tego kodowania lub obsługuje je jako nieprawidłowe kodowanie. Jeśli wszystko znajduje się w .Net, po co w ogóle konwertować na tablicę bajtów. W przeciwnym razie lepiej jest wyrazić swoje kodowanie
Joel Coehoorn

11
@Joel, Uważaj na System.Text.Encoding.Default, ponieważ może być inny na każdym komputerze, na którym jest uruchomiony. Dlatego zaleca się, aby zawsze określać kodowanie, takie jak UTF-8.
Ash

25
Nie potrzebujesz kodowania, chyba że ty (lub ktoś inny) zamierzasz interpretować dane, zamiast traktować je jako ogólny „blok bajtów”. W przypadku kompresji, szyfrowania itp. Martwienie się o kodowanie jest bez znaczenia. Zobacz moją odpowiedź, jak to zrobić, nie martwiąc się o kodowanie. (Mógłbym dać -1 za powiedzenie, że musisz martwić się o kodowanie, kiedy tego nie robisz, ale nie czuję się dzisiaj szczególnie wredny.: P)
user541686 30.04. Kwietnia

285

Przyjęta odpowiedź jest bardzo, bardzo skomplikowana. Użyj do tego dołączonych klas .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);

Nie wymyślaj koła, jeśli nie musisz ...


14
W przypadku zmiany zaakceptowanej odpowiedzi, do celów rejestracyjnych, jest to odpowiedź Mehrdada w bieżącym czasie i dacie. Mamy nadzieję, że PO ponownie to sprawdzi i zaakceptuje lepsze rozwiązanie.
Thomas Eding

7
dobre w zasadzie, ale kodowanie powinno System.Text.Encoding.Unicodebyć równoważne z odpowiedzią Mehrdada.
Jodrell

5
Pytanie zostało zredagowane kilka razy od oryginalnej odpowiedzi, więc może moja odpowiedź jest nieco nieaktualna. Nigdy nie zamierzałem podawać ekwiwalentu odpowiedzi Mehrdada, ale rozsądnie to zrobić. Ale możesz mieć rację. Jednak wyrażenie „pobierz bajty, w których zapisano ciąg znaków” w pierwotnym pytaniu, jest bardzo nieprecyzyjne. Przechowywane, gdzie? W pamięci? Na dysku? Jeśli w pamięci, System.Text.Encoding.Unicode.GetBytesprawdopodobnie byłby bardziej precyzyjny.
Erik A. Brandstadmoen

7
@AMissico, twoja sugestia jest błędna, chyba że masz pewność, że Twój ciąg jest zgodny z domyślnym kodowaniem systemu (ciąg zawierający tylko znaki ASCII w domyślnym zestawie znaków). Ale nigdzie PO tego nie stwierdza.
Frédéric

5
@AMissico Może jednak powodować, że program będzie dawał różne wyniki w różnych systemach . To nigdy nie jest dobre. Nawet jeśli jest to tworzenie skrótu lub czegoś takiego (zakładam, że to właśnie oznacza OP z „szyfrowaniem”), ten sam ciąg znaków powinien zawsze dawać ten sam skrót.
Nyerguds

114
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());

2
Możesz użyć tej samej instancji BinaryFormatter do wszystkich tych operacji
Joel Coehoorn

3
Bardzo interesujące. Wygląda na to, że porzuci każdą postać o wysokim zastępczym znaku Unicode. Zobacz dokumentację na [BinaryFormatter ]

95

Musisz wziąć pod uwagę kodowanie, ponieważ 1 znak może być reprezentowany przez 1 lub więcej bajtów (do około 6), a różne kodowania będą traktować te bajty inaczej.

Joel ma post na ten temat:

Absolutne minimum Każdy twórca oprogramowania absolutnie, pozytywnie musi wiedzieć o Unicode i zestawach znaków (bez wymówek!)


6
„1 znak może być reprezentowany przez 1 lub więcej bajtów” Zgadzam się. Chcę tylko tych bajtów, niezależnie od tego, w jakim kodowaniu znajduje się łańcuch. Jedynym sposobem, w jaki łańcuch można zapisać w pamięci, jest bajt. Nawet znaki są przechowywane jako 1 lub więcej bajtów. Chcę tylko dostać w swoje ręce bajty.
Agnel Kurian

16
Nie potrzebujesz kodowania, chyba że ty (lub ktoś inny) zamierzasz interpretować dane, zamiast traktować je jako ogólny „blok bajtów”. W przypadku kompresji, szyfrowania itp. Martwienie się o kodowanie jest bez znaczenia. Zobacz moją odpowiedź, jak to zrobić, nie martwiąc się o kodowanie.
user541686,

9
@ Mehrdad - Całkowicie, ale pierwotne pytanie, jak stwierdzono, kiedy początkowo odpowiedziałem, nie wyjaśniło, co OP będzie działo się z tymi bajtami po ich konwersji, a dla przyszłych poszukiwaczy informacje wokół tego są istotne - to jest dość ładnie ujęte w odpowiedź Joela - i jak podajesz w odpowiedzi: pod warunkiem, że będziesz trzymać się świata .NET i użyjesz swoich metod do konwersji do / z, jesteś szczęśliwy. Jak tylko wyjdziesz poza to, kodowanie będzie miało znaczenie.
Zhaph - Ben Duguid

Jeden punkt kodowy może być reprezentowany przez maksymalnie 4 bajty. (Jedna jednostka kodu UTF-32, para zastępcza UTF-16 lub 4 bajty UTF-8). Wartości, dla których UTF-8 potrzebowałby więcej niż 4 bajty, są poza zakresem 0x0..0x10FFFF Unicode. ;-)
DevSolar,

89

To popularne pytanie. Ważne jest, aby zrozumieć, o co pyta autor pytania, i że różni się on od najprawdopodobniej najczęstszej potrzeby. Aby zniechęcić do niewłaściwego użycia kodu, gdy nie jest on potrzebny, odpowiedziałem najpierw na później.

Wspólna potrzeba

Każdy ciąg ma zestaw znaków i kodowanie. Podczas konwersji System.Stringobiektu na tablicę System.Bytenadal masz zestaw znaków i kodowanie. W przypadku większości zastosowań będziesz wiedział, jakiego zestawu znaków i kodowania potrzebujesz, a .NET ułatwia „kopiowanie z konwersją”. Po prostu wybierz odpowiednią Encodingklasę.

// using System.Text;
Encoding.UTF8.GetBytes(".NET String to byte array")

Konwersja może wymagać obsługi przypadków, w których docelowy zestaw znaków lub kodowanie nie obsługuje znaku znajdującego się w źródle. Masz kilka możliwości: wyjątek, podstawienie lub pominięcie. Domyślną zasadą jest zastąpienie „?”.

// using System.Text;
var text = Encoding.ASCII.GetString(Encoding.ASCII.GetBytes("You win €100")); 
                                                      // -> "You win ?100"

Oczywiście konwersje niekoniecznie są bezstratne!

Uwaga: w przypadku System.Stringzestawu znaków źródłowych jest to Unicode.

Jedyne mylące jest to, że .NET używa nazwy zestawu znaków dla nazwy jednego konkretnego kodowania tego zestawu znaków. Encoding.Unicodepowinien zostać nazwany Encoding.UTF16.

To tyle w przypadku większości zastosowań. Jeśli tego potrzebujesz, przestań czytać tutaj. Zobacz zabawny artykuł Joela Spolsky'ego, jeśli nie rozumiesz, czym jest kodowanie.

Szczególna potrzeba

Teraz autor pytania pyta: „Każdy ciąg jest przechowywany jako tablica bajtów, prawda? Dlaczego nie mogę po prostu mieć tych bajtów?”

On nie chce żadnego nawrócenia.

Ze specyfikacji C # :

Przetwarzanie znaków i ciągów w języku C # wykorzystuje kodowanie Unicode. Typ char reprezentuje jednostkę kodu UTF-16, a typ ciągu reprezentuje sekwencję jednostek kodu UTF-16.

Wiemy zatem, że jeśli poprosimy o konwersję zerową (tj. Z UTF-16 na UTF-16), uzyskamy pożądany wynik:

Encoding.Unicode.GetBytes(".NET String to byte array")

Ale aby uniknąć wzmianki o kodowaniu, musimy zrobić to w inny sposób. Jeśli pośredni typ danych jest dopuszczalny, istnieje do tego skrót koncepcyjny:

".NET String to byte array".ToCharArray()

To nie daje nam pożądanego typu danych, ale odpowiedź Mehrdada pokazuje, jak przekonwertować tę tablicę Char na tablicę bajtów za pomocą BlockCopy . Spowoduje to jednak skopiowanie ciągu dwukrotnie! I zbyt wyraźnie używa kodu specyficznego dla kodowania: typu danych System.Char.

Jedynym sposobem na uzyskanie rzeczywistych bajtów, w których przechowywany jest ciąg, jest użycie wskaźnika. fixedZestawienie umożliwia podejmowanie adres wartości. Ze specyfikacji C #:

[Dla] wyrażenia typu ciąg, ... inicjator oblicza adres pierwszego znaku w ciągu.

Aby to zrobić, kompilator zapisuje pomijanie kodu nad innymi częściami obiektu ciągu za pomocą RuntimeHelpers.OffsetToStringData. Tak więc, aby uzyskać nieprzetworzone bajty, po prostu stwórz wskaźnik do łańcucha i skopiuj potrzebną liczbę bajtów.

// 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;
}

Jak wskazał @CodesInChaos, wynik zależy od endianizmu maszyny. Ale autor pytania nie jest tym zainteresowany.


3
@Jan To prawda, ale długość łańcucha już podaje liczbę jednostek kodowych (nie punktów kodowych).
Tom Blodget

1
Dzięki za zwrócenie na to uwagi! Z MSDN: „ LengthWłaściwość [of String] zwraca liczbę Charobiektów w tym przypadku, a nie liczbę znaków Unicode.” Twój przykładowy kod jest zatem poprawny, jak napisano.
Jan Hettich

1
@ superupat „Typ znaków reprezentuje jednostkę kodu UTF-16, a typ ciągu reprezentuje sekwencję jednostek kodu UTF-16.” —_ Specyfikacja C # 5. Chociaż tak, nic nie stoi na przeszkodzie, aby nieprawidłowy ciąg Unicode:new String(new []{'\uD800', '\u0030'})
Tom Blodget

1
@TomBlodget: Co ciekawe, jeśli weźmie się instancje Globalization.SortKey, wyodrębnia KeyDatai pakuje powstałe bajty z każdego do String[dwa bajty na znak, najpierw MSB ], wywoływanie String.CompareOrdinalpowstałych ciągów będzie znacznie szybsze niż wywoływanie SortKey.Compareinstancji SortKeylub nawet wzywając memcmpte przypadki. Biorąc to pod uwagę, zastanawiam się, dlaczego KeyDatazwraca Byte[]raczej niż a String?
supercat

1
Niestety, właściwa odpowiedź, ale o lata za późno, nigdy nie uzyska tylu głosów, ile zaakceptowano. Z powodu TL; DR ludzie pomyślą, że przyjęta odpowiedź budzi lęk. copyenpastit i głosuj w górę.
Martin Capodici,

46

Na pierwszą część twojego pytania (jak zdobyć bajty) odpowiedzieli już inni: spójrz w System.Text.Encodingprzestrzeń nazw.

Odpowiem na twoje dodatkowe pytanie: dlaczego musisz wybrać kodowanie? Dlaczego nie możesz tego uzyskać z samej klasy strun?

Odpowiedź składa się z dwóch części.

Przede wszystkim bajty używane wewnętrznie przez klasę łańcuchową nie mają znaczenia , a gdy tylko zakładasz, że tak, prawdopodobnie wprowadzasz błąd.

Jeśli twój program znajduje się całkowicie w świecie .Net, nie musisz się w ogóle martwić o uzyskanie tablic bajtów dla ciągów, nawet jeśli wysyłasz dane przez sieć. Zamiast tego użyj serializacji .Net, aby martwić się przesyłaniem danych. Nie musisz się już martwić o rzeczywiste bajty: formatyzator serializacji robi to za Ciebie.

Z drugiej strony, co jeśli wysyłasz te bajty gdzieś, co nie jest gwarantowane, że pobierze dane z serializowanego strumienia .Net? W takim przypadku zdecydowanie musisz martwić się o kodowanie, ponieważ oczywiście ten zewnętrzny system ma znaczenie. Zatem znowu bajty wewnętrzne używane przez ciąg nie mają znaczenia: musisz wybrać kodowanie, abyś mógł wyraźnie powiedzieć o tym kodowaniu po stronie odbierającej, nawet jeśli jest to to samo kodowanie używane wewnętrznie przez .Net.

Rozumiem, że w tym przypadku wolisz używać rzeczywistych bajtów przechowywanych w pamięci zmiennej ciągowej, o ile to możliwe, z pomysłem, że może to zaoszczędzić trochę pracy podczas tworzenia strumienia bajtów. Jednak przekazuję wam to, że nie jest to po prostu ważne w porównaniu do upewnienia się, że dane wyjściowe są zrozumiane na drugim końcu, i do zagwarantowania, że trzeba jawnie kodować. Ponadto, jeśli naprawdę chcesz dopasować bajty wewnętrzne, możesz już po prostu wybrać Unicodekodowanie i uzyskać oszczędności wydajności.

Który doprowadza mnie do drugiej części ... wybranie Unicodekodowania jest mówienie .Net używać bajtów bazowych. Musisz wybrać to kodowanie, ponieważ gdy pojawi się nowy, unikatowy kod Unicode-Plus, środowisko wykonawcze .Net musi mieć swobodę korzystania z tego nowszego, lepszego modelu kodowania bez zepsucia programu. Ale na razie (i dająca się przewidzieć przyszłość) wybranie kodowania Unicode daje to, czego chcesz.

Ważne jest również, aby zrozumieć, że łańcuch musi zostać przepisany na drut, a to wymaga co najmniej tłumaczenia wzoru bitowego, nawet jeśli używasz pasującego kodowania . Komputer musi uwzględniać takie rzeczy, jak Big vs. Little Endian, kolejność bajtów w sieci, pakietowanie, informacje o sesji itp.


9
Istnieją obszary w .NET, w których musisz uzyskać tablice bajtów dla łańcuchów. Wiele klas .NET Cryptrography zawiera metody takie jak ComputeHash (), które akceptują tablicę bajtów lub strumień. Nie masz innego wyjścia, jak najpierw przekonwertować ciąg znaków na tablicę bajtów (wybierając kodowanie), a następnie opcjonalnie owinąć go w strumień. Jednak dopóki wybierzesz kodowanie (np. UTF8), nie będziesz miał z tym żadnych problemów.
Ash

44

Wystarczy wykazać, że dźwięk Mehrdrad za odpowiedź dzieła, jego podejście może nawet trwać do niesparowanych znaków zastępczych (z których wiele było wyrównane przed moją odpowiedź, ale z których wszyscy są jednakowo winni, na przykład System.Text.Encoding.UTF8.GetBytes, System.Text.Encoding.Unicode.GetBytes; te metody kodowania nie mogą utrzymywać wysoką surogat znaki d800na przykład, i te właśnie jedynie wymienić wysokie znaków zastępczych z wartości 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);
    }        
}

Wynik:

T 54
e 65
s 73
t 74
? d800
T 54
e 65
s 73
t 74

Spróbuj tego z System.Text.Encoding.UTF8.GetBytes lub System.Text.Encoding.Unicode.GetBytes , po prostu zastąpią one znaki o wysokiej wartości zastępczej wartością fffd

Za każdym razem, gdy pojawia się ruch w tym pytaniu, wciąż myślę o serializatorze (czy to z Microsoft, czy z komponentu innej firmy), który może utrzymywać ciągi znaków, nawet jeśli zawiera niesparowane znaki zastępcze; Przeglądam to od czasu do czasu: serializacja niesparowanej postaci zastępczej .NET . Nie sprawia to, że tracę sen, ale czasami denerwuje mnie to, że ktoś komentuje moją odpowiedź, że jest wadliwa, ale ich odpowiedzi są równie błędne, jeśli chodzi o niesparowane postacie zastępcze.

Cholera, Microsoft powinien był po prostu użyć System.Buffer.BlockCopyw swoim BinaryFormatter

谢谢!


3
Czy zastępcy nie muszą pojawiać się w parach, aby tworzyć prawidłowe punkty kodowe? W takim przypadku rozumiem, dlaczego dane zostałyby zniekształcone.
dtanders

1
@dtanders Tak, to też są moje myśli, muszą pojawiać się w parach, niesparowane znaki zastępcze zdarzają się, jeśli celowo położysz je na sznurku i sprawisz, że nie będą sparowane. Nie wiem, dlaczego inni deweloperzy wciąż próbują wymyślić, że powinniśmy zamiast tego stosować podejście świadome kodowania, ponieważ uważali, że podejście do serializacji ( moja odpowiedź , która była akceptowaną odpowiedzią przez ponad 3 lata) nie utrzymuje niesparowanych postać zastępcza nienaruszona. Ale zapomnieli sprawdzić, czy ich rozwiązania rozpoznające kodowanie nie zachowują też niesparowanej postaci zastępczej, ironia ツ
Michael Buen

Jeśli istnieje biblioteka do serializacji, która korzysta System.Buffer.BlockCopywewnętrznie, wszystkie argumenty popierające kodowanie będą dyskusyjne
Michael Buen

2
@MichaelBuen Wydaje mi się, że głównym problemem jest to, że pisze się dużymi, pogrubionymi literami, mówiąc, że coś nie ma znaczenia, zamiast mówić, że w ich przypadku nie ma to znaczenia. W rezultacie zachęcasz ludzi, którzy patrzą na twoją odpowiedź, do popełniania podstawowych błędów programistycznych, które spowodują frustrację innych w przyszłości. Niesparowane parametry zastępcze są niepoprawne w ciągu. Nie jest to tablica char, więc ma sens, że konwersja łańcucha na inny format spowodowałaby błąd FFFDtego znaku. Jeśli chcesz wykonywać ręczne operacje na łańcuchach, użyj char [] zgodnie z zaleceniami.
Trisped

2
@dtanders: A System.Stringjest niezmienną sekwencją Char; .NET zawsze zezwalał Stringna konstruowanie dowolnego obiektu Char[]i eksportowanie jego zawartości do Char[]zawierających te same wartości, nawet jeśli oryginał Char[]zawiera niesparowane odpowiedniki.
supercat

41

Spróbuj tego, o wiele mniej kodu:

System.Text.Encoding.UTF8.GetBytes("TEST String");

Następnie spróbuj tego System.Text.Encoding.UTF8.GetBytes("Árvíztűrő tükörfúrógép);i płacz! Będzie działać, ale System.Text.Encoding.UTF8.GetBytes("Árvíztűrő tükörfúrógép").Length != System.Text.Encoding.UTF8.GetBytes("Arvizturo tukorfurogep").Lengthjednocześnie"Árvíztűrő tükörfúrógép".Length == "Arvizturo tukorfurogep".Length
mg30rg

9
@ mg30rg: Jak myślisz, dlaczego twój przykład jest dziwny? Z pewnością w kodowaniu o zmiennej szerokości nie wszystkie znaki mają takie same długości bajtów. Co jest z tym nie tak?
Vlad

@Vlad Ważniejszym komentarzem tutaj jest jednak to, że jako zakodowane symbole Unicode (czyli jako bajty) znaki zawierające własne znaki diakrytyczne dadzą inny wynik niż znaki diakrytyczne podzielone na symbole modyfikujące dodane do znaku. Ale iirc istnieją metody w .net, aby je specyficznie oddzielić, aby umożliwić uzyskanie spójnej reprezentacji bajtów.
Nyerguds

25

Cóż, przeczytałem wszystkie odpowiedzi i dotyczyły one kodowania lub serializacji, która odrzuca niesparowane zastępcze.

Jest źle, gdy na przykład ciąg pochodzi z SQL Server, gdzie został zbudowany z tablicy bajtów przechowującej, na przykład hash hasła. Jeśli coś z niego usuniemy, będzie przechowywać niepoprawny skrót, a jeśli chcemy przechowywać go w formacie XML, chcemy pozostawić go nienaruszonego (ponieważ program piszący XML odrzuca wyjątek dla każdego niesparowanego znalezionego surogatu).

Więc używam kodowania bajtów tablic Base64 w takich przypadkach, ale hej, w Internecie jest tylko jedno rozwiązanie tego w C #, i ma błąd i jest tylko jeden sposób, więc naprawiłem błąd i odpisałem procedura. Oto przyszli pracownicy Google:

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);
}

Zamiast używać niestandardowej metody do konwersji tablicy bajtów na base64, wystarczyło użyć wbudowanego konwertera: Convert.ToBase64String (arr);
Makotosan,

@Makotosan dziękuję, ale użyłem Convert.ToBase64String(arr); do konwersji base64 byte[] (data) <-> string (serialized data to store in XML file). Jednak, aby uzyskać wstępną byte[] (data)Musiałem coś zrobić z Stringzawartej binarnych danych (jest to droga MSSQL wróciła mi go). SO powyższe funkcje są dla String (binary data) <-> byte[] (easy accessible binary data).
Gman,

23

Proszę również wyjaśnić, dlaczego należy wziąć pod uwagę kodowanie. Czy nie mogę po prostu pobrać bajtów, w których zapisano ciąg? Skąd ta zależność od kodowania? !!!

Ponieważ nie ma czegoś takiego jak „bajty ciągu”.

Ciąg (lub bardziej ogólnie tekst) składa się ze znaków: liter, cyfr i innych symboli. To wszystko. Komputery jednak nie wiedzą nic o postaciach; mogą obsługiwać tylko bajty. Dlatego jeśli chcesz przechowywać lub przesyłać tekst za pomocą komputera, musisz przekształcić znaki w bajty. Jak to robisz? Oto, gdzie pojawiają się kodowania.

Kodowanie jest niczym innym jak konwencją służącą do tłumaczenia znaków logicznych na bajty fizyczne. Najprostszym i najlepiej znanym kodowaniem jest ASCII i to wszystko, czego potrzebujesz, jeśli piszesz po angielsku. W przypadku innych języków potrzebne będą bardziej kompletne kodowania, ponieważ jest to jeden z najbezpieczniejszych obecnie wyborów Unicode.

Krótko mówiąc, próba „pobrania bajtów ciągu bez użycia kodowania” jest tak niemożliwa, jak „napisanie tekstu bez użycia języka”.

Nawiasem mówiąc, zdecydowanie polecam wam (i każdemu, jeśli o to chodzi) przeczytanie tej małej mądrości: Absolutne minimum Każdy programista Absolutnie, pozytywnie musi wiedzieć o Unicode i zestawach znaków (bez wymówek!)


2
Pozwólcie, że wyjaśnię: kodowanie zostało użyte do przetłumaczenia „hello world” na bajty fizyczne. Ponieważ ciąg jest przechowywany na moim komputerze, jestem pewien, że musi być przechowywany w bajtach. Chcę tylko uzyskać dostęp do tych bajtów, aby zapisać je na dysku lub z dowolnego innego powodu. Nie chcę interpretować tych bajtów. Ponieważ nie chcę interpretować tych bajtów, potrzeba kodowania w tym momencie jest równie niewłaściwa, jak wymaganie linii telefonicznej do wywołania printf.
Agnel Kurian

3
Ale znowu, nie ma koncepcji tłumaczenia tekstu na fizyczny bajtów, chyba że użyjesz kodowania. Jasne, kompilator przechowuje ciągi w jakiś sposób w pamięci - ale używa tylko wewnętrznego kodowania, o którym ty (lub ktokolwiek poza deweloperem kompilatora) nie wiesz. Cokolwiek zrobisz, potrzebujesz kodowania, aby uzyskać fizyczne bajty z ciągu.
Konamiman

@Agnel Kurian: Oczywiście prawdą jest, że ciąg znaków ma gdzieś kilka bajtów, które przechowują jego zawartość (UTF-16 afair). Ale jest dobry powód, aby uniemożliwić ci dostęp do niego: ciągi są niezmienne i jeśli możesz uzyskać wewnętrzną tablicę bajtów [], możesz ją również zmodyfikować. To łamie niezmienność, co jest istotne, ponieważ wiele ciągów może dzielić te same dane. Użycie kodowania UTF-16 do uzyskania ciągu prawdopodobnie po prostu skopiuje dane.
ollb

2
@Gnafoo, kopia bajtów zrobi.
Agnel Kurian

22

C # do konwersji tablicy stringna bytetablicę:

public static byte[] StrToByteArray(string str)
{
   System.Text.UTF8Encoding  encoding=new System.Text.UTF8Encoding();
   return encoding.GetBytes(str);
}

17
byte[] strToByteArray(string str)
{
    System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
    return enc.GetBytes(str);
}

Ale dlaczego warto brać pod uwagę kodowanie? Dlaczego nie mogę po prostu pobrać bajtów bez konieczności sprawdzania, jakie kodowanie jest używane? Nawet gdyby był wymagany, czy sam obiekt String nie powinien wiedzieć, jakie kodowanie jest używane, i po prostu zrzucić to, co jest w pamięci?
Agnel Kurian

5
To nie zawsze działa. Niektóre postacie specjalne mogą się zgubić przy użyciu takiej metody, którą znalazłem na własnej skórze.
JB King

17

Możesz użyć następującego kodu do konwersji między tablicą łańcuchów znaków i bajtów.

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);

VUP Ten rozwiązał mój problem (bajt [] ff = ASCIIEncoding.ASCII.GetBytes (barcodetxt.Text);)
r.hamd

16

Wraz z nadejściem Span<T>wersji C # 7.2 kanoniczną techniką przechwytywania reprezentacji pamięci w łańcuchu w zarządzanej tablicy bajtów jest:

byte[] bytes = "rubbish_\u9999_string".AsSpan().AsBytes().ToArray();

Konwersja powinna być początkowa, ponieważ oznacza to, że faktycznie interpretujesz dane, ale ze względu na kompletność:

string s;
unsafe
{
    fixed (char* f = &bytes.AsSpan().NonPortableCast<byte, char>().DangerousGetPinnableReference())
    {
        s = new string(f);
    }
}

Imiona NonPortableCasti nazwiska oraz DangerousGetPinnableReferenceargument, że prawdopodobnie nie powinieneś tego robić.

Pamiętaj, że praca z Span<T>wymaga instalacji pakietu System.Memory NuGet .

Niezależnie od tego, aktualne oryginalne pytania i komentarze uzupełniające sugerują, że pamięć podstawowa nie jest „interpretowana” (co, jak zakładam, oznacza, że ​​nie jest modyfikowana ani czytana poza koniecznością zapisania jej w niezmienionej postaci), co wskazuje, że niektóre implementacje Streamklasy powinny być używane zamiast wnioskowania o danych jako ciągach.


13

Nie jestem pewien, ale myślę, że ciąg przechowuje informacje jako tablicę znaków, co jest nieefektywne w bajtach. W szczególności definicja Char to „Reprezentuje znak Unicode”.

weź przykładową próbkę:

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));
}

Zwróć uwagę, że odpowiedź Unicode wynosi 14 bajtów w obu przypadkach, podczas gdy odpowiedź UTF-8 ma tylko 9 bajtów dla pierwszego i tylko 7 dla drugiego.

Więc jeśli chcesz tylko bajtów używanych przez ciąg, po prostu użyj Encoding.Unicode, ale będzie to nieefektywne w przypadku przestrzeni dyskowej.


10

Kluczową kwestią jest to, że glif w ciągu zajmuje 32 bity (16 bitów w kodzie znaków), ale bajt ma tylko 8 bitów do zaoszczędzenia. Mapowanie jeden do jednego nie istnieje, chyba że ograniczysz się do ciągów zawierających tylko znaki ASCII. System.Text.Encoding ma wiele sposobów mapowania ciągu na bajt [], musisz wybrać taki, który pozwala uniknąć utraty informacji i który jest łatwy w użyciu dla twojego klienta, gdy potrzebuje on zamapować bajt [] z powrotem na ciąg .

Utf8 jest popularnym kodowaniem, jest kompaktowy i nie jest stratny.


3
UTF-8 jest kompaktowy tylko wtedy, gdy większość twoich znaków jest w zestawie znaków angielskich (ASCII). Jeśli miałbyś długi ciąg znaków chińskich, UTF-16 byłby bardziej kompaktowym kodowaniem niż UTF-8 dla tego ciągu. Wynika to z faktu, że UTF-8 używa jednego bajtu do kodowania ASCII, a 3 (lub 4) inaczej.
Joel Mueller

7
Prawdziwe. Ale skąd możesz wiedzieć o kodowaniu, jeśli znasz obsługę chińskiego tekstu?
Hans Passant

9

Posługiwać się:

    string text = "string";
    byte[] array = System.Text.Encoding.UTF8.GetBytes(text);

Wynik to:

[0] = 115
[1] = 116
[2] = 114
[3] = 105
[4] = 110
[5] = 103

OP konkretnie prosi NIE podawać kodowania ... "bez ręcznego określania konkretnego kodowania"
Ferdz

8

Najszybsza droga

public static byte[] GetBytes(string text)
{
    return System.Text.ASCIIEncoding.UTF8.GetBytes(text);
}

EDYCJA jak skomentował Makotosan, jest to teraz najlepszy sposób:

Encoding.UTF8.GetBytes(text)

8
Kodowanie ASCIIE ..... nie jest potrzebne. Preferowane jest po prostu użycie Encoding.UTF8.GetBytes (tekst).
Makotosan

8

Jak przekonwertować ciąg na bajt [] w .NET (C #) bez ręcznego określania konkretnego kodowania?

Ciąg w .NET reprezentuje tekst jako ciąg znaków UTF-16 jednostek kodowych, tak bajty są kodowane w pamięci w UTF-16 już.

Odpowiedź Mehrdada

Możesz użyć odpowiedzi Mehrdada , ale w rzeczywistości używa ona kodowania, ponieważ znaki to UTF-16. Wywołuje ToCharArray, który patrząc na źródło tworzy a char[]i bezpośrednio kopiuje do niego pamięć. Następnie kopiuje dane do przydzielonej tablicy bajtów. Więc pod maską kopiuje dwa razy leżące u podstaw bajty i przydziela tablicę znaków, która nie jest używana po wywołaniu.

Odpowiedź Toma Blodgeta

Odpowiedź Toma Blodgeta jest o 20-30% szybsza niż Mehrdad, ponieważ pomija etap pośredni przydzielania tablicy znaków i kopiowania do niej bajtów, ale wymaga kompilacji z /unsafeopcją. Jeśli absolutnie nie chcesz używać kodowania, myślę, że to jest właściwa droga. Jeśli umieścisz login szyfrowania w fixedbloku, nie musisz nawet przydzielać osobnej tablicy bajtów i kopiować do niej bajty.

Ponadto, dlaczego należy wziąć pod uwagę kodowanie? Czy nie mogę po prostu pobrać bajtów, w których zapisano ciąg? Dlaczego istnieje zależność od kodowania znaków?

Ponieważ to jest właściwy sposób, aby to zrobić. stringjest abstrakcją.

Korzystanie z kodowania może sprawić kłopoty, jeśli masz „ciągi znaków” z nieprawidłowymi znakami, ale to nie powinno się zdarzyć. Jeśli dostajesz dane do łańcucha z nieprawidłowymi znakami, robisz to źle. Najpierw prawdopodobnie powinieneś użyć tablicy bajtów lub kodowania Base64.

Jeśli go użyjesz System.Text.Encoding.Unicode, Twój kod będzie bardziej odporny. Nie musisz się martwić o endianizm systemu, na którym będzie działał Twój kod. Nie musisz się martwić, jeśli następna wersja CLR będzie używać innego wewnętrznego kodowania znaków.

Myślę, że pytanie nie dotyczy tego, dlaczego chcesz się martwić kodowaniem, ale dlaczego chcesz go zignorować i użyć czegoś innego. Kodowanie ma reprezentować abstrakcję ciągu w sekwencji bajtów. System.Text.Encoding.Unicodeda ci trochę kodowania kolejności bajtów endian i będzie działać tak samo na każdym systemie, teraz iw przyszłości.


W rzeczywistości ciąg znaków w języku C # NIE jest ograniczony tylko do UTF-16. Prawdą jest, że zawiera wektor 16-bitowych jednostek kodu, ale te 16-bitowe jednostki kodu nie są ograniczone do prawidłowego UTF-16. Ale ponieważ są one 16-bitowe, potrzebujesz kodowania (kolejności bajtów), aby przekonwertować je na 8-bitowe. Ciąg może następnie przechowywać dane inne niż Unicode, w tym kod binarny (np. Obraz bitmapowy). Jest interpretowany jako UTF-16 tylko w modułach we / wy i formatów tekstu, które dokonują takiej interpretacji.
verdy_p

Tak więc w ciągu C # możesz bezpiecznie przechowywać jednostkę kodową, taką jak 0xFFFF lub 0xFFFE, nawet jeśli nie są one znakami w UTF-16, i możesz przechowywać izolowane 0xD800, po którym nie następuje jednostka kodowa w 0xDC00..0xDFFF (tj. niesparowane parametry zastępcze, które są nieważne w UTF-16). Ta sama uwaga dotyczy ciągów w JavaScript / ECMAscript i Java.
verdy_p

Kiedy używasz „GetBytes”, oczywiście nie podajesz kodowania, ale zakładasz kolejność bajtów, aby uzyskać dwa bajty ze specyfikacją dla każdej jednostki kodu przechowywanej lokalnie w ciągu. Kiedy budujesz nowy ciąg z bajtów, potrzebujesz również konwertera, niekoniecznie UTF-8 na UTF-16, możesz wstawić dodatkowe 0 w bajcie wysokim lub spakować dwa bajty (w pierwszej kolejności MSB lub pierwszej kolejności LSB) w ta sama 16-bitowa jednostka kodu. Łańcuchy są wówczas zwartą formą dla tablic 16-bitowych liczb całkowitych. Relacja z „znakami” to kolejny problem, w języku C # nie są to typy rzeczywiste, ponieważ nadal są reprezentowane jako ciągi znaków
verdy_p

7

Najbliższym podejściem do pytania PO jest pytanie Toma Blodgeta, które faktycznie wchodzi do obiektu i wyodrębnia bajty. Mówię najbliżej, ponieważ zależy to od implementacji obiektu String.

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

Jasne, ale tutaj pojawia się podstawowy błąd w pytaniu. Łańcuch jest obiektem, który może mieć interesującą strukturę danych. Wiemy już, że tak, ponieważ umożliwia przechowywanie niesparowanych parametrów zastępczych. Może przechowywać długość. Może utrzymywać wskaźnik do każdego z „sparowanych” zastępców, umożliwiając szybkie liczenie. Itd. Wszystkie te dodatkowe bajty nie są częścią danych znakowych.

To, czego chcesz, to bajty każdego znaku w tablicy. I tu właśnie pojawia się „kodowanie”. Domyślnie otrzymasz UTF-16LE. Jeśli nie zależy ci na samych bajtach oprócz podróży w obie strony, możesz wybrać dowolne kodowanie, w tym „domyślne”, i przekonwertować je później (zakładając takie same parametry, jak domyślne kodowanie, punkty kodowe, poprawki błędów , dozwolone rzeczy, takie jak niesparowane zastępcze itp.

Ale dlaczego pozostawić „kodowanie” magii? Dlaczego nie podać kodowania, aby wiedzieć, jakie bajty otrzymasz?

"Why is there a dependency on character encodings?"

Kodowanie (w tym kontekście) oznacza po prostu bajty reprezentujące łańcuch. Nie bajty obiektu ciągu. Chcieliście bajtów, w których zapisano ciąg znaków - tutaj naiwnie zadano pytanie. Chcieliście bajtów łańcucha w ciągłej tablicy, która reprezentuje łańcuch, a nie wszystkich innych danych binarnych, które może zawierać obiekt łańcucha.

Co oznacza, że ​​sposób przechowywania łańcucha nie ma znaczenia. Chcesz ciąg „Zakodowany” w bajtach w tablicy bajtów.

Podoba mi się odpowiedź Toma Blogeta, ponieważ poprowadził cię w kierunku „bajtów obiektu sznurkowego”. Jest to jednak zależne od implementacji, a ponieważ zagląda do wewnętrznych elementów, odtworzenie kopii łańcucha może być trudne.

Odpowiedź Mehrdada jest błędna, ponieważ wprowadza w błąd na poziomie koncepcyjnym. Nadal masz listę bajtów, zakodowanych. Jego szczególne rozwiązanie pozwala na zachowanie niesparowanych parametrów zastępczych - zależy to od implementacji. Jego szczególne rozwiązanie nie wygenerowałoby bajtów łańcucha dokładnie, gdyby GetBytesdomyślnie zwrócił ciąg w UTF-8.


Zmieniłem zdanie na ten temat (rozwiązanie Mehrdada) - nie pobiera bajtów ciągu; raczej pobiera bajty tablicy znaków, która została utworzona z ciągu. Niezależnie od kodowania typ danych char w c # ma stały rozmiar. Pozwala to na utworzenie tablicy bajtów o stałej długości i pozwala na odtworzenie tablicy znaków na podstawie rozmiaru tablicy bajtów. Więc jeśli kodowanie to UTF-8, ale każdy znak ma 6 bajtów, aby pomieścić największą wartość utf8, nadal by działał. Tak więc - kodowanie postaci nie ma znaczenia.

Zastosowano jednak konwersję - każda postać została umieszczona w polu o stałym rozmiarze (typ znaku c #). Jednak to, czym jest ta reprezentacja, nie ma znaczenia, co jest technicznie odpowiedzią na PO. Więc - jeśli i tak zamierzasz dokonać konwersji ... Dlaczego nie „zakodować”?


Znaki te nieobsługiwane przez UTF-8 lub UTF-16, a nawet UTF-32, na przykład: 񩱠& (Char) 55906& (Char) 55655. Więc możesz się mylić, a odpowiedź Mehrdada to bezpieczna konwersja bez zastanawiania się, jakiego rodzaju kodowania się używa.
Mojtaba Rezaeian

Raymon, znaki są już reprezentowane przez pewną wartość Unicode - a wszystkie wartości Unicode mogą być reprezentowane przez wszystkie utf. Czy istnieje dłuższe wyjaśnienie tego, o czym mówisz? W jakim kodowaniu znaków istnieją te dwie wartości (lub 3 ...)?
Gerard ONeill

Są to nieprawidłowe znaki, które nie są obsługiwane przez żaden zakres kodowania. Nie oznacza to, że są w 100% bezużyteczne. Kod, który konwertuje dowolny typ łańcucha na ekwiwalent tablicy bajtów niezależnie od kodowania, wcale nie jest złym rozwiązaniem i ma swoje własne zastosowania w pożądanych sytuacjach.
Mojtaba Rezaeian

1
Ok, więc myślę, że nie rozumiesz problemu. Wiemy, że jest to tablica zgodna z Unicode - w rzeczywistości, ponieważ jest to .net, wiemy, że jest to UTF-16. Więc te postacie nie będą tam istnieć. Nie przeczytałeś też w pełni mojego komentarza na temat zmiany wewnętrznych reprezentacji. Łańcuch jest obiektem, a nie zakodowaną tablicą bajtów. Więc nie zgodzę się z twoim ostatnim stwierdzeniem. Chcesz, aby kod konwertował wszystkie ciągi Unicode na dowolne kodowanie UTF. Robi to, co chcesz, poprawnie.
Gerard ONeill

Obiekty są sekwencją danych pierwotnie sekwencją bitów, które opisują obiekt w jego obecnym stanie. Tak więc wszystkie dane w językach programowania można konwertować na tablicę bajtów (każdy bajt definiuje 8 bitów), ponieważ może być konieczne zachowanie pewnego stanu dowolnego obiektu w pamięci. Możesz zapisać i przechowywać sekwencję bajtów w pliku lub pamięci i rzutować ją jako liczbę całkowitą, bigint, obraz, ciąg Ascii, ciąg UTF-8, ciąg zaszyfrowany lub własny zdefiniowany typ danych po odczytaniu go z dysku. Nie można więc powiedzieć, że obiekty są czymś innym niż sekwencja bajtów.
Mojtaba Rezaeian

6

Możesz użyć następującego kodu, aby przekonwertować plik stringna byte array.NET

string s_unicode = "abcéabc";
byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(s_unicode);

3

Jeśli naprawdę chcesz kopii bazowych bajtów ciągu, możesz użyć funkcji takiej jak następująca. Nie powinieneś jednak czytać dalej, aby dowiedzieć się, dlaczego.

[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;
}

Ta funkcja dość szybko da ci kopię bajtów leżącą u podstaw łańcucha. Otrzymasz te bajty w dowolny sposób, w jaki są kodowane w twoim systemie. To kodowanie jest prawie na pewno UTF-16LE, ale jest to szczegół implementacji, o który nie powinieneś się martwić.

Dzwonienie byłoby bezpieczniejsze, prostsze i bardziej niezawodne ,

System.Text.Encoding.Unicode.GetBytes()

Najprawdopodobniej da to ten sam wynik, łatwiej jest pisać, a bajty zawsze będą w obie strony z wywołaniem

System.Text.Encoding.Unicode.GetString()

3

Oto moja niebezpieczne wdrożenie Stringdo Byte[]nawrócenia:

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;
}

Jest o wiele szybszy niż akceptowany anwser, nawet jeśli nie jest tak elegancki, jak to jest. Oto moje testy porównawcze stopera z ponad 10000000 iteracji:

[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

Aby go użyć, musisz zaznaczyć „Zezwól na niebezpieczny kod” we właściwościach kompilacji projektu. Zgodnie z .NET Framework 3.5, ta metoda może być również używana jako rozszerzenie String:

public static unsafe class StringExtensions
{
    public static Byte[] ToByteArray(this String s)
    {
        // Method Code
    }
}

Czy wartość RuntimeHelpers.OffsetToStringDatawielokrotności 8 w Itanium wersjach .NET? Ponieważ w przeciwnym razie to się nie powiedzie z powodu nieprzystosowanych odczytów.
Jon Hanna

czy nie byłoby łatwiej przywołać memcpy? stackoverflow.com/a/27124232/659190
Jodrell

2

Po prostu użyj tego:

byte[] myByte= System.Text.ASCIIEncoding.Default.GetBytes(myString);

2
... i stracisz wszystkie postacie z skokiem wyższym niż 127. W moim ojczystym języku napisanie „Árvíztűrő tükörfúrógép.” jest całkowicie poprawne. System.Text.ASCIIEncoding.Default.GetBytes("Árvíztűrő tükörfúrógép.").ToString();zwróci "Árvizturo tukörfurogép."utracone informacje, których nie można odzyskać. (I nie wspomniałem jeszcze o językach azjatyckich, w których stracisz wszystkie znaki).
mg30rg

2

Ciąg może zostać przekonwertowany na tablicę bajtów na kilka różnych sposobów, z powodu następującego faktu: .NET obsługuje Unicode, a Unicode standaryzuje kilka kodowań różnic nazywanych UTF. Mają różne długości reprezentacji bajtów, ale są równoważne w tym sensie, że gdy łańcuch jest kodowany, może być kodowany z powrotem do łańcucha, ale jeśli łańcuch jest kodowany za pomocą jednego UTF i dekodowany przy założeniu innego UTF, jeśli można go wkręcić w górę.

Ponadto .NET obsługuje kodowanie inne niż Unicode, ale nie są one poprawne w ogólnym przypadku (będą ważne tylko wtedy, gdy w rzeczywistym ciągu znaków, takim jak ASCII, zostanie zastosowany ograniczony podzbiór punktu kodowego Unicode). Wewnętrznie .NET obsługuje UTF-16, ale do reprezentacji strumienia zwykle używa się UTF-8. Jest to również de facto standard dla Internetu.

Nic dziwnego, że serializacja łańcucha na tablicę bajtów i deserializacja jest obsługiwana przez klasę System.Text.Encoding, która jest klasą abstrakcyjną; jego klasy pochodne obsługują konkretne kodowanie: ASCIIEncodingi cztery UTF ( System.Text.UnicodeEncodingobsługuje UTF-16)

Sprawdź ten link.

Do serializacji do tablicy bajtów za pomocą System.Text.Encoding.GetBytes. Do operacji odwrotnej użyj System.Text.Encoding.GetChars. Ta funkcja zwraca tablicę znaków, więc aby uzyskać ciąg, użyj konstruktora ciągów System.String(char[]).
Odwołaj się do tej strony.

Przykład:

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

To zależy od tego, dla których bajtów chcesz

Jest tak, ponieważ, jak trafnie powiedział Tyler : „Ciągi nie są czystymi danymi. Mają także informacje ”. W takim przypadku informacja jest kodowaniem przyjętym podczas tworzenia łańcucha.

Zakładając, że masz dane binarne (zamiast tekstu) zapisane w ciągu

Jest to oparte na komentarzu OP do jego własnego pytania i jest to prawidłowe pytanie, jeśli rozumiem wskazówki OP dotyczące przypadku użycia.

Przechowywanie danych binarnych w ciągach jest prawdopodobnie niewłaściwym podejściem ze względu na przyjęte wyżej kodowanie! Jakikolwiek program lub biblioteka przechowywał te dane binarne w string(zamiast byte[]tablicy, która byłaby bardziej odpowiednia), już przegrał bitwę przed jej rozpoczęciem. Jeśli wysyłają bajty do Ciebie w żądaniu / odpowiedzi REST lub czegokolwiek, co musi przesyłać ciągi, Base64 byłoby właściwym podejściem.

Jeśli masz ciąg tekstowy z nieznanym kodowaniem

Wszyscy inni odpowiedzieli niepoprawnie na to nieprawidłowe pytanie.

Jeśli ciąg znaków wygląda dobrze w obecnej postaci, po prostu wybierz kodowanie (najlepiej zaczynające się od UTF), użyj odpowiedniej System.Text.Encoding.???.GetBytes()funkcji i powiedz komuś, kto podaje bajty, które kodowanie wybrałeś.


2

Na pytanie, co zamierzasz zrobić z bajtami, odpowiedziałeś :

Zaszyfruję to. Mogę go zaszyfrować bez konwersji, ale nadal chciałbym wiedzieć, dlaczego tutaj kodowanie jest odtwarzane. Daj mi tylko bajty, co mówię.

Niezależnie od tego, czy zamierzasz wysłać te zaszyfrowane dane przez sieć, załadować je później z powrotem do pamięci, czy też przesłać do innego procesu, najwyraźniej zamierzasz je odszyfrować w pewnym momencie. W takim przypadku odpowiedź brzmi: definiujesz protokół komunikacyjny. Protokół komunikacyjny nie powinien być definiowany w kategoriach szczegółów implementacyjnych języka programowania i powiązanego z nim środowiska wykonawczego. Istnieje kilka powodów:

  • Może być konieczne komunikowanie się z procesem zaimplementowanym w innym języku lub środowisku wykonawczym. (Może to obejmować na przykład serwer działający na innym komputerze lub wysyłanie ciągu do klienta przeglądarki JavaScript).
  • Program może zostać w przyszłości ponownie wdrożony w innym języku lub środowisku wykonawczym.
  • Implementacja .NET może zmienić wewnętrzną reprezentację ciągów. Może ci się wydawać, że brzmi to zbyt daleko, ale tak naprawdę stało się to w Javie 9, aby zmniejszyć zużycie pamięci. Nie ma powodu .NET nie mógł pójść w jego ślady. Skeet sugeruje, że UTF-16 prawdopodobnie nie jest dzisiaj optymalny, co powoduje wzrost liczby emoji i innych bloków Unicode, które również wymagają więcej niż 2 bajtów do reprezentowania, zwiększając prawdopodobieństwo, że wewnętrzna reprezentacja może się zmienić w przyszłości.

Aby komunikować się (w przyszłości z całkowicie innym procesem lub z tym samym programem), musisz ściśle zdefiniować swój protokół, aby zminimalizować trudność z jego obsługą lub przypadkowym tworzeniem błędów. W zależności od wewnętrznej reprezentacji .NET nie jest to ścisła, jasna, a nawet gwarantowana spójna definicja. Standardowe kodowanie to ścisła definicja, która nie zawiedzie Cię w przyszłości.

Innymi słowy, nie można spełnić wymagań dotyczących spójności bez podania kodowania.

Z pewnością możesz zdecydować się na użycie UTF-16 bezpośrednio, jeśli okaże się, że proces działa znacznie lepiej, ponieważ .NET używa go wewnętrznie lub z dowolnego innego powodu, ale musisz wybrać to kodowanie jawnie i wykonać te konwersje jawnie w kodzie, a nie w zależności na wewnętrznej implementacji .NET.

Wybierz kodowanie i użyj go:

using System.Text;

// ...

Encoding.Unicode.GetBytes("abc"); # UTF-16 little endian
Encoding.UTF8.GetBytes("abc")

Jak widać, użycie wbudowanych obiektów kodujących jest w rzeczywistości mniejszym kodem niż zaimplementowanie własnych metod odczytu / zapisu.


1

Dwie drogi:

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();
}

I,

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;
}

Zazwyczaj używam dolnej części częściej niż górnej, nie testowałem ich pod kątem szybkości.


4
Co ze znakami wielobajtowymi?
Agnel Kurian

c.ToByte () jest prywatny: S
Khodor

@AgnelKurian Msdn mówi: „Ta metoda zwraca wartość bajtu bez znaku reprezentującą kod numeryczny przekazanego do niej obiektu Char. W .NET Framework obiekt Char jest wartością 16-bitową. Oznacza to, że metoda nadaje się do zwracania kody numeryczne znaków w zakresie znaków ASCII lub w kontrolkach Unicode C0 i Basic Latin oraz kontrolkach C1 i zakresach suplementu Latin-1, od U + 0000 do U + 00FF. ”
mg30rg

1
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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.