Base64长度计算?


153

阅读base64 Wiki之后 ...

我试图弄清楚该公式如何工作:

给定一个长度为的字符串n,base64的长度将为在此处输入图片说明

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

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

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

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

题:

上面的信息如何与输出长度相符 在此处输入图片说明

Answers:


207

每个字符用于表示6位(log2(64) = 6)。

因此,使用4个字符来表示4 * 6 = 24 bits = 3 bytes

因此,您需要使用4*(n/3)char来表示n字节,这需要四舍五入为4的倍数。

由于四舍五入到4的倍数而产生的未使用的填充字符数显然将为0、1、2或3。


填充物到哪里去了?
罗伊·纳米尔

1
考虑是否有一个字节的输入。这将产生四个字符的输出。但是只需要两个输出字符即可对输入进行编码。因此,两个字符将被填充。
大卫·史瓦兹

2
输出长度始终四舍五入为4的倍数,因此1、2或3个输入字节=> 4个字符;4、5或6个输入字节=> 8个字符;7、8或9个输入字节=> 12个字符。
Paul R

5
我在上面的答案中解释了所有这些:(i)每个输出char代表6 输入,(ii)4个输出char因此代表4 * 6 = 24 ,(iii)24 是3 个字节,(iv)3 个字节输入的因此导致4 个字符输出,(v)的输出的比率的字符输入字节因此是4/3
保罗- [R

2
@ techie_28:我将它变成27308个字符(20 * 1024字节),但是今天早上我还没有喝咖啡。
Paul R

60

4 * n / 3 给出未填充的长度。

将四舍五入到最接近的4的倍数进行填充,由于4是2的幂,因此可以使用按位逻辑运算。

((4 * n / 3) + 3) & ~3

1
你是对的!-> 4 * n / 3给出无填充长度!以上答案不正确。->(((4 * n / 3)+ 3)&
〜3

不能用作窗口API CryptBinaryToStringA的输入。
TarmoPikaro '16

为使用shell的人说明:$(( ((4 * n / 3) + 3) & ~3 ))
starfry

1
4 * n / 3已在处失败n = 1,一个字节使用两个字符编码,结果显然是一个字符。
Maarten Bodewes

1
@Crog如果n = 1记录下来,那么您将使用整数得到4/3 = 1。正如您所指出的,预期的结果是2,而不是1
马腾Bodewes

25

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

Base64编码器的长度公式

如您所说,给定n字节数据的Base64编码器将产生一串4n/3Base64字符。换句话说,每3个字节的数据将产生4个Base64字符。编辑正确的注释指出,我以前的图形未考虑填充;正确的公式是 Ceiling(4n/3)

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

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

123456为Base64编码器创建MTIzNDU2,这是8个字符长,正如我们的预期。


5
使用此公式时,请注意它不会给出填充的长度。因此,您可以拥有更长的长度。
Spilarix '16

要从base64文本计算预期的解码字节,我使用公式floor((3 * (length - padding)) / 4)。查看以下要点
Kurt Vangraefschepe

13

整数

通常,我们不想使用双精度,因为我们不想使用浮点运算,舍入误差等。它们只是不必要的。

为此,记住如何进行上限划分是一个好主意: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

但是,我们仍然可以除以二(如果需要):

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

不可读

如果您不信任编译器为您做最后的优化(或者您想使同事感到困惑):

加垫

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

未填充

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

因此,这里有两种逻辑计算方式,并且我们不需要任何分支,位运算或模运算-除非我们真的想要。

笔记:

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

5

我认为给出的答案缺少原始问题的要点,即对于给定长度为n个字节的二进制字符串,需要分配多少空间以适合base64编码。

答案是 (floor(n / 3) + 1) * 4 + 1

这包括填充和终止的空字符。如果您要进行整数运算,则可能不需要下限调用。

包括填充在内,base64字符串需要为原始字符串的每个三字节块(包括任何部分块)提供四个字节。添加填充后,字符串末尾额外增加一两个字节仍将转换为base64字符串中的四个字节。除非您有非常特殊的用途,否则最好添加填充(通常为等号)。我为C中的空字符添加了一个额外的字节,因为没有此字符的ASCII字符串会有些危险,您需要单独携带字符串长度。


5
您的公式是错误的。考虑n = 3的,预期的结果(无空填充)是4,但是您的公式返回8.
CodesInChaos

5
我还认为包括空终止符是很愚蠢的,尤其是因为我们在这里谈论.net时。
CodesInChaos 2014年

使用CryptBinaryToStringA在Windows中正常工作。我对此表示投票。
TarmoPikaro '16

5

这是一个函数,用于将编码的Base 64文件的原始大小计算为以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;
}

3

当其他所有人都在讨论代数公式时,我宁愿只使用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

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


1
我有些反对需要大量内存和CPU时间的计算,而计算可以在1 ns和一个或两个寄存器中执行。
Maarten Bodewes

因此,当您尝试处理未知数量的二进制数据时,这有什么帮助?
UKMonkey

问题全在于公式,这些公式有助于在执行base64本身的情况下计算输出大小。尽管此答案在某些情况下很有用,但对这个问题没有帮助。
亚历杭德罗(Alejandro)

2

在我看来正确的公式应该是:

n64 = 4 * (n / 3) + (n % 3 != 0 ? 4 : 0)

不考虑Ascii零填充-在Windows中不起作用。(CryptBinaryToStringA)
TarmoPikaro

2

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

每个输入字节都有8位,因此对于n个输入字节,我们得到:

n ×8个输入位

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

ceiln ×8/6)=  ceiln ×4/3)输出字节

这没有填充。

使用填充,我们将其四舍五入为四个输出的字节:

ceilceiln ×4/3)/ 4)×4 =  ceiln ×4/3/4 / 4)×4 =  ceiln / 3)×4个输出字节

有关第一个等效信息,请参见嵌套划分(维基百科)。

使用整数算法,ceiln / 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),根据是否需要终止换行符将其向上或向下取整。


感谢您的详细分析
P Satish Patro

1

我相信,如果n%3不为零,这是一个确切的答案,不是吗?

    (n + 3-n%3)
4 * ---------
       3

Mathematica版本:

SizeB64[n_] := If[Mod[n, 3] == 0, 4 n/3, 4 (n + 3 - Mod[n, 3])/3]

玩得开心

胃肠道


1

JavaScript中的简单实现

function sizeOfBase64String(base64String) {
    if (!base64String) return 0;
    const padding = (base64String.match(/(=*)$/) || [])[1].length;
    return 4 * Math.ceil((base64String.length / 3)) - padding;
}

1

对于所有讲C的人,请看以下两个宏:

// calculate the size of 'output' buffer required for a 'input' buffer of length x during Base64 encoding operation
#define B64ENCODE_OUT_SAFESIZE(x) ((((x) + 3 - 1)/3) * 4 + 1) 

// calculate the size of 'output' buffer required for a 'input' buffer of length x during Base64 decoding operation
#define B64DECODE_OUT_SAFESIZE(x) (((x)*3)/4) 

取自这里


0

在Windows中-我想估计mime64大小的缓冲区的大小,但是所有精确的计算公式都不适合我-最终,我得到了近似的公式,如下所示:

Mine64字符串分配大小(大约)=((((4 *((二进制缓冲区大小)+ 1))/ 3)+ 1)

所以last +1-它用于ascii-零-最后一个字符需要分配以存储零结尾-但是为什么“二进制缓冲区大小”为+ 1-我怀疑有一些mime64终止字符?也许这是一些对齐问题。


0

如果有人有兴趣在JS中实现@Pedro Silva解决方案,我只是为此移植了相同的解决方案:

const getBase64Size = (base64) => {
  let padding = base64.length
    ? getBase64Padding(base64)
    : 0
  return ((Math.ceil(base64.length / 4) * 3 ) - padding) / 1000
}

const getBase64Padding = (base64) => {
  return endsWith(base64, '==')
    ? 2
    : 1
}

const endsWith = (str, end) => {
  let charsFromEnd = end.length
  let extractedEnd = str.slice(-charsFromEnd)
  return extractedEnd === end
}

0

我在其他回应中看不到简化公式。涵盖了逻辑,但是我想要一种最基本的形式供嵌入式使用:

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

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

注意:在计算未填充的计数时,我们会舍入整数除法,即在这种情况下将Divisor-1加为+2

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.