创建零填充 JavaScript 数组的最有效方法是啥?

Posted

技术标签:

【中文标题】创建零填充 JavaScript 数组的最有效方法是啥?【英文标题】:Most efficient way to create a zero filled JavaScript array?创建零填充 JavaScript 数组的最有效方法是什么? 【发布时间】:2010-11-20 16:51:26 【问题描述】:

javascript 中创建任意长度的零填充数组最有效的方法是什么?

【问题讨论】:

一些实际数据:jsperf.com/zeroarrayjs ES6 fill 允许本地执行此操作。 arr = new Array(length+1).joint(character).split(''); 2016 年更新:此处的另一个自定义基准:jsfiddle.net/basickarl/md5z0Lqq let i = 0; Array.from(Array(10), ()=>i++); 【参考方案1】:

ES6 引入了Array.prototype.fill。可以这样使用:

new Array(len).fill(0);

不确定它是否很快,但我喜欢它,因为它很短且可以自我描述。

它仍然不在 IE (check compatibility) 中,但有一个 polyfill available。

【讨论】:

填充速度很快。 new Array(len) 非常缓慢。 (arr = []).length = len; arr.fill(0); 是我在任何地方见过的最快的解决方案……或者至少是并列的 @PimpTrizkit arr = Array(n)(arr = []).length = n 根据规范表现相同。在某些实现中,可能会更快,但我不认为有很大的不同。 ...我承认我错过了这部分...当我将第二行添加到测试中...arr.fill(0)...一切都发生了变化。现在,在大多数情况下,使用new Array() 会更快,除非您的数组大小> 100000...然后您可以再次开始看到速度增加。但是,如果您实际上不必用零预填充它并且可以使用空数组的标准错误。然后(arr = []).length = x 在我的测试用例中大部分时间都快疯了。 请注意,要遍历数组(例如 map 或 forEach),必须设置值,否则它将跳过这些索引。你设置的值可以是任何你想要的——甚至是未定义的。示例:尝试 new Array(5).forEach(val => console.log('hi'));new Array(5).fill(undefined).forEach(val => console.log('hi'));【参考方案2】:

虽然这是一个旧线程,但我想在其中添加 2 美分。不知道这有多慢/快,但它是一个快速的单线。这是我的工作:

如果我想预先填写一个数字:

Array.apply(null, Array(5)).map(Number.prototype.valueOf,0);
// [0, 0, 0, 0, 0]

如果我想预填充一个字符串:

Array.apply(null, Array(3)).map(String.prototype.valueOf,"hi")
// ["hi", "hi", "hi"]

其他答案建议:

new Array(5+1).join('0').split('')
// ["0", "0", "0", "0", "0"]

但如果你想要 0(数字)而不是“0”(字符串中的零),你可以这样做:

new Array(5+1).join('0').split('').map(parseFloat)
// [0, 0, 0, 0, 0]

【讨论】:

很好的答案!你能用Array.apply(null, new Array(5)).map(...)解释一下这个技巧吗?因为简单地做 (new Array(5)).map(...) 不会像规范所说的那样工作 (顺便说一句,我们真的不需要new)当你做Array(5)时,你正在创建一个看起来像这样的对象: length: 5, __proto__: Array.prototype - 试试console.dir( Array(5) )。请注意,它没有任何属性012 等。但是当您将apply 指向Array 构造函数时,就像在说Array(undefined, undefined, undefined, undefined, undefined)。你会得到一个看起来像 length: 5, 0: undefined, 1: undefined... 的对象。 map 适用于 01 等属性,这就是为什么您的示例不起作用,但当您使用 apply 时它会起作用。 .apply 的第一个参数实际上是您想要的 this。对于这些目的,this 无关紧要——我们只关心.apply 的参数传播“特征”——所以它可以是任何值。我喜欢null,因为它很便宜,你可能不想使用[],因为你会无缘无故地实例化一个对象。 同样用 size + assign 初始化比 push 快得多。见测试用例jsperf.com/zero-fill-2d-array Array.apply(null, Array(5)).map(x=>0) 怎么样?它有点短!【参考方案3】:

总之

最快的解决方案

let a = new Array(n); for (let i=0; i<n; ++i) a[i] = 0;

最短(方便)解决方案(小型阵列慢 3 倍,大型阵列稍慢(在 Firefox 上最慢))

Array(n).fill(0)


详情

今天 2020.06.09 我在 Chrome 83.0、Firefox 77.0 和 Safari 13.1 浏览器上对 macOS High Sierra 10.13.6 进行了测试。我为两个测试用例测试选择的解决方案

小数组 - 有 10 个元素 - 你可以执行测试 HERE 大数组 - 有 1M 元素 - 你可以执行测试 HERE

结论

基于new Array(n)+for (N) 的解决方案是小阵列和大阵列的最快解决方案(Chrome 除外,但仍然非常快),建议作为快速跨浏览器解决方案 基于new Float32Array(n) (I) 的解决方案返回非典型数组(例如,您不能在其上调用push(..)),因此我不会将其结果与其他解决方案进行比较 - 但是此解决方案比其他解决方案快 10-20 倍所有浏览器上的大数组 基于for (L,M,N,O) 的解决方案对于小型阵列来说速度很快 基于fill (B,C) 的解决方案在 Chrome 和 Safari 上速度很快,但在 Firefox 上却出奇地慢,用于大型阵列。对于小型阵列,它们的速度中等 基于Array.apply (P) 的解决方案对大数组抛出错误
function P(n) 
  return Array.apply(null, Array(n)).map(Number.prototype.valueOf,0);



try 
  P(1000000);
 catch(e)  
  console.error(e.message);

代码和示例

下面的代码展示了测量中使用的解决方案

function A(n) 
  return [...new Array(n)].fill(0);


function B(n) 
  return new Array(n).fill(0);


function C(n) 
  return Array(n).fill(0);


function D(n) 
  return Array.from(length: n, () => 0);


function E(n) 
  return [...new Array(n)].map(x => 0);


// arrays with type

function F(n) 
  return Array.from(new Int32Array(n));


function G(n) 
  return Array.from(new Float32Array(n));


function H(n) 
  return Array.from(new Float64Array(n)); // needs 2x more memory than float32


function I(n) 
  return new Float32Array(n); // this is not typical array


function J(n) 
  return [].slice.apply(new Float32Array(n));


// Based on for

function K(n) 
  let a = [];
  a.length = n;
  let i = 0;
  while (i < n) 
    a[i] = 0;
    i++;
  
  return a;


function L(n) 
  let a=[]; for(let i=0; i<n; i++) a[i]=0;
  return a;


function M(n) 
  let a=[]; for(let i=0; i<n; i++) a.push(0);
  return a;


function N(n) 
  let a = new Array(n); for (let i=0; i<n; ++i) a[i] = 0;
  return a;


function O(n) 
  let a = new Array(n); for (let i=n; i--;) a[i] = 0;
  return a;


// other

function P(n) 
  return Array.apply(null, Array(n)).map(Number.prototype.valueOf,0);


function Q(n) 
  return "0".repeat( n ).split("").map( parseFloat );


function R(n) 
  return new Array(n+1).join('0').split('').map(parseFloat)



// ---------
// TEST
// ---------

[A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R].forEach(f => 
  let a = f(10); 
  console.log(`$f.name length=$a.length, arr[0]=$a[0], arr[9]=$a[9]`)
);
This snippets only present used codes

Chrome 的示例结果

如果我的回答对你有帮助,可以buy me a coffee

【讨论】:

刚刚在 Chrome 77 上运行了一些测试,使用 push() 的简单循环比 fill() 快两倍...我想知道 fill() 的哪些微妙副作用会阻止更有效的实现? @EricGrange 我更新了答案 - 在底部我用你的提议更新了 benchamrk 的链接:案例 P let a=[]; for(i=n;i--;) a.push(0); - 但它比 fill(0) 慢 4 倍 - 所以我什至不会更新图片巫婆那个案子。 不错的测量结果。分析:G之所以慢,是因为每次迭代都会调整数组的大小,而调整大小意味着进行新的内存分配。 A,B,M 快,因为尺寸调整只进行一次。 +1 @Roland 我想你的意思是 N 而不是 M? for-loop (N) 在 Safari 中仅比 .fill (C) 快 1.835,有趣的是,当我现在运行它时,6 个月后,差异已经下降到只有 1.456 倍。因此对于 Safari,最快的解决方案 (N) 仅比最短和最简单的版本快 45%。道德:坚持使用最短和最简单的版本(对于大多数情况,如果不是所有情况)。它节省了昂贵的开发人员时间,因为它的阅读速度更快、更易于维护,而且随着时间和 CPU 速度的提高,它还可以带来越来越多的回报,而无需额外的维护。【参考方案4】:

用预先计算的值填充数组的优雅方法

这是另一种使用 ES6 的方法,目前还没有人提到:

> Array.from(Array(3), () => 0)
< [0, 0, 0]

它通过将映射函数作为Array.from 的第二个参数传递来工作。

在上面的示例中,第一个参数分配了一个包含 3 个位置的数组,其中填充了值 undefined,然后 lambda 函数将每个位置映射到值 0

虽然Array(len).fill(0) 更短,但如果您需要先进行一些计算来填充数组,它就不起作用了(我知道这个问题不是问的,但是很多人最终都到了这里正在寻找这个)

例如,如果您需要一个包含 10 个随机数的数组:

> Array.from(Array(10), () => Math.floor(10 * Math.random()))
< [3, 6, 8, 1, 9, 3, 0, 6, 7, 1]

它比同等的更简洁(和优雅):

const numbers = Array(10);
for (let i = 0; i < numbers.length; i++) 
    numbers[i] = Math.round(10 * Math.random());

此方法也可用于利用回调中提供的索引参数生成数字序列:

> Array.from(Array(10), (d, i) => i)
< [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

奖励答案:使用字符串 repeat() 填充数组

由于这个答案引起了很多关注,我也想展示这个很酷的技巧。虽然不如我的主要答案有用,但将介绍仍然不太为人所知但非常有用的 String repeat() 方法。这是诀窍:

> "?".repeat(10).split("").map(() => Math.floor(10 * Math.random()))
< [5, 6, 3, 5, 0, 8, 2, 7, 4, 1]

很酷吧? repeat() 是一种非常有用的创建字符串的方法,该字符串将原始字符串重复一定次数。之后,split() 为我们创建一个数组,然后将map()ped 转换为我们想要的值。分步分解:

> "?".repeat(10)
< "??????????"

> "?".repeat(10).split("")
< ["?", "?", "?", "?", "?", "?", "?", "?", "?", "?"]

> "?".repeat(10).split("").map(() => Math.floor(10 * Math.random()))
< [5, 6, 3, 5, 0, 8, 2, 7, 4, 1]

【讨论】:

那篇文章中有很多客厅技巧,但希望没有一个能达到生产代码:) 虽然repeat 技巧在生产中绝对不需要,但Array.from() 非常好:-) 不是真的,这里的 Array.from() 基本上是创建一个数组,用 map() 遍历它,在每个项目上调用一个函数来创建一个新数组,然后丢弃第一个数组.. . 对于小数组,这可能是无害的,对于较大的数组,这种模式会导致人们称浏览器为“内存猪”:) 处理大型数组的人肯定比这更清楚。但是,对于常见的应用程序,创建将立即处理的常规大小的辅助数组(最多 10k 个元素)是非常好的(与避免创建额外数组一样花费相同的时间 - 使用最新的 Chrome 测试)。对于这样的情况,可读性变得比微小的性能优化更重要。关于 O(n) 时间,如果您需要为每个元素计算不同的东西(我的回答的主要主题),则有必要。这个讨论很有趣,很高兴你提出来!【参考方案5】:

已经提到的 ES 6 填充方法很好地解决了这个问题。迄今为止,大多数现代桌面浏览器已经支持所需的 Array 原型方法(Chromium、FF、Edge 和 Safari)[1]。您可以在MDN 上查找详细信息。一个简单的使用例子是

a = new Array(10).fill(0);

鉴于当前的浏览器支持,除非您确定您的受众使用现代桌面浏览器,否则您应该谨慎使用它。

【讨论】:

如果您填写一个引用类型,它将是所有它们的相同引用。 new Array(10).fill(null).map(() => []) 将是解决此问题的一种简洁方法(一开始我被烧死了哈哈) 2016 年更新:此方法将其他所有内容都吹得一干二净,单击此处查看基准:jsfiddle.net/basickarl/md5z0Lqq 这适用于数组。 a = Array(10).fill(null).map(() =&gt; return []; ); @AndrewAnthonyGerst Terser:a = Array(10).fill(0).map( _ =&gt; [] );【参考方案6】:

2013 年 8 月添加的注释,2015 年 2 月更新:以下 2009 年的答案与 JavaScript 的通用 Array 类型有关。它与 ES2015 中定义的较新的 typed 数组无关 [现在在许多浏览器中都可用],例如 Int32Array 等。另请注意,ES2015 为 Arrays 和 typed arrays 添加了 fill 方法,这可能是填充它们的最有效方法...

此外,创建数组的方式对某些实现也有很大影响。尤其是 Chrome 的 V8 引擎,它会在认为可以的情况下尝试使用高效的连续内存数组,仅在必要时才转换为基于对象的数组。


对于大多数语言,它会预先分配,然后是零填充,如下所示:

function newFilledArray(len, val) 
    var rv = new Array(len);
    while (--len >= 0) 
        rv[len] = val;
    
    return rv;

但是,JavaScript 数组 aren't really arrays,它们是键/值映射,就像所有其他 JavaScript 对象一样,所以没有“预分配”要做(设置长度不会分配有那么多插槽要填充),也没有任何理由相信倒数到零的好处(这只是为了使循环中的比较快速)不会通过在实现时以相反的顺序添加键来抵消可能很好地优化了他们对与数组相关的键的处理,理论上你通常会按顺序进行。

事实上,Matthew Crumley 指出,在 Firefox 上倒数比倒数要慢得多,我可以确认这个结果——它是数组的一部分(循环到零仍然比循环到一个限制要快变量)。显然,以相反的顺序将元素添加到数组中是 Firefox 上的一个缓慢操作。事实上,结果因 JavaScript 实现而有很大差异(这并不令人惊讶)。这是一个用于浏览器实现的快速而肮脏的测试页面(如下)(非常肮脏,在测试期间不会产生,因此提供的反馈很少,并且会违反脚本时间限制)。我建议在测试之间刷新;如果你不这样做,FF(至少)会减慢重复测试的速度。

在 1,000 到 2,000 个元素数组之间的某个地方,使用 Array#concat 的相当复杂的版本比 FF 上的直接初始化更快。不过,在 Chrome 的 V8 引擎上,每次都是直接 init 胜出...

这是测试页面 (live copy):

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Zero Init Test Page</title>
<style type='text/css'>
body 
    font-family:    sans-serif;

#log p 
    margin:     0;
    padding:    0;

.error 
    color:      red;

.winner 
    color:      green;
    font-weight:    bold;

</style>
<script type='text/javascript' src='prototype-1.6.0.3.js'></script>
<script type='text/javascript'>
var testdefs = 
    'downpre':  
        total:  0,
        desc:   "Count down, pre-decrement",
        func:   makeWithCountDownPre
    ,
    'downpost': 
        total:  0,
        desc:   "Count down, post-decrement",
        func:   makeWithCountDownPost
    ,
    'up':       
        total:  0,
        desc:   "Count up (normal)",
        func:   makeWithCountUp
    ,
    'downandup':  
        total:  0,
        desc:   "Count down (for loop) and up (for filling)",
        func:   makeWithCountDownArrayUp
    ,
    'concat':   
        total:  0,
        desc:   "Concat",
        func:   makeWithConcat
    
;

document.observe('dom:loaded', function() 
    var markup, defname;

    markup = "";
    for (defname in testdefs) 
        markup +=
            "<div><input type='checkbox' id='chk_" + defname + "' checked>" +
            "<label for='chk_" + defname + "'>" + testdefs[defname].desc + "</label></div>";
    
    $('checkboxes').update(markup);
    $('btnTest').observe('click', btnTestClick);
);

function epoch() 
    return (new Date()).getTime();


function btnTestClick() 

    // Clear log
    $('log').update('Testing...');

    // Show running
    $('btnTest').disabled = true;

    // Run after a pause while the browser updates display
    btnTestClickPart2.defer();

function btnTestClickPart2() 

    try 
        runTests();
    
    catch (e) 
        log("Exception: " + e);
    

    // Re-enable the button; we don't yheidl
    $('btnTest').disabled = false;


function runTests() 
    var start, time, counter, length, defname, def, results, a, invalid, lowest, s;

    // Get loops and length
    s = $F('txtLoops');
    runcount = parseInt(s);
    if (isNaN(runcount) || runcount <= 0) 
        log("Invalid loops value '" + s + "'");
        return;
    
    s = $F('txtLength');
    length = parseInt(s);
    if (isNaN(length) || length <= 0) 
        log("Invalid length value '" + s + "'");
        return;
    

    // Clear log
    $('log').update('');

    // Do it
    for (counter = 0; counter <= runcount; ++counter) 

        for (defname in testdefs) 
            def = testdefs[defname];
            if ($('chk_' + defname).checked) 
                start = epoch();
                a = def.func(length);
                time = epoch() - start;
                if (counter == 0) 
                    // Don't count (warm up), but do check the algorithm works
                    invalid = validateResult(a, length);
                    if (invalid) 
                        log("<span class='error'>FAILURE</span> with def " + defname + ": " + invalid);
                        return;
                    
                
                else 
                    // Count this one
                    log("#" + counter + ": " + def.desc + ": " + time + "ms");
                    def.total += time;
                
            
        
    

    for (defname in testdefs) 
        def = testdefs[defname];
        if ($('chk_' + defname).checked) 
            def.avg = def.total / runcount;
            if (typeof lowest != 'number' || lowest > def.avg) 
                lowest = def.avg;
            
        
    

    results =
        "<p>Results:" +
        "<br>Length: " + length +
        "<br>Loops: " + runcount +
        "</p>";
    for (defname in testdefs) 
        def = testdefs[defname];
        if ($('chk_' + defname).checked) 
            results += "<p" + (lowest == def.avg ? " class='winner'" : "") + ">" + def.desc + ", average time: " + def.avg + "ms</p>";
        
    
    results += "<hr>";
    $('log').insert(top: results);


function validateResult(a, length) 
    var n;

    if (a.length != length) 
        return "Length is wrong";
    
    for (n = length - 1; n >= 0; --n) 
        if (a[n] != 0) 
            return "Index " + n + " is not zero";
        
    
    return undefined;


function makeWithCountDownPre(len) 
    var a;

    a = new Array(len);
    while (--len >= 0) 
        a[len] = 0;
    
    return a;


function makeWithCountDownPost(len) 
    var a;

    a = new Array(len);
    while (len-- > 0) 
        a[len] = 0;
    
    return a;


function makeWithCountUp(len) 
    var a, i;

    a = new Array(len);
    for (i = 0; i < len; ++i) 
        a[i] = 0;
    
    return a;


function makeWithCountDownArrayUp(len) 
    var a, i;

    a = new Array(len);
    i = 0;
    while (--len >= 0) 
        a[i++] = 0;
    
    return a;


function makeWithConcat(len) 
    var a, rem, currlen;

    if (len == 0) 
        return [];
    
    a = [0];
    currlen = 1;
    while (currlen < len) 
        rem = len - currlen;
        if (rem < currlen) 
            a = a.concat(a.slice(0, rem));
        
        else 
            a = a.concat(a);
        
        currlen = a.length;
    
    return a;


function log(msg) 
    $('log').appendChild(new Element('p').update(msg));

</script>
</head>
<body><div>
<label for='txtLength'>Length:</label><input type='text' id='txtLength' value='10000'>
<br><label for='txtLoops'>Loops:</label><input type='text' id='txtLoops' value='10'>
<div id='checkboxes'></div>
<br><input type='button' id='btnTest' value='Test'>
<hr>
<div id='log'></div>
</div></body>
</html>

【讨论】:

不确定向后填充在这里是否重要,因为您只是访问元素(而不是删除它们)并且您已经预先分配了。我错了吗? 反向填充的点并不是特别与数组有关,而是与 while 的转义条件有关 - falsey 0 非常有效地终止循环 (虽然我刚刚注意到这段代码实际上并没有使用它) @annakata,你不能在这里使用它,因为 0 是一个有效的索引。 @triptych:不正确,只需要正确的顺序 - 请参阅我的帖子【参考方案7】:

如果你使用 ES6,你可以像这样使用Array.from():

Array.from( length: 3 , () => 0);
//[0, 0, 0]

结果与

相同
Array.from( length: 3 ).map(() => 0)
//[0, 0, 0]

因为

Array.from( length: 3 )
//[undefined, undefined, undefined]

【讨论】:

【参考方案8】:

默认情况下,Uint8ArrayUint16ArrayUint32Array 类将零作为其值,因此您不需要任何复杂的填充技术,只需这样做:

var ary = new Uint8Array(10);

数组ary的所有元素默认为零。

【讨论】:

这很好,但请注意,这不能与普通数组一样对待,例如Array.isArray(ary)false。该长度也是只读的,因此您不能像ary.push 那样将新项目推送到它 Fwiw 所有类型化数组都将0 保留为默认值。 @MusikAnimal, Array.from(new Uint8Array(10)) 将提供一个普通数组。 @TomasLangkaas:是的,但是another answer 表明,如果您真正需要的是 JS 数组,这比 Chrome 中的Array(n).fill(0) 慢 5 倍。如果您可以使用 TypedArray,这甚至比 .fill(0) 快得多,特别是如果您可以使用默认初始值设定项 0。似乎没有像 C++ std::vector 那样采用填充值和长度的构造函数。似乎对于任何非零值,您都必须构造一个归零的 TypedArray 并 then 填充它。 ://【参考方案9】:
function makeArrayOf(value, length) 
  var arr = [], i = length;
  while (i--) 
    arr[i] = value;
  
  return arr;


makeArrayOf(0, 5); // [0, 0, 0, 0, 0]

makeArrayOf('x', 3); // ['x', 'x', 'x']

请注意,while 通常比for-inforEach 等效率更高。

【讨论】:

i 局部变量不是无关的吗? length 是按值传递的,所以你应该可以直接递减它。 虽然起初看起来不错,但不幸的是,在数组中的任意点分配值非常慢(例如arr[i] = value)。从头到尾循环并使用arr.push(value) 要快得多。这很烦人,因为我更喜欢你的方法。【参考方案10】:

使用对象表示法

var x = [];

零填充?喜欢...

var x = [0,0,0,0,0,0];

充满了“未定义”...

var x = new Array(7);

带零的obj表示法

var x = [];
for (var i = 0; i < 10; i++) x[i] = 0;

附带说明,如果您修改 Array 的原型,则两者都

var x = new Array();

var y = [];

会有这些原型修改

无论如何,我不会过分担心此操作的效率或速度,您可能会做很多其他事情,这些事情比实例化任意长度的数组更浪费和昂贵零。

【讨论】:

Err...这个数组中没有nulls - var x = new Array(7); 实际上,数组并没有用 new Array(n) 填充任何东西,甚至没有“未定义”,它只是将数组长度值设置为 n。您可以通过调用 (new Array(1)).forEach(...) 来检查这一点。 forEach 从不​​执行,不像你在 [undefined] 上调用它。 new Array(7) 确实创建一个“充满未定义”的数组。它创建一个长度为 7 的 empty 数组。 您可能需要重新考虑您的部分答案,因为 @RobG 所说的很关键(如果您所说的是真的,映射会容易得多) 这些天你可以做(new Array(10)).fill(0)【参考方案11】:

我已经在 IE 6/7/8、Firefox 3.5、Chrome 和 Opera 中测试了预分配/非预分配、向上/向下计数和 for/while 循环的所有组合。

以下函数在 Firefox、Chrome 和 IE8 中一直是最快或非常接近的,并且不比 Opera 和 IE 6 中的最快慢多少。我认为它也是最简单和最清晰的。我发现有几个浏览器的 while 循环版本稍微快一些,所以我也将它包括在内以供参考。

function newFilledArray(length, val) 
    var array = [];
    for (var i = 0; i < length; i++) 
        array[i] = val;
    
    return array;

function newFilledArray(length, val) 
    var array = [];
    var i = 0;
    while (i < length) 
        array[i++] = val;
    
    return array;

【讨论】:

实际上你也可以将var array = [] 声明扔到for循环的第一部分,只用逗号分隔。 我喜欢 damianb 的建议,但请记住将赋值和逗号放在递增之前! `for (var i = 0; i 对第二个做其他人所缺少的,并将数组的长度设置为已经给出的length 值,这样它就不会不断变化。在我的机器上带来了一个 100 万长度的零数组,从 40ms 到 8。 当我将此解决方案重构为单行时,我的速度似乎提高了 10-15%。 for (i = 0, array = []; i &lt; length; ++i) array[i] = val;.. 更少的块? ......无论如何,还有......如果我将新数组的array.length 设置为长度......我似乎在FF中获得了另外10%-15%的速度提升......在Chrome中,它似乎加倍speed -> var i, array = []; array.length = length; while(i &lt; length) array[i++] = val; (如果我将其保留为 for 循环会更快...但不再需要 init,因此在此版本上 while 似乎更快) 我也会在我的测试中注意到这一点。在我的大量测试用例中,上面的最终版本的执行速度似乎快了 3 倍到 10 倍以上……我不太清楚为什么……(在 chrome 和 FF 之间测试了不同的数组大小)【参考方案12】:

ES6 解决方案:

[...new Array(5)].map(x => 0); // [0, 0, 0, 0, 0]

【讨论】:

【参考方案13】:

const arr = Array.from( length: 10 ).fill(0);
console.log(arr)

【讨论】:

【参考方案14】:

如果您需要在代码执行期间创建许多不同长度的零填充数组,我发现实现此目的的最快方法是创建一个零数组一次,使用以下方法之一本主题中提到的方法,其长度是您知道永远不会超过的,然后根据需要对该数组进行切片。

例如(使用上面选择的答案中的函数来初始化数组),创建一个长度为 maxLength 的零填充数组,作为需要零数组的代码可见的变量:

var zero = newFilledArray(maxLength, 0);

现在每次你需要一个长度为零的填充数组时切片这个数组requiredLength maxLength:

zero.slice(0, requiredLength);

我在执行代码期间创建了数千次零填充数组,这极大地加快了进程。

【讨论】:

【参考方案15】:
function zeroFilledArray(size) 
    return new Array(size + 1).join('0').split('');

【讨论】:

您也可以使用new Array(size+1).join("x").split("x").map(function() return 0; ) 来获取实际数字 @Yuval 或者只是new Array(size+1).join('0').split('').map(Number)【参考方案16】:

使用lodash 或underscore

_.range(0, length - 1, 0);

或者如果你有一个数组并且你想要一个相同长度的数组

array.map(_.constant(0));

【讨论】:

很高兴你添加了这个答案,因为我使用下划线,我知道这有一些东西......但还没有找到它。我只是希望我可以使用它创建对象数组 @PandaWood _.range(0, length -1, 0).map(Object.new),我想。 应该是_.range(0, length, 0),我相信。 Lodash 不包含最终值【参考方案17】:

我不反对:

Array.apply(null, Array(5)).map(Number.prototype.valueOf,0);
new Array(5+1).join('0').split('').map(parseFloat);

由 Zertosh 建议,但在 new ES6 数组扩展中,您可以使用 fill 方法本地执行此操作。现在 IE edge、Chrome 和 FF 都支持了,但是查看compatibility table

new Array(3).fill(0) 会给你[0, 0, 0]。您可以使用任何值填充数组,例如 new Array(5).fill('abc')(甚至是对象和其他数组)。

除此之外,您还可以使用填充修改以前的数组:

arr = [1, 2, 3, 4, 5, 6]
arr.fill(9, 3, 5)  # what to fill, start, end

给你:[1, 2, 3, 9, 9, 6]

【讨论】:

【参考方案18】:

我通常使用Uint8Array 的方式(而且速度惊人)。例如,创建一个由 1M 个元素组成的零填充向量:

  var zeroFilled = [].slice.apply(new Uint8Array(1000000))

我是 Linux 用户,一直为我工作,但曾经有一位使用 Mac 的朋友有一些非零元素。我以为他的机器出了故障,但仍然是我们找到的最安全的修复方法:

  var zeroFilled = [].slice.apply(new Uint8Array(new Array(1000000)) 

已编辑

Chrome 25.0.1364.160

    弗雷德里克·戈特利布 - 6.43 山姆·巴纳姆 - 4.83 伊莱 - 3.68 约书亚 2.91 马修·克鲁姆利 - 2.67 bduran - 2.55 艾伦·赖斯 - 2.11 kangax - 0.68 Tj。克劳德 - 0.67 zertosh - 错误

火狐20.0

    艾伦·赖斯 - 1.85 约书亚 - 1.82 马修·克鲁姆利 - 1.79 bduran - 1.37 弗雷德里克·戈特利布 - 0.67 山姆·巴纳姆 - 0.63 伊莱 - 0.59 kagax - 0.13 Tj。克劳德 - 0.13 zertosh - 错误

缺少最重要的测试(至少对我而言):Node.js 测试。我怀疑它接近 Chrome 基准。

【讨论】:

这是对我的手指和眼睛最有效的方法。但是对于 Chrome 来说它非常非常慢(根据那个 jsperf。慢了 99%)。 我想知道您朋友 Mac 上的问题是否与:***.com/questions/39129200/… 或 Array.slice 没有处理 UInt8Array 并泄漏未初始化的内存有关? (一个安全问题!)。 @robocat 好收获!如果我记得很清楚,我们使用的是 Node.js 0.6 或 0.8。我们考虑过某种泄漏,但我们无法用生产堆栈重现它,所以我们决定忽略它。【参考方案19】:

创建一个全新的数组

new Array(arrayLength).fill(0);

在现有数组的末尾添加一些值

[...existingArray, ...new Array(numberOfElementsToAdd).fill(0)]

示例

//**To create an all new Array**

console.log(new Array(5).fill(0));

//**To add some values at the end of an existing Array**

let existingArray = [1,2,3]

console.log([...existingArray, ...new Array(5).fill(0)]);

【讨论】:

【参考方案20】:

从ECMAScript2016 开始,对于大型阵列,有一个明确的选择。

由于这个答案仍然显示在谷歌搜索的顶部附近,这里是 2017 年的答案。

这是一个当前的jsbench,其中包含几十种流行的方法,包括迄今为止就这个问题提出的许多方法。如果您发现更好的方法,请添加、分叉和分享。

我想指出,没有真正最有效的方法来创建任意长度的零填充数组。您可以针对速度进行优化,也可以针对清晰度和可维护性进行优化 - 根据项目的需要,两者都可以被认为是更有效的选择。

在优化速度时,您希望: 使用文字语法创建数组;设置长度,初始化迭代变量,并使用 while 循环遍历数组。这是一个例子。

const arr = [];
arr.length = 120000;
let i = 0;
while (i < 120000) 
  arr[i] = 0;
  i++;

另一种可能的实现是:

(arr = []).length = n;
let i = 0;
while (i < n) 
    arr[i] = 0;
    i++;

但我强烈反对在实践中使用第二次植入,因为它不太清楚,并且不允许您在数组变量上维护块范围。

这些比用 for 循环填充要快得多,比标准方法快 90%

const arr = Array(n).fill(0);

但由于其清晰、简洁和可维护性,这种填充方法对于较小的阵列仍然是最有效的选择。除非您制作大量长度为数千或更多的数组,否则性能差异可能不会杀死您。

其他一些重要说明。大多数样式指南建议您在使用 ES6 或更高版本时,如果没有非常特殊的原因,就不要再使用 var。将const 用于不会重新定义的变量,将let 用于将要重新定义的变量。 MDN 和 Airbnb's Style Guide 是获取有关最佳实践的更多信息的好地方。这些问题与语法无关,但重要的是让 JS 新手在搜索大量新旧答案时了解这些新标准。

【讨论】:

【参考方案21】:

在答案中没有看到这个方法,所以这里是:

"0".repeat( 200 ).split("").map( parseFloat )

结果你会得到长度为 200 的零值数组:

[ 0, 0, 0, 0, ... 0 ]

我不确定这段代码的性能,但如果你将它用于相对较小的数组,这应该不是问题。

【讨论】:

既不是最快也不是最短,但对解决方案的多样性做出了很好的贡献。【参考方案22】:

let filled = [];
filled.length = 10;
filled.fill(0);

console.log(filled);

【讨论】:

【参考方案23】:

在我在 Chrome (2013-03-21) 上的测试中,这个 concat 版本要快得多。 10,000,000 个元素大约需要 200 毫秒,而直接初始化大约需要 675 毫秒。

function filledArray(len, value) 
    if (len <= 0) return [];
    var result = [value];
    while (result.length < len/2) 
        result = result.concat(result);
    
    return result.concat(result.slice(0, len-result.length));

奖励:如果您想用字符串填充数组,这是一种简洁的方法(虽然不如concat 快):

function filledArrayString(len, value) 
    return new Array(len+1).join(value).split('');

【讨论】:

好吧,狂野的。这比使用 new Array(len) 快得多。但!我在 Chrome 中看到后续读取该数据需要更长的时间。这里有一些时间戳来说明我的意思:(使用新数组(len))0.365:制作数组4.526:执行卷积10.75:卷积完成(使用concat)0.339:制作数组0.591:执行卷积//OMG,方式更快18.056:卷积完成【参考方案24】:

我正在测试 T.J. 的出色答案。 Crowder,并提出了基于 concat 解决方案的递归合并,该解决方案优于他在 Chrome 中的任何测试(我没有测试其他浏览器)。

function makeRec(len, acc) 
    if (acc == null) acc = [];
    if (len <= 1) return acc;
    var b = makeRec(len >> 1, [0]);
    b = b.concat(b);
    if (len & 1) b = b.concat([0]);
    return b;
,

makeRec(29)调用方法。

【讨论】:

【参考方案25】:

new Array(51).join('0').split('') 呢?

【讨论】:

然后.map(function(a)return +a) ? 2020年,new Array(51).fill(0)呢?它提供完全相同的输出。【参考方案26】:

值得指出的是,Array.prototype.fill 已作为ECMAScript 6 (Harmony) proposal 的一部分添加。在考虑线程中提到的其他选项之前,我宁愿使用下面编写的 polyfill。

if (!Array.prototype.fill) 
  Array.prototype.fill = function(value) 

    // Steps 1-2.
    if (this == null) 
      throw new TypeError('this is null or not defined');
    

    var O = Object(this);

    // Steps 3-5.
    var len = O.length >>> 0;

    // Steps 6-7.
    var start = arguments[1];
    var relativeStart = start >> 0;

    // Step 8.
    var k = relativeStart < 0 ?
      Math.max(len + relativeStart, 0) :
      Math.min(relativeStart, len);

    // Steps 9-10.
    var end = arguments[2];
    var relativeEnd = end === undefined ?
      len : end >> 0;

    // Step 11.
    var final = relativeEnd < 0 ?
      Math.max(len + relativeEnd, 0) :
      Math.min(relativeEnd, len);

    // Step 12.
    while (k < final) 
      O[k] = value;
      k++;
    

    // Step 13.
    return O;
  ;

【讨论】:

【参考方案27】:

循环代码最短

a=i=[];for(;i<100;)a[i++]=0;

edit:
for(a=i=[];i<100;)a[i++]=0;
or
for(a=[],i=100;i--;)a[i]=0;

安全变量版本

var a=[],i=0;for(;i<100;)a[i++]=0;

edit:
for(var i=100,a=[];i--;)a[i]=0;

【讨论】:

鉴于长度是定义的变量n,这会更短:for(var a=[];n--;a[n]=0);【参考方案28】:

我最快的功能是:

function newFilledArray(len, val) 
    var a = [];
    while(len--)
        a.push(val);
    
    return a;


var st = (new Date()).getTime();
newFilledArray(1000000, 0)
console.log((new Date()).getTime() - st); // returned 63, 65, 62 milliseconds

使用原生 push 和 shift 向数组添加项比声明数组范围并引用每个项来设置它的值要快得多(大约 10 倍)。

fyi:在 firebug(firefox 扩展)中运行它时,第一个循环倒计时,我始终获得更快的时间。

var a = [];
var len = 1000000;
var st = (new Date()).getTime();
while(len)
    a.push(0);
    len -= 1;

console.log((new Date()).getTime() - st); // returned 863, 894, 875 milliseconds
st = (new Date()).getTime();
len = 1000000;
a = [];
for(var i = 0; i < len; i++)
    a.push(0);

console.log((new Date()).getTime() - st); // returned 1155, 1179, 1163 milliseconds

我很想知道 T.J.克劳德是这样的吗? :-)

【讨论】:

您可以通过将其更改为 while (len--).. 来加快速度。我的处理时间从大约 60 毫秒到大约 54 毫秒 Matthew Crumbly 的回答实际上仍然超过了这个(30 毫秒)!【参考方案29】:

我知道我在某个地方有这个原型 :)

Array.prototype.init = function(x,n)

    if(typeof(n)=='undefined')  n = this.length; 
    while (n--)  this[n] = x; 
    return this;


var a = (new Array(5)).init(0);

var b = [].init(0,4);

编辑:测试

针对 Joshua 和其他方法,我运行了自己的基准测试,我看到的结果与报告的完全不同。

这是我测试的:

//my original method
Array.prototype.init = function(x,n)

    if(typeof(n)=='undefined')  n = this.length; 
    while (n--)  this[n] = x; 
    return this;


//now using push which I had previously thought to be slower than direct assignment
Array.prototype.init2 = function(x,n)

    if(typeof(n)=='undefined')  n = this.length; 
    while (n--)  this.push(x); 
    return this;


//joshua's method
function newFilledArray(len, val) 
    var a = [];
    while(len--)
        a.push(val);
    
    return a;


//test m1 and m2 with short arrays many times 10K * 10

var a = new Date();
for(var i=0; i<10000; i++)

    var t1 = [].init(0,10);

var A = new Date();

var b = new Date();
for(var i=0; i<10000; i++)

    var t2 = [].init2(0,10);

var B = new Date();

//test m1 and m2 with long array created once 100K

var c = new Date();
var t3 = [].init(0,100000);
var C = new Date();

var d = new Date();
var t4 = [].init2(0,100000);
var D = new Date();

//test m3 with short array many times 10K * 10

var e = new Date();
for(var i=0; i<10000; i++)

    var t5 = newFilledArray(10,0);

var E = new Date();

//test m3 with long array created once 100K

var f = new Date();
var t6 = newFilledArray(100000, 0)
var F = new Date();

结果:

IE7 deltas:
dA=156
dB=359
dC=125
dD=375
dE=468
dF=412

FF3.5 deltas:
dA=6
dB=13
dC=63
dD=8
dE=12
dF=8

因此,据我估计,push 通常确实较慢,但在 FF 中使用更长的数组时表现更好,但在 IE 中表现更差,这通常很糟糕(令人惊讶)。

【讨论】:

我刚刚对此进行了测试:第二种方法 (b = []...) 比第一种方法快 10-15%,但比 Joshua 的回答慢 10 倍以上。 我知道这是一篇古帖。但也许其他人(比如我)仍然感兴趣。因此,我想建议对原型函数进行补充:在this.length-check 之后包含一个else this.length=n;。当重新init-ialising 到不同的长度n 时,这将在必要时缩短已经存在的数组。【参考方案30】:

匿名函数:

(function(n)  while(n-- && this.push(0)); return this; ).call([], 5);
// => [0, 0, 0, 0, 0]

用for循环稍微短一点:

(function(n)  for(;n--;this.push(0)); return this; ).call([], 5);
// => [0, 0, 0, 0, 0]

适用于任何Object,只需更改this.push() 中的内容。

你甚至可以保存函数:

function fill(size, content) 
  for(;size--;this.push(content));
  return this;

调用它使用:

var helloArray = fill.call([], 5, 'hello');
// => ['hello', 'hello', 'hello', 'hello', 'hello']

将元素添加到已经存在的数组中:

var helloWorldArray = fill.call(helloArray, 5, 'world');
// => ['hello', 'hello', 'hello', 'hello', 'hello', 'world', 'world', 'world', 'world', 'world']

性能:http://jsperf.com/zero-filled-array-creation/25

【讨论】:

'0 '.repeat(200).split(' ')

以上是关于创建零填充 JavaScript 数组的最有效方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

前向填充位数组的最有效方法

在Javascript中更新对象数组中的键的最有效方法是啥? [复制]

从 JavaScript 中的关联数组中获取第一项的最有效方法是啥?

创建初始重复数据的二维字符串数组的最有效方法是啥?

通过 JavaScript 对 HTML 表格进行排序的最有效方法是啥?

填充 UITableView 的最有效方法