1

这是我第四次尝试进行 base64 编码。我的第一次尝试工作,但它不是标准的。也超级慢!!!我使用了向量和 push_back 并擦除了很多。

所以我决定重写它,这要快得多!除了它会丢失数据。-__- 我需要尽可能快的速度,因为我正在压缩像素缓冲区并对压缩字符串进行 base64 编码。我正在使用 ZLib。图像是 1366 x 768 所以是的。

我不想复制我在网上找到的任何代码,因为...嗯,我喜欢自己写东西,我不喜欢担心版权问题或不得不在我的代码中放置大量来自不同来源的学分......

无论如何,我的代码如下。它非常简短。

const static std::string Base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

inline bool IsBase64(std::uint8_t C)
{
    return (isalnum(C) || (C == '+') || (C == '/'));
}

std::string Copy(std::string Str, int FirstChar, int Count)
{
    if (FirstChar <= 0)
        FirstChar = 0;
    else
        FirstChar -= 1;
    return Str.substr(FirstChar, Count);
}

std::string DecToBinStr(int Num, int Padding)
{
    int Bin = 0, Pos = 1;
    std::stringstream SS;
    while (Num > 0)
    {
        Bin += (Num % 2) * Pos;
        Num /= 2;
        Pos *= 10;
    }
    SS.fill('0');
    SS.width(Padding);
    SS << Bin;
    return SS.str();
}

int DecToBinStr(std::string DecNumber)
{
    int Bin = 0, Pos = 1;
    int Dec = strtol(DecNumber.c_str(), NULL, 10);

    while (Dec > 0)
    {
        Bin += (Dec % 2) * Pos;
        Dec /= 2;
        Pos *= 10;
    }
    return Bin;
}

int BinToDecStr(std::string BinNumber)
{
    int Dec = 0;
    int Bin = strtol(BinNumber.c_str(), NULL, 10);

    for (int I = 0; Bin > 0; ++I)
    {
        if(Bin % 10 == 1)
        {
            Dec += (1 << I);
        }
        Bin /= 10;
    }
    return Dec;
}

std::string EncodeBase64(std::string Data)
{
    std::string Binary = std::string();
    std::string Result = std::string();

    for (std::size_t I = 0; I < Data.size(); ++I)
    {
        Binary += DecToBinStr(Data[I], 8);
    }

    for (std::size_t I = 0; I < Binary.size(); I += 6)
    {
        Result += Base64Chars[BinToDecStr(Copy(Binary, I, 6))];
        if (I == 0) ++I;
    }

    int PaddingAmount = ((-Result.size() * 3) & 3);
    for (int I = 0; I < PaddingAmount; ++I)
        Result += '=';

    return Result;
}

std::string DecodeBase64(std::string Data)
{
    std::string Binary = std::string();
    std::string Result = std::string();

    for (std::size_t I = Data.size(); I > 0; --I)
    {
        if (Data[I - 1] != '=')
        {
            std::string Characters = Copy(Data, 0, I);
            for (std::size_t J = 0; J < Characters.size(); ++J)
                Binary += DecToBinStr(Base64Chars.find(Characters[J]), 6);
            break;
        }
    }

    for (std::size_t I = 0; I < Binary.size(); I += 8)
    {
        Result += (char)BinToDecStr(Copy(Binary, I, 8));
        if (I == 0) ++I;
    }

    return Result;
}

我一直在使用上面这样的:

int main()
{
    std::string Data = EncodeBase64("IMG." + ::ToString(677) + "*" + ::ToString(604));  //IMG.677*604
    std::cout<<DecodeBase64(Data);        //Prints IMG.677*601
}

正如您在上面看到的,它打印了错误的字符串。它相当接近,但由于某种原因,4 变成了 1!

现在,如果我这样做:

int main()
{
    std::string Data = EncodeBase64("IMG." + ::ToString(1366) + "*" + ::ToString(768));  //IMG.1366*768
    std::cout<<DecodeBase64(Data);        //Prints IMG.1366*768
}

它打印正确..我不确定发生了什么或从哪里开始寻找。

以防万一有人好奇并想看看我的其他尝试(缓慢的尝试): http: //pastebin.com/Xcv03KwE

我真的希望有人能对加快速度或至少弄清楚我的代码有什么问题有所了解:l

4

3 回答 3

2

主要的编码问题是您没有考虑不是 6 位倍数的数据。在这种情况下,您拥有的最终结果4将被转换为0100,而不是010000因为没有更多位要读取。你应该用0s 填充。

像这样改变你之后Copy,最终的编码字符是Q,而不是原来的E.

std::string data = Str.substr(FirstChar, Count);
while(data.size() < Count) data += '0';
return data;

此外,您添加填充的逻辑似乎已关闭,因为在这种情况下=它添加了太多。=

至于对速度的评论,我主要集中在尝试减少您对std::string. 考虑到可以使用按位运算符直接读取源,您当前将数据转换为具有 0 和 1 的字符串的方式非常低效。

于 2012-12-31T05:41:30.133 回答
2

我不确定我是否可以轻松地想出一种更慢的方法来进行 Base-64 转换。

该代码需要 4 个标头(在带有 G++ 4.7.1 的 Mac OS X 10.7.5 上)和编译器选项-std=c++11以使其#include <cstdint>可接受:

#include <string>
#include <iostream>
#include <sstream>
#include <cstdint>

它还需要一个ToString()未定义的函数;我建立:

std::string ToString(int value)
{
    std::stringstream ss;
    ss << value;
    return ss.str();
}

您的代码main()(即使用该ToString()函数的代码)有点奇怪:为什么需要从片段构建字符串而不是简单地使用"IMG.677*604"

此外,值得打印出中间结果:

int main()
{
    std::string Data = EncodeBase64("IMG." + ::ToString(677) + "*" + ::ToString(604));
    std::cout << Data << std::endl;
    std::cout << DecodeBase64(Data) << std::endl;        //Prints IMG.677*601
}

这产生:

SU1HLjY3Nyo2MDE===
IMG.677*601

输出字符串 ( SU1HLjY3Nyo2MDE===) 长 18 个字节;这一定是错误的,因为有效的 Base-64 编码字符串的长度必须是 4 个字节的倍数(因为三个 8 位字节被编码为四个字节,每个字节包含 6 位原始数据)。这立即告诉我们有问题。您应该只得到零个、一个或两个填充 ( =) 字符;从来没有三个。这也证实了有问题。

删除两个填充字符会留下有效的 Base-64 字符串。当我使用自己的自制 Base-64 编码和解码功能来解码您的(截断的)输出时,它给了我:

Base64:
0x0000: SU1HLjY3Nyo2MDE=
Binary:
0x0000: 49 4D 47 2E 36 37 37 2A 36 30 31 00               IMG.677*601.

因此,您似乎已经对终止字符串的 null 进行了编码。当我编码IMG.677*604时,我得到的输出是:

Binary:
0x0000: 49 4D 47 2E 36 37 37 2A 36 30 34                  IMG.677*604
Base64: SU1HLjY3Nyo2MDQ=

你说你想加快你的代码。除了修复它以使其正确编码(我还没有真正研究过解码)之外,您还需要避免您所做的所有字符串操作。它应该是一个位操作练习,而不是字符串操作练习。

我的代码中有 3 个小的编码例程,用于对三胞胎、双胞胎和单胞胎进行编码:

/* Encode 3 bytes of data into 4 */
static void encode_triplet(const char *triplet, char *quad)
{
    quad[0] = base_64_map[(triplet[0] >> 2) & 0x3F];
    quad[1] = base_64_map[((triplet[0] & 0x03) << 4) | ((triplet[1] >> 4) & 0x0F)];
    quad[2] = base_64_map[((triplet[1] & 0x0F) << 2) | ((triplet[2] >> 6) & 0x03)];
    quad[3] = base_64_map[triplet[2] & 0x3F];
}

/* Encode 2 bytes of data into 4 */
static void encode_doublet(const char *doublet, char *quad, char pad)
{
    quad[0] = base_64_map[(doublet[0] >> 2) & 0x3F];
    quad[1] = base_64_map[((doublet[0] & 0x03) << 4) | ((doublet[1] >> 4) & 0x0F)];
    quad[2] = base_64_map[((doublet[1] & 0x0F) << 2)];
    quad[3] = pad;
}

/* Encode 1 byte of data into 4 */
static void encode_singlet(const char *singlet, char *quad, char pad)
{
    quad[0] = base_64_map[(singlet[0] >> 2) & 0x3F];
    quad[1] = base_64_map[((singlet[0] & 0x03) << 4)];
    quad[2] = pad;
    quad[3] = pad;
}

这是作为 C 代码编写的,而不是使用本机 C++ 习惯用法,但显示的代码应该使用 C++ 编译(与源代码中其他地方的 C99 初始化程序不同)。该base_64_map[]数组对应于您的Base64Chars字符串。传入的pad字符是正常的'=',但可能是'\0'因为我使用的系统有关于不需要填充的古怪想法(在我参与代码之前,它使用非标准字母来引导)并且代码同时处理非标准和RFC 3548标准。

驱动代码是:

/* Encode input data as Base-64 string.  Output length returned, or negative error */
static int base64_encode_internal(const char *data, size_t datalen, char *buffer, size_t buflen, char pad)
{
    size_t outlen = BASE64_ENCLENGTH(datalen);
    const char *bin_data = (const void *)data;
    char *b64_data = (void *)buffer;

    if (outlen > buflen)
        return(B64_ERR_OUTPUT_BUFFER_TOO_SMALL);
    while (datalen >= 3)
    {
        encode_triplet(bin_data, b64_data);
        bin_data += 3;
        b64_data += 4;
        datalen -= 3;
    }
    b64_data[0] = '\0';

    if (datalen == 2)
        encode_doublet(bin_data, b64_data, pad);
    else if (datalen == 1)
        encode_singlet(bin_data, b64_data, pad);
    b64_data[4] = '\0';
    return((b64_data - buffer) + strlen(b64_data));
}

/* Encode input data as Base-64 string.  Output length returned, or negative error */
int base64_encode(const char *data, size_t datalen, char *buffer, size_t buflen)
{
    return(base64_encode_internal(data, datalen, buffer, buflen, base64_pad));
}

常数base64_pad是; '='还有一个base64_encode_nopad()功能可以'\0'代替。这些错误有些随意,但与代码相关。

要摆脱这一点的要点是,您应该进行位操作并构建一个字符串,该字符串是给定输入的 4 个字节的精确倍数。

于 2012-12-31T05:46:09.677 回答
1
std::string EncodeBase64(std::string Data)
{
    std::string Binary = std::string();
    std::string Result = std::string();

    for (std::size_t I = 0; I < Data.size(); ++I)
    {
        Binary += DecToBinStr(Data[I], 8);
    }

    if (Binary.size() % 6)
    {
        Binary.resize(Binary.size() + 6 - Binary.size() % 6, '0');
    }

    for (std::size_t I = 0; I < Binary.size(); I += 6)
    {
        Result += Base64Chars[BinToDecStr(Copy(Binary, I, 6))];
        if (I == 0) ++I;
    }

    if (Result.size() % 4)
    {
        Result.resize(Result.size() + 4 - Result.size() % 4, '=');
    }

    return Result;
}
于 2012-12-31T05:45:24.640 回答