在阅读base64维基之后…

我想知道这个公式是怎么运作的

给定一个长度为n的字符串,base64的长度为

即:4*Math.Ceiling(((double)s.Length/3)))

我已经知道base64的长度必须是%4==0,以允许解码器知道原始文本的长度。

序列的最大填充数可以是=或==。

wiki:每个输入字节的输出字节数大约是4 / 3 (33%) 开销)

问题:

以上信息是如何与输出长度相匹配的?


当前回答

当其他人都在讨论代数公式时,我宁愿用BASE64本身来告诉我:

$ echo "Including padding, a base64 string requires four bytes for every three-byte chunk of the original string, including any partial chunks. One or two bytes extra at the end of the string will still get converted to four bytes in the base64 string when padding is added. Unless you have a very specific use, it is best to add the padding, usually an equals character. I added an extra byte for a null character in C, because ASCII strings without this are a little dangerous and you'd need to carry the string length separately."| wc -c

525

$ echo "Including padding, a base64 string requires four bytes for every three-byte chunk of the original string, including any partial chunks. One or two bytes extra at the end of the string will still get converted to four bytes in the base64 string when padding is added. Unless you have a very specific use, it is best to add the padding, usually an equals character. I added an extra byte for a null character in C, because ASCII strings without this are a little dangerous and you'd need to carry the string length separately." | base64 | wc -c

710

因此,3个字节由4个base64字符表示的公式似乎是正确的。

其他回答

整数

通常我们不想使用双精度数,因为我们不想使用浮点运算,舍入错误等。他们只是没有必要。

为此,最好记住如何执行上限除法:双数的ceil(x / y)可以写成(x + y - 1) / y(同时避免负数,但要注意溢出)。

可读的

如果你追求可读性,你当然也可以像这样编程(例如在Java中,对于C你当然可以使用宏):

public static int ceilDiv(int x, int y) {
    return (x + y - 1) / y;
}

public static int paddedBase64(int n) {
    int blocks = ceilDiv(n, 3);
    return blocks * 4;
}

public static int unpaddedBase64(int n) {
    int bits = 8 * n;
    return ceilDiv(bits, 6);
}

// test only
public static void main(String[] args) {
    for (int n = 0; n < 21; n++) {
        System.out.println("Base 64 padded: " + paddedBase64(n));
        System.out.println("Base 64 unpadded: " + unpaddedBase64(n));
    }
}

内联

我们知道每3个字节(或更少)需要4个字符块。那么公式就变成了(对于x = n, y = 3)

blocks = (bytes + 3 - 1) / 3
chars = blocks * 4

或结合:

chars = ((bytes + 3 - 1) / 3) * 4

你的编译器会优化出3 - 1,所以就这样保持可读性。

填充物的

不太常见的是无填充的变体,为此我们记得每6位都需要一个字符,四舍五入:

bits = bytes * 8
chars = (bits + 6 - 1) / 6

或结合:

chars = (bytes * 8 + 6 - 1) / 6

然而,我们仍然可以除以2(如果我们愿意的话):

chars = (bytes * 4 + 3 - 1) / 3

不可读的

如果你不相信你的编译器会为你做最终的优化(或者如果你想让你的同事困惑):

((n + 2) / 3) << 2

填充物的

((n << 2) | 2) / 3

所以我们有两种逻辑计算方式,我们不需要任何分支,位运算或模运算,除非我们真的想这样做。

注:

显然,您可能需要在计算中添加1以包含空终止字节。 对于Mime,您可能需要注意可能的行终止字符等(寻找其他答案)。

我在其他回答中没有看到简化的公式。逻辑是覆盖的,但我想要一个最基本的形式为我的嵌入式使用:

  Unpadded = ((4 * n) + 2) / 3

  Padded = 4 * ((n + 2) / 3)

注意:当计算无填充计数时,我们四舍五入整数除法,即加上除数-1,在这种情况下是+2

下面是一个函数来计算一个base64编码文件的原始大小为KB的字符串:

private Double calcBase64SizeInKBytes(String base64String) {
    Double result = -1.0;
    if(StringUtils.isNotEmpty(base64String)) {
        Integer padding = 0;
        if(base64String.endsWith("==")) {
            padding = 2;
        }
        else {
            if (base64String.endsWith("=")) padding = 1;
        }
        result = (Math.ceil(base64String.length() / 4) * 3 ) - padding;
    }
    return result / 1000;
}

(试图给出一个简洁而完整的推导。)

每个输入字节有8位,所以对于n个输入字节,我们得到:

N × 8输入位

每6位是一个输出字节,因此:

ceil (n - 6) = ceil (n×8×4 - 3 )      输出字节

这是没有填充的。

对于填充,我们四舍五入为四个输出字节中的多个:

ceil (ceil (n×4 / 3))×4 = ceil (n×4 / 3 - 4)×4 = ceil (n - 3)×4字节输出

见嵌套划分(维基百科)的第一个等价。

使用整数算术,ceil(n / m)可以计算为(n + m - 1) div m, 因此我们得到:

(n * 4 + 2) div 3没有填充 (n + 2) div 3 * 4与填充

说明:

 n   with padding    (n + 2) div 3 * 4    without padding   (n * 4 + 2) div 3 
------------------------------------------------------------------------------
 0                           0                                      0
 1   AA==                    4            AA                        2
 2   AAA=                    4            AAA                       3
 3   AAAA                    4            AAAA                      4
 4   AAAAAA==                8            AAAAAA                    6
 5   AAAAAAA=                8            AAAAAAA                   7
 6   AAAAAAAA                8            AAAAAAAA                  8
 7   AAAAAAAAAA==           12            AAAAAAAAAA               10
 8   AAAAAAAAAAA=           12            AAAAAAAAAAA              11
 9   AAAAAAAAAAAA           12            AAAAAAAAAAAA             12
10   AAAAAAAAAAAAAA==       16            AAAAAAAAAAAAAA           14
11   AAAAAAAAAAAAAAA=       16            AAAAAAAAAAAAAAA          15
12   AAAAAAAAAAAAAAAA       16            AAAAAAAAAAAAAAAA         16

最后,在MIME Base64编码的情况下,每76个输出字节需要两个额外的字节(CR LF),这取决于是否需要一个结束换行符。

作为参考,Base64编码器的长度公式如下:

正如你所说的,给定n个字节的数据,一个Base64编码器将产生一个4n/3个Base64字符的字符串。换句话说,每3个字节的数据将导致4个Base64字符。编辑:一个评论正确地指出,我之前的图形没有说明填充;正确的填充公式是4(Ceiling(n/3))。

维基百科的文章在示例中准确地展示了ASCII字符串Man如何编码为Base64字符串TWFu。输入字符串的大小是3字节,或24位,因此公式正确地预测输出将是4字节(或32位)长:TWFu。该过程将每6位数据编码为64个Base64字符中的一个,因此24位输入除以6得到4个Base64字符。

您在注释中询问编码123456的大小。请记住,该字符串的每个字符的大小都是1字节或8位(假设ASCII/UTF8编码),我们正在编码6字节或48位的数据。根据公式,我们期望输出长度为(6字节/ 3字节)* 4个字符= 8个字符。

将123456放入Base64编码器中创建MTIzNDU2,正如我们预期的那样,它有8个字符长。