【问题标题】:Base64 length calculation?Base64长度计算?
【发布时间】:2012-11-02 22:46:04
【问题描述】:

读完base64后wiki...

我正在试图弄清楚 这个公式是如何工作的:

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

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

我已经知道 base64 长度必须是 %4==0 才能让解码器知道原始文本长度是多少。

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

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

问题:

上面的信息如何与输出长度一致?

【问题讨论】:

    标签: string base64 padding formula


    【解决方案1】:

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

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

    所以你需要4*(n/3) 字符来表示n 字节,这需要四舍五入到4的倍数。

    四舍五入到 4 的倍数后未使用的填充字符数显然是 0、1、2 或 3。

    【讨论】:

    • 填充在哪里?
    • 考虑是否有一个字节的输入。这将产生四个字符的输出。但是只需要两个输出字符来对输入进行编码。所以两个字符将被填充。
    • 输出长度总是四舍五入为 4 的倍数,因此 1、2 或 3 个输入字节 => 4 个字符; 4、5 或 6 个输入字节 => 8 个字符; 7、8 或 9 个输入字节 => 12 个字符。
    • 我在上面的答案中解释了所有这些:(i)每个输出 char 代表 6 个 bits 输入,(ii)4 个输出 chars 因此表示 4 * 6 = 24 bits,(iii) 24 bits 是 3 bytes,(iv) 3 bytes 的输入因此导致 4 chars 的输出,(v) 输出 chars 与输入 bytes 的比率为因此 4 / 3。
    • @techie_28:我将其设为 27308 个字符,大小为 20 * 1024 字节,但今天早上我还没有喝咖啡。
    【解决方案2】:

    作为参考,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 编码器会创建MTIzNDU2,它有8 个字符长,正如我们预期的那样。

    【讨论】:

    • 使用这个公式,注意它没有给出填充长度。所以你可以有更长的长度。
    • 为了从 base64 文本计算预期的解码字节,我使用公式 floor((3 * (length - padding)) / 4)。查看以下gist
    【解决方案3】:

    我认为给定的答案错过了原始问题的重点,即需要分配多少空间来适应给定长度为 n 字节的二进制字符串的 base64 编码。

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

    这包括填充和终止空字符。如果你在做整数运算,你可能不需要发言权。

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

    【讨论】:

    • 你的公式是错误的。考虑 n=3,预期结果(没有空填充)是 4,但您的公式返回 8。
    • 我也认为包含空终止符很愚蠢,尤其是因为我们在这里讨论的是 .net。
    • 使用 CryptBinaryToStringA 在 Windows 中正常工作。我对此投了赞成票。
    【解决方案4】:

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

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

    【讨论】:

    • 不考虑 Ascii 零填充 - 在 Windows 中不起作用。 (CryptBinaryToStringA)
    【解决方案5】:

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

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

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

    【讨论】:

    • 你是对的! -> 4 * n / 3 给出未填充的长度!上面的答案是不正确的。 -> ((4 * n / 3) + 3) & ~3 返回正确的结果
    • 不能作为窗口 API CryptBinaryToStringA 的输入。
    • 为使用 shell 的人拼写出来:$(( ((4 * n / 3) + 3) & ~3 ))
    • 4 * n / 3n = 1 已经失败,一个字节用两个字符编码,结果显然是一个字符。
    • @Crog 因为如果 n = 1 写下来,那么您将使用整数得到 4 / 3 = 1。正如您所指出的,预期结果是 2,而不是 1。
    【解决方案6】:

    在 Windows 中 - 我想估计 mime64 大小的缓冲区的大小,但所有精确的计算公式都对我不起作用 - 最后我得到了这样的近似公式:

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

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

    【讨论】:

      【解决方案7】:

      当其他人都在讨论代数公式时,我宁愿只使用 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 字符表示的公式似乎是正确的。

      【讨论】:

      • 我反对需要大量内存和 CPU 时间的计算,而计算可以在 1 ns 和一个或两个寄存器中执行。
      • 那么当您尝试处理未知数量的二进制数据时 - 这有什么帮助?
      • 问题都是关于公式的,它有助于计算输出大小而不自己做base64。虽然这个答案在某些情况下很有用,但对这个问题没有帮助。
      【解决方案8】:

      如果 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]
      

      玩得开心

      GI

      【讨论】:

        【解决方案9】:

        整数

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

        为此,最好记住如何执行天花板除法:双打中的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,您可能需要注意可能的行终止字符等(寻找其他答案)。

        【讨论】:

          【解决方案10】:

          这是一个函数,用于将编码的 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;
          }
          

          【讨论】:

            【解决方案11】:

            简单的javascript实现

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

            【讨论】:

              【解决方案12】:

              如果有人有兴趣在 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
              }
              

              【讨论】:

                【解决方案13】:

                对于所有说 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) 
                

                取自here

                【讨论】:

                  【解决方案14】:

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

                  每个输入字节都有 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      输出字节数

                  请参阅Nested Divisions(维基百科)了解第一个等效项。

                  使用整数运算,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),根据是否需要终止换行符向上或向下舍入。

                  【讨论】:

                  • 关于 CR LF 所需的额外字节非常好。在为 openssl 生成的 base64 编码字符串分配缓冲区时,我错过了它们。
                  【解决方案15】:

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

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

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

                  【讨论】:

                    猜你喜欢
                    • 2010-11-10
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2021-11-25
                    相关资源
                    最近更新 更多