Qt 格式化字符串


Qt字符串格式化性能比較

Qt字符串格式方法有三種, QString::arg(), QString::sprinft()和QStringList::join().
今天我做了個簡單的性能測試, 希望對各位有所幫助.


調用QString::arg()一次:
  1. QString s("1234567890");

    // 開始計時
    for (int i = 0; i < 10000; ++i) {
        QString str("%1");
        str.arg(s);
    }
    // 停止計時



調用QString::arg()十次:
  1. QString s("1234567890");

    // 開始計時
    for (int i = 0; i < 10000; ++i) {
        QString str("%1%2%3%4%5%6%7%8%9%10");
        str
            .arg(s)
            .arg(s)
            .arg(s)
            .arg(s)
            .arg(s)
            .arg(s)
            .arg(s)
            .arg(s)
            .arg(s)
            .arg(s);
    }
    // 停止計時



調用QString::sprinft()一次:
  1. char s2[] = {"1234567890"};


        // 開始計時
        for (int i = 0; i < times; ++i) {
        QString().sprintf("%d", s2);
        }
        // 停止計時



調用QString::sprinft()十次:
  1. char s2[] = {"1234567890"};


        // 開始計時
        for (int i = 0; i < times; ++i) {
        QString().sprintf("%d%d%d%d%d%d%d%d%d%d", s2, s2, s2, s2, s2, s2, s2, s2, s2, s2);
        }
        // 停止計時



調用QStringList::join()一次:
  1. QString s("1234567890");


        // 開始計時
        for (int i = 0; i < times; ++i) {
        QStringList strList;
        strList.append(s);
        strList.join(QString());
        }
        // 停止計時




調用QStringList::join()十次:
  1. QString s("1234567890");


        // 開始計時
        for (int i = 0; i < times; ++i) {
        QStringList strList;
        strList.append(s);
        strList.append(s);
        strList.append(s);
        strList.append(s);
        strList.append(s);
        strList.append(s);
        strList.append(s);
        strList.append(s);
        strList.append(s);
        strList.append(s);
        strList.join(QString());
        }
        // 停止計時



測試結果:
    運行一次的耗時: QString::arg() 0.412納秒 < QStringList::join() 0.625納秒 < QString::sprinft() 1.136納秒;
    運行十次的耗時: QStringList::join() 2.426納秒 < QString::arg() 5.175納秒 < QString::sprinft() 9.232納秒;


現在讓我們來看看這些函數是怎么完成各自的任務的.


    先來看看QString::arg(). 這個函數的實現非常簡單,  它先將傳入的參數轉換成QString格式的字符串參數, 然后調用內部函數replaceArgEscapes來替換源字符串里的替換符(比如%1, %2, %3什么的). replaceArgEscapes首先計算一下新的字符串將會有多長, 然后預先創建一個足夠長的臨時字符串, 再將源字符串和字符串參數合並到臨時字符串中. 一次操作就這么完成了.
    在這個過程中, 比較消耗時間的是創建了一個臨時字符串. 隨着累加調用QString::arg(), 當調用十次的時候就需要花費5.175納秒, 其中至少有1.500納秒是消耗在創建10個臨時字符串當中. 還有重復多次的內存拷貝. 所以QString::arg()累加調用的越多, 它的執行效率越低.


    下面再來看看QString::sprintf(), 這個函數是仿照C函數的sprintf(), 使用方法上一致, 這個函數也不復雜, 進去之后調用QString::vsprintf(), 在QString::vsprintf()中先創建個臨時字符串, 然后類似於replaceArgEscapes的方式循環追加到這個臨時字符串當中, 唯一的不同是, 這里沒有計算最終字符串的大小, 調用的是QString::append(). 這個函數在緩存夠大的時候直接追加數據, 如果不夠大則重新分配足夠大的內存. 所以QString::vsprintf()和QString::arg()本質上並沒有太大區別. 因為每次都會生成個新的字符串, 並將數據拷貝進去.


    好的東西都要放到最后面再說, 現在我們來看看QStringList::join()這個方法, 這個方法為什么合並10個數據只用了2.426納秒的? 看了源代碼就會豁然開朗了. 它先循環獲取每個字符串的長度, 這樣就可以計算出整個字符串的長度, 注意, 是整個, 不是一部分. 實際上不管QStringList里面有多少數據, 它合並的效率永遠都是O(1).


那么說到這里, QString::arg()和QString::sprintf()的方法基本一樣, 為什么差距那么大呢? 嘿嘿, 你們有沒有發現. 我給arg傳遞的是已經構建完成的QString(), 而給QString::sprintf傳遞的是char *, 所以arg執行的時候不需要再把參數轉換成字符串參數了, 這樣省了10次構建QString()的代價, 而QString::sprintf不得不將char*類型的字符串轉換成為QString(). 這也是沒辦法的事兒, 因為QString::sprintf()根本不接受QString()類型的參數.


現在我把QString::arg()和QString::join()里面預先構建的QString()字符串都替換成臨時構建. 讓我們來看看運行結果.

    運行一次的耗時: QString::arg() 0.642納秒 < QStringList::join() 0.792納秒 < QString::sprinft() 1.146納秒;
    運行十次的耗時: QStringList::join() 4.363納秒 < QString::arg() 7.171納秒 < QString::sprinft() 9.192納秒;


這次算是公平的了, 從上面的數據可以看出來, 為什么官方不推薦使用QString::sprintf()這個函數了, 首先是這么調用不太符合Qt的代碼風格, 除了兼容純C/C++程序員的使用習慣. 基本上可以說一無是處了. 那么為啥也沒有推薦使用QStringList.join()呢? 主要是這個函數的適應性不太好, 打個比方, "My father %1 has two bros, %2 was killed by malaria, %3 is still alive.", 如果用QString::arg()是如此簡單, 如果用QStringList::join(), 那就要打斷成這樣: "My father " << "Old John" << " has two bros, " << "one" << " was killed by malaria, " << "another" << " is still alive", 一個是替換三個, 一個是合並7個.


我希望能通過這次測試各位從事Qt的朋友們, QString::sprintf()完全可以放棄了, 認為這個性能會如何如何好的, 現在應該知道了. 的確是后娘沒人愛的一個函數; 剩下99%的時候都應該使用QString::arg() , 因為各位不會一下子格式化十個八個參數的, 一般都是一兩個的, 這個的性能還是非常高的. 參數超過十個則要果斷選擇QStringList::join()了.


寫了這么多, 希望能對各位有所幫助.


補充:
非常謝謝dbzhang800" data-card-url="pw_ajax.php?action=smallcard&type=showcard&uid=" target="_blank" onclick="return checkUrl(this)" id="url_1">dbzhang800的補充, QString::arg()的模板函數版本平時真的沒怎么用過, 哈哈. 由於QString::arg()的模板版本不接受>=10的參數, 所以把十個參數改成九個參數進行比較.
下面是最新的性能比較結果. 
運行
    運行一次的耗時: QStringBuilder 0.362納秒 < QString::arg() 0.636納秒 < QString::arg() 模板版本 0.637納秒 < QStringList::join() 0.821納秒 < QString::sprinft() 1.275納秒;
    運行十次的耗時: QStringBuilder 2.126納秒 < QStringList::join() 3.781納秒 < QString::arg() 模板版本 5.088納秒 < QString::arg() 6.290納秒 < QString::sprinft() 8.933納秒;


從上面的運行結果來看, QString::arg()模板版本只是略微強於QString::arg()的多次調用版本. 函數內部仍然采用的是追加字符串的方式, 所以這種處理方式仍然面臨多次分配內存的操作. 而且QString::arg()模板版本其實並不是模板函數, 只是一次接受多個QString類型的參數而已, 而且只能接受九個, 實際上它已經丟失了QString::arg()普通版本的一大優勢--基本類型通殺. 也就是說, 如果你要使用QString::arg()的模板版本(姑且這么說吧), 那么你不得不將數據都自己轉換成QString類型的, 否則你就沒辦法使用.
    牛叉的總要放在后面說, 是吧. dbzhang800" data-card-url="pw_ajax.php?action=smallcard&type=showcard&uid=" target="_blank" onclick="return checkUrl(this)" id="url_2">dbzhang800翻譯的文章中已經對這個有詳細的介紹了. 使用非常簡單, #include <QStringBuilder>, 這個是內建的, 然后注意字符串連接時候的連接符是%, 而不是+.


再次感謝dbzhang800" data-card-url="pw_ajax.php?action=smallcard&type=showcard&uid=" target="_blank" onclick="return checkUrl(this)" id="url_4">dbzhang800的幫助, 希望大家能夠繼續補充!

 

dbzhang800 2011-10-17 17:23
呵呵,寫的真不短啊。我補充一點。

都是QString的話,如果用 arg的話,應該選擇:
QString("%1 %2 %3 %4").arg(s1, s2, s3, s4)
而不是
QString("%1 %2 %3 %4").arg(s1).arg(s2).arg(s3).arg(s4)

另外字符串鏈接的話:
s1 + s2 + s3 + s4
也是比較常用的,但性能就不如
s1 % s2 % s3 % s4 
這種寫法了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM