Làm cách nào để có được biểu diễn byte nhất quán của các chuỗi trong C # mà không chỉ định mã hóa theo cách thủ công?


2190

Làm cách nào để chuyển đổi a stringthành byte[].NET (C #) mà không chỉ định thủ công một mã hóa cụ thể?

Tôi sẽ mã hóa chuỗi. Tôi có thể mã hóa nó mà không cần chuyển đổi, nhưng tôi vẫn muốn biết tại sao mã hóa lại xuất hiện ở đây.

Ngoài ra, tại sao mã hóa thậm chí nên được xem xét? Tôi không thể đơn giản nhận được byte nào mà chuỗi đã được lưu trữ? Tại sao có sự phụ thuộc vào mã hóa ký tự?


23
Mỗi chuỗi được lưu trữ dưới dạng một mảng byte phải không? Tại sao tôi không thể có những byte đó?
Agnel Kurian

135
Mã hóa những gì ánh xạ các ký tự đến các byte. Ví dụ, trong ASCII, chữ 'A' ánh xạ tới số 65. Trong một mã hóa khác, nó có thể không giống nhau. Tuy nhiên, cách tiếp cận cấp cao đối với các chuỗi được lấy trong khung .NET làm cho điều này phần lớn không liên quan, mặc dù (ngoại trừ trong trường hợp này).
Lucas Jones

20
Để chơi người ủng hộ của quỷ: Nếu bạn muốn lấy các byte của chuỗi trong bộ nhớ (vì .NET sử dụng chúng) và thao tác chúng bằng cách nào đó (ví dụ CRC32), và KHÔNG BAO GIỜ muốn giải mã nó trở lại chuỗi ban đầu ... nó không thẳng thắn tại sao bạn quan tâm đến mã hóa hoặc cách bạn chọn sử dụng cái nào.
Greg

78
Không có ai ngạc nhiên khi đưa ra liên kết này: joelonsoftware.com/articles/Unicode.html
Bevan

28
Một char không phải là một byte và một byte không phải là một char. Một char là cả một chìa khóa vào một bảng phông chữ và một truyền thống từ vựng. Một chuỗi là một chuỗi các ký tự. (Một từ, đoạn văn, câu và tiêu đề cũng có truyền thống từ vựng riêng để chứng minh các định nghĩa loại riêng của họ - nhưng tôi lạc đề). Giống như số nguyên, số dấu phẩy động và mọi thứ khác, ký tự được mã hóa thành byte. Đã có lúc mã hóa đơn giản thành một: ASCII. Tuy nhiên, để phù hợp với tất cả các ký hiệu của con người, 256 hoán vị của một byte là không đủ và mã hóa được đưa ra để sử dụng có chọn lọc nhiều byte hơn.
George

Câu trả lời:


1855

Trái với câu trả lời ở đây, bạn KHÔNG cần lo lắng về việc mã hóa nếu các byte không cần phải được giải thích!

Giống như bạn đã đề cập, mục tiêu của bạn chỉ đơn giản là "lấy chuỗi byte nào đã được lưu trữ" .
(Và, tất nhiên, để có thể xây dựng lại chuỗi từ các byte.)

Đối với những mục tiêu đó, tôi thực sự không hiểu tại sao mọi người cứ nói với bạn rằng bạn cần mã hóa. Bạn chắc chắn KHÔNG cần phải lo lắng về mã hóa cho việc này.

Chỉ cần làm điều này thay vào đó:

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

Miễn là chương trình của bạn (hoặc các chương trình khác) không cố gắng diễn giải các byte bằng cách nào đó, điều mà rõ ràng là bạn không đề cập đến việc bạn dự định làm, thì không có sai với cách tiếp cận này! Lo lắng về mã hóa chỉ làm cho cuộc sống của bạn phức tạp hơn mà không có lý do thực sự.

Lợi ích bổ sung cho phương pháp này:

Sẽ không có vấn đề gì nếu chuỗi chứa các ký tự không hợp lệ, bởi vì bạn vẫn có thể lấy dữ liệu và xây dựng lại chuỗi gốc!

Nó sẽ được mã hóa và giải mã giống nhau, bởi vì bạn chỉ đang nhìn vào các byte .

Tuy nhiên, nếu bạn đã sử dụng một mã hóa cụ thể, nó sẽ gây rắc rối cho việc mã hóa / giải mã các ký tự không hợp lệ.


247
Điều xấu xí về cái này là, cái đó GetStringGetBytescần phải được thực thi trên một hệ thống có cùng độ bền để hoạt động. Vì vậy, bạn không thể sử dụng điều này để có được các byte bạn muốn biến thành một chuỗi ở nơi khác. Vì vậy, tôi có một thời gian khó khăn để đưa ra một tình huống mà tôi muốn sử dụng nó.
CodeInChaos

72
@CodeInChaos: Như tôi đã nói, toàn bộ vấn đề này là nếu bạn muốn sử dụng nó trên cùng một loại hệ thống, với cùng một bộ chức năng. Nếu không, thì bạn không nên sử dụng nó.
dùng541686

193
-1 Tôi đảm bảo rằng ai đó (người không hiểu byte và ký tự) sẽ muốn chuyển đổi chuỗi của họ thành một mảng byte, họ sẽ google nó và đọc câu trả lời này, và họ sẽ làm sai, vì hầu như tất cả trường hợp, mã hóa IS có liên quan.
artbristol

401
@artbristol: Nếu họ không cảm thấy phiền khi đọc câu trả lời (hoặc các câu trả lời khác ...), thì tôi xin lỗi, vậy thì không có cách nào tốt hơn để tôi giao tiếp với họ. Tôi thường chọn cách trả lời OP thay vì cố gắng đoán người khác có thể làm gì với câu trả lời của tôi - OP có quyền biết và chỉ vì ai đó có thể lạm dụng một con dao không có nghĩa là chúng ta cần giấu tất cả các con dao trên thế giới cho chính chúng ta Mặc dù nếu bạn không đồng ý điều đó cũng tốt.
dùng541686

185
Câu trả lời này sai ở rất nhiều cấp độ nhưng quan trọng nhất là do nó giải mã "bạn không cần phải lo lắng về mã hóa!". Hai phương thức, GetBytes và GetString là không cần thiết vì chúng chỉ đơn thuần là triển khai lại những gì Encoding.Unicode.GetBytes () và Encoding.Unicode.GetString () đã làm. Câu lệnh "Miễn là chương trình của bạn (hoặc các chương trình khác) không cố gắng diễn giải các byte" về cơ bản là thiếu sót vì chúng có nghĩa là các byte nên được hiểu là Unicode.
David

1108

Nó phụ thuộc vào mã hóa chuỗi của bạn ( ASCII , UTF-8 , ...).

Ví dụ:

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

Một mẫu nhỏ tại sao mã hóa lại quan trọng:

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 đơn giản là không được trang bị để đối phó với các ký tự đặc biệt.

Trong nội bộ, .NET framework sử dụng UTF-16 để biểu diễn các chuỗi, vì vậy nếu bạn chỉ muốn lấy các byte chính xác mà .NET sử dụng, hãy sử dụng System.Text.Encoding.Unicode.GetBytes (...).

Xem Mã hóa ký tự trong .NET Framework (MSDN) để biết thêm thông tin.


14
Nhưng, tại sao mã hóa nên được xem xét? Tại sao tôi không thể đơn giản nhận được các byte mà không cần phải xem mã hóa nào đang được sử dụng? Ngay cả khi nó được yêu cầu, không nên tự đối tượng String biết mã hóa nào đang được sử dụng và chỉ cần bỏ đi những gì trong bộ nhớ?
Agnel Kurian

57
Một chuỗi .NET luôn được mã hóa dưới dạng Unicode. Vì vậy, sử dụng System.Text.Encoding.Unicode.GetBytes (); để lấy tập hợp byte mà .NET sẽ sử dụng để biểu diễn các ký tự. Tuy nhiên tại sao bạn muốn điều đó? Tôi khuyên dùng UTF-8 đặc biệt là khi hầu hết các ký tự nằm trong bộ Latin phía tây.
AnthonyWJones

8
Ngoài ra: các byte chính xác được sử dụng bên trong chuỗi không thành vấn đề nếu hệ thống truy xuất chúng không xử lý mã hóa đó hoặc xử lý nó như mã hóa sai. Nếu tất cả nằm trong .Net, tại sao lại chuyển đổi thành một mảng byte. Mặt khác, tốt hơn là nên rõ ràng với mã hóa của bạn
Joel Coehoorn

11
@Joel, hãy cẩn thận với System.Text.Encoding.Default vì nó có thể khác nhau trên mỗi máy mà nó đang chạy. Đó là lý do tại sao nên luôn chỉ định mã hóa, chẳng hạn như UTF-8.
Tro

25
Bạn không cần mã hóa trừ khi bạn (hoặc ai đó) thực sự có ý định giải thích dữ liệu, thay vì coi đó là một "khối byte" chung chung. Đối với những thứ như nén, mã hóa, v.v., lo lắng về mã hóa là vô nghĩa. Xem câu trả lời của tôi để biết cách làm điều này mà không phải lo lắng về mã hóa. (Tôi có thể đã đưa ra -1 khi nói rằng bạn cần lo lắng về mã hóa khi bạn không, nhưng tôi không cảm thấy đặc biệt có ý nghĩa gì hôm nay .: P)
user541686

285

Câu trả lời được chấp nhận là rất, rất phức tạp. Sử dụng các lớp .NET đi kèm cho việc này:

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

Đừng phát minh lại bánh xe nếu bạn không phải ...


14
Trong trường hợp câu trả lời được chấp nhận bị thay đổi, vì mục đích thu âm, đó là câu trả lời của Mehrdad tại thời điểm hiện tại. Hy vọng OP sẽ xem xét lại điều này và chấp nhận một giải pháp tốt hơn.
Thomas Eding

7
tốt về nguyên tắc nhưng, mã hóa phải System.Text.Encoding.Unicodetương đương với câu trả lời của Mehrdad.
Jodrell

5
Câu hỏi đã được chỉnh sửa hàng triệu lần kể từ câu trả lời ban đầu, vì vậy, có thể câu trả lời của tôi hơi lỗi thời. Tôi chưa bao giờ có ý định đưa ra một ngoại lệ tương đương với câu trả lời của Mehrdad, nhưng đưa ra một cách hợp lý để làm điều đó. Nhưng, bạn có thể đúng. Tuy nhiên, cụm từ "lấy chuỗi byte nào đã được lưu trữ" trong câu hỏi ban đầu là rất không chính xác. Lưu trữ, ở đâu? Trong trí nhớ? Trên đĩa? Nếu trong bộ nhớ, System.Text.Encoding.Unicode.GetBytescó lẽ sẽ chính xác hơn.
Erik A. Brandstadmoen

7
@AMissico, đề xuất của bạn là lỗi, trừ khi bạn chắc chắn chuỗi của bạn tương thích với mã hóa mặc định của hệ thống (chuỗi chỉ chứa ký tự ASCII trong bộ ký tự kế thừa mặc định của hệ thống). Nhưng không nơi nào OP nói rằng.
Frédéric

5
@AMissico Nó có thể khiến chương trình cho kết quả khác nhau trên các hệ thống khác nhau . Đó không bao giờ là một điều tốt. Ngay cả khi đó là để tạo ra một hàm băm hoặc thứ gì đó (tôi cho rằng đó là ý nghĩa của OP với 'mã hóa'), thì cùng một chuỗi vẫn luôn cung cấp cùng một hàm băm.
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
Bạn có thể sử dụng cùng một thể hiện BinaryFormatter cho tất cả các hoạt động đó
Joel Coehoorn

3
Rất thú vị. Rõ ràng nó sẽ giảm bất kỳ ký tự Unicode thay thế cao nào. Xem tài liệu trên [BinaryFormatter ]

95

Bạn cần đưa mã hóa vào tài khoản, vì 1 ký tự có thể được biểu thị bằng 1 hoặc nhiều byte (tối đa khoảng 6) và các mã hóa khác nhau sẽ xử lý các byte này khác nhau.

Joel có một bài viết về điều này:

Tối thiểu tuyệt đối Mỗi nhà phát triển phần mềm Tuyệt đối, Tích cực phải biết về bộ ký tự và Unicode (Không có lý do!)


6
"1 ký tự có thể được biểu thị bằng 1 hoặc nhiều byte" Tôi đồng ý. Tôi chỉ muốn các byte đó bất kể mã hóa chuỗi là gì. Cách duy nhất một chuỗi có thể được lưu trữ trong bộ nhớ là theo byte. Ngay cả các ký tự được lưu trữ dưới dạng 1 hoặc nhiều byte. Tôi chỉ muốn có được bàn tay của họ trên byte.
Agnel Kurian

16
Bạn không cần mã hóa trừ khi bạn (hoặc ai đó) thực sự có ý định giải thích dữ liệu, thay vì coi đó là một "khối byte" chung chung. Đối với những thứ như nén, mã hóa, v.v., lo lắng về mã hóa là vô nghĩa. Xem câu trả lời của tôi để biết cách làm điều này mà không phải lo lắng về mã hóa.
dùng541686

9
@Mehrdad - Hoàn toàn, nhưng câu hỏi ban đầu, như đã nêu khi tôi trả lời ban đầu, đã không báo trước những gì OP sẽ xảy ra với các byte đó sau khi họ chuyển đổi chúng và cho những người tìm kiếm trong tương lai thông tin phù hợp - đây là được bao phủ bởi câu trả lời của Joel khá độc đáo - và khi bạn nêu trong câu trả lời của mình: miễn là bạn gắn bó với thế giới .NET và sử dụng các phương thức của bạn để chuyển đổi sang / từ, bạn rất vui. Ngay khi bạn bước ra ngoài điều đó, mã hóa sẽ có vấn đề.
Zhaph - Ben Duguid

Một điểm mã có thể được biểu thị bằng tối đa 4 byte. (Một đơn vị mã UTF-32, cặp thay thế UTF-16 hoặc 4 byte UTF-8.) Các giá trị mà UTF-8 sẽ cần nhiều hơn 4 byte nằm ngoài phạm vi Unicode 0x0..0x10FFFF. ;-)
DevSolar

89

Đây là một câu hỏi phổ biến. Điều quan trọng là phải hiểu những gì tác giả câu hỏi đang hỏi, và nó khác với những gì có thể là nhu cầu phổ biến nhất. Để ngăn chặn việc sử dụng sai mã không cần thiết, tôi đã trả lời sau.

Nhu cầu chung

Mỗi chuỗi có một bộ ký tự và mã hóa. Khi bạn chuyển đổi một System.Stringđối tượng thành một mảng, System.Bytebạn vẫn có một bộ ký tự và mã hóa. Đối với hầu hết các cách sử dụng, bạn sẽ biết bộ ký tự và mã hóa nào bạn cần và .NET giúp việc "sao chép với chuyển đổi" trở nên đơn giản. Chỉ cần chọn Encodinglớp thích hợp .

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

Việc chuyển đổi có thể cần xử lý các trường hợp trong đó bộ ký tự đích hoặc mã hóa không hỗ trợ ký tự trong nguồn. Bạn có một số lựa chọn: ngoại lệ, thay thế hoặc bỏ qua. Chính sách mặc định là thay thế '?'.

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

Rõ ràng, chuyển đổi không nhất thiết là mất mát!

Lưu ý: Đối với System.Stringbộ ký tự nguồn là Unicode.

Điều khó hiểu duy nhất là .NET sử dụng tên của một bộ ký tự cho tên của một mã hóa cụ thể của bộ ký tự đó. Encoding.Unicodenên được gọi Encoding.UTF16.

Đó là cho hầu hết các công dụng. Nếu đó là những gì bạn cần, dừng đọc ở đây. Xem bài viết vui nhộn Joel Spolsky nếu bạn không hiểu mã hóa là gì.

Cần cụ thể

Bây giờ, tác giả câu hỏi hỏi, "Mỗi chuỗi được lưu trữ dưới dạng một mảng byte, phải không? Tại sao tôi không thể có các byte đó?"

Anh ta không muốn bất kỳ chuyển đổi.

Từ thông số kỹ thuật C # :

Xử lý ký tự và chuỗi trong C # sử dụng mã hóa Unicode. Kiểu char đại diện cho một đơn vị mã UTF-16 và loại chuỗi đại diện cho một chuỗi các đơn vị mã UTF-16.

Vì vậy, chúng tôi biết rằng nếu chúng tôi yêu cầu chuyển đổi null (nghĩa là từ UTF-16 sang UTF-16), chúng tôi sẽ nhận được kết quả mong muốn:

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

Nhưng để tránh đề cập đến mã hóa, chúng ta phải làm theo cách khác. Nếu một kiểu dữ liệu trung gian có thể chấp nhận được, có một lối tắt khái niệm cho điều này:

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

Điều đó không mang lại cho chúng ta kiểu dữ liệu mong muốn nhưng câu trả lời của Mehrdad cho thấy cách chuyển đổi mảng Char này thành mảng Byte bằng cách sử dụng BlockCopy . Tuy nhiên, điều này sao chép chuỗi hai lần! Và, nó quá rõ ràng sử dụng mã dành riêng cho mã hóa: kiểu dữ liệu System.Char.

Cách duy nhất để có được các byte thực tế mà String được lưu trữ là sử dụng một con trỏ. Các fixedtuyên bố cho phép lấy địa chỉ của giá trị. Từ thông số kỹ thuật C #:

[Đối với] một biểu thức của chuỗi kiểu, ... trình khởi tạo sẽ tính địa chỉ của ký tự đầu tiên trong chuỗi.

Để làm như vậy, trình biên dịch ghi mã bỏ qua các phần khác của đối tượng chuỗi với RuntimeHelpers.OffsetToStringData. Vì vậy, để có được các byte thô, chỉ cần tạo một con trỏ tới chuỗi và sao chép số byte cần thiết.

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

Như @CodesInChaos đã chỉ ra, kết quả phụ thuộc vào độ bền của máy. Nhưng tác giả câu hỏi không quan tâm đến điều đó.


3
@Jan Điều đó đúng nhưng độ dài chuỗi đã cho số đơn vị mã (không phải mã hóa).
Tom Blodget

1
Cảm ơn đã chỉ ra rằng! Từ MSDN: " LengthThuộc tính [của String] trả về số lượng Charđối tượng trong trường hợp này, không phải số lượng ký tự Unicode." Do đó, mã ví dụ của bạn là chính xác như được viết.
Jan Hettich

1
@supercat "Loại char đại diện cho đơn vị mã UTF-16 và loại chuỗi đại diện cho một chuỗi các đơn vị mã UTF-16." Đặc điểm kỹ thuật.__new String(new []{'\uD800', '\u0030'})
Tom Blodget

1
@TomBlodget: Thật thú vị, nếu người ta lấy các thể hiện của Globalization.SortKey, trích xuất KeyDatavà gói các byte kết quả từ mỗi thành một String[hai byte cho mỗi ký tự, trước tiên là MSB ], việc gọi String.CompareOrdinalcác chuỗi kết quả sẽ nhanh hơn đáng kể so với việc gọi SortKey.Comparecác thể hiện của SortKey, hoặc thậm chí kêu gọi memcmpnhững trường hợp đó. Cho rằng, tôi tự hỏi tại sao KeyDatatrả về một Byte[]thay vì một String?
supercat

1
Than ôi, câu trả lời đúng, nhưng năm quá muộn, sẽ không bao giờ có nhiều phiếu như được chấp nhận. Do TL; DR mọi người sẽ nghĩ đá trả lời được chấp nhận. copyenpastit và bỏ phiếu cho nó.
Martin Capodici

46

Phần đầu tiên của câu hỏi của bạn (làm thế nào để lấy byte) đã được người khác trả lời: tìm trong System.Text.Encodingkhông gian tên.

Tôi sẽ giải quyết câu hỏi tiếp theo của bạn: tại sao bạn cần chọn một mã hóa? Tại sao bạn không thể có được điều đó từ chính lớp chuỗi?

Câu trả lời là hai phần.

Trước hết, các byte được sử dụng bên trong lớp chuỗi không thành vấn đề và bất cứ khi nào bạn cho rằng chúng có khả năng gây ra lỗi.

Nếu chương trình của bạn hoàn toàn nằm trong thế giới .Net thì bạn không cần phải lo lắng về việc nhận mảng byte cho chuỗi, ngay cả khi bạn đang gửi dữ liệu qua mạng. Thay vào đó, hãy sử dụng .Net serialization để lo lắng về việc truyền dữ liệu. Bạn không còn lo lắng về các byte thực tế nữa: trình định dạng Nối tiếp thực hiện điều đó cho bạn.

Mặt khác, nếu bạn đang gửi các byte này ở đâu đó mà bạn không thể đảm bảo sẽ lấy dữ liệu từ luồng nối tiếp .Net thì sao? Trong trường hợp này, bạn chắc chắn cần phải lo lắng về mã hóa, bởi vì rõ ràng hệ thống bên ngoài này quan tâm. Vì vậy, một lần nữa, các byte bên trong được sử dụng bởi chuỗi không thành vấn đề: bạn cần chọn một mã hóa để bạn có thể rõ ràng về mã hóa này ở đầu nhận, ngay cả khi đó là cùng một mã hóa được sử dụng bởi .Net.

Tôi hiểu rằng trong trường hợp này, bạn có thể thích sử dụng các byte thực được lưu trữ bởi biến chuỗi trong bộ nhớ nếu có thể, với ý tưởng rằng nó có thể lưu một số công việc tạo luồng byte của bạn. Tuy nhiên, tôi nói với bạn điều đó không quan trọng so với việc đảm bảo rằng đầu ra của bạn được hiểu ở đầu kia và để đảm bảo rằng bạn phải rõ ràng với mã hóa của mình. Ngoài ra, nếu bạn thực sự muốn khớp các byte nội bộ của mình, bạn có thể chỉ cần chọn Unicodemã hóa và nhận mức tiết kiệm hiệu suất đó.

Điều này đưa tôi đến phần thứ hai ... chọn Unicodemã hóa bảo .Net sử dụng các byte bên dưới. Bạn cần phải chọn mã hóa này, bởi vì khi một số Unicode-Plus mới xuất hiện, thời gian chạy .Net cần được tự do sử dụng mô hình mã hóa mới hơn, tốt hơn này mà không làm hỏng chương trình của bạn. Nhưng, hiện tại (và tương lai có thể thấy được), chỉ cần chọn mã hóa Unicode sẽ mang lại cho bạn những gì bạn muốn.

Điều quan trọng là phải hiểu chuỗi của bạn phải được viết lại thành dây và điều đó liên quan đến ít nhất một số bản dịch của mẫu bit ngay cả khi bạn sử dụng mã hóa phù hợp . Máy tính cần tính đến những thứ như Big vs Little Endian, thứ tự byte mạng, gói, thông tin phiên, v.v.


9
Có các khu vực trong .NET nơi bạn phải lấy các mảng byte cho chuỗi. Nhiều lớp mã hóa .NET chứa các phương thức như ComputeHash () chấp nhận mảng byte hoặc luồng. Trước tiên, bạn không có cách nào khác ngoài việc chuyển đổi một chuỗi thành một mảng byte (chọn Mã hóa) và sau đó tùy ý bọc nó trong một luồng. Tuy nhiên, miễn là bạn chọn một mã hóa (ví dụ UTF8), một thanh với nó sẽ không có vấn đề gì với điều này.
Tro

44

Chỉ để chứng minh rằng câu trả lời âm thanh của Mehrdrad hoạt động, cách tiếp cận của anh ta thậm chí có thể duy trì các nhân vật thay thế không ghép đôi (trong đó nhiều người đã chống lại câu trả lời của tôi, nhưng trong đó mọi người đều có tội như nhau, ví dụ System.Text.Encoding.UTF8.GetBytes, System.Text.Encoding.Unicode.GetBytesnhững phương pháp mã hóa đó không thể duy trì sự thay thế cao d800ví dụ như các ký tự và những ký tự chỉ thay thế các ký tự thay thế cao bằng giá trị 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);
    }        
}

Đầu ra:

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

Hãy thử điều đó với System.Text.Encoding.UTF8.GetBytes hoặc System.Text.Encoding.Unicode.GetBytes , họ sẽ chỉ thay thế các ký tự thay thế cao bằng giá trị fffd

Mỗi khi có một chuyển động trong câu hỏi này, tôi vẫn nghĩ về một bộ nối tiếp (có thể là từ Microsoft hoặc từ thành phần bên thứ 3) có thể duy trì các chuỗi ngay cả khi nó chứa các ký tự thay thế không ghép cặp; Tôi google cái này mỗi giờ và sau đó: tuần tự hóa ghép cặp ký tự thay thế .NET . Điều này không làm tôi mất ngủ, nhưng thật khó chịu khi thỉnh thoảng có ai đó bình luận về câu trả lời của tôi rằng nó thật thiếu sót, nhưng câu trả lời của họ cũng không hoàn hảo khi nói đến các nhân vật thay thế không ghép đôi.

Darn, Microsoft nên chỉ được sử dụng System.Buffer.BlockCopytrong nó BinaryFormatter

谢谢!


3
Không thay thế phải xuất hiện theo cặp để tạo thành điểm mã hợp lệ? Nếu đó là trường hợp, tôi có thể hiểu tại sao dữ liệu sẽ bị sai lệch.
dtanders

1
@dtanders Vâng, đó cũng là suy nghĩ của tôi, chúng phải xuất hiện theo cặp, nhân vật thay thế không ghép đôi chỉ xảy ra nếu bạn cố tình đưa chúng vào chuỗi và khiến chúng không ghép đôi. Điều tôi không biết là tại sao các nhà phát triển khác cứ nói rằng chúng ta nên sử dụng phương pháp nhận biết mã hóa thay vì họ coi cách tiếp cận tuần tự hóa ( câu trả lời của tôi , đó là một câu trả lời được chấp nhận trong hơn 3 năm) không giữ được sự tuyệt vọng nhân vật thay thế nguyên vẹn. Nhưng họ đã quên kiểm tra rằng các giải pháp nhận biết mã hóa của họ cũng không giữ được tính cách thay thế không ghép đôi, trớ trêu
Michael Buen

Nếu có một thư viện tuần tự hóa sử dụng System.Buffer.BlockCopynội bộ, tất cả các đối số của những người ủng hộ mã hóa sẽ được đưa ra
Michael Buen

2
@MichaelBuen Dường như với tôi rằng vấn đề chính là bạn đang viết những chữ in đậm lớn nói điều gì đó không quan trọng, thay vì nói rằng điều đó không quan trọng trong trường hợp của họ. Do đó, bạn đang khuyến khích những người nhìn vào câu trả lời của bạn để mắc lỗi lập trình cơ bản, điều này sẽ khiến người khác thất vọng trong tương lai. Người thay thế không ghép đôi là không hợp lệ trong một chuỗi. Nó không phải là một mảng char, do đó, việc chuyển đổi một chuỗi sang định dạng khác sẽ dẫn đến lỗi FFFDtrên ký tự đó. Nếu bạn muốn thực hiện thao tác chuỗi thủ công, hãy sử dụng char [] theo khuyến nghị.
Đã xem

2
@dtanders: A System.Stringlà một chuỗi bất biến của Char; .NET luôn cho phép một Stringđối tượng được xây dựng từ bất kỳ Char[]và xuất nội dung của nó sang một Char[]giá trị có cùng giá trị, ngay cả khi bản gốc Char[]chứa các đại diện không ghép cặp.
supercat

41

Hãy thử điều này, rất ít mã:

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

Sau đó thử điều này System.Text.Encoding.UTF8.GetBytes("Árvíztűrő tükörfúrógép);, và khóc! Nó sẽ hoạt động, nhưng System.Text.Encoding.UTF8.GetBytes("Árvíztűrő tükörfúrógép").Length != System.Text.Encoding.UTF8.GetBytes("Arvizturo tukorfurogep").Lengthtrong khi"Árvíztűrő tükörfúrógép".Length == "Arvizturo tukorfurogep".Length
mg30rg

9
@ mg30rg: Tại sao bạn nghĩ ví dụ của bạn là lạ? Chắc chắn trong một mã hóa có chiều rộng thay đổi, không phải tất cả các ký tự đều có cùng độ dài byte. Có gì sai với nó?
Vlad

@Vlad Một nhận xét hợp lệ hơn ở đây, tuy nhiên, là các ký hiệu unicode được mã hóa (vì vậy, dưới dạng byte), các ký tự bao gồm các dấu phụ của chúng sẽ cho kết quả khác với các dấu phụ được tách thành các ký hiệu sửa đổi được thêm vào ký tự. Nhưng iirc có các phương thức trong .net để tách riêng chúng ra, để cho phép nhận được một biểu diễn byte nhất quán.
Nyerguds

25

Chà, tôi đã đọc tất cả các câu trả lời và chúng là về việc sử dụng mã hóa hoặc một về việc tuần tự hóa làm giảm các chất thay thế không ghép đôi.

Thật tệ khi chuỗi, ví dụ, đến từ SQL Server , nơi nó được xây dựng từ việc lưu trữ mảng byte, ví dụ, hàm băm mật khẩu. Nếu chúng ta bỏ bất cứ thứ gì từ nó, nó sẽ lưu trữ một hàm băm không hợp lệ và nếu chúng ta muốn lưu trữ nó trong XML, chúng ta muốn giữ nguyên nó (vì trình soạn thảo XML sẽ loại bỏ một ngoại lệ đối với bất kỳ thay thế nào được tìm thấy).

Vì vậy, tôi sử dụng hóa Base64 của mảng byte trong những trường hợp như vậy, nhưng này, trên Internet chỉ có một giải pháp cho vấn đề này trong C #, và nó có lỗi trong đó và chỉ là một cách, vì vậy tôi đã sửa lỗi và viết lại thủ tục. Bạn đang ở đây, nhân viên của Google trong tương lai:

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

Thay vì sử dụng phương thức tùy chỉnh của bạn để chuyển đổi một mảng byte thành base64, tất cả những gì bạn phải làm là sử dụng trình chuyển đổi tích hợp: Convert.ToBase64String (mảng);
Makotosan

@Makotosan cảm ơn bạn, nhưng tôi đã sử dụng Convert.ToBase64String(arr); cho các chuyển đổi cơ sở64 byte[] (data) <-> string (serialized data to store in XML file). Nhưng để có được ban đầu byte[] (data)tôi cần phải làm một cái gì đó với dữ liệu nhị phânString có chứa dữ liệu nhị phân (đó là cách MSSQL trả lại cho tôi). Vì vậy, các chức năng trên là cho String (binary data) <-> byte[] (easy accessible binary data).
Gman

23

Ngoài ra xin vui lòng giải thích tại sao mã hóa nên được xem xét. Tôi không thể đơn giản nhận được byte nào mà chuỗi đã được lưu trữ? Tại sao sự phụ thuộc này vào mã hóa? !!!

Bởi vì không có thứ gọi là "byte của chuỗi".

Một chuỗi (hoặc tổng quát hơn, một văn bản) bao gồm các ký tự: chữ cái, chữ số và các ký hiệu khác. Đó là tất cả. Máy tính, tuy nhiên, không biết gì về các nhân vật; họ chỉ có thể xử lý byte. Do đó, nếu bạn muốn lưu trữ hoặc truyền văn bản bằng cách sử dụng máy tính, bạn cần chuyển đổi các ký tự thành byte. Làm thế nào để bạn làm điều đó? Đây là nơi mã hóa đến hiện trường.

Mã hóa không là gì ngoài một quy ước để dịch các ký tự logic thành các byte vật lý. Mã hóa đơn giản và nổi tiếng nhất là ASCII, và đó là tất cả những gì bạn cần nếu bạn viết bằng tiếng Anh. Đối với các ngôn ngữ khác, bạn sẽ cần mã hóa hoàn chỉnh hơn, trở thành bất kỳ hương vị Unicode nào là sự lựa chọn an toàn nhất hiện nay.

Vì vậy, trong ngắn hạn, cố gắng "lấy các byte của một chuỗi mà không sử dụng mã hóa" là không thể như "viết một văn bản mà không sử dụng bất kỳ ngôn ngữ nào".

Nhân tiện, tôi thực sự khuyên bạn (và bất cứ ai, vì vấn đề đó) nên đọc phần khôn ngoan nhỏ này: Tối thiểu tuyệt đối Mỗi nhà phát triển phần mềm Tuyệt đối, Phải tích cực phải biết về Unicode và Bộ ký tự (Không có lý do!)


2
Cho phép tôi làm rõ: Một mã hóa đã được sử dụng để dịch "hello world" sang các byte vật lý. Vì chuỗi được lưu trữ trên máy tính của tôi, tôi chắc chắn rằng nó phải được lưu trữ theo byte. Tôi chỉ muốn truy cập các byte đó để lưu chúng trên đĩa hoặc vì bất kỳ lý do nào khác. Tôi không muốn giải thích các byte này. Vì tôi không muốn diễn giải các byte này, nên nhu cầu mã hóa vào thời điểm này cũng bị đặt sai chỗ khi yêu cầu một đường dây điện thoại để gọi printf.
Agnel Kurian

3
Nhưng một lần nữa, không có khái niệm dịch từ văn bản sang vật lý-byte-dịch trừ khi bạn sử dụng mã hóa. Chắc chắn, trình biên dịch lưu trữ các chuỗi bằng cách nào đó trong bộ nhớ - nhưng nó chỉ sử dụng một mã hóa nội bộ, mà bạn (hoặc bất kỳ ai trừ nhà phát triển trình biên dịch) không biết. Vì vậy, bất cứ điều gì bạn làm, bạn cần một mã hóa để có được các byte vật lý từ một chuỗi.
Konamiman

@Agnel Kurian: Tất nhiên là đúng, rằng một chuỗi có một loạt byte ở đâu đó lưu trữ nội dung của nó (UTF-16 afair). Nhưng có một lý do chính đáng để ngăn bạn truy cập vào nó: các chuỗi là bất biến và nếu bạn có thể có được mảng byte [] bên trong, bạn cũng có thể sửa đổi nó. Điều này phá vỡ tính bất biến, điều này rất quan trọng vì nhiều chuỗi có thể chia sẻ cùng một dữ liệu. Sử dụng mã hóa UTF-16 để lấy chuỗi có thể sẽ chỉ sao chép dữ liệu ra.
ollb

2
@Gnafoo, Một bản sao của byte sẽ làm.
Agnel Kurian

22

C # để chuyển đổi a stringthành một bytemảng:

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

Nhưng, tại sao mã hóa nên được xem xét? Tại sao tôi không thể đơn giản nhận được các byte mà không cần phải xem mã hóa nào đang được sử dụng? Ngay cả khi nó được yêu cầu, không nên tự đối tượng String biết mã hóa nào đang được sử dụng và chỉ cần bỏ đi những gì trong bộ nhớ?
Agnel Kurian

5
Điều này không phải lúc nào cũng hoạt động. Một số nhân vật đặc biệt có thể bị lạc khi sử dụng một phương pháp như vậy tôi đã tìm thấy một cách khó khăn.
JB King

17

Bạn có thể sử dụng đoạn mã sau để chuyển đổi giữa chuỗi và mảng byte.

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 đã giải quyết vấn đề của tôi (byte [] ff = ASCIIEncoding.ASCII.GetBytes (barcodetxt.Text);)
r.hamd

16

Với sự ra đời của Span<T>C # 7.2, kỹ thuật chính tắc để nắm bắt biểu diễn bộ nhớ cơ bản của chuỗi thành một mảng byte được quản lý là:

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

Chuyển đổi nó trở lại phải là một công cụ không bắt đầu bởi vì điều đó có nghĩa là trên thực tế bạn đang diễn giải dữ liệu bằng cách nào đó, nhưng vì mục đích hoàn chỉnh:

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

Các tên NonPortableCastDangerousGetPinnableReferencenên tiếp tục lập luận rằng bạn có thể không nên làm điều này.

Lưu ý rằng làm việc với Span<T>yêu cầu cài đặt gói System.Memory NuGet .

Bất kể, câu hỏi ban đầu thực tế và nhận xét tiếp theo ngụ ý rằng bộ nhớ cơ bản không bị "giải thích" (mà tôi cho rằng có nghĩa là không được sửa đổi hoặc đọc vượt quá nhu cầu viết như hiện tại), cho thấy rằng việc thực hiện Streamlớp này nên được sử dụng thay vì lý luận về dữ liệu dưới dạng chuỗi.


13

Tôi không chắc chắn, nhưng tôi nghĩ chuỗi lưu trữ thông tin của nó dưới dạng một mảng Chars, không hiệu quả với byte. Cụ thể, định nghĩa của Char là "Đại diện cho một ký tự Unicode".

lấy ví dụ mẫu này:

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

Xin lưu ý rằng câu trả lời Unicode là 14 byte trong cả hai trường hợp, trong khi câu trả lời UTF-8 chỉ có 9 byte cho lần đầu tiên và chỉ 7 cho lần thứ hai.

Vì vậy, nếu bạn chỉ muốn các byte được sử dụng bởi chuỗi, chỉ cần sử dụng Encoding.Unicode, nhưng nó sẽ không hiệu quả với không gian lưu trữ.


10

Vấn đề chính là một glyph trong chuỗi mất 32 bit (16 bit cho mã ký tự) nhưng một byte chỉ có 8 bit dự phòng. Ánh xạ một-một không tồn tại trừ khi bạn giới hạn bản thân trong các chuỗi chỉ chứa các ký tự ASCII. System.Text.Encoding có rất nhiều cách để ánh xạ chuỗi thành byte [], bạn cần chọn một cách tránh mất thông tin và khách hàng của bạn dễ dàng sử dụng khi cô ấy cần ánh xạ byte [] trở lại chuỗi .

Utf8 là một mã hóa phổ biến, nó nhỏ gọn và không mất mát.


3
UTF-8 chỉ nhỏ gọn nếu phần lớn các ký tự của bạn nằm trong bộ ký tự tiếng Anh (ASCII). Nếu bạn có một chuỗi ký tự Trung Quốc dài, UTF-16 sẽ là một mã hóa nhỏ gọn hơn UTF-8 cho chuỗi đó. Điều này là do UTF-8 sử dụng một byte để mã hóa ASCII và 3 (hoặc có thể 4) nếu không.
Joel Mueller

7
Thật. Nhưng, làm thế nào bạn có thể không biết về mã hóa nếu bạn quen xử lý văn bản tiếng Trung?
Hans Passant

9

Sử dụng:

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

Kết quả là:

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

OP đặc biệt yêu cầu KHÔNG chỉ định mã hóa ... "mà không chỉ định thủ công mã hóa cụ thể"
Ferdz

8

Cách nhanh nhất

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

EDIT như Makotosan nhận xét đây là cách tốt nhất:

Encoding.UTF8.GetBytes(text)

8
ASCIIEncoding ..... là không cần thiết. Đơn giản chỉ cần sử dụng Encoding.UTF8.GetBytes (văn bản) được ưu tiên.
Makotosan

8

Làm cách nào để chuyển đổi một chuỗi thành một byte [] trong .NET (C #) mà không chỉ định thủ công một mã hóa cụ thể?

Một chuỗi trong .NET biểu thị văn bản dưới dạng một chuỗi các đơn vị mã UTF-16, do đó, các byte đã được mã hóa trong bộ nhớ trong UTF-16.

Câu trả lời của Mehrdad

Bạn có thể sử dụng câu trả lời của Mehrdad , nhưng thực tế nó sử dụng mã hóa vì ký tự là UTF-16. Nó gọi ToCharArray, việc nhìn vào nguồn sẽ tạo char[]và sao chép bộ nhớ vào nó trực tiếp. Sau đó, nó sao chép dữ liệu vào một mảng byte cũng được phân bổ. Vì vậy, dưới mui xe, nó sao chép các byte bên dưới hai lần và phân bổ một mảng char không được sử dụng sau cuộc gọi.

Câu trả lời của Tom Blodget

Câu trả lời của Tom Blodget nhanh hơn 20-30% so với Mehrdad vì nó bỏ qua bước trung gian phân bổ một mảng char và sao chép các byte vào nó, nhưng nó yêu cầu bạn biên dịch với /unsafetùy chọn. Nếu bạn hoàn toàn không muốn sử dụng mã hóa, tôi nghĩ rằng đây là cách để đi. Nếu bạn đặt thông tin đăng nhập mã hóa của mình bên trong fixedkhối, bạn thậm chí không cần phân bổ một mảng byte riêng biệt và sao chép các byte vào nó.

Ngoài ra, tại sao nên mã hóa? Tôi không thể đơn giản nhận được byte nào mà chuỗi đã được lưu trữ? Tại sao có sự phụ thuộc vào mã hóa ký tự?

Bởi vì đó là cách thích hợp để làm điều đó. stringlà một sự trừu tượng.

Sử dụng mã hóa có thể gây rắc rối cho bạn nếu bạn có 'chuỗi' với các ký tự không hợp lệ, nhưng điều đó không nên xảy ra. Nếu bạn đang nhận dữ liệu vào chuỗi của mình với các ký tự không hợp lệ, bạn đang làm sai. Có lẽ bạn nên sử dụng một mảng byte hoặc mã hóa Base64 để bắt đầu.

Nếu bạn sử dụng System.Text.Encoding.Unicode, mã của bạn sẽ linh hoạt hơn. Bạn không phải lo lắng về tính lâu dài của hệ thống mà mã của bạn sẽ chạy. Bạn không cần phải lo lắng nếu phiên bản tiếp theo của CLR sẽ sử dụng mã hóa ký tự nội bộ khác.

Tôi nghĩ rằng câu hỏi không phải là lý do tại sao bạn muốn lo lắng về mã hóa, nhưng tại sao bạn muốn bỏ qua nó và sử dụng một cái gì đó khác. Mã hóa có nghĩa là đại diện cho sự trừu tượng của một chuỗi trong một chuỗi các byte. System.Text.Encoding.Unicodesẽ cung cấp cho bạn một mã hóa thứ tự byte cuối cùng và sẽ thực hiện tương tự trên mọi hệ thống, bây giờ và trong tương lai.


Trên thực tế, một chuỗi trong C # KHÔNG bị giới hạn chỉ với UTF-16. Điều đúng là nó chứa một vectơ đơn vị mã 16 bit, nhưng các đơn vị mã 16 bit này không bị hạn chế đối với UTF-16 hợp lệ. Nhưng vì chúng là 16 bit, bạn cần mã hóa (thứ tự byte) để chuyển đổi chúng thành 8 bit. Sau đó, một chuỗi có thể lưu trữ dữ liệu không phải là Unicode, bao gồm cả mã nhị phân (ví dụ: hình ảnh bitmap). Nó được hiểu là UTF-16 chỉ trong I / O và các trình định dạng văn bản tạo ra sự giải thích như vậy.
verdy_p

Vì vậy, trong chuỗi C #, bạn có thể lưu trữ một đơn vị mã như 0xFFFF hoặc 0xFFFE một cách an toàn, ngay cả khi chúng không phải là ký tự trong UTF-16 và bạn có thể lưu trữ 0xD800 bị cô lập không theo sau bởi đơn vị mã trong 0xDC00..0xDFFF (nghĩa là các chất thay thế không ghép đôi không hợp lệ trong UTF-16). Nhận xét tương tự áp dụng cho các chuỗi trong Javascript / ECMAscript và Java.
verdy_p

Khi bạn sử dụng "GetBytes", tất nhiên bạn không chỉ định mã hóa, nhưng bạn giả sử một thứ tự byte để có được hai byte trong một đặc tả cho mỗi đơn vị mã được lưu trữ cục bộ trong chuỗi. Khi bạn xây dựng một chuỗi mới từ byte, bạn cũng cần một trình chuyển đổi, không nhất thiết phải là UTF-8 sang UTF-16, bạn có thể chèn thêm 0 vào byte cao hoặc đóng gói hai byte (theo thứ tự MSB trước hoặc LSB trước) cùng đơn vị mã 16 bit. Các chuỗi sau đó là dạng nhỏ gọn cho các mảng số nguyên 16 bit. Mối quan hệ với "ký tự" là một vấn đề khác, trong C # chúng không phải là loại thực tế vì chúng vẫn được biểu diễn dưới dạng chuỗi
verdy_p

7

Cách tiếp cận gần nhất với câu hỏi của OP là Tom Blodget, thực sự đi vào đối tượng và trích xuất các byte. Tôi nói gần nhất bởi vì nó phụ thuộc vào việc thực hiện String Object.

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

Chắc chắn, nhưng đó là nơi phát sinh lỗi cơ bản trong câu hỏi. Chuỗi là một đối tượng có thể có cấu trúc dữ liệu thú vị. Chúng tôi đã biết điều đó, bởi vì nó cho phép các chất thay thế không ghép đôi được lưu trữ. Nó có thể lưu trữ chiều dài. Nó có thể giữ một con trỏ tới từng người thay thế 'được ghép đôi' cho phép đếm nhanh. V.v. Tất cả các byte bổ sung này không phải là một phần của dữ liệu ký tự.

Những gì bạn muốn là mỗi byte của nhân vật trong một mảng. Và đó là nơi 'mã hóa' xuất hiện. Theo mặc định, bạn sẽ nhận được UTF-16LE. Nếu bạn không quan tâm đến các byte ngoại trừ chuyến đi khứ hồi thì bạn có thể chọn bất kỳ mã hóa nào bao gồm 'mặc định' và chuyển đổi lại sau (giả sử các tham số tương tự như mã hóa mặc định là gì, điểm mã, sửa lỗi , những thứ được cho phép như người thay thế không ghép đôi, v.v.

Nhưng tại sao lại để 'mã hóa' thành ma thuật? Tại sao không chỉ định mã hóa để bạn biết bạn sẽ nhận được byte nào?

"Why is there a dependency on character encodings?"

Mã hóa (trong ngữ cảnh này) chỉ đơn giản là các byte đại diện cho chuỗi của bạn. Không phải là byte của đối tượng chuỗi. Bạn muốn các byte mà chuỗi đã được lưu trữ - đây là nơi câu hỏi được hỏi một cách ngây thơ. Bạn muốn các byte của chuỗi trong một mảng liền kề đại diện cho chuỗi và không phải tất cả các dữ liệu nhị phân khác mà một đối tượng chuỗi có thể chứa.

Có nghĩa là làm thế nào một chuỗi được lưu trữ là không liên quan. Bạn muốn một chuỗi "Được mã hóa" thành byte trong một mảng byte.

Tôi thích câu trả lời của Tom Bloget vì anh ấy đưa bạn về hướng 'byte của đối tượng chuỗi'. Mặc dù điều đó phụ thuộc vào việc thực hiện và bởi vì anh ta nhìn trộm vào bên trong nên có thể khó khôi phục lại một bản sao của chuỗi.

Phản ứng của Mehrdad là sai bởi vì nó sai lệch ở cấp độ khái niệm. Bạn vẫn có một danh sách các byte, được mã hóa. Giải pháp đặc biệt của anh ấy cho phép những người thay thế không ghép đôi được bảo tồn - điều này phụ thuộc vào việc thực hiện. Giải pháp cụ thể của anh ta sẽ không tạo ra các byte chính xác của chuỗi nếu GetBytestrả về chuỗi theo UTF-8 theo mặc định.


Tôi đã thay đổi suy nghĩ về điều này (giải pháp của Mehrdad) - đây không phải là lấy byte của chuỗi; thay vào đó, nó nhận được các byte của mảng ký tự được tạo từ chuỗi. Bất kể mã hóa, kiểu dữ liệu char trong c # là một kích thước cố định. Điều này cho phép tạo ra một mảng byte có độ dài nhất quán và nó cho phép mảng ký tự được sao chép dựa trên kích thước của mảng byte. Vì vậy, nếu mã hóa là UTF-8, nhưng mỗi char là 6 byte để chứa giá trị utf8 lớn nhất, nó vẫn hoạt động. Vì vậy, thực sự - mã hóa của nhân vật không quan trọng.

Nhưng một chuyển đổi đã được sử dụng - mỗi ký tự được đặt vào một hộp có kích thước cố định (loại ký tự của c #). Tuy nhiên, đại diện đó không quan trọng, về mặt kỹ thuật là câu trả lời cho OP. Vì vậy - nếu bạn định chuyển đổi bằng mọi cách ... Tại sao không 'mã hóa'?


Các ký tự này không được UTF-8 hoặc UTF-16 hoặc thậm chí UTF-32 hỗ trợ cho exapmle: 񩱠& (Char) 55906& (Char) 55655. Vì vậy, bạn có thể sai và câu trả lời của Mehrdad là một chuyển đổi an toàn mà không xem xét loại mã hóa nào được sử dụng.
Mojtaba Rezaeian

Raymon, các ký tự đã được đại diện bởi một số giá trị unicode - và tất cả các giá trị unicode có thể được đại diện bởi tất cả các utf. Có một lời giải thích dài hơn về những gì bạn đang nói về? Mã hóa ký tự nào làm hai giá trị đó (hoặc 3 ..) tồn tại?
Gerard ONeill

Chúng là các ký tự không hợp lệ không được hỗ trợ bởi bất kỳ phạm vi mã hóa nào. Điều này không có nghĩa là họ vô dụng 100%. Một mã chuyển đổi bất kỳ loại chuỗi nào thành mảng byte tương đương với bất kỳ mã hóa nào cũng không phải là một giải pháp sai và có cách sử dụng riêng trong các trường hợp mong muốn.
Mojtaba Rezaeian

1
Ok, sau đó tôi nghĩ rằng bạn không hiểu vấn đề. Chúng tôi biết đó là một mảng tuân thủ unicode - trên thực tế, vì nó là .net, chúng tôi biết đó là UTF-16. Vì vậy, những nhân vật sẽ không tồn tại ở đó. Bạn cũng không đọc hết bình luận của tôi về việc thay đổi nội bộ. Chuỗi là một đối tượng, không phải là một mảng byte được mã hóa. Vì vậy, tôi sẽ không đồng ý với tuyên bố cuối cùng của bạn. Bạn muốn mã để chuyển đổi tất cả các chuỗi unicode sang bất kỳ mã hóa UTF nào. Điều này làm những gì bạn muốn, chính xác.
Gerard ONeill

Các đối tượng là chuỗi dữ liệu ban đầu là các bit mô tả một đối tượng ở trạng thái hiện tại. Vì vậy, mọi dữ liệu trong các ngôn ngữ lập trình đều có thể chuyển đổi thành mảng byte (mỗi byte xác định 8 bit) vì bạn có thể cần giữ một số trạng thái của bất kỳ đối tượng nào trong bộ nhớ. Bạn có thể lưu và giữ một chuỗi byte trong tệp hoặc bộ nhớ và chuyển nó thành số nguyên, bigint, hình ảnh, chuỗi Ascii, chuỗi UTF-8, chuỗi được mã hóa hoặc kiểu dữ liệu do chính bạn xác định sau khi đọc nó từ đĩa. Vì vậy, bạn không thể nói các đối tượng là một cái gì đó khác với chuỗi byte.
Mojtaba Rezaeian

6

Bạn có thể sử dụng đoạn mã sau để chuyển đổi stringthành a byte arraytrong .NET

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

3

Nếu bạn thực sự muốn một bản sao của các byte bên dưới của một chuỗi, bạn có thể sử dụng một hàm giống như hàm theo sau. Tuy nhiên, bạn không nên đọc tiếp để tìm hiểu lý do tại sao.

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

Hàm này sẽ giúp bạn có một bản sao các byte nằm dưới chuỗi của bạn, khá nhanh chóng. Bạn sẽ nhận được các byte đó theo bất cứ cách nào chúng mã hóa trên hệ thống của bạn. Mã hóa này gần như chắc chắn là UTF-16LE nhưng đó là một chi tiết triển khai bạn không cần phải quan tâm.

Sẽ an toàn hơn, đơn giản hơn và đáng tin cậy hơn nếu chỉ gọi,

System.Text.Encoding.Unicode.GetBytes()

Trong tất cả khả năng, điều này sẽ cho kết quả tương tự, dễ nhập hơn và các byte sẽ luôn đi khứ hồi với một cuộc gọi đến

System.Text.Encoding.Unicode.GetString()

3

Đây là cách thực hiện không an toàn Stringđể Byte[]chuyển đổi:

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

Nó nhanh hơn cách của người được chấp nhận, ngay cả khi không thanh lịch như nó. Dưới đây là điểm chuẩn Đồng hồ bấm giờ của tôi trên 10000000 lần lặp:

[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

Để sử dụng nó, bạn phải đánh dấu vào "Cho phép mã không an toàn" trong thuộc tính xây dựng dự án của bạn. Theo .NET Framework 3.5, phương thức này cũng có thể được sử dụng làm chuỗi mở rộng:

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

Là giá trị của RuntimeHelpers.OffsetToStringDatabội số 8 trên các phiên bản Itanium của .NET? Bởi vì nếu không điều này sẽ thất bại do các lần đọc không được phân bổ.
Jon Hanna

nó sẽ đơn giản hơn để gọi memcpy? stackoverflow.com/a/27124232/659190
Jodrell

2

Đơn giản chỉ cần sử dụng này:

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

2
... và mất tất cả các ký tự với bước nhảy cao hơn 127. Trong ngôn ngữ mẹ đẻ của tôi, việc viết "Árvíztűrő tükörfúrógép là hoàn toàn hợp lệ". System.Text.ASCIIEncoding.Default.GetBytes("Árvíztűrő tükörfúrógép.").ToString();sẽ trả lại "Árvizturo tukörfurogép."mất thông tin mà không thể lấy được. (Và tôi chưa đề cập đến các ngôn ngữ châu Á nơi bạn sẽ mất tất cả các ký tự.)
mg30rg

2

Chuỗi có thể được chuyển đổi thành mảng byte theo một số cách khác nhau, do thực tế sau: .NET hỗ trợ Unicode và Unicode tiêu chuẩn hóa một số mã hóa khác nhau được gọi là UTF. Chúng có độ dài biểu diễn byte khác nhau nhưng tương đương theo nghĩa đó là khi một chuỗi được mã hóa, nó có thể được mã hóa trở lại chuỗi, nhưng nếu chuỗi được mã hóa bằng một UTF và được giải mã theo giả định UTF khác nhau nếu có thể được vặn lên.

Ngoài ra, .NET hỗ trợ mã hóa phi Unicode, nhưng chúng không hợp lệ trong trường hợp chung (sẽ chỉ hợp lệ nếu một tập hợp con giới hạn của điểm mã Unicode được sử dụng trong một chuỗi thực, chẳng hạn như ASCII). Trong nội bộ, .NET hỗ trợ UTF-16, nhưng để thể hiện luồng, UTF-8 thường được sử dụng. Nó cũng là một tiêu chuẩn thực tế cho Internet.

Không có gì đáng ngạc nhiên, việc tuần tự hóa chuỗi thành một mảng byte và giải tuần tự hóa được hỗ trợ bởi lớp System.Text.Encoding, đây là một lớp trừu tượng; các lớp dẫn xuất của nó hỗ trợ mã hóa cụ thể: ASCIIEncodingvà bốn UTF ( System.Text.UnicodeEncodinghỗ trợ UTF-16)

Tham khảo liên kết này.

Để tuần tự hóa đến một mảng byte bằng cách sử dụng System.Text.Encoding.GetBytes. Đối với việc sử dụng hoạt động nghịch đảo System.Text.Encoding.GetChars. Hàm này trả về một mảng các ký tự, vì vậy để có được một chuỗi, hãy sử dụng hàm tạo chuỗi System.String(char[]).
Tham khảo trang này.

Thí dụ:

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

Nó phụ thuộc vào những gì bạn muốn các byte CHO

Điều này là do, như Tyler rất thông minh đã nói , "Chuỗi không phải là dữ liệu thuần túy. Họ cũng có thông tin ." Trong trường hợp này, thông tin là một mã hóa được giả sử khi chuỗi được tạo.

Giả sử rằng bạn có dữ liệu nhị phân (chứ không phải văn bản) được lưu trữ trong một chuỗi

Điều này dựa trên nhận xét của OP về câu hỏi của chính anh ấy và là câu hỏi chính xác nếu tôi hiểu gợi ý của OP trong trường hợp sử dụng.

Lưu trữ dữ liệu nhị phân trong chuỗi có lẽ là cách tiếp cận sai vì mã hóa giả định được đề cập ở trên! Bất cứ chương trình hay thư viện nào lưu trữ dữ liệu nhị phân đó trong mộtstring (thay vì một byte[]mảng phù hợp hơn) đã thua trận trước khi nó bắt đầu. Nếu họ đang gửi byte cho bạn trong yêu cầu / phản hồi REST hoặc bất cứ điều gì phải truyền chuỗi, Base64 sẽ là phương pháp phù hợp.

Nếu bạn có một chuỗi văn bản với một mã hóa không xác định

Mọi người khác trả lời sai câu hỏi này không chính xác.

Nếu chuỗi có vẻ tốt như hiện tại, chỉ cần chọn một mã hóa (tốt nhất là bắt đầu bằng UTF), sử dụng System.Text.Encoding.???.GetBytes()hàm tương ứng và cho biết bất cứ ai bạn cung cấp byte cho mã hóa mà bạn đã chọn.


2

Khi được hỏi bạn định làm gì với các byte, bạn đã trả lời :

Tôi sẽ mã hóa nó. Tôi có thể mã hóa nó mà không cần chuyển đổi nhưng tôi vẫn muốn biết tại sao mã hóa lại xuất hiện ở đây. Chỉ cần cho tôi các byte là những gì tôi nói.

Bất kể bạn có ý định gửi dữ liệu được mã hóa này qua mạng hay không, tải lại vào bộ nhớ sau hoặc hấp nó sang quy trình khác, rõ ràng bạn đang có ý định giải mã nó vào một lúc nào đó. Trong trường hợp đó, câu trả lời là bạn đang xác định một giao thức truyền thông. Một giao thức truyền thông không nên được xác định theo các chi tiết triển khai của ngôn ngữ lập trình và thời gian chạy liên quan của nó. Cái này có một vài nguyên nhân:

  • Bạn có thể cần giao tiếp với một quy trình được thực hiện bằng ngôn ngữ hoặc thời gian chạy khác. (Điều này có thể bao gồm một máy chủ đang chạy trên một máy khác hoặc gửi chuỗi đến máy khách trình duyệt JavaScript chẳng hạn.)
  • Chương trình có thể được thực hiện lại bằng một ngôn ngữ hoặc thời gian chạy khác trong tương lai.
  • Việc triển khai .NET có thể thay đổi biểu diễn bên trong của chuỗi. Bạn có thể nghĩ rằng điều này nghe có vẻ xa vời, nhưng điều này thực sự đã xảy ra trong Java 9 để giảm mức sử dụng bộ nhớ. Không có lý do gì .NET không thể làm theo. Skeet cho rằng UTF-16 có thể không tối ưu ngày hôm nay làm tăng biểu tượng cảm xúc và các khối Unicode khác cần nhiều hơn 2 byte để thể hiện, làm tăng khả năng biểu diễn bên trong có thể thay đổi trong tương lai.

Để giao tiếp (với quy trình hoàn toàn khác biệt hoặc với cùng một chương trình trong tương lai), bạn cần xác định nghiêm ngặt giao thức của mình để giảm thiểu khó khăn khi làm việc với nó hoặc vô tình tạo ra lỗi. Tùy thuộc vào đại diện nội bộ của .NET không phải là một định nghĩa chặt chẽ, rõ ràng hoặc thậm chí được đảm bảo là định nghĩa nhất quán. Mã hóa tiêu chuẩn một định nghĩa nghiêm ngặt sẽ không làm bạn thất vọng trong tương lai.

Nói cách khác, bạn không thể đáp ứng yêu cầu về tính nhất quán mà không chỉ định mã hóa.

Bạn chắc chắn có thể chọn sử dụng trực tiếp UTF-16 nếu bạn thấy rằng quy trình của mình hoạt động tốt hơn đáng kể vì .NET sử dụng nội bộ hoặc vì bất kỳ lý do nào khác, nhưng bạn cần chọn mã hóa rõ ràng và thực hiện các chuyển đổi đó một cách rõ ràng trong mã của mình thay vì phụ thuộc về triển khai nội bộ của .NET.

Vì vậy, chọn một mã hóa và sử dụng nó:

using System.Text;

// ...

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

Như bạn có thể thấy, thực tế cũng ít mã hơn khi chỉ sử dụng các đối tượng mã hóa được tích hợp sẵn để thực hiện các phương thức đọc / ghi của riêng bạn.


1

Hai lối:

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

Và,

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

Tôi có xu hướng sử dụng cái dưới thường xuyên hơn cái trên, không đánh giá chúng về tốc độ.


4
Thế còn nhân vật đa nhân?
Agnel Kurian

c.ToByte () là riêng tư: S
Khodor

@AgnelKurian Msd nói "Phương thức này trả về giá trị byte không dấu đại diện cho mã số của đối tượng Char được truyền cho nó. Trong .NET Framework, đối tượng Char là giá trị 16 bit. Điều này có nghĩa là phương thức này phù hợp để trả về mã số của các ký tự trong phạm vi ký tự ASCII hoặc trong Điều khiển Unicode C0 và Latin cơ bản, và Điều khiển C1 và Phạm vi bổ sung Latin-1, từ U + 0000 đến 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.