如何在C#中直接将十进制字符串转换为字节数组?

3
我希望您能翻译一个长度可变的字符串输入,将其数值化并解析为字节数组,同时不对其长度施加任何限制。我已经完成了二进制和十六进制的部分:
public static byte[] GetHexBytes(this string hex, bool preTrimmed = false)
{
    if (!preTrimmed)
    {
        hex = hex.Trim();
        if (hex.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
            hex = hex.Substring(2);
        else if (hex.StartsWith("16#"))
            hex = hex.Substring(3);
    }

    if (hex.Length % 2 != 0) hex = hex.PadLeft(hex.Length + 1, '0');

    return Enumerable.Range(0, hex.Length)
         .Where(x => x % 2 == 0)
         .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
         .ToArray();
}

public static byte[] GetBinaryBytes(this string binary, bool preTrimmed = false)
{
    if (!preTrimmed)
    {
        binary = binary.Trim();
        if (binary.StartsWith("0b", StringComparison.OrdinalIgnoreCase) || binary.StartsWith("2#"))
            binary = binary.Substring(2);
    }

    if (binary.Length % 8 != 0) binary = binary.PadLeft(binary.Length + 8 - binary.Length % 8, '0');

    return Enumerable.Range(0, binary.Length)
         .Where(x => x % 8 == 0)
         .Select(x => Convert.ToByte(binary.Substring(x, 8), 2))
         .ToArray();
}

public static byte[] GetDecimalBytes(this string dec, bool preTrimmed = false)
{
    if (!preTrimmed)
    {
        dec = dec.Trim();
        if (dec.StartsWith("10#"))
            dec = dec.Substring(3);
    }

    //???
}

是否可能以类似于十六进制和二进制版本的方式(从字符串开头开始,向前工作)进行正向转换?

如果不行,是否可以倒序工作,而不对字符串长度施加任何限制,而且不使用System.Numerics或BigInteger之类的东西(手动从字符串中执行)?

我希望它能处理任何字符串长度,最大字符串长度为C#(1,073,741,823)。

示例

"FF".GetHexBytes() => [ 0xFF ]
"11111111".GetBinaryBytes() => [ 0xFF ]
"255".GetDecimalBytes() => [ 0xFF ]

"FFFF".GetHexBytes() => [ 0xFF, 0xFF ]
"1111111111111111".GetBinaryBytes() => [ 0xFF, 0xFF ]
"65535".GetDecimalBytes() => [ 0xFF, 0xFF ]

如果我理解正确的话,您可以通过以下方式将字符串转换为字节数组:byte[] byteKey = System.Text.ASCIIEncoding.ASCII.GetBytes("yourString") - Zippy
1
请举一个输入和输出的例子? - Lasse V. Karlsen
1
我有这样的印象,你想要结果是这样的,例如 "0xFFFFFFFF".GetHexBytes() == "4294967295".GetDecimalBytes(),因为 0xFFFFFFFF == 4294967295。这自然而然地扩展到其他值。这个印象是正确的吗? - user743382
1
更好的方法是查看CoreFX实现。与参考源代码不同,CoreFX的许可证允许您根据自己的需求调整代码。BigInteger在内部以非常接近所需格式的格式存储值。只需要进行最小的更改即可提取数字解析以使用byte数组而不是uint数组。 - user743382
1
你不能按从左到右的方式处理十进制数,因为10不是2的幂,并且进位的位只有在开始处理后才会被发现。从左到右处理二进制、八进制和十六进制的原因是没有未使用的位依赖于尚未被处理的右侧数据。你可以使用缓冲数组来缓存位,但这将需要缓冲区与原始字符串一样大(在最坏的情况下),所以这不值得。 - user5069935
显示剩余13条评论
2个回答

2

我无法抵制构建一个有点可行的解决方案来回答这个问题。然而,正如我之前评论的那样,称呼这种方法为“直接”有点言过其实,而且所呈现的代码既不验证输入也不高效。

我的基本方法如下:

  1. 将每个数字转换为零基位索引列表,并与数字的索引位置相关联
  2. 根据数字位置转换位索引列表
    • 使用的方法:value * 10 == value * (8 + 2) == (value << 3) + (value << 1)
  3. 通过消除两个相等的索引值并添加它们的后继值(按位加法)来总结位索引

从理论上讲,在最后应用一次位加法就足够了,但在现实中,需要中间压缩以避免大数出现内存异常。

// example input
string input = "6524562164126412641206685";

var result = input
    // interpret the string as a list of digits with position
    .Reverse()
    // transfer from list of positioned digits to list of actual bit positions,
    // by repeatedly multiplying with 10
    // the resulting bits need to be added for the final result
    .SelectMany((x, i) =>
    {
        // digit value
        var val1 = x - '0';
        var res1 = new List<int>();
        // to bit positions, as if it was the first digit
        for (int j = 0; j < 8; j++)
        {
            if ((val1 & (1 << j)) != 0) res1.Add(j);
        }
        // to absolute bit positions, taking the digit position into account
        for (int j = 1; j <= i; j++)
        {
            var res = new List<int>();
            // multiply by 10, until actual position is reached
            foreach (var item in res1)
            {
                res.Add(item + 1);
                res.Add(item + 3);
            }
            // compress bits
            res1 = res.Aggregate(new HashSet<int>(), (set, i1) =>
                {
                    // two bits in the same position add up to one bit in a higher position
                    while (set.Contains(i1))
                    {
                        set.Remove(i1);
                        i1++;
                    }
                    set.Add(i1);
                    return set;
                }).ToList();
        }
        return res1;
    }).
    // final elimination of duplicate bit indices
    Aggregate(new HashSet<int>(), (set, i) =>
    {
        while (set.Contains(i))
        {
            set.Remove(i);
            i++;
        }
        set.Add(i);
        return set;
    })
    // transfer bit positions into a byte array - lowest bit is the last bit of the first byte
    .Aggregate(new byte[(long)Math.Ceiling(input.Length / 2.0)], (res, bitpos) =>
    {
        res[bitpos / 8] |= (byte)(1 << (bitpos % 8));
        return res;
    });

实际上,将这些操作拆分成单独的函数比使用这个庞大的linq代码块更为推荐。 ;)

既然您对linq非常了解,能否看一下我如何实现GetOctalBytes(有些是基于您的示例),并给我一些指导?https://github.com/Ehryk/HashCompute/blob/master/SourceCode/Core/Extensions.cs - Ehryk
"256".ToDecimalBytes() => 数组索引超出了范围。 - Ehryk
@Ehryk 已修复。昨天有点晚了,所以我没有检查所有情况,当我评估时,字节数组的长度应该是输入字符串长度的一半。 - grek40
@Ehryk 关于你的 GetOctalBytes 函数:这有点离题,如果你想要反馈,可以在 CodeReview 上发起一个问题。不过我可以给你一个指针:如果你使用 .SelectMany((element, index) => { ... }) 重载,你应该能够一次性计算出字节数组的位置,而不是通过 bool 数组进行遍历。 - grek40
问题提出:http://codereview.stackexchange.com/questions/120111/converting-from-strings-to-byte-arrays-with-linq-in-c - Ehryk

0

虽然已经晚了5年,但或许这篇文章能帮到其他人:

public static byte[] GetHexBytes(this string hex)
{
    return BigInteger.Parse(hex, System.Globalization.NumberStyles.HexNumber).ToByteArray(true);
}

public static byte[] GetDecimalBytes(this string dec)
{
     return BigInteger.Parse(dec).ToByteArray(true);
}

// Example:
using System.Numerics;

byte[] inHex = "FFFF".GetHexBytes();      // => [ 0xFF, 0xFF ]
byte[] inDec = "65535".GetDecimalBytes(); // => [ 0xFF, 0xFF ]

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