今天遇到一个问题,需要将一组数字格式化为指定长度进行前置补零。于是突发奇想,到底哪种方法效率最高呢?今天我们就来测试一下,废话不多说,直接lie代码(这里我们还是用NodeJS内的perf_hooks
库来实现性能监控)
起初的测试
这里按照常规套路,将重复执行的次数设置为了interval=1000000
,并将原始数字设置为num=123,len=10
,或想着通过多次的执行来放大延迟的效果,得到的结果如下:
// 引入performance包
const {
performance,
PerformanceObserver
} = require('perf_hooks')
// 新建一个Observer,并在面实现打印duration
const obs = new PerformanceObserver((list, observer) => {
list.getEntries().forEach(i => console.log(`Performance ${i.name} duration:${i.duration}`))
observer.disconnect()
});
// 配置这个Observer只接受measure和function,并开启结果缓存
obs.observe({
entryTypes: ['measure', 'function'],
buffered: true
});
// 华丽而不失优雅的分割线=========================================
// 下面开始网上几种比较常见的方法的性能测试
const interval = 1000000
// 这里我们取数字123,要求返回10位补零后的字符串
const num = 123,
len = 10
let tmp
performance.mark('1 S')
for (let i = 0; i < interval; i++) {
tmp = num.toString()
let j = tmp.length;
while (j < len) {
tmp = "0" + tmp;
j++;
}
}
performance.mark('1 E')
console.log('1', tmp)
performance.measure('1', '1 S', '1 E')
performance.mark('2 S')
for (let i = 0; i < interval; i++) {
tmp = (Array(len).join('0') + num).slice(-len)
}
performance.mark('2 E')
console.log('2', tmp)
performance.measure('2', '2 S', '2 E')
performance.mark('3 S')
for (let i = 0; i < interval; i++) {
tmp = (num / Math.pow(10, len)).toFixed(len).substr(2)
}
performance.mark('3 E')
console.log('3', tmp)
performance.measure('3', '3 S', '3 E')
performance.mark('4 S')
for (let i = 0; i < interval; i++) {
tmp = ("00000000000000000000000" + num).substr(-len);
}
performance.mark('4 E')
console.log('4', tmp)
performance.measure('4', '4 S', '4 E')
看下结果吧:
1 0000000123
2 0000000123
3 0000000123
4 0000000123
Performance 1 duration:124.4382
Performance 2 duration:609.8136
Performance 3 duration:350.801199
Performance 4 duration:121.3448
经过多次测试,虽然每次时间都不一样,但是结果还是大致相同的。与网上那么多教程中说的恰恰相反,速度最快的是1和4。网上很多文章转载的结果基本都是说第二种方法是最快的???这是什么情况呢???到底哪里出了问题?
其实小伙伴们也许已经发现,方法2和方法4其实原理几乎一样,只不过方法2更加灵活,不受len
的限制,但是这样牺牲的却是运行效率(Array(len).join('0')
的效率比较低)。如果硬要用这种更为灵活的方法,还不如选择方法1或方法3
减少重复次数后测试
既然有了performance神器,我们将循环去掉,看看每个方法的单次执行结果如何呢:
// interval = 0 直接将for循环注释掉了得到的结果
1 0000000123
2 0000000123
3 0000000123
4 0000000123
Performance 1 duration:0.0905
Performance 2 duration:0.3641
Performance 3 duration:0.1794
Performance 4 duration:0.007101
多次测试后结果基本一致,那就是方法4效率最高!这个说得通啊,方法四只是一个字符串拼接和取substr的过程,在一定长度下没理由比方法1慢啊!方法2依然最慢。那么我们发现,只执行一次的时候方法四最快。
接下来的测试我们都把for循环去掉,就用单次执行的时间来对比。
两位数测试
这里再来测试比较常见的场景,如2位数字的格式化(日期啊时间中常用),设置上面的num=2,len=2
后,看看哪个方法的效率更高呢:
1 02
2 02
3 02
4 02
Performance 1 duration:0.0955
Performance 2 duration:0.5985
Performance 3 duration:0.2262
Performance 4 duration:0.010299
多次测试是方法4完胜。那如果len=num.length
的情况下呢?
// len=2,num=12
1 12
2 12
3 12
4 12
Performance 1 duration:0.2626
Performance 2 duration:0.5767
Performance 3 duration:0.455599
Performance 4 duration:0.0163
依旧是方法4。
异常测试
一、num<len的情况
这种情况下,不同的算法得到的结果是不同的,没有对错,只能说看你需要,直接上结果:num=1234,len=2
1 1234
2 34
3 .34
4 34
Performance 1 duration:0.0865
Performance 2 duration:0.6398
Performance 3 duration:0.238099
Performance 4 duration:0.0074
看到了咩!!!出现了BUG!方法3中出现了一个小数点。原因是因为这个方法之前用的substr(2)
,也就是取第二个字符后的字符串(作者可能想当然认为不存在小数点前有超过1位的情况了),所以这里我们改成substr(-len)
更为合适。
二、超长位数测试
其实这个测试的意义不大,因为每个方法,根据它们的实现原理来说都有各自的限制,比如方法4,如果长度超出了前面预设的000000000
的长度,则不能返回正确的结果。所以在常用的场景下,方法四是最好的选择。
三、num为小数的测试
这里设置num=12.345,len=10
,得到的结果如下:
1 0000012.34
2 0000012.34
3 0000000012
4 0000012.34
Performance 1 duration:0.1214
Performance 2 duration:0.364799
Performance 3 duration:0.184201
Performance 4 duration:0.007899
方法3由于实现原理上的问题导致数据异常,接下来再测试一下num=0.123,len=2
的场景:
1 0.1234
2 34
3 00
4 34
Performance 1 duration:0.101199
Performance 2 duration:0.5365
Performance 3 duration:0.195501
Performance 4 duration:0.008401
可以看到,根据2和4的实现原理,只取了最后两位字符。方法一没有在小数点前补0,方法3经过运算后在0的前面补了2个0
方法总结
对于可控长度范围下的前置补零或返回原数据,则通过字符串运算的方式效率最高:
num.length>len?num:('00000000000000'+num).substr(-len)
对于最长需要补多少个0不太确定的场景,可以通过方法1来实现:
tmp = num.toString()
let j = tmp.length;
while (j < len) {
tmp = "0" + tmp;
j++;
}
网友评论