Это моя четвертая попытка кодирования 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
Основная проблема кодирования заключается в том, что вы не учитываете данные, не кратные 6 битам. В этом случае финал 4
у вас превращается в 0100
вместо 010000
потому что больше нет битов для чтения. Вы должны дополнить 0
s.
После изменения вашего Copy
вот так, последний закодированный символ Q
вместо оригинала E
,
std::string data = Str.substr(FirstChar, Count);
while(data.size() < Count) data += '0';
return data;
Кроме того, похоже, что ваша логика для добавления отступов =
выключен, потому что он добавляет один слишком много =
в этом случае.
Что касается комментариев по скорости, я бы сосредоточился в первую очередь на попытках сократить использование вами std::string
, То, как вы в настоящее время конвертируете данные в строку с 0 и 1, довольно неэффективно, учитывая, что источник может быть прочитан напрямую с помощью побитовых операторов.
Я не уверен, смогу ли я легко придумать более медленный метод преобразования Base-64.
Код требует 4 заголовка (в Mac OS X 10.7.5 с G ++ 4.7.1) и опцию компилятора -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.
Таким образом, кажется, что вы закодировали нулевое окончание строки. Когда я кодирую 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 байтам для данного ввода.
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;
}