繁体   English   中英

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

[英]Binary String to Hex c++

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

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 << " ";
}

它彻底而绝对,但速度很慢。

有更简单的方法吗?

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

这是基于完美哈希的另一种看法。 使用gperf生成了完美的哈希(如下所述: 是否可以比使用hashmap更快地将字符串映射到int? )。

通过移动函数局部静态并将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类,稍微编辑并使用hexchar查找进行扩展。 请注意,它确实使用assert验证DEBUG构建中的输入:

住在Coliru

#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);
    }
}

例如od -A none -to /dev/urandom | tr -cd '01' | dd bs=1 count=4096 | ./test演示输出 od -A none -to /dev/urandom | tr -cd '01' | dd bs=1 count=4096 | ./test

基准

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

  1. naive.cpp(没有黑客,没有图书馆) ; Godbolt实时拆卸
  2. spirit.cpp (Trie) ; 在pastebin上 实时 拆卸
  3. 这个答案: perfect.cpp hash based based; Godbolt实时拆卸

为了做一些比较,我已经

  • 使用相同的编译器(GCC 4.9)和标志( -O3 -march=native -g0 -DNDEBUG )编译它们
  • 优化的输入/输出,因此它不会被4个字符/单个字符读取
  • 创建了一个大输入文件(1千兆字节)

结果如下:

在此输入图像描述

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

*总结我会说天真的方法非常好, 因为我在10小时前突发奇想。 如果你真的想要高吞吐量,完美的哈希是一个不错的开始,但考虑手动推出基于SIMD的解决方案

UPDATE2请参阅此处了解基于完美哈希的解决方案 这个解决方案会有我的偏好,因为

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

编辑确实现在基准测试表明完美的哈希解决方案比Spirit方法大约340倍 看这里:

UPDATE

添加了基于Trie的解决方案

这里的查找表使用Boost Spirit的内部Trie实现来快速查找。

当然,如果您愿意out可以将矢量back_inserterostreambuf_iterator<char>替换为字符串流。 现在它永远不会分配4个字符(当然,查询表分配一次)。

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

住在Coliru

#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;
}

使用od -A none -to /dev/urandom | tr -cd '01' | dd bs=1 count=4096 | ./test等随机数据进行测试时 od -A none -to /dev/urandom | tr -cd '01' | dd bs=1 count=4096 | ./test 你得到了od -A none -to /dev/urandom | tr -cd '01' | dd bs=1 count=4096 | ./test


旧的, 基本的答案:

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

这是要点:

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
        ];
}

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

我是这样做的:

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

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

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

  1. 使用char HexChar[n]构建表char HexChar[n] HexChar[0x30303030 % n] = '0', HexChar[0x30303031 % n] = '1'等(或HexChar[0x31303030 % n] = '1'等等,如果您的机器是小端)。

现在转换是闪电般的(我假设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];

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

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;
    }
}

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

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

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

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

在任何情况下,使用它看起来像这样:

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

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

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

这似乎有效。

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

也许这样的事情

#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;
}

这是我能想到的最快的:

#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的基准,它在这个问题上比其他任何事情都要快。

您可以尝试二进制决策树:

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];

hexDigitTablechar[16]类型)的内容取决于你是使用little-endian还是big-endian机器。

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

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整除,会发生什么,而且我还没有测试过这段代码的性能。

实例

我觉得用Trie Tree或者其他什么的太复杂了,我宁愿选择一些代价高昂的计算,但背后的算法似乎更合理和整洁:反向扫描和分组4个元素。

但我下面的解决方案仍然是强大的和功能性的,她可以处理长二进制字符串,并检查代码并删除前缀 0 function:

string bstohs(const string& bs) {
    string hs = {};
    auto c = bs.rbegin();
    auto end = bs.rend();
    if (bs.rfind("0b", 0) == 0) {
        end -= 2;
    }
    int localSum = 0;
    while (end - c > 3) {
        localSum = (*c - '0') + ((*(c + 1) - '0') << 1) + ((*(c + 2) - '0') << 2) + ((*(c + 3) - '0') << 3);
        hs = (localSum > 9 ? string(1, 'a' + localSum - 10) : to_string(localSum)) + hs;
        c += 4;
    }
    localSum = 0;
    for (auto lst = c; end - lst > 0; lst++) {
        if (*lst != '0') {
            localSum += ((*lst - '0') << (lst - c));
        }
    }
    hs = to_string(localSum) + hs;
    return "0x" + hs.erase(0, min(hs.find_first_not_of('0'), hs.size() - 1));
}

这是测试主要功能:

int main(){
    string bs = "0b";
    srand((unsigned)time(NULL)); 
    int test_time, test_max_len;
    cout << "input test time: ";
    cin >> test_time;
    cout << "input test max len: ";
    cin >> test_max_len;
    for (int i = 0; i < test_time; i++) {
        int test_len = rand() % test_max_len;
        bs += "1";
        for (int j = 1; j < test_len; j++) {
            bs += to_string(rand() % 2);
        }
        cout << "test case " << i << "\nraw bs: " << bs << "\nbin len: " << bs.size() - 2 << "\nnow hs: " << bstohs(bs) << endl;
        bs = "0b";
    }
    return 0;
}

一个测试用例:

input test time: 3
input test max len: 400
test case 0
raw bs: 0b1111000001010110010011101010111110001101111101001101000110111111000110011000111000001100111101010111010010000111011001000010100110010100001000101001010001111111010010111111111000000101100110100000000101110111110000110100011001100111111100101011101111010000001101001000011000110110011000011000110110001101010011000001111101111010010010100001010100010110010
bin len: 355
now hs: 0x782b2757c6fa68df8cc7067aba43b214ca114a3fa5ff02cd00bbe1a333f95de81a431b30c6c6a60fbd250a8b2
test case 1
raw bs: 0b110111101010110011001110000110001101010100011011100111000000000111010011000100000011110010100100001101111000100010011110001010100000101010001101011001110111010010001100111011101010100010000110011111011110100010110100010000010010001011111000101110101100110010111010001111010101110011111110
bin len: 288
now hs: 0xdeacce18d51b9c01d3103ca437889e2a0a8d67748ceea8867de8b44122f8baccba3d5cfe
test case 2
raw bs: 0b11010001010111100010001010100111011010111110001111011100000111111011001010110010011011110101001111011
bin len: 101
now hs: 0x1a2bc454ed7c7b83f6564dea7b

环境:

#include <iostream>
#include <string>
#include <stdlib.h>
using namespace std;

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM