在JavaScript中创建任意长度的零填充数组最有效的方法是什么?
当前回答
2013年8月添加的注释,2015年2月更新:以下2009年的答案与JavaScript的通用数组类型有关。它与ES2015中定义的较新类型数组(现在在许多浏览器中都可用)无关,如Int32Array等。还要注意,ES2015为数组和类型化数组都添加了填充方法,这可能是填充它们的最有效方法。。。
此外,它可以对某些实现的创建数组的方式产生很大的影响。特别是Chrome的V8引擎,如果它认为可以的话,它会尝试使用一个高效的、连续的内存数组,只有在必要的时候才转向基于对象的数组。
对于大多数语言,它将是预分配的,然后是零填充,如下所示:
function newFilledArray(len, val) {
var rv = new Array(len);
while (--len >= 0) {
rv[len] = val;
}
return rv;
}
但是,JavaScript数组并不是真正的数组,它们是键/值映射,就像所有其他JavaScript对象一样,所以没有“预分配”(设置长度不会分配那么多槽来填充),也没有任何理由相信倒计时到零的好处(这只是为了使循环中的比较更快)不会被按相反顺序添加键所抵消,因为实现可能已经优化了它们对与数组相关的键的处理,理论上你通常会按顺序进行。
事实上,Matthew Crumley指出,在Firefox上,向下计数比向上计数要慢得多,我可以证实这一点——这是数组的一部分(向下循环到零仍然比向上循环到var中的一个极限更快)。显然,在Firefox上,以相反的顺序将元素添加到数组是一个缓慢的操作。事实上,结果因JavaScript实现而异(这并不奇怪)。下面是一个针对浏览器实现的快速而肮脏的测试页面(非常脏,在测试过程中不会产生结果,因此提供的反馈最少,并且会违反脚本时间限制)。我建议在测试之间刷新;如果你不这样做,FF(至少)会在重复测试中减速。
使用Array#concat的相当复杂的版本比FF上的直接初始化快,大约在1000到2000个元素数组之间。不过,在Chrome的V8引擎上,每次都是直接初始化获胜。。。
这里有一个测试:
常量测试=[{name:“倾盆大雨”,总计:0,desc:“倒计数,预减量”,函数:makeWithCountDownPre},{name:“downpost”,总计:0,desc:“倒数,递减后”,函数:makeWithCountDownPost},{name:“up”,总计:0,desc:“向上计数(正常)”,func:makeWithCountUp},{name:“向下和向上”,总计:0,desc:“向下计数(循环)和向上计数(填充)”,func:makeWithCountDownArrayUp},{name:“concat”,总计:0,desc:“凹形”,函数:makeWithConcat}];const q=sel=>document.querySelector(sel);let markup=“”;for(测试的常量{name,desc}){标记+=`<div><input type=“checkbox”id=“chk_${name}”checked><label for=“chk_${name}”>${desc}</label></div>`;}q(“#checkbox”).innerHTML=标记;q(“#btnTest”).addEventListener(“单击”,btnTestClick);函数btnTestClick(){//清除日志q(“#log”).innerHTML=“测试…”;//显示正在运行q(“#btnTest”).disabled=true;//在浏览器更新显示时暂停后运行setTimeout(btnTestClickPart2,0);}函数btnTestClickPart2(){尝试{运行测试();}捕获(e){日志(`异常:${e.message}`);}//重新启用按钮q(“#btnTest”).disabled=false;}函数getNumField(名称){const val=q(“#”+名称).value.trim();常量num=/^\d+$/.test(val)?parseInt(val):NaN;如果(isNaN(num)||num<=0){抛出新错误(`无效的${name}值${JSON.stringify(val)}`);}返回num;}函数runTests(){尝试{//清除日志q(“#log”).innerHTML=“”;const runCount=getNumField(“loops”);const length=getNumField(“长度”);//做它(我们运行runCount+1次,第一次是热身)for(让counter=0;counter<=runCount;++counter){for(测试的常量测试){如果(q(“#chk_”+测试名称)已选中){const start=Date.now();const a=测试函数(长度);const time=Date.now()-开始;如果(计数器==0){//不要计算(预热),但要检查算法是否有效常量无效=validateResult(a,长度);if(无效){日志(`<span class=error>FAILURE</span>,测试${test.name}:${invalid}`);回来}}其他{//数数这个日志(“#${counter}:${test.desc}:${time}毫秒”);test.total+=时间;}}}}for(测试的常量测试){如果(q(“#chk_”+测试名称)已选中){test.avg=测试总数/运行计数;if(typeof lowest!=“number”||最低>测试.avg){最低=测试平均值;}}}let结果=“<p>结果:”+“<br>长度:”+长度+“<br>循环:”+runCount+“</p>”;for(测试的常量测试){如果(q(“#chk_”+测试名称)已选中){结果+=`<p${最低==test.avg?“class=winner”:“”}>${test.desc},平均时间:${test.avg}毫秒</p>`;}}结果+=“<hr>”;q(“#log”).insertAdjacentHTML(“afterbegin”,结果);}捕获(e){日志(e.message);回来}}函数validateResult(a,长度){如果(a.length!=长度){return“长度错误”;}for(设n=长度-1;n>=0;--n){如果(a[n]!=0){return“索引”+n+“不为零”;}}返回未定义;}函数makeWithCountDownPre(len){const a=新数组(len);而(--len>=0){a[len]=0;}返回a;}函数makeWithCountDownPost(len){const a=新数组(len);而(len-->0){a[len]=0;}返回a;}函数makeWithCountUp(len){const a=新数组(len);for(设i=0;i<len;++i){a[i]=0;}返回a;}函数makeWithCountDownArrayUp(len){const a=新数组(len);设i=0;而(--len>=0){a[i++]=0;}返回a;}函数makeWithConcat(len){如果(长度==0){返回[];}设a=[0];设currlen=1;while(currlen<len){常量rem=长度-电流;如果(rem<currlen){a=a.oncat(a.slice(0,rem));}其他{a=混凝土(a);}currlen=a.length;}返回a;}函数日志(msg){const p=document.createElement(“p”);p.text内容=消息;q(“#log”).appendChild(p);}正文{字体系列:无衬线;}#对数p{边距:0;填充:0;}.错误{颜色:红色;}.获胜者{颜色:绿色;}<div><label for='textLength'>长度:</label><input type='text'id='Length'value='1000'><br><label for='xtLoops'>循环:</label><input type='text'id='Loops'value='100000'><div id='checkboxs'></div><br><input-type='button'id='btnTest'value='Test'><小时><div id='log'></div></div>
其他回答
我已经测试了IE 6/7/8、Firefox 3.5、Chrome和Opera中预分配/不预分配、向上/向下计数和for/while循环的所有组合。
下面的功能在Firefox、Chrome和IE8中始终是最快的或非常接近的,并且不比Opera和IE6中最快的慢太多。在我看来,这也是最简单明了的。我发现了几个while循环版本稍快的浏览器,所以我也将其包括在内以供参考。
function newFilledArray(length, val) {
var array = [];
for (var i = 0; i < length; i++) {
array[i] = val;
}
return array;
}
or
function newFilledArray(length, val) {
var array = [];
var i = 0;
while (i < length) {
array[i++] = val;
}
return array;
}
其他人似乎缺少的是预先设置数组的长度,这样解释器就不会不断改变数组的大小。
我的简单一行代码是Array.protocol.slice.apply(newUint8Array(长度))
但是,如果我要创建一个函数来快速执行,而不需要一些复杂的变通方法,我可能会编写一个这样的函数:
var filledArray = function(value, l) {
var i = 0, a = []; a.length = l;
while(i<l) a[i++] = value;
return a;
}
我知道我在某个地方有这个原型:)
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
因此,据我估计,总体而言,推送速度确实较慢,但在FF中使用较长的阵列时表现更好,但在IE中表现更差,这只是总体上很糟糕(quel surprise)。
let filled=[];填充长度=10;fill.fill(0);console.log(已填充);
这里最快的是
(arr = []).length = len; arr.fill(0);