(在.NET中)我将任意二进制数据存储在 byte [] (例如图像)中。现在,我需要将该数据存储在字符串(旧版API的“注释”字段)中。是否有一种标准技术可以将打包这个二进制数据转换为字符串?通过“打包”,我的意思是对于任何合理大的随机数据集, bytes.Length / 2 与 packed.Length 大致相同;因为两个字节或多或少都是一个字符。
这两个“明显”的答案不符合所有标准:
string base64 = System.Convert.ToBase64String(bytes)
没有非常有效地使用字符串,因为它只使用大约60,000个可用的64个字符(我的存储是 System.String )。跟着
string utf16 = System.Text.Encoding.Unicode.GetString(bytes)
更好地利用字符串,但它不适用于包含无效Unicode字符的数据(例如错误匹配的代理项对)。 This MSDN article显示了这种确切(差)技术。
让我们看一个简单的例子:
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);
在这种情况下, bytes 和 utf16_bytes 是相同的,因为原始字节是UTF-16字符串。使用base64编码执行相同的过程会产生16个成员的 base64_bytes 数组。
现在,使用无效的UTF-16数据重复该过程:
byte[] bytes = new byte[] { 0x41, 0x00, 0x00, 0xD8};
你会发现 utf16_bytes 与原始数据不匹配。
我编写的代码在无效的Unicode字符之前使用U + FFFD作为转义符;它有效,但我想知道是否有一种比我自己制作的更标准的技术。更不用说了,我不喜欢 catch 将 DecoderFallbackException 作为检测无效字符的方法。
我猜你可以称之为“基本BMP”或“基本UTF-16”编码(使用Unicode基本多语言平面中的所有字符)。是的,理想情况下我会关注Shawn Steele's advice并传递 byte [] 。
<击> 我将把Peter Housel的建议作为“正确”的答案,因为他是唯一接近建议“标准技术”的人。 击>
更好地修改base16k looks。吉姆贝弗里奇有implementation。
答案 0 :(得分:12)
我建议你做使用base64吗?它可能不是最有效的存储方式,但确实有其好处:
答案 1 :(得分:5)
阅读完问题后,我偶然发现了Base16k。不是严格的标准,但似乎运行良好,并且很容易在C#中实现。
答案 2 :(得分:3)
首先,请记住,Unicode并不意味着16位。 System.String在内部使用UTF-16的事实既不在这里,也不在那里。 Unicode字符是抽象的 - 它们只能通过编码获得位表示。
你说“我的存储是一个System.String” - 如果是这种情况,你不能谈论位和字节,只能谈论Unicode字符。 System.String肯定有它自己的内部编码,但(理论上)可能不同。
顺便提一下,如果您认为System.String的内部表示对于Base64编码的数据而言内存效率太低,为什么您还不担心拉丁语/西方字符串?
如果要在System.String中存储二进制数据,则需要在位和字符集之间进行映射。
选项A:有一个预制的Base64编码形状。正如您所指出的,这会为每个字符编码六位数据。
选项B:如果要为每个字符打包更多位,则需要创建128,256,512等Unicode字符的数组(或编码),并打包7,8,9等位每个字符的数据。这些字符必须是真正的Unicode字符。
简单地回答你的问题,是的,有一个标准,它是Base64编码。
这是一个真正的问题吗?您是否有perf数据支持您不使用Base64的想法?
答案 3 :(得分:2)
您可以将二进制数据视为UTF-8b。 UTF-8b编码假定字节是UTF-8多字节序列,但是对于非字节序列具有回退编码。
答案 4 :(得分:1)
这是Jim Beveridge的C#版{C} 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));
}
}
}
}
答案 5 :(得分:0)
我骗了直接字符数组,你的一个失败案例适用于我的实现。代码已经过良好测试:首先进行测试。
您可以使用不安全的代码加快速度。但我确信UnicodeEncoding也一样慢(如果不慢)。
/// <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);
}
}
以下是一些测试代码:
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();
}
测试有效,但您必须使用API函数对其进行测试。
答案 6 :(得分:0)
还有另一种解决这个限制的方法:虽然我不确定它的效果如何。
首先,您需要确定API调用期望的字符串类型 - 以及此字符串的结构。如果我举一个简单的例子,让我们考虑.Net字符串:
为API调用添加重载,因此:
[DllImport("legacy.dll")]
private static extern void MyLegacyFunction(byte[] data);
[DllImport("legacy.dll")]
private static extern void MyLegacyFunction(string comment);
然后,当您需要调用字节版本时,您可以执行以下操作:
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);
}