2009-03-15 20 views
8

(Trong .NET) Tôi có dữ liệu nhị phân tùy ý được lưu trữ trong một byte [] (ví dụ như một hình ảnh). Bây giờ, tôi cần lưu trữ dữ liệu đó trong một chuỗi (trường "Nhận xét" của một API cũ). Có kỹ thuật tiêu chuẩn cho đóng gói dữ liệu nhị phân này vào một chuỗi không? Bằng cách "đóng gói" tôi có nghĩa là đối với bất kỳ tập hợp dữ liệu lớn và ngẫu nhiên hợp lý nào, bytes.Length/2 tương tự như được đóng gói.Length; bởi vì hai byte là nhiều hơn hoặc ít hơn một ký tự đơn.Có một kỹ thuật tiêu chuẩn để đóng gói dữ liệu nhị phân vào một chuỗi UTF-16 không?

Hai "rõ ràng" câu trả lời không thỏa mãn tất cả các tiêu chí:

string base64 = System.Convert.ToBase64String(bytes) 

không thể sử dụng rất hiệu quả trong những chuỗi vì nó chỉ sử dụng 64 ký tự ra khỏi khoảng 60.000 sẵn (của tôi bộ nhớ là System.String). Đi với

string utf16 = System.Text.Encoding.Unicode.GetString(bytes) 

tận dụng tốt hơn về chuỗi, nhưng nó sẽ không làm việc cho dữ liệu có chứa các ký tự Unicode không hợp lệ (nói cặp thay thế mis-phù hợp). This MSDN article cho thấy kỹ thuật chính xác (kém) này.

Hãy xem xét một ví dụ đơn giản:

byte[] bytes = new byte[] { 0x41, 0x00, 0x31, 0x00}; 
string utf16 = System.Text.Encoding.Unicode.GetString(bytes); 
byte[] utf16_bytes = System.Text.Encoding.Unicode.GetBytes(utf16); 

Trong trường hợp này byteutf16_bytes đều giống nhau, bởi vì gốc byte là một chuỗi UTF-16. Thực hiện thủ tục tương tự với mã hóa base64 này cung cấp mảng base64_bytes 16 thành viên.

Bây giờ, lặp lại các thủ tục với không hợp lệ UTF-16 dữ liệu:

byte[] bytes = new byte[] { 0x41, 0x00, 0x00, 0xD8}; 

Bạn sẽ thấy rằng utf16_bytes không phù hợp với dữ liệu gốc.

Tôi đã viết mã sử dụng U + FFFD làm lối thoát trước các ký tự Unicode không hợp lệ; nó hoạt động, nhưng tôi muốn biết nếu có một kỹ thuật tiêu chuẩn hơn là một cái gì đó tôi chỉ nấu chín một mình. Chưa kể, tôi không thích bắt nhập DecoderFallbackException làm cách phát hiện các ký tự không hợp lệ.

Tôi đoán bạn có thể gọi đây là mã hóa "cơ sở BMP" hoặc "cơ sở UTF-16" (sử dụng tất cả các ký tự trong Unicode Basic Multilingual Plane). Có, lý tưởng tôi sẽ theo dõi Shawn Steele's advice và vượt qua khoảng byte [].


Tôi sẽ đi với đề nghị Peter Housel như câu trả lời "đúng" vì anh ấy là duy nhất mà đến gần gợi ý một "kỹ thuật tiêu chuẩn".


Sửa base16klooks thậm chí tốt hơn. Jim Beveridge có một số implementation.

+0

nào Unicode mã hóa chính xác? .NET sử dụng UTF-8 theo mặc định, nó không hoàn toàn phù hợp với kiểu sử dụng này vì các chuỗi thoát hay bất cứ thứ gì được gọi. –

+0

Điều tôi ngụ ý là việc thoát từng cặp byte trong UTF-8 sẽ lãng phí nhiều hơn so với phương pháp "6 bit trên 8" của Base64. –

+0

@DrJokepu - từ/2, ** có lẽ ** UTF-16 –

Trả lời

4

Tôi tình cờ gặp Base16k sau khi đọc câu hỏi của bạn. Không đúng tiêu chuẩn nhưng nó có vẻ hoạt động tốt và đủ dễ thực hiện trong C#.

+0

Bingo! Điều này dường như gần như chính xác những gì tôi đang tìm kiếm; tùy thuộc vào định nghĩa của bạn về "tiêu chuẩn". –

1

Bạn có thể coi dữ liệu nhị phân là UTF-8b. Mã hóa UTF-8b giả định rằng các byte là các chuỗi đa byte UTF-8, nhưng có mã hóa dự phòng cho những thứ không phải là.

+0

UTF-8b trông rất thú vị. Nhưng không phải những người thay thế thấp không được kết nối sẽ dẫn đến một chuỗi UTF-16 không đúng định dạng? –

+0

Chính xác. Nhưng hầu hết mọi thứ sẽ không quan tâm, và sẽ chỉ vượt qua UTF-16 không hợp lệ. –

+0

Giả sử điều này "API cũ" hợp lý giả định rằng nó đang nhận được một chuỗi thực, vì vậy ai biết nó sẽ làm gì với nó. Nếu chuỗi UTF-16 không đúng định dạng là OK, tôi chỉ có thể "truyền" byte gốc [] thành chuỗi. Tất nhiên, người đại diện chưa được kết nối có thể được coi là một "vi phạm nhỏ". –

0

Tôi bị lừa xung quanh với mảng char trực tiếp và trường hợp không hoạt động của bạn hoạt động với triển khai của tôi. Mã đã được thử nghiệm tốt: vì vậy hãy thực hiện các thử nghiệm của bạn trước.

Bạn có thể tăng tốc độ này bằng cách sử dụng mã không an toàn. Nhưng tôi chắc chắn UnicodeEncoding chỉ là chậm (nếu không chậm hơn).

/// <summary> 
/// Represents an encoding that packs bytes tightly into a string. 
/// </summary> 
public class ByteEncoding : Encoding 
{ 
    /// <summary> 
    /// Gets the Byte Encoding instance. 
    /// </summary> 
    public static readonly Encoding Encoding = new ByteEncoding(); 

    private ByteEncoding() 
    { 
    } 

    public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex) 
    { 
     for (int i = 0; i < chars.Length; i++) 
     { 
      // Work out some indicies. 
      int j = i * 2; 
      int k = byteIndex + j; 

      // Get the bytes. 
      byte[] packedBytes = BitConverter.GetBytes((short) chars[charIndex + i]); 

      // Unpack them. 
      bytes[k] = packedBytes[0]; 
      bytes[k + 1] = packedBytes[1]; 
     } 

     return chars.Length * 2; 
    } 

    public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) 
    { 
     for (int i = 0; i < byteCount; i += 2) 
     { 
      // Work out some indicies. 
      int j = i/2; 
      int k = byteIndex + i; 

      // Make sure we don't read too many bytes. 
      byte byteB = 0; 
      if (i + 1 < byteCount) 
      { 
       byteB = bytes[k + 1]; 
      } 

      // Add it to the array. 
      chars[charIndex + j] = (char) BitConverter.ToInt16(new byte[] { bytes[k], byteB }, 0); 
     } 

     return (byteCount/2) + (byteCount % 2); // Round up. 
    } 

    public override int GetByteCount(char[] chars, int index, int count) 
    { 
     return count * 2; 
    } 

    public override int GetCharCount(byte[] bytes, int index, int count) 
    { 
     return (count/2) + (count % 2); 
    } 

    public override int GetMaxByteCount(int charCount) 
    { 
     return charCount * 2; 
    } 

    public override int GetMaxCharCount(int byteCount) 
    { 
     return (byteCount/2) + (byteCount % 2); 
    } 
} 

Dưới đây là một số mã kiểm tra:

static void Main(string[] args) 
    { 
     byte[] original = new byte[256]; 

     // Note that we can't tell on the decode side how 
     // long the array was if the original length is 
     // an odd number. This will result in an 
     // inconclusive result. 
     for (int i = 0; i < original.Length; i++) 
      original[i] = (byte) Math.Abs(i - 1); 

     string packed = ByteEncoding.Encoding.GetString(original); 
     byte[] unpacked = ByteEncoding.Encoding.GetBytes(packed); 

     bool pass = true; 

     if (original.Length != unpacked.Length) 
     { 
      Console.WriteLine("Inconclusive: Lengths differ."); 
      pass = false; 
     } 

     int min = Math.Min(original.Length, unpacked.Length); 
     for (int i = 0; i < min; i++) 
     { 
      if (original[i] != unpacked[i]) 
      { 
       Console.WriteLine("Fail: Invalid at a position {0}.", i); 
       pass = false; 
      } 
     } 

     Console.WriteLine(pass ? "All Passed" : "Failure Present"); 

     Console.ReadLine(); 
    } 

Bài kiểm tra hoạt động, nhưng bạn sẽ phải thử nghiệm nó với chức năng API của bạn.

0

Có một cách khác để giải quyết giới hạn này: mặc dù tôi không chắc nó sẽ hoạt động tốt như thế nào.

Thứ nhất, bạn sẽ cần phải tìm ra loại chuỗi cuộc gọi API mong đợi - và cấu trúc của chuỗi này là gì. Nếu tôi lấy một ví dụ đơn giản, hãy xem xét chuỗi .Net:

  • Int32 _length;
  • byte [] _data;
  • byte _terminator = 0;

Thêm một tình trạng quá tải để gọi API của bạn, do đó:

[DllImport("legacy.dll")] 
private static extern void MyLegacyFunction(byte[] data); 

[DllImport("legacy.dll")] 
private static extern void MyLegacyFunction(string comment); 

Sau đó, khi bạn cần phải gọi phiên bản byte bạn có thể làm như sau:

public static void TheLegacyWisperer(byte[] data) 
    { 
     byte[] realData = new byte[data.Length + 4 /* _length */ + 1 /* _terminator */ ]; 
     byte[] lengthBytes = BitConverter.GetBytes(data.Length); 
     Array.Copy(lengthBytes, realData, 4); 
     Array.Copy(data, 0, realData, 4, data.Length); 
     // realData[end] is equal to 0 in any case. 
     MyLegacyFunction(realData); 
    } 
10

Tôi có thể đề nghị bạn do sử dụng base64? Nó có thể không phải là cách hiệu quả nhất để làm điều đó theo chiều kim đồng hồ, nhưng nó có những lợi ích của nó:

  1. Nỗi lo của bạn đã hết.
  2. Bạn sẽ gặp phải sự cố tương thích ít nhất với những người chơi khác, nếu có.
  3. Chuỗi được mã hóa có được coi là ASCII trong quá trình chuyển đổi, xuất, nhập, sao lưu, khôi phục, bất kỳ điều gì, bạn cũng sẽ không gặp bất kỳ sự cố nào.
  4. Nếu bạn đã từng chết hoặc kết thúc dưới một chiếc xe buýt hay gì đó, bất kỳ lập trình viên nào từng nắm tay trên lĩnh vực nhận xét sẽ ngay lập tức biết rằng đó là base64 và không cho rằng nó được mã hóa hoặc gì đó.
+0

Điểm # 4 chính xác là lý do tại sao tôi đang tìm kiếm "một kỹ thuật tiêu chuẩn hơn so với thứ tôi vừa tự nấu". Sử dụng chỉ 6 bit với base64 có vẻ như một sự lãng phí khi tôi BIẾT chuỗi là UTF-16 và có gần 16 bit có sẵn ("gần" vì chuỗi bit không phải là ký tự hợp lệ). –

+0

True, nó có thể âm thanh như một sự lãng phí, nhưng như vậy là bất kỳ tập tin khác được lưu dưới dạng UTF-16 vì hầu hết chúng ta không sử dụng toàn bộ không gian ký tự. Bên cạnh đó nếu ứng dụng kế thừa đó là thông minh, nó sẽ lưu dưới dạng UTF-8 vào một cơ sở dữ liệu hoặc một tệp, trong trường hợp đó nó sẽ có tác dụng phụ nếu bạn định sử dụng tất cả Unicode. –

+0

Có lẽ chuỗi UTF-16 luôn nằm trong bộ nhớ; do đó, nó (phần nào) quan trọng để sử dụng không gian một cách hiệu quả. Bộ nhớ vẫn còn tương đối đắt so với đĩa, đặc biệt là với không gian địa chỉ hạn chế hơn trên máy 32 bit. –

3

Trước hết, hãy nhớ rằng Unicode không có nghĩa là 16 bit. Thực tế là System.String sử dụng UTF-16 nội bộ không phải là ở đây cũng không có. Các ký tự Unicode là trừu tượng - chúng chỉ thu được các biểu diễn bit thông qua các mã hóa.

Bạn nói "lưu trữ của tôi là một System.String" - nếu đó là trường hợp, bạn không thể nói về bit và byte, chỉ ký tự Unicode. System.String chắc chắn có mã hóa nội bộ riêng, nhưng (theo lý thuyết) có thể khác nhau.

Ngẫu nhiên, nếu bạn tin rằng biểu diễn bên trong của System.String quá bộ nhớ không hiệu quả đối với dữ liệu được mã hóa Base64, tại sao bạn cũng không lo lắng về các chuỗi tiếng Latinh/phương Tây?

Nếu bạn muốn lưu trữ dữ liệu nhị phân trong System.String, bạn cần ánh xạ giữa các bộ sưu tập bit và ký tự.

Tùy chọn A: Có một mẫu được tạo sẵn trong hình dạng mã hóa Base64. Như bạn đã chỉ ra, điều này mã hóa sáu bit dữ liệu cho mỗi ký tự.

Tùy chọn B: Nếu bạn muốn đóng gói nhiều bit hơn cho mỗi ký tự, bạn sẽ cần tạo một mảng (hoặc mã hóa) gồm 128, 256, 512, vv Ký tự Unicode và gói 7, 8, 9, v.v. bit dữ liệu trên mỗi ký tự. Những ký tự đó cần phải là các ký tự Unicode thực.

Để trả lời câu hỏi của bạn đơn giản, có một tiêu chuẩn, đó là mã hóa Base64.

Đây có phải là vấn đề thực sự không? Bạn có dữ liệu perf để hỗ trợ ý tưởng của bạn không sử dụng Base64?

+0

Đó là cách sử dụng bộ nhớ. Trong ví dụ trên, base64 sử dụng bộ nhớ nhiều gấp 4 lần UTF-16: 16 byte so với 4 byte. Ứng dụng kế thừa mà tôi đang làm việc đã cảm thấy áp lực bộ nhớ trên các hệ thống 32 bit, vì vậy tôi muốn tìm một cách "dễ dàng" và "chuẩn" để có hiệu quả hơn. –

+0

Base64 sử dụng 4/3 (1,333 ~) lần kích thước bộ nhớ làm dữ liệu nhị phân nguồn. Nếu bạn lưu trữ base64 dưới dạng chuỗi UTF-8, việc sử dụng bộ nhớ sẽ nhỏ hơn đáng kể so với lưu trữ dưới dạng chuỗi UTF-16. –

1

Dưới đây là một # phiên bản C của C++ của Jim Beveridge implementation:

using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Text.RegularExpressions; 
using System.Linq; 


// 
// Base16k.cpp : Variant of base64 used to efficiently encode binary into Unicode UTF16 strings. Based on work by 
// Markus Scherer at https://sites.google.com/site/markusicu/unicode/base16k 
// 
// This code is hereby placed in the Public Domain. 
// Jim Beveridge, November 29, 2011. 
// 
// C# port of http://qualapps.blogspot.com/2011/11/base64-for-unicode-utf16.html 
// This code is hereby placed in the Public Domain. 
// J. Daniel Smith, February 23, 2015 
// 

namespace JDanielSmith 
{ 
    public static partial class Convert 
    { 
     /// <summary> 
     /// Encode a binary array into a Base16k string for Unicode. 
     /// </summary> 
     public static string ToBase16kString(byte[] inArray) 
     { 
      int len = inArray.Length; 

      var sb = new StringBuilder(len*6/5); 
      sb.Append(len); 

      int code = 0; 

      for (int i=0; i<len; ++i) 
      { 
       byte byteValue = inArray[i]; 
       switch (i%7) 
       { 
       case 0: 
        code = byteValue<<6; 
        break; 

       case 1: 
        code |= byteValue>>2; 
        code += 0x5000; 
        sb.Append(System.Convert.ToChar(code)); 
        code = (byteValue&3)<<12; 
        break; 

       case 2: 
        code |= byteValue<<4; 
        break; 

       case 3: 
        code |= byteValue>>4; 
        code+=0x5000; 
        sb.Append(System.Convert.ToChar(code)); 
        code = (byteValue&0xf)<<10; 
        break; 

       case 4: 
        code |= byteValue<<2; 
        break; 

       case 5: 
        code|=byteValue>>6; 
        code+=0x5000; 
        sb.Append(System.Convert.ToChar(code)); 
        code=(byteValue&0x3f)<<8; 
        break; 

       case 6: 
        code|=byteValue; 
        code+=0x5000; 
        sb.Append(System.Convert.ToChar(code)); 
        code=0; 
        break; 
       } 
      } 

      // emit a character for remaining bits 
      if (len%7 != 0) { 
       code += 0x5000; 
       sb.Append(System.Convert.ToChar(code)); 
      } 

      return sb.ToString(); 
     } 

     /// <summary> 
     /// Decode a Base16k string for Unicode into a binary array. 
     /// </summary> 
     public static byte[] FromBase16kString(string s) 
     { 
      // read the length 
      var r = new Regex(@"^\d+", RegexOptions.None, matchTimeout: TimeSpan.FromMilliseconds(100)); 
      Match m = r.Match(s); 
      if (!m.Success) 
       return null; 

      int length; 
      if (!Int32.TryParse(m.Value, out length)) 
       return null; 

      var buf = new List<byte>(length); 

      int pos=0; // position in s 
      while ((pos < s.Length) && (s[pos] >= '0' && s[pos] <= '9')) 
       ++pos; 

      // decode characters to bytes 
      int i = 0; // byte position modulo 7 (0..6 wrapping around) 
      int code=0; 
      byte byteValue=0; 

      while (length-- > 0) 
      { 
       if (((1<<i)&0x2b)!=0) 
       { 
        // fetch another Han character at i=0, 1, 3, 5 
        if(pos >= s.Length) 
        { 
         // Too few Han characters representing binary data. 
         System.Diagnostics.Debug.Assert(pos < s.Length); 
         return null; 
        } 

        code=s[pos++]-0x5000; 
       } 

       switch (i%7) 
       { 
       case 0: 
        byteValue = System.Convert.ToByte(code>>6); 
        buf.Add(byteValue); 
        byteValue = System.Convert.ToByte((code&0x3f)<<2); 
        break; 

       case 1: 
        byteValue |= System.Convert.ToByte(code>>12); 
        buf.Add(byteValue); 
        break; 

       case 2: 
        byteValue = System.Convert.ToByte((code>>4)&0xff); 
        buf.Add(byteValue); 
        byteValue = System.Convert.ToByte((code&0xf)<<4); 
        break; 

       case 3: 
        byteValue |= System.Convert.ToByte(code>>10); 
        buf.Add(byteValue); 
        break; 

       case 4: 
        byteValue = System.Convert.ToByte((code>>2)&0xff); 
        buf.Add(byteValue); 
        byteValue = System.Convert.ToByte((code&3)<<6); 
        break; 

       case 5: 
        byteValue |= System.Convert.ToByte(code>>8); 
        buf.Add(byteValue); 
        break; 

       case 6: 
        byteValue = System.Convert.ToByte(code&0xff); 
        buf.Add(byteValue); 
        break; 
       } 

       // advance to the next byte position 
       if(++i==7) 
        i=0; 
      } 

      return buf.ToArray(); 
     } 
    } 
} 

namespace Base16kCS 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var drand = new Random(); 

      // Create 500 different binary objects, then encode and decode them. 
      // The first 16 objects will have length 0,1,2 ... 16 to test boundary conditions. 
      for (int loop = 0; loop < 500; ++loop) 
      { 
       Console.WriteLine("{0}", loop); 

       int dw = drand.Next(128000); 
       var org = new List<byte>(dw); 
       for (int i = 0; i < dw; ++i) 
        org.Add(Convert.ToByte(drand.Next(256))); 

       if (loop < 16) 
        org = org.Take(loop).ToList(); 

       string wstr = JDanielSmith.Convert.ToBase16kString(org.ToArray()); 

       byte[] bin = JDanielSmith.Convert.FromBase16kString(wstr); 

       System.Diagnostics.Debug.Assert(org.SequenceEqual(bin)); 
      } 
     } 
    } 
} 
Các vấn đề liên quan