阅读 base64 wiki后...
我试图弄清楚公式是如何工作的:
给定一个长度为 的字符串n
,base64 长度将为
这是:4*Math.Ceiling(((double)s.Length/3)))
我已经知道 base64 长度必须是%4==0
让解码器知道原始文本长度是多少。
序列的最大填充数可以是=
或==
。
wiki :每个输入字节的输出字节数约为 4 / 3(33% 开销)
问题:
上面的信息如何与输出长度一致 ?
每个字符用于表示 6 位 ( log2(64) = 6
)。
因此使用 4 个字符来表示4 * 6 = 24 bits = 3 bytes
.
所以你需要4*(n/3)
chars 来表示n
字节,这需要四舍五入到 4 的倍数。
四舍五入到 4 的倍数后未使用的填充字符数显然是 0、1、2 或 3。
4 * n / 3
给出未填充的长度。
并且四舍五入到最接近的 4 的倍数进行填充,因为 4 是 2 的幂,所以可以使用按位逻辑运算。
((4 * n / 3) + 3) & ~3
作为参考,Base64编码器的长度公式如下:
正如您所说,给定n
数据字节的 Base64 编码器将生成一串4n/3
Base64 字符。换句话说,每 3 个字节的数据将产生 4 个 Base64 字符。编辑:评论正确地指出我之前的图形没有考虑填充;填充的正确公式是 4(Ceiling(n/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 编码器会创建8个字符长。MTIzNDU2
通常我们不想使用双精度数,因为我们不想使用浮点运算、舍入误差等。它们只是没有必要。
为此,记住如何执行天花板除法是一个好主意: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
所以我们有两种逻辑计算方式,我们不需要任何分支、位运算或模运算——除非我们真的想要。
笔记:
(试图给出一个简洁而完整的推导。)
每个输入字节都有 8 位,因此对于n 个输入字节,我们得到:
n × 8 输入位
每 6 位是一个输出字节,因此:
ceil ( n × 8 / 6) = ceil ( n × 4 / 3) 输出字节数
这是没有填充的。
使用填充,我们将其四舍五入为四的倍数:
ceil ( ceil ( n × 4 / 3) / 4) × 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),根据是否需要终止换行符向上或向下舍入。
这是一个将编码的 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;
}
我认为给定的答案错过了原始问题的要点,即需要分配多少空间来适应给定长度为 n 字节的二进制字符串的 base64 编码。
答案是(floor(n / 3) + 1) * 4 + 1
这包括填充和终止空字符。如果您进行整数运算,您可能不需要发言权。
包括填充在内,base64 字符串对于原始字符串的每个三字节块需要四个字节,包括任何部分块。添加填充时,字符串末尾额外的一或两个字节仍将转换为 base64 字符串中的四个字节。除非您有非常特殊的用途,否则最好添加填充,通常是等号字符。我在 C 中为空字符添加了一个额外的字节,因为没有这个的 ASCII 字符串有点危险,你需要单独携带字符串长度。
我在其他回复中看不到简化公式。涵盖了逻辑,但我想要一个用于嵌入式使用的最基本形式:
Unpadded = ((4 * n) + 2) / 3
Padded = 4 * ((n + 2) / 3)
注意:在计算未填充计数时,我们将整数除法四舍五入,即添加 Divisor-1,在这种情况下为 +2
当其他人都在讨论代数公式时,我宁愿只使用 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 字符表示的公式似乎是正确的。
对于所有说 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)
取自这里。
在我看来,正确的公式应该是:
n64 = 4 * (n / 3) + (n % 3 != 0 ? 4 : 0)
如果 n%3 不为零,我相信这是一个准确的答案,不是吗?
(n + 3-n%3)
4 * ---------
3
数学版本:
SizeB64[n_] := If[Mod[n, 3] == 0, 4 n/3, 4 (n + 3 - Mod[n, 3])/3]
玩得开心
胃肠道
javascript中的简单实现
function sizeOfBase64String(base64String) {
if (!base64String) return 0;
const padding = (base64String.match(/(=*)$/) || [])[1].length;
return 4 * Math.ceil((base64String.length / 3)) - padding;
}
如果有人有兴趣在 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
}
在 Windows 中 - 我想估计 mime64 大小的缓冲区的大小,但所有精确的计算公式都对我不起作用 - 最后我得到了这样的近似公式:
Mine64 字符串分配大小(近似值)= (((4 * ((二进制缓冲区大小) + 1)) / 3) + 1)
所以最后 +1 - 它用于 ascii-zero - 最后一个字符需要分配以存储零结尾 - 但是为什么“二进制缓冲区大小”是 + 1 - 我怀疑有一些 mime64 终止字符?或者可能这是一些对齐问题。