二进制如何转化成二进制字符串?

5
Redis键是二进制安全的。我想用C#把二进制数据存入Redis中。我的客户端不支持写入二进制键,使用键是有道理的。但我只是在瞎搞,告诉我怎么做。
如何将一个原始的byte[]转换为字符串?起初我想将byte[]转换为utf8字符串,但Unicode有一些检查来判断它是否有效。所以原始的二进制应该会失败。
实际上,我试过了。没有失败,反而得到了奇怪的结果。我的主要问题是如何将原始的byte[]转换为等效的字符串?也就是将原始的byte[]作为字符串,而不是编码为base32/64/hex或其他格式。我的次要问题是为什么我得到了一个512字节的字符串,而不是一个异常,说这不是一个有效的UTF8字符串?
代码
var rainbow = new byte[256];
for (int i = 0; i < 256; i++)
{
    rainbow[i] = (byte)i;
}
var sz = Encoding.UTF8.GetString(rainbow);
var szarr = Encoding.UTF8.GetBytes(sz);
Console.WriteLine("{0} {1} {2}", ByteArraysEqual(szarr, rainbow), szarr.Length, rainbow.Length);

Output

False 512 256


@dbaseman:不,我不想将二进制编码为文本。我想要字符串结构中的原始二进制数据。现在我编辑了一行代码... - user34537
我认为被接受的答案非常错误... 另外:如果你正在使用BookSleeve,我花了很多时间考虑二进制键(BookSleeve使用二进制协议,所以这很容易实现)-问题很简单:我认为绝大多数用户将使用字符串键-不确定值得将API加倍以支持两者。我有一个聪明的方法可以在一个API上同时支持两者,但这将是一个破坏性的变化。 - Marc Gravell
@MarcGravell,你可以这样做,但我实际上并没有计划这样做。也许对于某个特定的问题可以使用C语言,但是......我必须进行测试以确定它是否值得。除非有大量的键需要处理,否则我不会考虑它,而且我仍然需要测试,还需要进行前缀处理(不能使用bytesForId,需要一个字节或文本前缀来区分评论、帖子和其他ID)。除非你在一个重要的项目中看到了用途,否则我认为这不值得。就像我说的,唯一能想到的生产项目(我只是为了好玩在做redis)是使用C语言,而不是C#。 - user34537
2个回答

9
如果您有一个任意的byte[],将其转换为字符串的方法是将其转换为类似于十六进制或base-64的东西。最简单的方法如下:
byte[] key = ...
string s = Convert.ToBase64String(key);

反过来也是一样:

key = Convert.FromBase64String();

尽管使用System.Text.Encoding等工具很诱人,但这是完全不正确的,且无法用于进行强大的数据转换。如果您使用Encoding,会出现两个问题:

  • 许多键无法成功地互相转换
  • 许多不同的byte[]键可能会变成相同的string键

这两个问题都很严重!问题在于使用方向相反:编码(Encoding)将任意字符串转换为/从结构化的byte[],允许您对任何字符串进行编码/解码。Base-64将任意byte[]转换为/从结构化的字符串。非常微妙的区别,但非常重要。


感谢您的澄清,特别是有用的区分二进制数据的字符串表示(base 64)和字符串的二进制表示(编码)。然而,由于iso-8859-1将字符1-256一对一地映射到它们的二进制值,我认为它应该适用于Op问题的范围...? - McGarnagle
1
调用encoding.GetString对于任意的byte[]来说是完全错误的 - 只有当byte[]恰好包含通过相同编码方式编码的字符串数据时才有意义。因此,在任意的byte[]上调用GetString可能会导致返回不是原始byte[]的随机字符串 - 或者它可能会抛出异常。关于容器是否具有二进制安全性:是的,但这只有在传递原始byte[]时才有意义 - 实际上,在传递到redis之前,基于字符串的键已经被UTF-8编码。老实说:在任意集合上调用GetString是没有意义的。 - Marc Gravell
实际上,在进入Redis之前,基于字符串的键已经是UTF-8编码的。等等,Redis明确表示它是二进制安全的。为什么会干扰我的字节?是客户端还是Redis本身的某些东西改变了UTF8吗?我也理解你所说的不安全是因为它不是原始编码,但我没有将编码用作字符串,只是一种将我的字节转换为字符串对象的方法。我也理解实现可能会改变,但它是ISO标准,所以我不认为这个实现会改变。它使用8位(更多内容如下)。 - user34537
不要对位进行转换,这就是我需要的。UTF8保留了一些位并且有无效的字节表示,我知道这一点并且不能使用它。这个标准已经写好了并且不会改变。它允许8位并且不会乱搞,所以我认为它适合用于交流,只要我不期望0xAB被表示为特定的字符(这就是为什么我说iso-8859-X可以,西里尔文也可以,这是iso-8859-5)。 - user34537
@acidzombie24 "为什么会影响我的字节": 因为你明确表示你正在使用一个以字符串键术语交流的客户端。Redis键确实是二进制的,因此客户端将把字符串键转换为二进制,并且显而易见/典型的方法是:UTF8。记住:你没有给它字节 - 你给了它一个字符串。 - Marc Gravell
显示剩余4条评论

3

您需要使用某种编码将字节转换为字符串。编码iso-8859-1将会给出正确的结果:

var sz = Encoding.GetEncoding("iso-8859-1").GetString(rainbow);
var szarr = Encoding.GetEncoding("iso-8859-1").GetBytes(sz);
Console.WriteLine("{0} {1} {2}", ByteArraysEqual(szarr, rainbow), szarr.Length, rainbow.Length);

真实 256 256

事实上,UTF8需要一个以上的字节来表示一个字符。它可以用一个字节编码前128个字符:

Console.Write(Encoding.UTF8.GetBytes(Encoding.UTF8.GetString(new byte[] { 127 })).Length);

1

但其余的需要三个字节:

Console.Write(Encoding.UTF8.GetBytes(Encoding.UTF8.GetString(new byte[] { 128 })).Length);

因此,当您使用UTF8将字节0-255转换为字符串再转回来时,前128个字节返回为一个字节,但后128个字节返回为3个字节。128 + 3 * 128 = 512,因此您得到的结果为512。ASCII不知道如何处理128以后的字节,所以它们只被编码为“?”并作为一个字节返回。

ASCII 编码出现错误,最后 128 个字符是无效的。-编辑- 现在你修改了。好的,很好理解,但我认为它不应该尝试将无效的 byte[] 转换为 UTF8 并损坏 byte[]。这就是为什么我认为应该抛出异常的原因。 - user34537
@acidzombie24 噢,对了。前128个将相等,但之后的所有字节将只是 ? 63 - McGarnagle
1
我搞定了。Encoding.Default 是 "iso-8859-1"。var encoding = Encoding.GetEncoding("iso-8859-1"); var sz = encoding.GetString(rainbow); var szarr = encoding.GetBytes(sz); 运行正常。 - user34537
2
Encoding.DefaultEncoding不存在;Encoding.Default指的是操作系统默认的代码页,可能有很多不同的设置 - 它不是8859-1。 - Marc Gravell
在我的情况下,当我使用Encoding.GetEncoding("iso-8859-1")时,它返回一个编码为28591的编码,但它不起作用,但是Encoding.GetEncoding(1252)可以正常工作! - S.Serpooshan
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接