簡體   English   中英

在JavaScript中檢查字符串是否為空(即僅包含空格)的最高性能方法?

[英]The most performant way to check if a string is blank (i.e. only contains whitespace) in JavaScript?

我需要編寫一個測試函數,如果給定的字符串是“空白”,在某種意義上它只包含空白字符。 空白字符如下:

'\u0009',
'\u000A',
'\u000B',
'\u000C',
'\u000D',
' ',
'\u0085',
'\u00A0',
'\u1680',
'\u180E',
'\u2000',
'\u2001',
'\u2002',
'\u2003',
'\u2004',
'\u2005',
'\u2006',
'\u2007',
'\u2008',
'\u2009',
'\u200A',
'\u2028',
'\u2029',
'\u202F',
'\u205F',
'\u3000'

該函數將被調用很多次,因此它必須真正,真正高效。 但是不應該占用太多內存(比如將每個字符映射到數組中的true / false)。 到目前為止我嘗試過的事情:

  • regexp - 性能不高
  • 修剪並檢查長度是否為0 - 不太高效,還使用額外的內存來保持修剪后的字符串
  • 檢查包含空格字符的哈希集的每個字符串字符( if (!whitespaceCharactersMap[str[index]]) ... ) - 運行良好
  • 我目前的解決方案使用硬編碼比較:

     function(str) { var length = str.length; if (!length) { return true; } for (var index = 0; index < length; index++) { var c = str[index]; if (c === ' ') { // skip } else if (c > '\
    ' && c < '\…') { return false; } else if (c < '\ ') { if (c < '\	') { return false; } else if (c > '\…') { return false; } } else if (c > '\ ') { if (c < '\
') { if (c < '\᠎') { if (c < '\ ') { return false; } else if(c > '\ ') { return false; } } else if (c > '\᠎') { if (c < '\ ') { return false; } else if (c > '\ ') { return false; } } } else if (c > '\
') { if (c < '\ ') { if (c < '\ ') { return false; } else if (c > '\ ') { return false; } } else if (c > '\ ') { if (c < '\ ') { return false; } else if (c > '\ ') { return false; } } } } } return true; } 

這似乎比哈希集(在Chrome上測試)快50-100%。

有人看到或知道更多選擇嗎?

更新1

我會在這里回答一些評論:

  • 它不只是檢查用戶輸入的空白​​。 我必須解析必須單獨處理空格的某些數據格式。
  • 值得優化。 我之前已經對代碼進行過分析。 檢查空字符串似乎是一個問題。 而且,正如我們所看到的,方法之間的性能差異可能高達10倍,這絕對值得付出努力。
  • 一般來說,我發現這種“哈希集與正則表達式與交換與分支”的挑戰非常有教育意義。
  • 我需要與瀏覽器以及node.js相同的功能。

現在,這是我對性能測試的看法:

http://jsperf.com/hash-with-comparisons/6

如果你們經常進行這些測試,我將不勝感激。

初步結論:

  • branchlessTest( a^9*a^10*a^11... )在Chrome和Firefox中非常快,但在Safari中卻沒有。 從性能角度來看,可能是Node.js的最佳選擇。
  • 在Chrom和Firefox上,switchTest的速度也相當快,但令人驚訝的是Safari和Opera的速度最慢
  • re.test(str)的regexps在各處表現都很好,甚至在Opera中表現最快。
  • 哈希和分支幾乎在所有地方都顯示出幾乎相同的差異結果。 比較也類似,通常表現最差(這可能是由於實施,檢查' '應該是第一個)。

總而言之,對於我的情況,我將選擇以下正則表達式版本:

var re = /[^\s]/;
return !re.test(str);

原因:

  • 無分支版本在Chrome和Firefox中很酷,但不太便攜
  • 在Safari中切換太慢
  • regexps似乎無處不在,它們在代碼中也非常緊湊

硬編碼解決方案似乎是最好的,但我認為switch應該更快。 這取決於JavaScript解釋器處理這些的方式(大多數編譯器非常有效地執行此操作),因此它可能是特定於瀏覽器的(即,某些編譯器速度快,而其他編譯器速度慢)。 此外,我不確定JavaScript對UTF字符串的速度有多快,因此您可以嘗試在比較值之前將字符轉換為整數代碼。

for (var index = 0; index < length; index++)
{
    var c = str.charCodeAt(index);
    switch (c) {
        case 0x0009: case 0x000A: case 0x000B: case 0x000C: case 0x000D: case 0x0020:
        case 0x0085: case 0x00A0: case 0x1680: case 0x180E: case 0x2000: case 0x2001:
        case 0x2002: case 0x2003: case 0x2004: case 0x2005: case 0x2006: case 0x2007:
        case 0x2008: case 0x2009: case 0x200A: case 0x2028: case 0x2029: case 0x202F:
        case 0x205F: case 0x3000: continue;
    }
    return false;
}

另一個要考慮的是改變for

for (var index in str)
{
    ...
}

編輯

你的jsPerf測試得到了一些修改,現在可以在這里修改 我的代碼在Chrome 26和27以及IE10中明顯更快,但它也是Firefox 18中最慢的代碼。

在64位Linux上的Firefox 20.0上運行了相同的測試(我不知道如何使jsPerf保存那些),結果發現它是兩個最快的測試之一(與trimTest ,兩者都在大約11.8M ops /秒)。 我還在WinXP上測試了Firefox 20.0.1 ,但是在VirtualBox下(仍然在64位Linux下,這可能會產生顯着的差異),這給了switchTest 10M ops / sec,其中trimTestswitchTest ops / sec的速度獲得第二。

所以,我猜測性能取決於瀏覽器版本和/或甚至可能在底層OS /硬件上(我認為上面的FF18測試是在Win上)。 在任何情況下,要制作一個真正優化的版本,你必須制作許多版本,在所有瀏覽器,操作系統,架構上測試每個版本......你可以掌握,然后在你的頁面中包含最適合的版本對於訪問者的瀏覽器,操作系統,架構,......我不確定哪種代碼值得麻煩。

由於分支比大多數其他操作昂貴得多,因此您希望將分支保持在最低限度。 因此,您的if / else語句序列可能不是非常高效。 一種主要使用數學的方法會快得多。 例如:

在不使用任何分支的情況下執行相等性檢查的一種方法是使用按位運算。 一個例子是,檢查a == b:

a ^ b == 0

由於兩個相似位(即1 ^ 1或0 ^ 0)的xor為0,因此xor-two兩個相等的值產生0.這很有用,因為它允許我們將0視為“真”值,並執行更多操作數學。 想象一下,我們有一堆以這種方式表示的布爾變量:非零數字為假,零意味着為真。 如果我們想問,“這些都是真的嗎?” 我們簡單地將它們相乘。 如果它們中的任何一個為真(等於零),則整個結果將為零。

因此,例如,代碼看起來像這樣:

function(str) {
    for (var i = 0; i < str.length; i++) {
        var c = str[i];
        if ((c ^ '\u0009') * (c ^ '\u000A') * (c ^ '\u000B') ... == 0)
            continue;
        return false;
    }
    return true;
}

這樣做的主要原因是比僅執行以下操作更具性能:

if ((c == '\u0009') || (c == '\u000A') || (c == '\u0008') ...)

是JavaScript有短路布爾運算符,意味着每次都是|| 運算符被使用,它不僅執行或操作,而且還檢查它是否可以證明該語句到目前為止必須為真,這是一個昂貴的分支操作。 另一方面,數學方法不涉及分支,除了if語句本身,因此應該更快。

這會在字符串的字符上創建並使用'hash'查找,如果它檢測到非空格,則返回false:

var wsList=['\u0009','\u000A','\u000B','\u000C','\u000D',' ','\u0085','\u00A0','\u1680','\u180E','\u2000','\u2001','\u2002','\u2003','\u2004','\u2005','\u2006','\u2007','\u2008','\u2009','\u200A','\u2028','\u2029','\u202F','\u205F','\u3000'];
var ws=Object.create(null);
wsList.forEach(function(char){ws[char]=true});
function isWhitespace(txt){
    for(var i=0, l=txt.length; i<l; ++i){
        if(!ws[txt[i]])return false;
    }
    return true;
}

var test1=" \u1680 \u000B \u2002 \u2004";
isWhitespace(test1);
/*
true
*/
var test2=" _ . a ";
isWhitespace(test2);
/*
false
*/

不確定它的性能 (還) 在對jsperf進行快速測試之后,與使用/^\\s*$/ RegExp相比,它變得非常慢。


編輯:

您應該使用的解決方案似乎可能取決於您正在使用的數據的性質:數據主要是空白還是大多數非空白? 也主要是ascii范圍文本? 您可以通過對常見的非空白字符范圍使用范圍檢查(通過if ),使用最常見的空格上的switch ,然后對其他所有內容使用哈希查找來加快平均測試用例的速度。 如果測試的大多數數據由最常見的字符組成(在0x0--0x7F之間),這可能會提高測試的平均性能。

也許像這樣(if / switch / hash的混合)可以工作:

/*same setup as above with variable ws being a hash lookup*/
function isWhitespaceHybrid(txt){
    for(var i=0, l=txt.length; i<l; ++i){
        var cc=txt.charCodeAt(i)
        //above space, below DEL
        if(cc>0x20 && cc<0x7F)return false;
        //switch only the most common whitespace
        switch(cc){
            case 0x20:
            case 0x9:
            case 0xA:
            case 0xD:
            continue;
        }
        //everything else use a somewhat slow hash lookup (execute for non-ascii range text)
        if(!ws[txt[i]])return false;
    }
    return true;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM