【问题标题】:Repeat String - Javascript [duplicate]重复字符串 - Javascript [重复]
【发布时间】:2010-09-17 04:28:13
【问题描述】:

返回重复任意次数的字符串的最佳或最简洁的方法是什么?

以下是我迄今为止最好的镜头:

function repeat(s, n){
    var a = [];
    while(a.length < n){
        a.push(s);
    }
    return a.join('');
}

【问题讨论】:

  • 10 多年前,我有一个众所周知的解决方案来解决这个问题,在你问这个问题的几个月前,我在一篇 JavaScript 优化文章中将其用作示例:webreference.com/programming/javascript/jkm3/3.html显然,大多数人都忘记了该代码,而且我认为下面没有任何解决方案和我的一样好。最好的算法看起来像是从我的代码中提取的;除了由于对我的代码工作方式的误解之外,它执行了一个额外的指数级联步骤,这在我的原始代码中通过一个特殊的循环被消除了。
  • 没有人提出约瑟夫的解决方案。 The algorithm 已有 3700 年历史。额外步骤的成本可以忽略不计。 this article 包含关于 Javascript 中字符串连接的错误和误解。对于任何对 Javascript 如何真正在内部处理字符串感兴趣的人,请参阅Rope
  • 似乎没有人注意到 String protoype repeat 的定义和实现,至少在 firefox 中是如此。
  • @kennebec:是的,这是 EcmaScript 6 的一个特性,当被问到这个问题时,它并不存在。它现在得到了很好的支持。
  • @rvighne - 我刚刚检查了kangax.github.io/compat-table/es6/#String.prototype.repeat 我不会认为仅从 Firefox 和 chrome 获得的支持“得到了很好的支持”

标签: javascript string


【解决方案1】:

好消息! String.prototype.repeatnow a part of JavaScript

"yo".repeat(2);
// returns: "yoyo"

除 Internet Explorer 外,所有主流浏览器都支持该方法。如需最新列表,请参阅MDN: String.prototype.repeat > Browser compatibility

MDN 为不支持的浏览器提供a polyfill

【讨论】:

  • 感谢报告当前的事态,虽然我认为 Mozilla polyfill 对于大多数需求来说都非常复杂(我假设他们试图模仿他们高效的 C 实现的行为) - 所以不会真正回答OP 对简洁性的要求。任何其他设置为 polyfill 的方法都一定会更简洁 ;-)
  • 绝对!但是使用内置的一定是最简洁的版本。由于 polyfill 基本上只是后向端口,因此它们会有点复杂以确保与规范(或提议的规范,在这种情况下)的兼容性。我添加它是为了完整性,我猜是由 OP 决定使用哪种方法。
【解决方案2】:

ES-Next方式中有很多方式

1。 ES2015/ES6已经实现了这个repeat()方法!

/** 
 * str: String
 * count: Number
 */
const str = `hello repeat!\n`, count = 3;

let resultString = str.repeat(count);

console.log(`resultString = \n${resultString}`);
/*
resultString = 
hello repeat!
hello repeat!
hello repeat!
*/

({ toString: () => 'abc', repeat: String.prototype.repeat }).repeat(2);
// 'abcabc' (repeat() is a generic method)

// Examples

'abc'.repeat(0);    // ''
'abc'.repeat(1);    // 'abc'
'abc'.repeat(2);    // 'abcabc'
'abc'.repeat(3.5);  // 'abcabcabc' (count will be converted to integer)
// 'abc'.repeat(1/0);  // RangeError
// 'abc'.repeat(-1);   // RangeError

2。 ES2017/ES8新添加String.prototype.padStart()

const str = 'abc ';
const times = 3;

const newStr = str.padStart(str.length * times, str.toUpperCase());

console.log(`newStr =`, newStr);
// "newStr =" "ABC ABC abc "

3。 ES2017/ES8新添加String.prototype.padEnd()

const str = 'abc ';
const times = 3;

const newStr = str.padEnd(str.length * times, str.toUpperCase());

console.log(`newStr =`, newStr);
// "newStr =" "abc ABC ABC "

参考

http://www.ecma-international.org/ecma-262/6.0/#sec-string.prototype.repeat

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd

【讨论】:

    【解决方案3】:

    要重复一个字符串指定的次数,我们可以使用 JavaScript 中内置的repeat() 方法。

    这是一个将以下字符串重复 4 次的示例:

    const name = "king";
    
    const repeat = name.repeat(4);
    
    console.log(repeat);
    

    输出:

    "kingkingkingking"
    

    或者我们可以像这样创建自己的repeat() 函数版本:

    function repeat(str, n) {
      if (!str || !n) {
        return;
      }
    
     let final = "";
      while (n) {
        final += s;
        n--;
      }
      return final;
    }
    
    console.log(repeat("king", 3))
    

    (原发于https://reactgo.com/javascript-repeat-string/

    【讨论】:

      【解决方案4】:

      我已经测试了所有建议方法的性能。

      这是我所拥有的最快的变体

      String.prototype.repeat = function(count) {
          if (count < 1) return '';
          var result = '', pattern = this.valueOf();
          while (count > 1) {
              if (count & 1) result += pattern;
              count >>= 1, pattern += pattern;
          }
          return result + pattern;
      };
      

      或作为独立功能:

      function repeat(pattern, count) {
          if (count < 1) return '';
          var result = '';
          while (count > 1) {
              if (count & 1) result += pattern;
              count >>= 1, pattern += pattern;
          }
          return result + pattern;
      }
      

      它基于artistoex 算法。 它真的很快。与传统的new Array(count + 1).join(string) 方法相比,count 越大,运行速度就越快。

      我只改变了两件事:

      1. pattern = this 替换为pattern = this.valueOf()(清除一个明显的类型转换);
      2. 在函数顶部添加了if (count &lt; 1)prototypejs 的检查,以排除这种情况下不必要的操作。
      3. Dennis answer 应用优化(加速 5-7%)

      UPD

      为感兴趣的人创建了一个小的性能测试游乐场here

      变量count ~ 0 .. 100:

      常量count = 1024:

      如果可以的话,使用它并让它更快:)

      【讨论】:

      • 干得好!我认为count &lt; 1的情况确实是不必要的优化。
      • 优秀的算法 O(log N)。感谢 valueOf() 的出色优化
      • 图片链接失效。
      • 链接很好。可能暂时不可用
      • 测试 JSFiddle 不再正常工作;它似乎只是一遍又一遍地运行第一个函数(确保它运行了半个小时)
      【解决方案5】:

      对于 ES8,您还可以使用 padStartpadEnd 来实现这一点。例如。

      var str = 'cat';
      var num = 23;
      var size = str.length * num;
      "".padStart(size, str) // outputs: 'catcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcat'
      

      【讨论】:

        【解决方案6】:

        只是另一个重复功能:

        function repeat(s, n) {
          var str = '';
          for (var i = 0; i < n; i++) {
            str += s;
          }
          return str;
        }
        

        【讨论】:

          【解决方案7】:

          基于数字连接字符串。

          function concatStr(str, num) {
             var arr = [];
          
             //Construct an array
             for (var i = 0; i < num; i++)
                arr[i] = str;
          
             //Join all elements
             str = arr.join('');
          
             return str;
          }
          
          console.log(concatStr("abc", 3));
          

          希望有帮助!

          【讨论】:

            【解决方案8】:
            function repeat(pattern, count) {
              for (var result = '';;) {
                if (count & 1) {
                  result += pattern;
                }
                if (count >>= 1) {
                  pattern += pattern;
                } else {
                  return result;
                }
              }
            }
            

            您可以在JSFiddle 进行测试。以 hacky Array.join 为基准,粗略地说,我的是 10 (Chrome) to 100 (Safari) to 200 (Firefox) times faster(取决于浏览器)。

            【讨论】:

              【解决方案9】:

              适用于所有浏览器

              这是最简洁的:

              function repeat(s, n) { return new Array(n+1).join(s); }
              

              如果您还关心性能,这是一个更好的方法:

              function repeat(s, n) { var a=[],i=0;for(;i<n;)a[i++]=s;return a.join(''); }
              

              如果您想比较这两个选项的性能,请参阅this Fiddlethis Fiddle 进行基准测试。在我自己的测试中,第二个选项在 Firefox 中大约快 2 倍,在 Chrome 中大约快 4 倍!

              仅适用于现代浏览器:

              在现代浏览器中,您现在也可以这样做:

              function repeat(s,n) { return s.repeat(n) };
              

              这个选项不仅比其他两个选项都短,而且比第二个选项短even faster

              很遗憾,它不适用于任何版本的 Internet Explorer。表中的数字指定了第一个完全支持该方法的浏览器版本:

              【讨论】:

                【解决方案10】:

                将 Lodash 用于 Javascript 实用程序功能,例如重复字符串。

                Lodash 提供了出色的性能和 ECMAScript 兼容性。

                我强烈推荐它用于 UI 开发,它在服务器端也能很好地工作。

                以下是如何使用 Lodash 将字符串“yo”重复 2 次:

                > _.repeat('yo', 2)
                "yoyo"
                

                【讨论】:

                  【解决方案11】:

                  使用Array(N+1).join("string_to_repeat")

                  【讨论】:

                  • 我喜欢这个。不知道为什么它不在上面。
                  • 喜欢它。如此简单干净
                  【解决方案12】:

                  新读者注意:这个答案很老,而且不是很实用 - 它只是“聪明”,因为它使用 Array 的东西来获取 把事情串起来。当我写“更少的过程”时,我的意思是 “更少的代码”,因为正如其他人在随后的答案中所指出的那样,它 表现得像猪。因此,如果速度对您很重要,请不要使用它。

                  我会直接把这个函数放到 String 对象上。与其创建一个数组,填充它,然后用一个空字符连接它,只需创建一个适当长度的数组,然后用你想要的字符串连接它。相同的结果,更少的过程!

                  String.prototype.repeat = function( num )
                  {
                      return new Array( num + 1 ).join( this );
                  }
                  
                  alert( "string to repeat\n".repeat( 4 ) );
                  

                  【讨论】:

                  • 我尽量不扩展原生对象,否则这是一个很好的解决方案。谢谢!
                  • @brad - 为什么不呢?您宁愿使用具有相当明确的 home(String 对象)的函数来污染全局命名空间?
                  • 实际上,您的两个参数也适用于全局命名空间。如果我要扩展一个命名空间并有潜在的冲突,我宁愿这样做 1)不在全局 2)在一个相关的和 3)易于重构。这意味着将其放在 String 原型中,而不是全局中。
                  • 如果您不想扩展原生对象,可以将函数放在 String 对象上,如下所示:String.repeat = function(string, num){ return new Array(parseInt(num) + 1).join(string); };。像这样称呼它:String.repeat('/\', 20)
                  • @nickf:人们不应该将字符串传递给期望数字的函数。
                  【解决方案13】:

                  String.prototype.repeat 现在是 ES6 标准。

                  'abc'.repeat(3); //abcabcabc
                  

                  【讨论】:

                  • 不错! .. 但对我不可用(ios kangax.github.io/compat-table/es6
                  • @Benjamin 在链接上使用 polyfill。
                  • 我认为应该是一个固定的答案。
                  【解决方案14】:

                  如果您认为所有这些原型定义、数组创建和连接操作都太过分了,只需在需要的地方使用一行代码即可。字符串 S 重复 N 次:

                  for (var i = 0, result = ''; i < N; i++) result += S;
                  

                  【讨论】:

                  • 代码应该是可读的。如果您实际上每个人都只会使用一次,那么请正确格式化它(或者如果它不是性能瓶颈,则使用Array(N + 1).join(str) 方法)。如果您有可能会使用它两次,请将其移至适当命名的函数中。
                  【解决方案15】:

                  首先,OP 的问题似乎是关于简洁性——我理解它的意思是“简单易读”,而大多数答案似乎是关于效率——这显然不是一回事,而且我认为除非你实现一些非常具体的大数据操作算法,当你来实现基本的数据操作 Javascript 函数时不应该担心。简洁更为重要。

                  其次,正如 André Laszlo 所指出的,String.repeat 是 ECMAScript 6 的一部分,并且已经在几个流行的实现中可用 - 所以String.repeat 最简洁的实现是不实现它;-)

                  最后,如果您需要支持不提供 ECMAScript 6 实现的主机,André Laszlo 提到的 MDN 的 polyfill 绝不是简洁的。

                  所以,事不宜迟 - 这是我的简洁 polyfill:

                  String.prototype.repeat = String.prototype.repeat || function(n){
                      return n<=1 ? this : this.concat(this.repeat(n-1));
                  }
                  

                  是的,这是一个递归。我喜欢递归——它们很简单,如果做得正确很容易理解。关于效率,如果语言支持它,如果编写正确,它们会非常有效。

                  根据我的测试,此方法比 Array.join 方法快约 60%。尽管它显然与 disfated 的实现相去甚远,但它比两者都简单得多。

                  我的测试设置是节点 v0.10,使用“严格模式”(我认为它启用某种 TCO),在 10 个字符串上调用 repeat(1000) 一百万次。

                  【讨论】:

                    【解决方案16】:

                    简单的递归连接

                    我只是想给它一个狂欢,并做了这个:

                    function ditto( s, r, c ) {
                        return c-- ? ditto( s, r += s, c ) : r;
                    }
                    
                    ditto( "foo", "", 128 );
                    

                    我不能说我想了很多,它可能表明:-)

                    这个可以说更好

                    String.prototype.ditto = function( c ) {
                        return --c ? this + this.ditto( c ) : this;
                    };
                    
                    "foo".ditto( 128 );
                    

                    这很像已经发布的答案 - 我知道这一点。

                    但为什么要递归呢?

                    还有一点默认行为呢?

                    String.prototype.ditto = function() {
                        var c = Number( arguments[ 0 ] ) || 2,
                            r = this.valueOf();
                        while ( --c ) {
                            r += this;
                        }
                        return r;
                    }
                    
                    "foo".ditto();
                    

                    因为,虽然非递归方法可以处理任意大的重复而不会达到调用堆栈限制,但速度要慢很多。

                    为什么我要添加更多的方法,这些方法不如已经发布的方法的一半聪明

                    部分是为了我自己的娱乐,部分是为了以最简单的方式指出我知道给猫剥皮的方法有很多种,并且根据情况,很可能显然是最好的方法不理想。

                    一种相对快速和复杂的方法可能会在某些情况下有效地崩溃和烧毁,而一种更慢、更简单的方法可能最终完成工作。

                    有些方法可能只是漏洞利用,因此容易被修复不复存在,而其他方法可能在所有条件下都能很好地工作,但构造得如此一个 根本不知道它是如何工作的。

                    “如果我不知道它是如何工作的怎么办?!”

                    真的吗?

                    JavaScript 受到其最大优势之一的影响;它对不良行为的容忍度很高,而且非常灵活,它会向后弯腰返回结果,如果它被折断可能对每个人都更好!

                    “权力越大,责任越大” ;-)

                    但更严肃和重要的是,虽然像这样的一般性问题确实会以聪明的形式令人敬畏,但如果不出意外,扩大一个人的知识和视野,最终,手头的任务- 使用结果方法的实用脚本 - 可能需要比建议的少一点或多一点聪明

                    这些“完美”算法很有趣,但“一刀切”很少会比量身定制的更好。

                    由于睡眠不足和一时的兴趣,给您带来了这篇布道。 快去编码吧!

                    【讨论】:

                      【解决方案17】:

                      人们将其复杂化到荒谬的程度或浪费性能。数组?递归?你一定是在开玩笑吧。

                      function repeat (string, times) {
                        var result = ''
                        while (times-- > 0) result += string
                        return result
                      }
                      

                      编辑。我进行了一些简单的测试,以与 Artistoex / disfated 和其他一些人发布的按位版本进行比较。后者只是稍微快一点,但内存效率要高几个数量级。对于单词“blah”的 1000000 次重复,Node 进程使用简单的连接算法(上图)达到 46 兆字节,但使用对数算法只有 5.5 兆字节。后者绝对是要走的路。为了清楚起见重新发布:

                      function repeat (string, times) {
                        var result = ''
                        while (times > 0) {
                          if (times & 1) result += string
                          times >>= 1
                          string += string
                        }
                        return result
                      }
                      

                      【讨论】:

                      • 你有一半的时间是多余的string += string
                      【解决方案18】:

                      小提琴:http://jsfiddle.net/3Y9v2/

                      function repeat(s, n){
                          return ((new Array(n+1)).join(s));
                      }
                      alert(repeat('R', 10));
                      

                      【讨论】:

                        【解决方案19】:

                        这个问题是 JavaScript 的一个众所周知的/“经典”优化问题,原因是 JavaScript 字符串是“不可变的”,并且通过将单个字符连接到字符串来添加,包括为并复制到一个全新的字符串。

                        不幸的是,此页面上接受的答案是错误的,其中“错误”意味着简单的单字符字符串的性能因子为 3 倍,短字符串重复多次的性能因子为 8x-97x,重复句子的性能因子为 300x,并且当将算法的复杂度比率限制为n 时,无限错误。此外,此页面上还有另一个答案几乎是正确的(基于过去 13 年在互联网上流传的正确解决方案的许多代和变体之一)。然而,这种“几乎正确”的解决方案错过了正确算法的关键点,导致性能下降 50%。

                        JS Performance Results for the accepted answer, the top-performing other answer (based on a degraded version of the original algorithm in this answer), and this answer using my algorithm created 13 years ago

                        ~ 2000 年 10 月,我针对这个确切的问题发布了一个算法,该算法被广泛地改编、修改,但最终却很少被理解和遗忘。为了解决这个问题,我在 2008 年 8 月发表了一篇文章http://www.webreference.com/programming/javascript/jkm3/3.html,解释了算法并将其用作简单的通用 JavaScript 优化示例。到目前为止,Web Reference 已经从这篇文章中删除了我的联系信息,甚至我的名字。再一次,该算法已被广泛采用、修改,然后却鲜为人知,并在很大程度上被遗忘了。

                        原始字符串重复/乘法JavaScript算法由 Joseph Myers,大约 Y2K,作为 Text.js 中的文本乘法函数; 2008 年 8 月由 Web Reference 以这种形式发布: http://www.webreference.com/programming/javascript/jkm3/3.html(该 文章使用该函数作为 JavaScript 优化的示例, 这是奇怪名称“stringFill3”的唯一名称。)

                        /*
                         * Usage: stringFill3("abc", 2) == "abcabc"
                         */
                        
                        function stringFill3(x, n) {
                            var s = '';
                            for (;;) {
                                if (n & 1) s += x;
                                n >>= 1;
                                if (n) x += x;
                                else break;
                            }
                            return s;
                        }
                        

                        在那篇文章发表后的两个月内,同样的问题被发布到 Stack Overflow 并一直在我的雷达下飞行,直到现在,显然这个问题的原始算法再次被遗忘了。此 Stack Overflow 页面上可用的最佳解决方案是我的解决方案的修改版本,可能由几代人分开。不幸的是,这些修改破坏了解决方案的最优性。事实上,通过改变我原来的循环结构,修改后的解决方案执行了一个完全不需要的额外的指数复制步骤(因此将正确答案中使用的最大字符串与自身连接额外的时间,然后丢弃它)。

                        下面将讨论一些与此问题的所有答案相关的 JavaScript 优化,并造福于所有人。

                        技术:避免引用对象或对象属性

                        为了说明这种技术的工作原理,我们使用了一个真实的 JavaScript 函数,它可以创建所需长度的字符串。正如我们将看到的,可以添加更多优化!

                        这里使用的功能是创建填充以对齐文本列、格式化货币或将块数据填充到边界。文本生成函数还允许可变长度输入,以测试对文本进行操作的任何其他函数。该函数是JavaScript文本处理模块的重要组成部分之一。

                        随着我们的继续,我们将介绍另外两种最重要的优化技术,同时将原始代码开发为用于创建字符串的优化算法。最终结果是我在任何地方都使用过的工业级高性能函数——在 JavaScript 订单表单、数据格式和电子邮件/文本消息格式以及许多其他用途中对齐项目价格和总计。

                        创建字符串的原始代码stringFill1()

                        function stringFill1(x, n) { 
                            var s = ''; 
                            while (s.length < n) s += x; 
                            return s; 
                        } 
                        /* Example of output: stringFill1('x', 3) == 'xxx' */ 
                        

                        这里的语法很清楚。如您所见,在进行更多优化之前,我们已经使用了局部函数变量。

                        请注意,代码中有一个对对象属性s.length 的无辜引用会损害其性能。更糟糕的是,使用这个对象属性会假设读者知道 JavaScript 字符串对象的属性,从而降低了程序的简单性。

                        使用这个对象属性破坏了计算机程序的通用性。该程序假定x 必须是长度为1 的字符串。这将stringFill1() 函数的应用限制为除了单个字符的重复之外的任何内容。如果单个字符包含多个字节,例如 HTML 实体 &amp;nbsp;,则即使单个字符也无法使用。

                        这种不必要地使用对象属性导致的最严重问题是,如果在空输入字符串x 上进行测试,该函数会创建一个无限循环。要检查一般性,请将程序应用于尽可能少的输入。当被要求超过可用内存量时崩溃的程序有一个借口。像这样的程序在被要求不产生任何内容时崩溃是不可接受的。有时漂亮的代码是有毒的代码。

                        简单性可能是计算机编程的一个模棱两可的目标,但通常不是。当一个程序缺乏任何合理的通用性水平时,说“程序就其发展而言已经足够好”是无效的。如您所见,使用string.length 属性会阻止此程序在一般设置下运行,实际上,不正确的程序已准备好导致浏览器或系统崩溃。

                        有没有办法提高这个 JavaScript 的性能,同时解决这两个严重的问题?

                        当然。只需使用整数。

                        优化了创建字符串的代码stringFill2()

                        function stringFill2(x, n) { 
                            var s = ''; 
                            while (n-- > 0) s += x; 
                            return s; 
                        } 
                        

                        比较stringFill1()stringFill2()的时序代码

                        function testFill(functionToBeTested, outputSize) { 
                            var i = 0, t0 = new Date(); 
                            do { 
                                functionToBeTested('x', outputSize); 
                                t = new Date() - t0; 
                                i++; 
                            } while (t < 2000); 
                            return t/i/1000; 
                        } 
                        seconds1 = testFill(stringFill1, 100); 
                        seconds2 = testFill(stringFill2, 100); 
                        

                        stringFill2()迄今为止的成功

                        stringFill1() 需要 47.297 微秒(百万分之一秒)来填充 100 字节的字符串,stringFill2() 需要 27.68 微秒来完成相同的操作。通过避免引用对象属性,这几乎使性能翻了一番。

                        技巧:避免在长字符串中添加短字符串

                        我们之前的结果看起来不错——实际上非常好。由于使用了我们的前两个优化,改进的函数stringFill2() 更快。如果我告诉你它可以改进到比现在快很多倍,你会相信吗?

                        是的,我们可以实现这个目标。现在我们需要解释如何避免将短字符串附加到长字符串。

                        与我们的原始函数相比,短期行为似乎相当不错。计算机科学家喜欢分析函数或计算机程序算法的“渐近行为”,这意味着通过使用更大的输入对其进行测试来研究其长期行为。有时不做进一步的测试,人们永远不会意识到可以改进计算机程序的方法。为了看看会发生什么,我们将创建一个 200 字节的字符串。

                        stringFill2() 出现的问题

                        使用我们的计时函数,我们发现 200 字节字符串的时间增加到 62.54 微秒,而 100 字节字符串的时间为 27.68 微秒。做两倍工作的时间似乎应该加倍,但实际上是三倍或四倍。从编程经验来看,这个结果似乎很奇怪,因为如果有的话,函数应该稍微快一些,因为工作效率更高(每个函数调用 200 字节而不是每个函数调用 100 字节)。这个问题与 JavaScript 字符串的一个隐蔽属性有关:JavaScript 字符串是“不可变的”。

                        不可变意味着字符串一旦创建就不能更改。通过一次添加一个字节,我们不会再消耗一个字节。我们实际上是在重新创建整个字符串加上一个字节。

                        实际上,要向 100 字节的字符串添加一个字节,需要 101 个字节的工作量。让我们简要分析一下创建N 字节字符串的计算成本。添加第一个字节的成本是 1 个计算工作单位。添加第二个字节的成本不是一个单位而是 2 个单位(将第一个字节复制到新的字符串对象以及添加第二个字节)。第三个字节需要 3 个单位的成本,以此类推。

                        C(N) = 1 + 2 + 3 + ... + N = N(N+1)/2 = O(N^2)。符号O(N^2)发音为Big O of N squared,这意味着长期的计算成本与字符串长度的平方成正比。创建 100 个字符需要 10,000 个工作单位,创建 200 个字符需要 40,000 个工作单位。

                        这就是为什么创建 200 个字符所用的时间是创建 100 个字符所用时间的两倍多。事实上,它应该花费四倍的时间。我们的编程经验是正确的,因为对于较长的字符串,工作效率稍高一些,因此只用了大约三倍的时间。一旦函数调用的开销对于我们创建的字符串的长度变得可以忽略不计,实际上创建两倍长的字符串需要四倍的时间。

                        (历史记录:这种分析不一定适用于源代码中的字符串,例如html = 'abcd\n' + 'efgh\n' + ... + 'xyz.\n',因为JavaScript源代码编译器可以在将字符串组合成JavaScript字符串对象之前将它们连接在一起。仅仅几年以前,JavaScript 的 KJS 实现在加载加号连接的长串源代码时会死机或崩溃。由于计算时间为O(N^2),因此制作超载 Konqueror Web 浏览器或 Safari 的网页并不难,这使用的是KJS JavaScript引擎核心。我在开发标记语言和JavaScript标记语言解析器时第一次遇到这个问题,然后我在编写JavaScript Includes脚本时发现了导致问题的原因。)

                        显然,这种性能的快速下降是一个大问题。鉴于我们无法改变 JavaScript 将字符串作为不可变对象处理的方式,我们该如何处理呢?解决方案是使用尽可能少地重新创建字符串的算法。

                        澄清一下,我们的目标是避免将短字符串添加到长字符串中,因为要添加短字符串,还必须复制整个长字符串。

                        算法如何避免将短字符串添加到长字符串中

                        这是减少创建新字符串对象次数的好方法。将较长的字符串连接在一起,以便一次将一个以上的字节添加到输出中。

                        例如,制作一个长度为N = 9的字符串:

                        x = 'x'; 
                        s = ''; 
                        s += x; /* Now s = 'x' */ 
                        x += x; /* Now x = 'xx' */ 
                        x += x; /* Now x = 'xxxx' */ 
                        x += x; /* Now x = 'xxxxxxxx' */ 
                        s += x; /* Now s = 'xxxxxxxxx' as desired */
                        

                        这样做需要创建一个长度为 1 的字符串,创建一个长度为 2 的字符串,创建一个长度为 4 的字符串,创建一个长度为 8 的字符串,最后创建一个长度为 9 的字符串。我们节省了多少成本?

                        旧成本C(9) = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 9 = 45.

                        新费用C(9) = 1 + 2 + 4 + 8 + 9 = 24

                        请注意,我们必须将长度为 1 的字符串添加到长度为 0 的字符串中,然后将长度为 1 的字符串添加到长度为 1 的字符串中,然后将长度为 2 的字符串添加到长度为 2 的字符串中,然后再添加一个字符串将长度为 4 的字符串转换为长度为 4 的字符串,然后将长度为 8 的字符串转换为长度为 1 的字符串,从而获得长度为 9 的字符串。我们所做的可以概括为避免在长字符串中添加短字符串,或者换句话说,尝试将长度相等或几乎相等的字符串连接在一起。

                        对于旧的计算成本,我们找到了一个公式N(N+1)/2。新成本有公式吗?是的,但它很复杂。重要的是它是O(N),因此将字符串长度加倍大约会使工作量增加一倍而不是四倍。

                        实现这一新想法的代码几乎与计算成本的公式一样复杂。阅读时请记住,&gt;&gt;= 1 表示右移 1 个字节。所以如果n = 10011 是一个二进制数,那么n &gt;&gt;= 1 的结果就是n = 1001

                        您可能不认识的代码的另一部分是按位和运算符,写作&amp;。如果n 的最后一个二进制数字为1,则表达式n &amp; 1 计算结果为真,如果n 的最后一个二进制数字为0,则计算结果为假。

                        新的高效stringFill3()函数

                        function stringFill3(x, n) { 
                            var s = ''; 
                            for (;;) { 
                                if (n & 1) s += x; 
                                n >>= 1; 
                                if (n) x += x; 
                                else break; 
                            } 
                            return s; 
                        } 
                        

                        在未经训练的人眼中看起来很丑,但它的表现却不亚于可爱。

                        让我们看看这个函数的性能如何。看到结果后,您可能永远不会忘记O(N^2) 算法和O(N) 算法之间的区别。

                        stringFill1() 需要 88.7 微秒(百万分之一秒)来创建一个 200 字节的字符串,stringFill2() 需要 62.54,stringFill3() 只需 4.608。是什么让这个算法变得更好?所有函数都利用了局部函数变量,但利用第二和第三个优化技术,stringFill3() 的性能提高了 20 倍。

                        深入分析

                        是什么让这个特殊的功能让竞争对手脱颖而出?

                        正如我所提到的,stringFill1()stringFill2() 这两个函数运行如此缓慢的原因是 JavaScript 字符串是不可变的。无法重新分配内存以允许一次多一个字节附加到 JavaScript 存储的字符串数据。每在字符串末尾增加一个字节,就会从头到尾重新生成整个字符串。

                        因此,为了提高脚本的性能,必须通过提前将两个字符串连接在一起来预先计算更长的字符串,然后递归地建立所需的字符串长度。

                        例如,要创建一个 16 个字母的字节字符串,首先要预先计算一个两个字节的字符串。然后将重用两个字节的字符串来预先计算一个四字节的字符串。然后将重新使用四字节字符串来预先计算八字节字符串。最后,将重用两个 8 字节字符串来创建所需的 16 字节新字符串。总共需要创建四个新字符串,一个长度为 2,一个长度为 4,一个长度为 8,一个长度为 16。总成本为 2 + 4 + 8 + 16 = 30。

                        从长远来看,这个效率可以通过以相反的顺序相加并使用以第一项 a1 = N 开始并且具有共同比率 r = 1/2 的几何级数来计算。几何级数的总和由a_1 / (1-r) = 2N 给出。

                        这比添加一个字符来创建一个长度为 2 的新字符串、创建一个长度为 3、4、5 等直到 16 的新字符串更有效。之前的算法使用添加单个字节的过程一次,总成本为n (n + 1) / 2 = 16 (17) / 2 = 8 (17) = 136

                        显然,136 是一个比 30 大得多的数字,因此前面的算法需要很多很多的时间来构建一个字符串。

                        要比较这两种方法,您可以看到递归算法(也称为“分治法”)在长度为 123,457 的字符串上的速度要快得多。在我的 FreeBSD 计算机上,这个算法在 stringFill3() 函数中实现,在 0.001058 秒内创建字符串,而原始 stringFill1() 函数在 0.0808 秒内创建字符串。新功能速度提高了 76 倍。

                        随着字符串的长度变大,性能上的差异也越来越大。在创建越来越大的字符串的限制下,原始函数的行为大致类似于C1(常量)乘以N^2,而新函数的行为类似于C2(常量)乘以N

                        通过我们的实验,我们可以确定C1 的值为C1 = 0.0808 / (123457)2 = .00000000000530126997C2 的值为C2 = 0.001058 / 123457 = .00000000856978543136。在 10 秒内,新函数可以创建一个包含 1,166,890,359 个字符的字符串。为了创建相同的字符串,旧函数需要 7,218,384 秒的时间。

                        与十秒相比,这几乎是三个月!

                        我只是回答(晚了几年),因为我对这个问题的最初解决方案已经在互联网上流传了 10 多年,而且显然仍然为少数记得它的人所理解。我认为通过在这里写一篇关于它的文章我会有所帮助:

                        Performance Optimizations for High Speed JavaScript / Page 3

                        不幸的是,这里介绍的其他一些解决方案仍然需要三个月才能产生与适当解决方案在 10 秒内产生的相同数量的输出。

                        我想花时间在此处复制部分文章作为 Stack Overflow 上的规范答案。

                        请注意,这里表现最好的算法显然是基于我的算法,并且可能是从其他人的第 3 代或第 4 代改编继承而来的。不幸的是,这些修改导致其性能降低。我在这里提出的解决方案的变体可能不理解我令人困惑的for (;;) 表达式,它看起来像用 C 编写的服务器的主无限循环,它的设计目的只是为了允许仔细定位的 break 语句进行循环控制,最一种紧凑的方法来避免以指数方式复制字符串一个额外的不必要的时间。

                        【讨论】:

                        • 这个答案不应该得到那么多赞。首先,约瑟夫的所有权主张是可笑的。 The underlying algorithm 已有 3700 年历史。
                        • 第二,它包含很多错误信息。现代 Javascript 实现在执行连接时甚至不会触及字符串的内容(v8 将连接的字符串表示为 ConsString 类型的对象)。所有剩余的增强都可以忽略不计(就渐近复杂度而言)。
                        • 您对如何连接字符串的想法是错误的。为了连接两个字符串,Javascript 根本不读取组成字符串的字节。相反,它只是创建一个引用左右部分的对象。这就是为什么循环中的最后一个连接并不比第一个更昂贵。
                        • 当然,这会导致对字符串进行索引的成本大于 O(1),因此连接可能会在稍后变平,这确实值得进一步评估。
                        • 这是一本优秀的读物。你应该写一本关于效率之类的书!
                        【解决方案20】:

                        简单方法:

                        String.prototype.repeat = function(num) {
                            num = parseInt(num);
                            if (num < 0) return '';
                            return new Array(num + 1).join(this);
                        }
                        

                        【讨论】:

                          【解决方案21】:
                          /**  
                          @desc: repeat string  
                          @param: n - times  
                          @param: d - delimiter  
                          */
                          
                          String.prototype.repeat = function (n, d) {
                              return --n ? this + (d || '') + this.repeat(n, d) : '' + this
                          };
                          

                          这是使用分隔符多次重复字符串的方法。

                          【讨论】:

                            【解决方案22】:

                            这是 JSLint 安全版本

                            String.prototype.repeat = function (num) {
                              var a = [];
                              a.length = num << 0 + 1;
                              return a.join(this);
                            };
                            

                            【讨论】:

                              【解决方案23】:

                              我是随机来到这里的,以前从来没有理由在 javascript 中重复一个字符。

                              artistoex 的做法和 disfated 的结果给我留下了深刻的印象。我注意到最后一个字符串 concat 是不必要的,正如 Dennis 也指出的那样。

                              当我把采样disfated放在一起玩时,我注意到了更多的东西。

                              结果差异很大,通常有利于最后一次运行,类似的算法通常会争夺位置。我改变的一件事是不使用 JSLitmus 生成的计数作为调用的种子;由于各种方法产生的计数不同,我输入了一个索引。这使事情变得更加可靠。然后我查看了确保将不同大小的字符串传递给函数。这阻止了我看到的一些变化,其中一些算法在单个字符或更小的字符串上做得更好。然而,无论字符串大小如何,前 3 种方法都表现良好。

                              分叉测试集

                              http://jsfiddle.net/schmide/fCqp3/134/

                              // repeated string
                              var string = '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789';
                              // count paremeter is changed on every test iteration, limit it's maximum value here
                              var maxCount = 200;
                              
                              var n = 0;
                              $.each(tests, function (name) {
                                  var fn = tests[name];
                                  JSLitmus.test(++n + '. ' + name, function (count) {
                                      var index = 0;
                                      while (count--) {
                                          fn.call(string.slice(0, index % string.length), index % maxCount);
                                          index++;
                                      }
                                  });
                                  if (fn.call('>', 10).length !== 10) $('body').prepend('<h1>Error in "' + name + '"</h1>');
                              });
                              
                              JSLitmus.runAll();
                              

                              然后我加入了丹尼斯的解决方案,并决定看看我是否能找到更多方法。

                              由于 javascript 并不能真正优化事物,因此提高性能的最佳方法是手动避免事物。如果我将前 4 个微不足道的结果从循环中取出,我可以避免 2-4 个字符串存储并将最终存储直接写入结果。

                              // final: growing pattern + prototypejs check (count < 1)
                              'final avoid': function (count) {
                                  if (!count) return '';
                                  if (count == 1) return this.valueOf();
                                  var pattern = this.valueOf();
                                  if (count == 2) return pattern + pattern;
                                  if (count == 3) return pattern + pattern + pattern;
                                  var result;
                                  if (count & 1) result = pattern;
                                  else result = '';
                                  count >>= 1;
                                  do {
                                      pattern += pattern;
                                      if (count & 1) result += pattern;
                                      count >>= 1;
                                  } while (count > 1);
                                  return result + pattern + pattern;
                              }
                              

                              与 Dennis 的修复相比,这平均提高了 1-2%。但是,不同的运行和不同的浏览器会显示出足够公平的差异,因此这些额外的代码可能不值得为之前的 2 种算法付出努力。

                              A chart

                              编辑:我主要是在 chrome 下完成的。 Firefox 和 IE 通常会偏爱几 % 的 Dennis。

                              【讨论】:

                                【解决方案24】:

                                这比 disfated 的回答提高了 5-7%。

                                通过在count &gt; 1 处停止来展开循环,并在循环之后执行额外的result += pattnern concat。这将避免之前未使用的循环最终 pattern += pattern 而不必使用昂贵的 if-check。 最终结果如下所示:

                                String.prototype.repeat = function(count) {
                                    if (count < 1) return '';
                                    var result = '', pattern = this.valueOf();
                                    while (count > 1) {
                                        if (count & 1) result += pattern;
                                        count >>= 1, pattern += pattern;
                                    }
                                    result += pattern;
                                    return result;
                                };
                                

                                这里是 disfated 为展开版本分叉的小提琴:http://jsfiddle.net/wsdfg/

                                【讨论】:

                                  【解决方案25】:

                                  各种方法的测试:

                                  var repeatMethods = {
                                      control: function (n,s) {
                                          /* all of these lines are common to all methods */
                                          if (n==0) return '';
                                          if (n==1 || isNaN(n)) return s;
                                          return '';
                                      },
                                      divideAndConquer:   function (n, s) {
                                          if (n==0) return '';
                                          if (n==1 || isNaN(n)) return s;
                                          with(Math) { return arguments.callee(floor(n/2), s)+arguments.callee(ceil(n/2), s); }
                                      },
                                      linearRecurse: function (n,s) {
                                          if (n==0) return '';
                                          if (n==1 || isNaN(n)) return s;
                                          return s+arguments.callee(--n, s);
                                      },
                                      newArray: function (n, s) {
                                          if (n==0) return '';
                                          if (n==1 || isNaN(n)) return s;
                                          return (new Array(isNaN(n) ? 1 : ++n)).join(s);
                                      },
                                      fillAndJoin: function (n, s) {
                                          if (n==0) return '';
                                          if (n==1 || isNaN(n)) return s;
                                          var ret = [];
                                          for (var i=0; i<n; i++)
                                              ret.push(s);
                                          return ret.join('');
                                      },
                                      concat: function (n,s) {
                                          if (n==0) return '';
                                          if (n==1 || isNaN(n)) return s;
                                          var ret = '';
                                          for (var i=0; i<n; i++)
                                              ret+=s;
                                          return ret;
                                      },
                                      artistoex: function (n,s) {
                                          var result = '';
                                          while (n>0) {
                                              if (n&1) result+=s;
                                              n>>=1, s+=s;
                                          };
                                          return result;
                                      }
                                  };
                                  function testNum(len, dev) {
                                      with(Math) { return round(len+1+dev*(random()-0.5)); }
                                  }
                                  function testString(len, dev) {
                                      return (new Array(testNum(len, dev))).join(' ');
                                  }
                                  var testTime = 1000,
                                      tests = {
                                          biggie: { str: { len: 25, dev: 12 }, rep: {len: 200, dev: 50 } },
                                          smalls: { str: { len: 5, dev: 5}, rep: { len: 5, dev: 5 } }
                                      };
                                  var testCount = 0;
                                  var winnar = null;
                                  var inflight = 0;
                                  for (var methodName in repeatMethods) {
                                      var method = repeatMethods[methodName];
                                      for (var testName in tests) {
                                          testCount++;
                                          var test = tests[testName];
                                          var testId = methodName+':'+testName;
                                          var result = {
                                              id: testId,
                                              testParams: test
                                          }
                                          result.count=0;
                                  
                                          (function (result) {
                                              inflight++;
                                              setTimeout(function () {
                                                  result.start = +new Date();
                                                  while ((new Date() - result.start) < testTime) {
                                                      method(testNum(test.rep.len, test.rep.dev), testString(test.str.len, test.str.dev));
                                                      result.count++;
                                                  }
                                                  result.end = +new Date();
                                                  result.rate = 1000*result.count/(result.end-result.start)
                                                  console.log(result);
                                                  if (winnar === null || winnar.rate < result.rate) winnar = result;
                                                  inflight--;
                                                  if (inflight==0) {
                                                      console.log('The winner: ');
                                                      console.log(winnar);
                                                  }
                                              }, (100+testTime)*testCount);
                                          }(result));
                                      }
                                  }
                                  

                                  【讨论】:

                                    【解决方案26】:

                                    使用分而治之的递归解决方案:

                                    function repeat(n, s) {
                                        if (n==0) return '';
                                        if (n==1 || isNaN(n)) return s;
                                        with(Math) { return repeat(floor(n/2), s)+repeat(ceil(n/2), s); }
                                    }
                                    

                                    【讨论】:

                                      【解决方案27】:

                                      这可能是最小的递归了:-

                                      String.prototype.repeat = function(n,s) {
                                      s = s || ""
                                      if(n>0) {
                                         s += this
                                         s = this.repeat(--n,s)
                                      }
                                      return s}
                                      

                                      【讨论】:

                                        【解决方案28】:

                                        这个效率很高

                                        String.prototype.repeat = function(times){
                                            var result="";
                                            var pattern=this;
                                            while (times > 0) {
                                                if (times&1)
                                                    result+=pattern;
                                                times>>=1;
                                                pattern+=pattern;
                                            }
                                            return result;
                                        };
                                        

                                        【讨论】:

                                        • @Olegs,我认为投票的想法不如投票给一个人或一个人的创造力(这确实值得称赞),但想法是投票给最完整的解决方案,所以可以很容易地在列表的顶部找到它,而无需阅读所有答案来寻找完美的答案。 (因为很遗憾,我们的时间有限……)
                                        【解决方案29】:

                                        扩展P.Bailey's solution

                                        String.prototype.repeat = function(num) {
                                            return new Array(isNaN(num)? 1 : ++num).join(this);
                                            }
                                        

                                        这样你就可以避免意外的参数类型:

                                        var foo = 'bar';
                                        alert(foo.repeat(3));              // Will work, "barbarbar"
                                        alert(foo.repeat('3'));            // Same as above
                                        alert(foo.repeat(true));           // Same as foo.repeat(1)
                                        
                                        alert(foo.repeat(0));              // This and all the following return an empty
                                        alert(foo.repeat(false));          // string while not causing an exception
                                        alert(foo.repeat(null));
                                        alert(foo.repeat(undefined));
                                        alert(foo.repeat({}));             // Object
                                        alert(foo.repeat(function () {})); // Function
                                        

                                        编辑:感谢jerone 他优雅的++num 想法!

                                        【讨论】:

                                        • 稍微改变了你的:String.prototype.repeat = function(n){return new Array(isNaN(n) ? 1 : ++n).join(this);}
                                        • 无论如何,根据这个测试 (jsperf.com/string-repeat/2),与使用 Array.join 相比,在 Chrome 上使用字符串连接进行简单的 for 循环似乎要快得多。是不是很好笑?!
                                        【解决方案30】:
                                        function repeat(s, n) { var r=""; for (var a=0;a<n;a++) r+=s; return r;}
                                        

                                        【讨论】:

                                        • 字符串连接不是很昂贵吗?至少在 Java 中是这样。
                                        • 为什么是的。但是,它并不能真正在javascript中进行优化。 :(
                                        • 这个性能改进怎么样:var r=s; for (var a=1;... :)))) 无论如何,根据这个测试 (jsperf.com/string-repeat/2) 用字符串连接做一个简单的 for 循环,就像你建议的那样似乎更快Chrome 与使用 Array.join 的比较。
                                        • @VijayDev - 不是根据这个测试:jsperf.com/ultimate-concat-vs-join
                                        猜你喜欢
                                        • 2013-01-11
                                        • 2019-10-03
                                        • 2011-02-24
                                        • 2012-07-23
                                        • 2011-07-20
                                        • 2017-02-05
                                        • 2013-03-06
                                        • 2017-06-27
                                        • 2019-10-29
                                        相关资源
                                        最近更新 更多