将字节数组转换为任何基数

时间:2010-07-16 12:45:16

标签: c# .net algorithm encoding base64

我有一个字节数组(任意长度),我想用我自己的基本编码器将这个数组编码成字符串。在.NET中是标准Base64编码器,但如果我想在Base62Base53Base13编码数组怎么办?

甚至可以创建这样的通用基础编码器吗?

我知道我可以用简单的方法做到这一点,也就是说,对于每个字节保留固定数量的字符(在Base62的情况下,这将是5个字符),并做直接字节 - >字符编码,但我会浪费空间,因为5个Base62字符可以包含多于1个字节,但少于2个字节。

我该如何编写这样的编码器?或者已经有了一些课程呢? 请注意我也需要通用解码器,否则这对我来说没用。

资源

由于解决方案已经知道(使用BigInteger),我只想在这里提供一些与BigInteger类相关的资源,因为它在.NET 3.5中不可用:

Big integers in C#
http://intx.codeplex.com/
https://svn.apache.org/repos/asf/incubator/heraldry/libraries/csharp/openid/trunk/Mono/Mono.Math/BigInteger.cs
http://www.codeproject.com/KB/cs/BigInteger_Library.aspx
http://www.codeproject.com/KB/cs/biginteger.aspx

9 个答案:

答案 0 :(得分:11)

派对有点晚了,但是......

因为您的规范要求任意数量的位,所以必须具有可以使用任意位数的整数类型。如果您无法定位.NET 4.0,则必须在某处(例如.NET 4.0)乞讨,借用或窃取BigInteger实现。

public static class GenericBaseConverter
{
    public static string ConvertToString(byte[] valueAsArray, string digits, int pad)
    {
        if (digits == null)
            throw new ArgumentNullException("digits");
        if (digits.Length < 2)
            throw new ArgumentOutOfRangeException("digits", "Expected string with at least two digits");

        BigInteger value = new BigInteger(valueAsArray);
        bool isNeg = value < 0;
        value = isNeg ? -value : value;

        StringBuilder sb = new StringBuilder(pad + (isNeg ? 1 : 0));

        do
        {
            BigInteger rem;
            value = BigInteger.DivRem(value, digits.Length, out rem);
            sb.Append(digits[(int)rem]);
        } while (value > 0);

        // pad it
        if (sb.Length < pad)
            sb.Append(digits[0], pad - sb.Length);

        // if the number is negative, add the sign.
        if (isNeg)
            sb.Append('-');

        // reverse it
        for (int i = 0, j = sb.Length - 1; i < j; i++, j--)
        {
            char t = sb[i];
            sb[i] = sb[j];
            sb[j] = t;
        }

        return sb.ToString();

    }

    public static BigInteger ConvertFromString(string s, string digits)
    {
        BigInteger result;

        switch (Parse(s, digits, out result))
        {
            case ParseCode.FormatError:
                throw new FormatException("Input string was not in the correct format.");
            case ParseCode.NullString:
                throw new ArgumentNullException("s");
            case ParseCode.NullDigits:
                throw new ArgumentNullException("digits");
            case ParseCode.InsufficientDigits:
                throw new ArgumentOutOfRangeException("digits", "Expected string with at least two digits");
            case ParseCode.Overflow:
                throw new OverflowException();
        }

        return result;
    }

    public static bool TryConvertFromString(string s, string digits, out BigInteger result)
    {
        return Parse(s, digits, out result) == ParseCode.Success;
    }

    private enum ParseCode
    {
        Success,
        NullString,
        NullDigits,
        InsufficientDigits,
        Overflow,
        FormatError,
    }

    private static ParseCode Parse(string s, string digits, out BigInteger result)
    {
        result = 0;

        if (s == null)
            return ParseCode.NullString;
        if (digits == null)
            return ParseCode.NullDigits;
        if (digits.Length < 2)
            return ParseCode.InsufficientDigits;

        // skip leading white space
        int i = 0;
        while (i < s.Length && Char.IsWhiteSpace(s[i]))
            ++i;
        if (i >= s.Length)
            return ParseCode.FormatError;

        // get the sign if it's there.
        BigInteger sign = 1;
        if (s[i] == '+')
            ++i;
        else if (s[i] == '-')
        {
            ++i;
            sign = -1;
        }

        // Make sure there's at least one digit
        if (i >= s.Length)
            return ParseCode.FormatError;


        // Parse the digits.
        while (i < s.Length)
        {
            int n = digits.IndexOf(s[i]);
            if (n < 0)
                return ParseCode.FormatError;
            BigInteger oldResult = result;
            result = unchecked((result * digits.Length) + n);
            if (result < oldResult)
                return ParseCode.Overflow;

            ++i;
        }

        // skip trailing white space
        while (i < s.Length && Char.IsWhiteSpace(s[i]))
            ++i;

        // and make sure there's nothing else.
        if (i < s.Length)
            return ParseCode.FormatError;

        if (sign < 0)
            result = -result;

        return ParseCode.Success;
    }
}

答案 1 :(得分:3)

如果性能不是问题,请在后台使用BigInteger课程。你有一个BigInteger的构造函数,它接受字节数组,然后你可以手动运行除法和模数的循环来获得其他非标准基数的表示。

另请查看this

答案 2 :(得分:3)

以下是我blog的副本,希望能帮助我转换为Base62的原因(以及为什么)

我目前正致力于自己的网址缩短:konv.es。为了创建url的尽可能短的字符哈希,我使用字符串的GetHashCode()方法,然后将结果数转换为base 62([0-9a-zA-Z])。到目前为止,我发现最优雅的解决方案是转换(这也是收益率回报的一个方便的例子):

public static IEnumerable<char> ToBase62(int number)
    {
        do
        {
            yield return "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"[number % 62];
            number /= 62;

        } while (number > 0);
    }

额外学分:重新考虑作为一种扩展方法

答案 3 :(得分:1)

您可以从Michael Giagnocavo的Base32实施的C#实现中获得灵感。

答案 4 :(得分:1)

BASE64运行良好,因为64是2(2 ^ 6)的幂,所以每个字符保存6位数据,3个字节(3 * 8 = 24位)可以编码为4个字符(4 * 6 = 24)。编码&amp;解码只能是位移位。

对于不与2的幂对齐的基数(如基数62​​或Base 53),则必须将要尝试编码的消息视为一个长数,并对其执行divison和modulo操作。使用Base32编码并浪费一点带宽可能会更好。

答案 5 :(得分:0)

另一个要查看的示例是Ascii85,用于Adobe PostScript和PDF文档。在Ascii85中,5个字符用于编码4个字节。你可以计算出这种编码的效率为(256 ^ 4)/(85 ^ 5)= 96.8%。这是实际使用的比特组合的一部分。

因此,对于您希望用于编码数据的新基础,如果您正在尝试最大化编码效率,那么您希望寻找能够使其高于256的幂的功率。对于每个基地来说,这可能并不容易。检查基数53表明,你可能得到的最好的是使用7个字节来编码5个字节(效率为93.6%),除非你想用88个字节来编码63个字节。

答案 6 :(得分:0)

我编写了一个article,它描述了一个完全处理问题的Python解决方案。我没有使用Python的非常特殊的功能来获得一个可以很容易地用其他语言实现的解决方案。您可以查看一下是否符合您的需求。

答案 7 :(得分:0)

A post on CodeReview提示我创建一个RadixEncoding类,它能够处理字节数组到/从base-N字符串的编码/解码。

在处理BigInteger,endian-ness支持和类的整体性能

时,可以找到类in this Q&A thread,以及一些边缘情况的文档(以及解决方案)。

答案 8 :(得分:0)

这是将字节数组转换为base64的示例代码片段。有一篇很好的文章,我从this那里获得了参考。

public class Test {

    private static final char[] toBase64URL = {
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
            'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
            'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
    };

    public static void main(String[] args) {

        byte[] mess = "ABC123".getBytes();

        byte[] masks = { -128, 64, 32, 16, 8, 4, 2, 1 };
        StringBuilder builder = new StringBuilder();

        for(int i = 0; i < mess.length; i++) {
            for (byte m : masks) {
                if ((mess[i] & m) == m) {
                    builder.append('1');
                } else {
                    builder.append('0');
                }
            }
        }

        System.out.println(builder.toString());

        int i =0;
        StringBuilder output = new StringBuilder();
        while (i < builder.length()){
            String part = builder.substring(i, i+6);
            int index = Integer.parseInt(part, 2);
            output.append(toBase64URL[index]);
            i += 6;
        }

        System.out.println(output.toString());

    }
}