二进制字符串到十六进制c ++

时间:2015-03-23 12:22:31

标签: c++ binary hex

当将二进制字符串更改为十六进制时,我只能根据我找到的答案将其设置为特定大小。但我想以更有效的方式将MASSIVE二进制字符串更改为完整的Hex字符串,这是我完全遇到的唯一方法:

for(size_t i = 0; i < (binarySubVec.size() - 1); i++){
    string binToHex, tmp = "0000";
    for (size_t j = 0; j < binaryVecStr[i].size(); j += 4){
        tmp = binaryVecStr[i].substr(j, 4);
        if      (!tmp.compare("0000")) binToHex += "0";
        else if (!tmp.compare("0001")) binToHex += "1";
        else if (!tmp.compare("0010")) binToHex += "2";
        else if (!tmp.compare("0011")) binToHex += "3";
        else if (!tmp.compare("0100")) binToHex += "4";
        else if (!tmp.compare("0101")) binToHex += "5";
        else if (!tmp.compare("0110")) binToHex += "6";
        else if (!tmp.compare("0111")) binToHex += "7";
        else if (!tmp.compare("1000")) binToHex += "8";
        else if (!tmp.compare("1001")) binToHex += "9";
        else if (!tmp.compare("1010")) binToHex += "A";
        else if (!tmp.compare("1011")) binToHex += "B";
        else if (!tmp.compare("1100")) binToHex += "C";
        else if (!tmp.compare("1101")) binToHex += "D";
        else if (!tmp.compare("1110")) binToHex += "E";
        else if (!tmp.compare("1111")) binToHex += "F";
        else continue;
    }
    hexOStr << binToHex;
    hexOStr << " ";
}

它彻底而绝对,但很慢。

有更简单的方法吗?

9 个答案:

答案 0 :(得分:5)

更新最后添加了比较和基准

这是基于完美哈希的另一种观点。使用gperf生成完美哈希(如此处所述:Is it possible to map string to int faster than using hashmap?)。

我通过将函数局部静态函数移开并将hexdigit()hash()标记为constexpr来进一步优化。这消除了不必要的任何初始化开销,并为编译器提供了充分的优化空间/

我不希望事情比这更快。

可以尝试阅读,例如如果可能,一次1024个半字节,并使编译器有机会使用AVX / SSE指令集对操作进行矢量化。 (我没有检查生成的代码,看看是否会发生这种情况。)

在流媒体模式下将std::cin翻译为std::cout的完整示例代码为:

#include <iostream>

int main()
{
    char buffer[4096];
    while (std::cin.read(buffer, sizeof(buffer)), std::cin.gcount())
    {
        size_t got = std::cin.gcount();
        char* out = buffer;

        for (auto it = buffer; it < buffer+got; it += 4)
            *out++ = Perfect_Hash::hexchar(it);

        std::cout.write(buffer, got/4);
    }
}

这是Perfect_Hash类,稍微编辑并使用{​​{1}}查找进行扩展。请注意,它确实使用hexchar验证DEBUG版本中的输入:

<强> Live On Coliru

assert

示例的演示输出#include <array> #include <algorithm> #include <cassert> class Perfect_Hash { /* C++ code produced by gperf version 3.0.4 */ /* Command-line: gperf -L C++ -7 -C -E -m 100 table */ /* Computed positions: -k'1-4' */ /* maximum key range = 16, duplicates = 0 */ private: static constexpr unsigned char asso_values[] = { 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 15, 7, 3, 1, 0, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27}; template <typename It> static constexpr unsigned int hash(It str) { return asso_values[(unsigned char)str[3] + 2] + asso_values[(unsigned char)str[2] + 1] + asso_values[(unsigned char)str[1] + 3] + asso_values[(unsigned char)str[0]]; } static constexpr char hex_lut[] = "???????????fbead9c873625140"; public: #ifdef DEBUG template <typename It> static char hexchar(It binary_nibble) { assert(Perfect_Hash::validate(binary_nibble)); // for DEBUG only return hex_lut[hash(binary_nibble)]; // no validation! } #else template <typename It> static constexpr char hexchar(It binary_nibble) { return hex_lut[hash(binary_nibble)]; // no validation! } #endif template <typename It> static bool validate(It str) { static constexpr std::array<char, 4> vocab[] = { {{'?', '?', '?', '?'}}, {{'?', '?', '?', '?'}}, {{'?', '?', '?', '?'}}, {{'?', '?', '?', '?'}}, {{'?', '?', '?', '?'}}, {{'?', '?', '?', '?'}}, {{'?', '?', '?', '?'}}, {{'?', '?', '?', '?'}}, {{'?', '?', '?', '?'}}, {{'?', '?', '?', '?'}}, {{'?', '?', '?', '?'}}, {{'1', '1', '1', '1'}}, {{'1', '0', '1', '1'}}, {{'1', '1', '1', '0'}}, {{'1', '0', '1', '0'}}, {{'1', '1', '0', '1'}}, {{'1', '0', '0', '1'}}, {{'1', '1', '0', '0'}}, {{'1', '0', '0', '0'}}, {{'0', '1', '1', '1'}}, {{'0', '0', '1', '1'}}, {{'0', '1', '1', '0'}}, {{'0', '0', '1', '0'}}, {{'0', '1', '0', '1'}}, {{'0', '0', '0', '1'}}, {{'0', '1', '0', '0'}}, {{'0', '0', '0', '0'}}, }; int key = hash(str); if (key <= 26 && key >= 0) return std::equal(str, str+4, vocab[key].begin()); else return false; } }; constexpr unsigned char Perfect_Hash::asso_values[]; constexpr char Perfect_Hash::hex_lut[]; #include <iostream> int main() { char buffer[4096]; while (std::cin.read(buffer, sizeof(buffer)), std::cin.gcount()) { size_t got = std::cin.gcount(); char* out = buffer; for (auto it = buffer; it < buffer+got; it += 4) *out++ = Perfect_Hash::hexchar(it); std::cout.write(buffer, got/4); } }

  

03bef5fb79c7da917e3ebffdd8c41488d2b841dac86572cf7672d22f1f727627a2c4a48b15ef27eb0854dd99756b24c678e3b50022d695cc5f5c8aefaced2a39241bfd5deedcfa0a89060598c6b056d934719eba9ccf29e430d2def5751640ff17860dcb287df8a94089ade0283ee3d76b9fefcce3f3006b8c71399119423e780cef81e9752657e97c7629a9644be1e7c96b5d0324ab16d20902b55bb142c0451e675973489ae4891ec170663823f9c1c9b2a11fcb1c39452aff76120b21421069af337d14e89e48ee802b1cecd8d0886a9a0e90dea5437198d8d0d7ef59c46f9a069a83835286a9a8292d2d7adb4e7fb0ef42ad4734467063d181745aaa6694215af7430f95e854b7cad813efbbae0d2eb099523f215cff6d9c45e3edcaf63f78a485af8f2bfc2e27d46d61561b155d619450623b7aa8ca085c6eedfcc19209066033180d8ce1715e8ec9086a7c28df6e4202ee29705802f0c2872fbf06323366cf64ecfc5ea6f15ba6467730a8856a1c9ebf8cc188e889e783c50b85824803ed7d7505152b891cb2ac2d6f4d1329e100a2e3b2bdd50809b48f0024af1b5092b35779c863cd9c6b0b8e278f5bec966dd0e5c4756064cca010130acf24071d02de39ef8ba8bd1b6e9681066be3804d36ca83e7032274e4c8e8cacf520e8078f8fa80eb8e70af40367f53e53a7d7f7afe8704c 46f58339d660b8151c91bddf82b4096

的基准

我提出了三种不同的方法:

  1. naive.cpp (no hacks, no libraries) ;实时反汇编on Godbolt
  2. spirit.cpp (Trie); Live 反汇编on pastebin
  3. this answer: perfect.cpp hash基于;实时反汇编on Godbolt
  4. 为了做一些比较,我已经

    • 使用相同的编译器(GCC 4.9)和标志(od -A none -t o /dev/urandom | tr -cd '01' | dd bs=1 count=4096 | ./test
    • 对它们进行编译
    • 优化的输入/输出,因此不会被4个字符读/写单个字符
    • 创建了一个大输入文件(1千兆字节)

    结果如下:

    enter image description here

    • 令人惊讶的是,第一个答案中的-O3 -march=native -g0 -DNDEBUG方法确实很好
    • 灵魂在这里确实很糟糕;它可以达到3.4MB / s,因此整个文件需要294秒(!!!)。我们把它从图表中删除了
    • naive.cpp 的平均吞吐量为~720MB / s, perfect.cpp
    • 的平均吞吐量为〜1.14GB / s
    • 这使得完美的哈希方法比天真的方法快大约50%。
      

    *摘要我会说10小时前,天真的做法非常好as I posted it。如果你真的想要高吞吐量,完美的哈希是一个很好的开始,但考虑手动推出基于SIMD的解决方案

答案 1 :(得分:3)

UPDATE2 请参阅 here for my perfect-hash based solution 。这个解决方案会有我的偏好,因为

  • 编译速度更快
  • 它具有更可预测的运行时间(由于所有数据都是静态的,因此会进行零分配)
  

编辑确实现在基准测试显示完美的哈希解决方案大致 340 x 比Spirit方法。的 See here:

<强>更新

添加了 Trie-based solution

此处的查找表使用Boost Spirit的内部Trie实现进行快速查找。

当然用例如out替换back_inserter如果您愿意,可以在字符串流中添加ostreambuf_iterator<char>#include <iostream> #include <boost/spirit/include/qi.hpp> #include <boost/spirit/include/phoenix.hpp> namespace qi = boost::spirit::qi; int main() { std::ostreambuf_iterator<char> out(std::cout); qi::symbols<char, char> lookup_table; lookup_table.add ("0000", '0') ("0001", '1') ("0010", '2') ("0011", '3') ("0100", '4') ("0101", '5') ("0110", '6') ("0111", '7') ("1000", '8') ("1001", '9') ("1010", 'a') ("1011", 'b') ("1100", 'c') ("1101", 'd') ("1110", 'e') ("1111", 'f') ; boost::spirit::istream_iterator bof(std::cin), eof; if (qi::parse(bof, eof, +lookup_table [ *boost::phoenix::ref(out) = qi::_1 ])) return 0; else return 255; } 向量。现在它永远不会分配4个字符(当然,查询表只分配了一次)。

您还可以简单地将输入迭代器替换为您可用的输入范围,而无需更改其余代码的行。

<强> Live On Coliru

od -A none -t o /dev/urandom | tr -cd '01' | dd bs=1 count=4096 | ./test

使用char nibble[4]; while (std::cin.read(nibble, 4)) { std::cout << "0123456789abcdef"[ (nibble[0]!='0')*8 + (nibble[1]!='0')*4 + (nibble[2]!='0')*2 + (nibble[3]!='0')*1 ]; } 之类的随机数据进行测试时,您会得到

  

dfc87abf674d8fdb28ed2e36d8ac99faa9c9c4a8aa2253763510482c887e07b2e24cf36ecb7abdcb31521becca54ba1c2ff4be0399f76c2ca28c87fe13a735a0f8031959e5ed213a5d02fb71cbf32b978d2ee9e390a0e2fc6b65b24b2922fb7554a9b211ca1db1b757d1cd0b468d1cd399b114f4f8ef93ade4f33a18bcdb25e2b8138dcd7ec7ef7d2a53f905369c261e19556356ab96f0608bd07f908d3430d3fe7ec21a234c321cc79788f934250da6d2d8e2cb51173ad64ffb4769e7a28224e9bc68123249bbd9c19c01ebbdf2fe4824fb854cf018268d7a988bfd0169f395b30937230733e0f17ba3d8f979341ebde6ff48aac764c2a460625a3ec1349351fe15c8cd4cd3e2933a2840392e381e3c8fc69456eaaf4e8257837f92124e8918a071d7a569fba5e7b189831aa761b3a63feb45d317b1724c53659c00bc82ce7a0c4bcbdc196bc5c990eddc70248d49cc419721d82714256ed13568c4f0740efe42401b0ce644dceaf3507e4acae718265101562f81c237ea8551d051cba38a087fc260af83e123f774e8da956d885d0f87e72e336d8599631f3a44d30676088149b5a1292ecc8682cfbd6982bc37b7e6a5c44f42fcfaabd32c29696a6985fdca5bd6c986dfd4670c4456ac0a7e6ae50ba4218e090f829a2391dd9fc863b31c05a


旧的,小学答案:

从流中读取输入并输出每4个字节的单个字符。

这里是要点:

boost::flat_map

您可以将转换确实作为查找表。不要使用地图作为它的树,最终会追逐很多指针。但是,{{1}}可能没问题。

答案 2 :(得分:3)

我将如何做到这一点:

  1. 找到最小的正整数n,使这些整数具有模n的不同余数:

    0x30303030 0x30303031 0x30303130 0x30303131 0x30313030 0x30313031 0x30313130 0x30313131 0x31303030 0x31303031 0x31303130 0x31303131 0x31313030 0x31313031 0x31313130 0x31313131

  2. 这些是“0000”,“0001”等的ASCII表示。我已按顺序列出它们,假设您的机器是大端的;如果是小端,则表示例如“0001”将为0x31303030,而不是0x30303031。你只需要这样做一次。 n不会很大 - 我希望它不会超过100。

    1. 使用char HexChar[n]等建立一个表HexChar[0x30303030 % n] = '0', HexChar[0x30303031 % n] = '1'(如果您的计算机是小端,则为HexChar[0x31303030 % n] = '1'等。)
    2. 现在转换速度非常快(我假设为sizeof (int) = 4):

      unsigned int const* s = binaryVecStr[a].c_str();
      for (size_t i = 0; i < binaryVecStr[a].size(); i += 4, s++)
          hexOStr << HexChar[*s % n];
      

答案 3 :(得分:3)

我有这种奇怪的感觉,我必须在这里遗漏一些重要的问题。乍一看,这似乎应该有效:

template <class RanIt, class OutIt>
void make_hex(RanIt b, RanIt e, OutIt o) {
    static const char rets[] = "0123456789ABCDEF";

    if ((e-b) %4 != 0)
        throw std::runtime_error("Length must be a multiple of 4");

    while (b != e) {
        int index = 
            ((*(b + 0) - '0') << 3) |
            ((*(b + 1) - '0') << 2) |
            ((*(b + 2) - '0') << 1) |
            ((*(b + 3) - '0') << 0);
        *o++ = rets[index];
        b += 4;
    }
}

至少随便看来,它似乎应该和任何事情一样快 - 它在我看来它接近于对输出所需的每个输入的最小处理。

为了最大限度地提高速度,它确实最大限度地减少了对最小值(也可能低于)输入的错误检查。您当然可以确保输入中的每个字符都是&#39; 0&#39; 0或者&#39; 1&#39;之前取决于减法的结果。或者,您可以非常轻松地使用(*(b + 0) != '0') << 30视为0,将其他任何内容视为1。同样,您可以使用:(*(b + 0) == '1') << 31视为1,将其他任何内容视为0。

代码确实避免了计算每个index值所需的4个计算之间的依赖关系,因此智能编译器应该可以并行执行这些计算。

因为它只适用于迭代器,所以它避免了输入数据的额外副本,因为(例如)使用substr的几乎任何东西都可以(特别是std::string的实现没有&#39}。包括短字符串优化)。

无论如何,使用它看起来像这样:

int main() { 
    char input[] = "0000000100100011010001010110011110001001101010111100110111101111";

    make_hex(input, input+64, std::ostream_iterator<char>(std::cout));
}

由于它确实使用了迭代器,因此可以很容易地(仅用于一个明显的示例)从istreambuf_iterator获取输入以直接从文件处理数据。这很少是尽可能快的方法 - 你通常会使用istream::read来读取大块的速度更快,ostream::write一次写出一个大块。这不需要影响实际的转换代码 - 您只需将指针传递给输入和输出缓冲区,它就会将它们用作迭代器。

答案 4 :(得分:2)

这似乎有效。

std::vector<char> binaryVecStr = { '0', '0', '0', '1', '1', '1', '1', '0' };

string binToHex;
binToHex.reserve(binaryVecStr.size()/4);
for (uint32_t * ptr = reinterpret_cast<uint32_t *>(binaryVecStr.data()); ptr < reinterpret_cast<uint32_t *>(binaryVecStr.data()) + binaryVecStr.size() / 4; ++ptr) {
    switch (*ptr) {
        case 0x30303030:
            binToHex += "0";
            break;
        case 0x31303030:
            binToHex += "1";
            break;
        case 0x30313030:
            binToHex += "2";
            break;
        case 0x31313030:
            binToHex += "3";
            break;
        case 0x30303130:
            binToHex += "4";
            break;
        case 0x31303130:
            binToHex += "5";
            break;
        case 0x30313130:
            binToHex += "6";
            break;
        case 0x31313130:
            binToHex += "7";
            break;
        case 0x30303031:
            binToHex += "8";
            break;
        case 0x31303031:
            binToHex += "9";
            break;
        case 0x30313031:
            binToHex += "A";
            break;
        case 0x31313031:
            binToHex += "B";
            break;
        case 0x30303131:
            binToHex += "C";
            break;
        case 0x31303131:
            binToHex += "D";
            break;
        case 0x30313131:
            binToHex += "E";
            break;
        case 0x31313131:
            binToHex += "F";
            break;
        default:
            // invalid input
            binToHex += "?";
    }
}

std::cout << binToHex;
小心,用的很少:

1)char有8位(在所有平台上都不是)

2)它需要小端(意味着它至少在x86,x86_64上工作)

它假设binaryVecStr是std :: vector,但也适用于字符串。它假设binaryVecStr.size() % 4 == 0

答案 5 :(得分:2)

这样的事可能

#include <iostream>
#include <string>
#include <iomanip>
#include <sstream>
int main()
{
    std::cout << std::hex << std::stoll("100110110010100100111101010001001101100101010110000101111111111",NULL,  2) << std::endl;

    std::stringstream ss;
    ss << std::hex << std::stoll("100110110010100100111101010001001101100101010110000101111111111", NULL, 2);
    std::cout << ss.str() << std::endl;

    return 0;
}

答案 6 :(得分:2)

这是我能想到的最快的:

#include <iostream>

int main(int argc, char** argv) {
    char buffer[4096];
    while (std::cin.read(buffer, sizeof(buffer)), std::cin.gcount() > 0) {
        size_t got = std::cin.gcount();
        char* out = buffer;

        for (const char* it = buffer; it < buffer + got; it += 4) {
            unsigned long r;
            r  = it[3];
            r += it[2] * 2;
            r += it[1] * 4;
            r += it[0] * 8;
            *out++ = "0123456789abcdef"[r - 15*'0'];
        }

        std::cout.write(buffer, got / 4);
    }
}

根据@ sehe的基准,它在这个问题上比其他任何事情都要快。

答案 7 :(得分:1)

您可以尝试二元决策树:

string binToHex;
for (size_t i = 0; i < binaryVecStr[a].size(); i += 4) {
    string tmp = binaryVecStr[a].substr(i, 4);
    if (tmp[0] == '0') {
        if (tmp[1] == '0') {
            if (tmp[2] == '0') {
                if tmp[3] == '0') {
                    binToHex += "0";
                } else {
                    binToHex += "1";
                }
            } else {
                if tmp[3] == '0') {
                    binToHex += "2";
                } else {
                    binToHex += "3";
                }
            }
        } else {
            if (tmp[2] == '0') {
                if tmp[3] == '0') {
                    binToHex += "4";
                } else {
                    binToHex += "5";
                }
            } else {
                if tmp[3] == '0') {
                    binToHex += "6";
                } else {
                    binToHex += "7";
                }
            }
        }
    } else {
        if (tmp[1] == '0') {
            if (tmp[2] == '0') {
                if tmp[3] == '0') {
                    binToHex += "8";
                } else {
                    binToHex += "9";
                }
            } else {
                if tmp[3] == '0') {
                    binToHex += "A";
                } else {
                    binToHex += "B";
                }
            }
        } else {
            if (tmp[2] == '0') {
                if tmp[3] == '0') {
                    binToHex += "C";
                } else {
                    binToHex += "D";
                }
            } else {
                if tmp[3] == '0') {
                    binToHex += "E";
                } else {
                    binToHex += "F";
                }
            }
        }
    }
}
hexOStr << binToHex;

您可能还想考虑更紧凑的表示 相同的决策树,例如

string binToHex;
for (size_t i = 0; i < binaryVecStr[a].size(); i += 4) {
    string tmp = binaryVecStr[a].substr(i, 4);
    binToHex += (tmp[0] == '0' ?
                    (tmp[1] == '0' ?
                        (tmp[2] == '0' ?
                            (tmp[3] == '0' ? "0" : "1") :
                            (tmp[3] == '0' ? "2" : "3")) :
                        (tmp[2] == '0' ?
                            (tmp[3] == '0' ? "4" : "5") :
                            (tmp[3] == '0' ? "6" : "7"))) :
                    (tmp[1] == '0' ?
                        (tmp[2] == '0' ?
                            (tmp[3] == '0' ? "8" : "9") :
                            (tmp[3] == '0' ? "A" : "B")) :
                        (tmp[2] == '0' ?
                            (tmp[3] == '0' ? "C" : "D") :
                            (tmp[3] == '0' ? "E" : "F"))));
}
hexOStr << binToHex;

更新:在ASCII到整数解决方案的基础上:

unsigned int nibble = static_cast<unsigned int*>(buffer);
nibble &= 0x01010101;     // 0x31313131 --> 0x01010101
nibble |= (nibble >> 15); // 0x01010101 --> 0x01010303
nibble |= (nibble >> 6);  // 0x01010303 --> 0x0105070C
char* hexDigit = hexDigitTable[nibble & 15];

hexDigitTable(类型char[16])的内容取决于是否 你是一个小端或大端机器。

答案 8 :(得分:1)

至于一个简单的方法,我觉得这个很漂亮:

std::string bintxt_2_hextxt(const std::string &bin)
{
    std::stringstream reader(bin);
    std::stringstream result;

    while (reader)
    {
        std::bitset<8> digit;
        reader >> digit;
        result << std::hex << digit.to_ulong();
    }

    return result.str();
}

我不知道你的数据应该从哪里读取,所以我使用std::string作为输入数据;但如果它来自文本文件或数据流,那么将reader更改为std::ifstream应该不会令人头痛。

小心!我不知道如果流字符不能被8整除,可能会发生什么,而且我还没有测试过这段代码的性能。

Live example