经常会遇到一些场景,比如把一个很大的对象保存于数组中,数组长度很长,遍历次数又多,消耗的时间比较久。
这个对象内有个ID字段是GUID或者UID,反正能保证它唯一。
作为都一名老程序员首先想到的是先排序,再二分法。瞬间优化提上去。但是javascript的字符串比较是个痛点,排序需要比较,二分查找的时候又需要比较。当然,如果主键是整数,建议还是排序再按二分法。另外Javascript有个很好的内置对象,叫做Map,可以很好的管理按主键快事查询,用来代替常常需要遍历的数组也是不错的选择。
于是我另辟蹊径做了个尝试,有些心得记录一下。
菜鸟做法
首先看看在大数组中常规的遍历效果:
var f = function(id) {
for (var i = _this.instances.length - 1; i >= 0; i--) {
if (_this.instances[i].id == id)
return _this.instances[i];
}
return null;
}
instance = f(obj.Ii);
这是在instances这个1.5万长度的数组里面找出一个id匹配的对象。耗时看一下:
进阶做法(Map)
别说话,看示例代码
let myMap = new Map();
// 添加键
myMap.set(key1, obj1);
myMap.set(key2, obj2);
myMap.set(key3, obj3);
myMap.size; // 3
// 读取值
myMap.get(key1); // obj1
测算的效率如下,确实很666
野蛮人做法
想了个小主意,用id的长字符串作为识别特征,把数组整理成树形表。比如ID的长度有36个字符,可以使用倒序读取字符,把字符作为key组建一个树表,如下图,理论上每一层最多可能有a..z+0..9,有36个节点。如果配置为5层,理论上如果足够随机,36^5=60466176个对象,随机落在每个叶子节点的数量平均只有1个。根据ID找到目标的遍历次数最小代价为5次比较,最高为36*5=180次比较。比起直接遍历平均需要7500次比较是快了许多。
好了,直接上代码。第一次构建此类,测试一下性能:
function QmodelList(key1, obj1, level) {
this.key = key1;
this.obj = obj1;
this.heepList = [];
this.lv = level;
}
QmodelList.prototype = Object.create(QmodelList.prototype);
QmodelList.prototype.constructor = QmodelList;
QmodelList.prototype.add = function(obj, id) {
this.addObj(obj, id, id.length - 1, this.lv - 1);
return this;
};
QmodelList.prototype.find = function(id) {
return this.findObj(id, id.length - 1);;
};
QmodelList.prototype.addObj = function(obj, id, keyindex, level) {
var find = -1;
var key = id[keyindex];
for (var i = 0; i < this.heepList.length; i++) {
if (this.heepList[i].key === key) {
find = i;
break;
}
}
var o;
if (find === -1) {
o = new QmodelList(key, null, level);
this.heepList.push(o);
} else {
o = this.heepList[i];
}
if (level === 0) {
o.heepList.push(new QmodelList(id, obj));
} else
o.addObj(obj, id, keyindex - 1, o.lv - 1);
return this;
};
QmodelList.prototype.findObj = function(id, keyindex) {
var find = -1;
var key = id[keyindex];
for (var i = 0; i < this.heepList.length; i++) {
if (this.heepList[i].key === key) {
find = i;
break;
}
}
var o;
if (find === -1) {
return null;
} else {
o = this.heepList[find];
}
if (o.lv === 0) {
for (var i = 0; i < o.heepList.length; i++) {
if (o.heepList[i].key === id) {
return o.heepList[i];
}
}
} else
return o.findObj(id, keyindex - 1, o.lv - 1);
}
当 instanceqm=new QmodelList(null,null,5),查找的效率如下,还不如Map算了!
勇士做法
成功了叫勇士,不成功叫莽夫!
在叶子节点,使用一个数组去存储长度上限为36的对象并不是很好的办法。为啥呢?通过for去查找一个36长度的数组,效率还是有些影响,正如上面所说,很不走运的时候,极限比较次数仍然需要180次。这些可以再次改造,大家用过javascript的都知道,比如你有一个对象obj,你可以这样赋值然后使用:obj.a={key:0,value:1},然后,obj就已经有属性a可以使用了。当obj的属性达到36个,从obj里面找一个属性更快,还是在一个数组[]里面循环对比出一个结果更快?这个需要看下测试结果:
废话不说,先上改造后的代码:
function QmodelList(key1, obj1, level) {
this.key = key1;
this.obj = obj1;
this.heepList = [];
this.lv = level;
}
QmodelList.prototype = Object.create(QmodelList.prototype);
QmodelList.prototype.constructor = QmodelList;
QmodelList.prototype.add = function(obj, id) {
this.addObj(obj, id, id.length - 1, this.lv - 1);
return this;
};
QmodelList.prototype.find = function(id) {
return this.findObj(id, id.length - 1);;
};
QmodelList.prototype.addObj = function(obj, id, keyindex, level) {
var key = id[keyindex];
if (this[key] == null)
this[key] = new QmodelList(key, null, level);
var o = this[key];
if (level === 0) {
o.heepList.push(new QmodelList(id, obj));
} else
o.addObj(obj, id, keyindex - 1, o.lv - 1);
return this;
};
QmodelList.prototype.findObj = function(id, keyindex) {
var key = id[keyindex];
var o = this[key];
if (o.lv === 0) {
for (var i = 0; i < o.heepList.length; i++) {
if (o.heepList[i].key === id) {
return o.heepList[i];
}
}
} else
return o.findObj(id, keyindex - 1, o.lv - 1);
}
同样,当 instanceqm=new QmodelList(null,null,5),查找的效率如下:加入的数组长度为1.5万。
又提升了几毫秒,666...,实际上因为随机的关系,每一层叶子,通常没有达到36个,就我这个案例而言,很多叶子到了第4层已经剩下1个对象了,找起来还是很快的。