在一个字符串中替换所有特定字符的最快方法

805

在JavaScript中,替换字符串/字符的所有实例的最快方法是什么?使用 while 循环、for 循环还是正则表达式?


4
使用简单算法,while循环和for循环的时间复杂度均为O(n)。不确定JavaScript正则表达式引擎在这种情况下的时间复杂度是什么,但我猜测它已经优化到可以在简单的字符串匹配中以O(n)的速度运行。 - Anurag
3
这对我来说似乎是过于微观优化了 - 性能分析是否显示字符串替换是您程序中最慢的部分? - JBRWilkinson
5
我已经做了一个JSPerf的测试,比较了全局正则表达式和for循环的性能:http://jsperf.com/javascript-replace-all。如果我正确编写了测试,答案似乎是“这取决于情况”。 - Paul D. Waite
我不确定replace何时被优化,但是split-join现在变慢了40%以上。 - Chiramisu
显示剩余3条评论
14个回答

1308

最简单的方法是使用带有 g 标志的正则表达式来替换所有实例:

str.replace(/foo/g, "bar")

这将替换字符串str中所有出现的foobar。如果你只有一个字符串,你可以像下面这样将其转换为RegExp对象:

var pattern = "foobar",
    re = new RegExp(pattern, "g");

2
str.replace(/foo/g, "bar") 对我来说出现了错误。str.replace(/foo/, "bar") 可以正常工作。 - Asmussen
10
警告:此方法不适用于包含换行符的字符串。XRegExp 有一个替换方法可以解决这个问题。 - kgriffs
107
我的内心龟毛提醒我,OP要求的是最快的方法,而不是最简单的方法。 - tomfumb
6
我执行了 user.email.replace(/./g,','),整个电子邮件中的字符都被逗号替换,替换次数等于电子邮件字符数。感到困惑... - Jared Tomaszewski
34
@JaredTomaszewski,正则表达式中的句号(period)字符代表“任何字符”。如果要表示实际的句号,你需要在其前面加上反斜杠,即user.email.replace(/./g,',')。请注意不要改变原文意思,同时让翻译更通俗易懂。 - Squig
显示剩余10条评论

162

尝试使用replaceAll函数:

http://dumpsite.com/forum/index.php?topic=4.msg8#msg8
String.prototype.replaceAll = function(str1, str2, ignore) 
{
    return this.replace(new RegExp(str1.replace(/([\/\,\!\\\^\$\{\}\[\]\(\)\.\*\+\?\|\<\>\-\&])/g,"\\$&"),(ignore?"gi":"g")),(typeof(str2)=="string")?str2.replace(/\$/g,"$$$$"):str2);
} 

这个方法非常快,而且可以适用于其他许多方法无法处理的所有情况:

"x".replaceAll("x", "xyz");
// xyz

"x".replaceAll("", "xyz");
// xyzxxyz

"aA".replaceAll("a", "b", true);
// bb

"Hello???".replaceAll("?", "!");
// Hello!!!

如果您能够打破它或者有更好的解决方案,请告诉我,但请确保它能通过这4个测试。


1
这对于替换未知内容的字符串非常好,但他的字符串是固定的,不需要转义正则表达式的复杂性。我提高了这个因为我正在寻找一个replaceAll函数。 - NickSoft
2
@jens 我想说的是要非常小心。正则表达式非常复杂,一个随意从互联网上找到的答案可能会解决某个问题,但很有可能存在一些潜在的错误。我们不需要对显然正确的答案进行单元测试,这是理所当然的。但是我们希望答案能够让读者了解风险,特别是当一个经验不足的程序员容易被误导时。我的评论有助于警告那些初学者不要轻信这种未经测试的代码。 - ErikE
我已经开始在这个测试套件的底部为这个函数添加单元测试:https://github.com/agrothe/alphanumbers/blob/master/test/index.js - Andrew Grothe
1
“x”.replaceAll("", "xyz") 的行为对我来说看起来像是一个 bug。如果我尝试替换空字符串,我真的希望会出现错误提示。 - Slater Victoroff
2
由于现在有一个标准的replaceAll方法,因此此答案现在覆盖它。请通过首先检查属性的存在来正确进行猴子补丁! - Sebastian Simon
显示剩余3条评论

106
var mystring = 'This is a string';
var newString = mystring.replace(/i/g, "a");

newString现在是'Thas as a strang'


1
这个例程在Firefox上是最快的,但在Chrome上非常慢:请查看我的答案:https://dev59.com/_3I95IYBdhLWcg3w3h_G#57697050 - Zibri

64

也可以尝试以下方法:

string.split('foo').join('bar');

这个处理正则表达式吗?我猜是的。但对于字符串替换,这是我最喜欢的 :) 在Firefox中非常快速。 - yota
@yota 是的。您可以使用正则表达式。 "12px (2) bar-456-foo 44".split(/\d/).join("#") - Vlada
2
这非常适用于大多数简单情况。在一个漂亮的小函数中运行得很好,例如:function replaceAll( s, f, r ){ return s.split( f ).join( r ); }。或者如果您认为正则表达式更快:function replaceAll( s, f, r ){ f = RegExp( f, 'gi' ); return s.replace( f, r ); }。然后只需执行 foo = replaceAll( 'aaa', 'a', 'b' ); - Beejor
1
最佳答案 - Juan Joya
如果您的“needle”是包含一个或多个正则表达式保留字符的变量,则其他答案的方法可能会进行意外的替换。这种方法的好处在于它以相同的方式处理所有字符。 - Quinten
不一定理想,但相当出色。 - code

20
您可以使用以下内容:
newStr = str.replace(/[^a-z0-9]/gi, '_');

或者

newStr = str.replace(/[^a-zA-Z0-9]/g, '_');

这将替换所有不是字母或数字的字符为“_”。只需更改下划线的值,以替换为您想要的任何内容。

3
应该使用.replace(/[a-zA-Z0-9]/g, '_'),去掉^符号。 - pmeyer

14

使用正则表达式对象的方式如下:

var regex = new RegExp('"', 'g'); str = str.replace(regex, '\'');

它将把所有出现的 " 替换成 '


2
其他答案无法解决的问题,其中needle是一个变量,这个答案非常有效。 - Davey

13

我认为真正的答案完全取决于你的输入是什么样子。我创建了一个 JsFiddle 来尝试一些这些方法和我的一些方法来处理各种输入。无论如何,我都没有看到明显的获胜者。

  • 在任何测试案例中,RegExp 都不是最快的,但也不错。
  • 对于稀疏替换,分割/连接方法似乎是最快的。
  • 我写的这个似乎是处理小输入和密集替换最快的:

function replaceAllOneCharAtATime(inSource, inToReplace, inReplaceWith) {
    var output="";
    var firstReplaceCompareCharacter = inToReplace.charAt(0);
    var sourceLength = inSource.length;
    var replaceLengthMinusOne = inToReplace.length - 1;
    for(var i = 0; i < sourceLength; i++){
        var currentCharacter = inSource.charAt(i);
        var compareIndex = i;
        var replaceIndex = 0;
        var sourceCompareCharacter = currentCharacter;
        var replaceCompareCharacter = firstReplaceCompareCharacter;
        while(true){
            if(sourceCompareCharacter != replaceCompareCharacter){
            output += currentCharacter;
            break;
        }
        if(replaceIndex >= replaceLengthMinusOne) {
            i+=replaceLengthMinusOne;
            output += inReplaceWith;
            //was a match
            break;
        }
        compareIndex++; replaceIndex++;
        if(i >= sourceLength){
            // not a match
            break;
        }
        sourceCompareCharacter = inSource.charAt(compareIndex)
            replaceCompareCharacter = inToReplace.charAt(replaceIndex);
        }   
        replaceCompareCharacter += currentCharacter;
    }
    return output;
}

1
另一个需要考虑的因素是,拆分/合并方法是最简单、最短、最直接的方法,因此很可能成为未来浏览器内部优化的最佳选择。在JIST编译期间,它可以被优化为几倍速度更快的东西(例如,它不会创建新数组和新字符串,而是像正则表达式一样线性搜索并复制粘贴)。 - Jack G

13

仅从速度问题考虑,我相信上面链接提供的区分大小写的示例将是迄今为止最快的解决方案。

var token = "\r\n";
var newToken = " ";
var oldStr = "This is a test\r\nof the emergency broadcasting\r\nsystem.";
newStr = oldStr.split(token).join(newToken);

newStr将是"This is a test of the emergency broadcast system."


8

我刚编写了一个基准测试并测试了前三个答案。对于短字符串(<500字符),第三个最受欢迎的答案似乎比第二个更快。

对于长字符串(将“.repeat(300)”添加到测试字符串中),速度最快的是第一种解决方案,其次是第二种和第三种。

注意:

以上内容适用于使用v8引擎的浏览器(chrome/chromium等)。
对于Firefox(SpiderMonkey引擎),结果完全不同
自己检查一下吧! Firefox使用第三种解决方案似乎比使用第一种解决方案的Chrome快4.5倍以上…太疯狂了:D

function log(data) {
  document.getElementById("log").textContent += data + "\n";
}

benchmark = (() => {

  time_function = function(ms, f, num) {
    var z;
    var t = new Date().getTime();
    for (z = 0;
      ((new Date().getTime() - t) < ms); z++) f(num);
    return (z / ms)
  } // returns how many times the function was run in "ms" milliseconds.


  function benchmark() {
    function compare(a, b) {
      if (a[1] > b[1]) {
        return -1;
      }
      if (a[1] < b[1]) {
        return 1;
      }
      return 0;
    }

    // functions

    function replace1(s) {
      s.replace(/foo/g, "bar")
    }

String.prototype.replaceAll2 = function(_f, _r){ 

  var o = this.toString();
  var r = '';
  var s = o;
  var b = 0;
  var e = -1;
//      if(_c){ _f = _f.toLowerCase(); s = o.toLowerCase(); }

  while((e=s.indexOf(_f)) > -1)
  {
    r += o.substring(b, b+e) + _r;
    s = s.substring(e+_f.length, s.length);
    b += e+_f.length;
  }

  // Add Leftover
  if(s.length>0){ r+=o.substring(o.length-s.length, o.length); }

  // Return New String
  return r;
};

String.prototype.replaceAll = function(str1, str2, ignore) {
      return this.replace(new RegExp(str1.replace(/([\/\,\!\\\^\$\{\}\[\]\(\)\.\*\+\?\|\<\>\-\&])/g, "\\$&"), (ignore ? "gi" : "g")), (typeof(str2) == "string") ? str2.replace(/\$/g, "$$$$") : str2);
    }

    function replace2(s) {
      s.replaceAll("foo", "bar")
    }

    function replace3(s) {
      s.split('foo').join('bar');
    }

    function replace4(s) {
      s.replaceAll2("foo", "bar")
    }


    funcs = [
      [replace1, 0],
      [replace2, 0],
      [replace3, 0],
      [replace4, 0]
    ];

    funcs.forEach((ff) => {
      console.log("Benchmarking: " + ff[0].name);
      ff[1] = time_function(2500, ff[0], "foOfoobarBaR barbarfoobarf00".repeat(10));
      console.log("Score: " + ff[1]);

    })
    return funcs.sort(compare);
  }

  return benchmark;
})()
log("Starting benchmark...\n");
res = benchmark();
console.log("Winner: " + res[0][0].name + " !!!");
count = 1;
res.forEach((r) => {
  log((count++) + ". " + r[0].name + " score: " + Math.floor(10000 * r[1] / res[0][1]) / 100 + ((count == 2) ? "% *winner*" : "% speed of winner.") + " (" + Math.round(r[1] * 100) / 100 + ")");
});
log("\nWinner code:\n");
log(res[0][0].toString());
<textarea rows="50" cols="80" style="font-size: 16; resize:none; border: none;" id="log"></textarea>

当你点击按钮时,测试将持续10秒(+2秒)。

我的结果(在同一台电脑上):

Chrome/Linux Ubuntu 64:
1. replace1 score: 100% *winner* (766.18)
2. replace4 score: 99.07% speed of winner. (759.11)
3. replace3 score: 68.36% speed of winner. (523.83)
4. replace2 score: 59.35% speed of winner. (454.78)

Firefox/Linux Ubuntu 64
1. replace3 score: 100% *winner* (3480.1)
2. replace1 score: 13.06% speed of winner. (454.83)
3. replace4 score: 9.4% speed of winner. (327.42)
4. replace2 score: 4.81% speed of winner. (167.46)

很混乱,对吧?

我冒昧地添加了更多的测试结果

Chrome/Windows 10
1. replace1 score: 100% *winner* (742.49)
2. replace4 score: 85.58% speed of winner. (635.44)
3. replace2 score: 54.42% speed of winner. (404.08)
4. replace3 score: 50.06% speed of winner. (371.73)

Firefox/Windows 10
1. replace3 score: 100% *winner* (2645.18)
2. replace1 score: 30.77% speed of winner. (814.18)
3. replace4 score: 22.3% speed of winner. (589.97)
4. replace2 score: 12.51% speed of winner. (331.13)

Edge/Windows 10
1. replace1 score: 100% *winner* (1251.24)
2. replace2 score: 46.63% speed of winner. (583.47)
3. replace3 score: 44.42% speed of winner. (555.92)
4. replace4 score: 20% speed of winner. (250.28)

在Galaxy Note 4上安装Chrome

1. replace4 score: 100% *winner* (99.82)
2. replace1 score: 91.04% speed of winner. (90.88)
3. replace3 score: 70.27% speed of winner. (70.15)
4. replace2 score: 38.25% speed of winner. (38.18)

6

最快的方法我不知道,但我知道最易读的方法是最短和最简单的。即使它比其他解决方案慢一点,也值得使用。

因此,请使用以下内容:

 "string".replace("a", "b");
 "string".replace(/abc?/g, "def");

而不是更快(好吧... 1/100000 秒没有区别)和丑陋的代码。 ;)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接