在JavaScript中,替换字符串中一系列字符的最佳方法是什么?

5
我正在努力改进一个函数的性能,该函数接受一个XML字符串并替换其中某些字符(编码)后返回该字符串。由于该函数经常被使用,因此运行速度尤为重要。通常情况下,没有任何字符需要被替换 - 因此我希望特别针对这种情况进行优化。正如您在示例代码中所看到的,需要替换的字符串很短且数量相对较少。源字符串通常很短(例如10-20个字符),但也可能更长(例如大约200个字符)。
到目前为止,我已确保正则表达式被预编译,并消除了嵌套函数对操作造成的影响(毫秒级别的时间差在这一点上很重要)。
var objXMLToString = new XMLToStringClass();
function XMLToStringClass(){
    this.tester= /\\34|\\39|\\62|\\60|\\13\\10|\\09|\\92|&amp/;
    this.replacements=[];
    var self=this;
    function init(){        
        var re = new regexReplacePair(/\\34/g,'"');
        self.replacements.push(re);
        re = new regexReplacePair(/\\39/g,"'");
        self.replacements.push(re);
        re = new regexReplacePair(/\\62/g,">");
        self.replacements.push(re);
        re = new regexReplacePair(/\\60/g,"<");
        self.replacements.push(re);
        re = new regexReplacePair(/\\13\\10/g,"\n");
        self.replacements.push(re);
        re = new regexReplacePair(/\\09/g,"\t");
        self.replacements.push(re);
        re = new regexReplacePair(/\\92/g,"\\");
        self.replacements.push(re);
        re = new regexReplacePair(/\&amp;/g,"&");       
        self.replacements.push(re);     
    }
    init();
}


function regexReplacePair(regex,replacementString){
    this.regex = regex;
    this.replacement = replacementString;
}

String.prototype.XMLToString = function() {
        newString=this;
        if(objXMLToString.tester.test(this)){
            for (var x = 0;x<objXMLToString.replacements.length;x++){
                newString=newString.replace(objXMLToString.replacements[x].regex,objXMLToString.replacements[x].replacement);
            }
            return newString;
        }
        return this;
}
  • 在这种情况下,使用String.replace函数是否更好?
  • 目前,如果单个字符匹配,我正在替换所有字符-是否可能测试并有条件地进行替换会更好?如果是这样,对于这个数据集,indexOf比正则表达式更快吗?

如果您可以更改编码,那么有一种更快的方法。我更新了我的答案。 - some
3个回答

6

您可以使用哈希查找:

str.replace(
    /(\\34|\\39|\\62|\\60|\\13\\10|\\09|\\92|&amp)/g,
    function () {
        return {
            "\\34": "\"",
            "\\39": "'",
            //...
            "&amp": "&"
        }[arguments(1)];
    }
);

或者您坚持扩展原型:

var hash = {
    "\\34": "\"",
    "\\39": "'",
    //...
    "&amp": "&"
};

String.prototype.XMLToString = function () {
    return this.replace(
        /(\\34|\\39|\\62|\\60|\\13\\10|\\09|\\92|&amp)/g,
        function () { return hash[arguments(1)]; }
    }
);

这可能会更快(需要进行基准测试):
String.prototype.XMLToString = function () {
    var s = this;

    for (var r in hash) {
        s = s.split(r).join(hash[r]);
    }

    return s;
);

更新

您可以从哈希值生成正则表达式:

var arr = [];

for (var r in hash) {
    arr.push(r);
}

var re = new RegExp("(" + arr.join("|") + ")", "g");

然后将其用作:

s = s.replace(re, function () { ... });

优雅的解决方案 - 我忘记了较新版本的JavaScript可以使用函数来确定替换值。 - Peter Bailey
优雅的解决方案,不幸的是它比原来的慢。 - some

6
我已经对您的原始版本、Ates Gorals哈希、我的改进版哈希、使用switch的版本以及简单解决方案进行了基准测试。获胜者是什么?简单的解决方案!
使用匹配数据(85个字符的字符串)
        original  simple  hash  switch  ag-hash
FF3          194     188   250     240     424
IE7          188     172   641     906    2203
Chrome1      161     156   165     165     225
Opera9       640     625   531     515     734

非匹配数据(85个字符的字符串):

        original  simple  hash  switch  ag-hash
FF3           39       4    34      34      39
IE7          125      15   125     125     156
Chrome1       45       2    54      54      57
Opera9       156      15   156     156     156

(在我的Windows XP笔记本电脑上测试,1.7GHz,结果因人而异)

简单的解决方案:

function XMLToString(str) {
    return (str.indexOf("\\")<0 && str.indexOf("&")<0) ? str :
    str
    .replace(/\\34/g,'"')
    .replace(/\\39/g,"'")
    .replace(/\\62/g,">")
    .replace(/\\60/g,"<")
    .replace(/\\13\\10/g,"\n")
    .replace(/\\09/g,"\t")
    .replace(/\\92/g,"\\")
    .replace(/\&amp;/g,"&");               
}

解释:

首先会检查字符串中是否包含反斜杠或者和号(在所有浏览器中使用indexOf比正则表达式更快)。如果没有,字符串将保持不变。否则,将执行所有替换操作。在这种情况下,缓存正则表达式并没有太大的区别。我尝试了两个数组,一个用于正则表达式,一个用于替换,但是效果并不大。

在您的原始版本中,您对所有组合进行测试,只是为了检测是否应该进行替换。由于正则表达式引擎必须将每个位置与每个组合进行比较,这是昂贵的。我简化了它。

我通过将哈希对象移到外部(因此它不会在调用内部函数时被创建和丢弃),将内部函数移到外部,以便可以重复使用而不是丢弃来改进了Ates Gorals的方法。

更新1 错误修复:移动了和号测试中的括号。

更新2

你的一条评论让我相信你自己对字符串进行了编码,如果是这样,我建议你切换到标准编码,这样你就可以使用内置函数了。

用"%xx"代替"\dd",其中xx是十六进制数。然后你可以使用内置的decodeURIComponent,它更快,并且作为额外奖励可以解码任何字符,包括unicode。

          matching    non match
FF3          44           3
IE7          93          16
Chrome1     132           1
Opera9      109          16

.

function XMLToString_S1(str) {
    return (str.indexOf("%")<0) ? str : decodeURIComponent(str).replace(/\x0D\x0A/g,"\n")
}

因此,您不再拥有像“\09test \60\34string\34\62\13\10\”这样的字符串,而是拥有像“%09test %3c%22string%22%3e%0d%0a”这样的字符串。


太好了!我很惊讶——我的理解是编译正则表达式可能会很耗费资源,在你的最终解决方案中,每次调用函数都会发生这种情况。 - pc1oad1etter
1
编译正则表达式的成本很高,但浏览器会缓存它。 - some
啊,我明白了。我几乎想用反斜杠编码&符号,以消除第二次扫描的影响! - pc1oad1etter
事先对朴素测试的想法很好。 - pc1oad1etter

2
这是我重构你的代码的尝试:
  • 将objXMLToString作为静态对象 - 不需要被实例化
  • 删除内部init()函数 - 使用数组字面量替代
  • 使用数组字面量转换regexReplacePair对象
  • 将for循环转换为while循环(通常更快)
  • 单一返回点
  • newString变量进行了范围限定(现在不再是全局变量)
这是代码:
var objXMLToString = {
     tester: /\\34|\\39|\\62|\\60|\\13\\10|\\09|\\92|&amp/
    ,replacements: [
         [/\\34/g,'"']
        ,[/\\39/g,"'"]
        ,[/\\62/g,">"]
        ,[/\\60/g,"<"]
        ,[/\\13\\10/g,"\n"]
        ,[/\\09/g,"\t"]
        ,[/\\92/g,"\\"]
        ,[/\&amp;/g,"&"]
    ]
}

String.prototype.XMLToString = function()
{
        var newString = this;
        if ( objXMLToString.tester.test( this ) )
        {
                var x = 0, replacer;
                while ( replacer = objXMLToString.replacements[x++] )
                {
                        newString = newString.replace( replacer[0], replacer[1] );
                }
        }
        return newString;
}

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