簡體   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