求解,在对象数组中如何快速的检索出自己想要的数据?
发布于 5 年前 作者 orangeChu 4560 次浏览 来自 问答

RT,目前做了个小项目,数据源是几千到一万的通讯录数据,对象包含姓名、电话、住址之类balabala的信息。 想要对这些数据做检索,目前做法是把这些数据后台load出来成对象数组,放在页面的变量上,使用filter、set等方式来进行检索。但是给我的感觉,就几千到一万的对象数组,检索都要花费1s左右的时间。加了loading动画后,慢的感觉特别明显。 所以求问,有什么方法能更加快速的检索?望不吝赐教,先谢过~

18 回复

第一种是让后台服务端提供需要的数据,或者自己写node请求他的接口然后检索返回给前端,服务端肯定比客户端要快些。 第二种可以使用lodsh这个模块,针对json有很多方法,总有种适合你

@tzbcf 感谢回复。 1、后端基于express搭建,尝试过这种方式,发现速度上,没有明显的提升; 2、试过lodsh的filter,速度上跟es的filter不相上下;

贴个我写的豆腐渣(编辑器支持markdown吗?):

let dictionary = [{ name: "zhangsan", tel: "8888", company: "balabala", ... }, { name: "lisi", tel: "8888", company: "balabala", ... }, { name: "wangwu", tel: "8888", company: "balabala", ... }];

function search(searchTerm) {
  return dictionary.filter(t => { return t.name.includes(searchTerm); });
}

几千到一万的通讯录数据,这应该是一份固定的数据。既然是固定的。我觉得两个方法 1.如果只用某个关键词来检索,将数组变成object,用你的关键词做key如{“手机号码”:{xxxx}},这样利用js本身的特性。速度飞快。 2.如果有多个关键词,则做简单的索引。同样利用object特性。{“手机号码”:[1,2,3],“姓名”:[3,4,5]},1,2,3 3,4,5为对象在数组中的位置。

实在复杂,你就做索引,然后排序,然后用双叉树来检索

@pzzcn 先赞美大佬的回答。:) 是的,数据是固定的。 您的建议听起来比我的方法好多了。 没有这方面的经验,问两个不成熟的问题: 1、每次的搜索词都要做一次索引吗? 2、索引需要存储吗?是否每次搜索都要重新做索引,然后根据索引取出对应的对象?

数据固定的,将数组先做好索引,存储起来。下次直接使用索引来查找数据

@pzzcn 再次感谢。。 类似索引的实现有demo参考吗?不胜感激

@pzzcn 好的! 根据您提供的思路,我大概写了一版。 根据查询的字段长度,进行拆分,最大值为12,构建12个对象,类似{0:{“a”:[0,1],“b”:[0,1]…}} 通过搜索的关键字的长度和关键字,得到对象下标数组,最后得到值。类似indexes[关键字长度][关键字]得到对象下标数组,最后循环下标数组,得到结果。 但是这样处理后,运行的结果对比(数据量在2500左右),比for循环、filter过滤来得慢? TIM截图20190718171114.png

贴一下方法的最后运行代码,前面处理索引的没啥好看的,处理索引的时间不在结果比较中:

var index_nr = text.length - 1;
var ids = this.indexes[index_nr][text];
var result = [];
for (var i = 0, len = ids.length; i < len; i++) {
    result.push(this.data[ids[i]]);
}
return result;

TIM截图20190718171557.png

@orangeChu 这样对比不合适的,因为没有考虑引擎的 JIT 功能。如果没有 JIT 的话,filter 处理的时候因为需要调用回调,肯定有 JMP,而 forloop 的实现,可以看成是 inline 版的 filter,这已经会导致 forloop 比 filter 快一点了。所以要对比实际执行情况的话,最好是先将函数热身,然后再对比执行结果。

function impl_filter(kw) {
  const res = // do filter relative process
  return res;
}

function impl_forloop(kw) {
  const res = // do for-loop relative process
  return res;
}

// warms JIT
for(let i = 0; i < 1000; i++) {
  impl_filter('some_kw');
  impl_forloop('some_kw');
}

console.time('filter');
impl_filter('some_kw');
console.timeEnd('filter');

console.time('forloop');
impl_forloop('some_kw');
console.timeEnd('forloop');

@hsiaosiyuan0 收到!(不好意思,昨晚蹦迪去了,没注意看到您的回复。 按照您的建议,函数执行前进行热身,以下是测试结果(编写的方法居然更快了!): Snipaste_2019-07-19_07-22-38.png

@iceyang

感谢您的回复。 主体是通过electron来做加载的,主进程中已经将数据从数据库加载出来了,使用buffer存储。渲染进程获取该数据后,放在变量中进行操作,操作的同时缓存一份数据到localforage中,之后获取localforage中的缓存数据,不再重复获取主进程中的数据源。

为啥我是一次性加载所有数据,而不是需要时再查询? 我是这么考虑的:暂时使用nedb来做数据的存储。使用时再去查询数据,文件的io流,要消耗不少时间,所以我的做法是一次性加载所有数据,并且这些数据不需要实时更新,这样做我认为会有比较好的体验。 如果大伙有比较好的建议,我会认真考虑一下。先谢过~

为啥cnode的编辑器,加了---后,会自动粗体上面的文字,我该怎么编辑才能取消粗体呢?

@orangeChu markdown 中的 — 是会将上面的文字变成把标题的。

不好意思前面的回复被我不小心删了。所以就变成你在回复空气了:-D

是这样的,对于筛选这件事,我认为用js做不太合适,既然你的数据源是数据库,那么是可以通过数据库索引最大速度来做这个筛选动作的。

另外是这么多条数据你是在一页就展示了全部吗。

@iceyang 感谢回复。

  1. 试过nedb的索引以及查询,发现效果不怎么理想,所以才改为这种方案来实现。
  2. 数据不是一页就全部展示,这些数据用于检索,应用场景类似手机上的通讯录搜索栏,只展示检索后的结果。

搜索需求是关系型数据库的基本功能。

@waitingsong 感谢回复。 之前试过使用主进程来做搜索,然后返回,发现效果一般,也许我该让渲染进程直接查询。

@orangeChu 少量数据的特定字段的检索这个可以在前端实现,这个叫做缓存。 这么多数据并且检索多个字段,你是想自己实现一个精简版数据库么?

@waitingsong 哈哈,您说的是对的。 前端的做法就是把数据都load出来,然后检索。 dammit,我一个写后端的好像被传染了。 马上改正。

回到顶部