在论坛和聊天室这样的场景里,为了保证用户体验,我们经常需要屏蔽很多不良词语。对于单个关键词查找,自然是indexOf、正则那样的方式效率比较高。但对于关键词较多的情况下,多次重复调用indexOf、正则的话去匹配全文的话,性能消耗非常大。由于目标字符串通常来说体积都比较大,所以必须要保证一次遍历就得到结果。根据这样的需求,很容易就想到对全文每个字符依次匹配的方式。比如对于这段文字:“Mike Jordan had said "Just do IT", so Mark has been a coder.”,假如我们的关键词是“Mike”“Mark”,那么可以遍历整句话,当找到“M”就接着看能不能匹配到“i”或者“a”,能一直匹配到最后则成功找到一个关键词,否则继续遍历。那么关键词的结构就应该是这样的:

var keywords = {M: {i: {k: {e: {end: true}}},a: {r: {k: {end: true}}}}
}

由上文可以看出这个数据就是一个树结构,而根据关键词组来创建树结构还是比较耗时的,而关键词却又是我们早已给定的,所以可以在匹配前预先创建这样的数据结构。代码如下:

function buildTree(keywords) {var tblCur = {},key, str_key, Length, j, i;var tblRoot = tblCur;for(j = keywords.length - 1; j >= 0; j -= 1) {str_key = keywords[j];Length = str_key.length;for(i = 0; i < Length; i += 1) {key = str_key.charAt(i);if(tblCur.hasOwnProperty(key)) {tblCur = tblCur[key];} else {tblCur = tblCur[key] = {};}}tblCur.end = true; //最后一个关键字tblCur = tblRoot;}return tblRoot;
}

这段代码中用了一个连等语句:tblCur = tblCur[key] = {},这里要注意的是语句的执行顺序,由于[]的运算级比=高,所以首先是在 tblCur对象中先创建一个key属性。结合tblRoot = tblCur = {} 看,执行顺序就是:

var tblRoot = tblCur = {};
tblRoot = tblCur;
tblCur['key'] = undefined;  // now tblRoot = {key: undefined}
tblCur['key'] = {};
tblCur = tblCur['key'];
通过上面的代码就构建了好了所需的查询数据,下面看看查询接口的写法。

对于目标字符串的每一字,我们都从这个keywords顶部开始匹配。首先是 keywords[a] ,若存在,则看 keyword[a][b],若最后 keyword[a][b]…[x]=true 则说明匹配成功,若 keyword[a][b]…[x]=undefined,则从下一个位置重新开始匹配 keywords[a] 。

function search(content) {var tblCur,p_star = 0,n = content.length,p_end,match, //是否找到匹配match_key,match_str,arrMatch = [], //存储结果arrLength = 0; //arrMatch的长度索引while(p_star < n) {tblCur = tblRoot; //回溯至根部p_end = p_star;match_str = "";match = false;do {match_key = content.charAt(p_end);if(!(tblCur = tblCur[match_key])) { //本次匹配结束p_star += 1;break;} else {match_str += match_key;}p_end += 1;if(tblCur.end) //是否匹配到尾部{match = true;}} while (true);if(match) { //最大匹配arrMatch[arrLength] = { key: match_str,begin: p_star - 1,end: p_end};arrLength += 1;p_star = p_end;}}return arrMatch;
}

以上就是整个关键词匹配系统的核心。这里很好的用到了js的语言特性,效率非常高。我用一篇50万字的《搜神记》来做测试,从中查找给定的300个成语,匹配的效果是1秒左右。重要的是,由于目标文本是一次遍历的,所以目标文本的长短对查询时间的影响几乎不计。对查询时间影响较大的是关键词的数量,目标文本的每个字都遍历一遍关键词,所以对查询有一定影响。

简单分析

看到上文估计你也纳闷,对每个字都遍历一遍所有关键词,就算有些关键词有部分相同,但是完全遍历也是挺耗时的呀。但js中对象的属性是使用哈希表来进行构建的,这种结构的数据跟单纯的数组遍历是有很大不同的,效率要比基于顺序的数组遍历高得多。可能有些同学对数据结构不太熟悉,这里我简单说一下哈希表的相关内容。

首先看看数据的存储。

数据在内存的存储由两部分组成,一部分是值,另一部分是地址。把内存想象成一本新华字典,那字的解释就是值,而目录就是地址。字典里面是按拼音排序的,比如相同发音的“ni”就排在同一块,也就是说数组整齐排列在一块内存区域里面,这样的结构就是数组,你可以指定“ni” 1号,10号来访问。结构图如下:

数组的优势是遍历简单,通过下标就能直接访问相应的数据了。但是它要增删某一项就非常困难。比如你要把第6项删掉,那第5项之后的数据都要向前移一个位置。如果你要删除第一位,整个数组都要移动,消耗非常大。

为了解决数组增删的问题,链表就出现了。如果我们将值分成两部分,一部分用来储存原来的值,另一部分用来储存一个地址,这个地址指向另外一个同样的结构,以此类推就构成了一个链表。结构如下:

从上图可以明显看出,对链表进行增删非常简单,只要把目标项和前一项的next改写就完成了。但是要查询某个项的值就非常困难了,你必须依次遍历才可以访问到目标位置。

为了整合这两种结构的优势,聪明如你一定想到了下面这种结构。

这种数据结构就是哈希表结构。数组里面存储链表的头地址,就可以形成一个二维数据表。至于数据如何分布,这个就是哈希算法,正规的翻译应该是散列算法。算法虽然有很多种,原理上都是通过一个函数对key进行求解,再根据求解得到的结果安放数据。也就是说key和实际地址之间形成了一个映射,所以这个时候我们不再以数组下标或者单纯的遍历来访问数组,而是以散列函数的反函数来定位数据。js中的对象就是一个哈希结构,比如我们定义一个obj,obj.name通过散列,他在内存中的位置可能是上图中的90,那我们想要操作obj.name的时候,底层就会自动帮我们通过哈希算法定位到90的位置,也就是说直接从数组的12项开始查找链表,而不是从0开始遍历整个内存块。

js中定义一个对象obj{key: value},key是被转换成字符串然后经过哈希处理得到一个内存地址,然后将值放入其中。这就可以理解为什么我们可以随意增删属性,也能理解为什么在js中还能为数组赋属性,而且数组也没有所谓的越界了。

在数据量较大的场合,哈希表具有非常明显的优势,因为它通过哈希算法减少了很多不必要的计算。所谓性能优化,其实就是让计算机少运算;最大的优化,就是不计算!

算法的优化

现在理解算法底层实现,回过头来就可以考虑对算法进行优化了。不过在优化前还是要强调一句:不要盲目追求性能!比如本案例中,我们最多就是5000字的匹配,那现有算法足矣,所有优化都是不必要的。之所以还来说说优化,就是为了提高自己对算法对程序的理解,而不是真的要去做那1ms的优化。

我们发现我们的关键词都没有一个字的,那我们按照一个字的单位进行关键词遍历显然就是一个浪费了。这里的优化就是预先统计关键词的最大最小长度,每次以最小长度为单位进行查找。比如说我测试用例的关键词是成语,最短都是4个字,那么我每次匹配都是4个字一起匹配,如果命中就继续深入查找到最大长度。也就是说我们最开始构造树的时候首先是以最小长度构建的,然后再逐字增加。

简单计算一下,按照我们的测试用例,300个成语,我们匹配一个词只需一次对比,而单字查询的话我们需要对比4次,而每次对比我们都要访问我们的树结构,这就是可避免的性能消耗。更重要的是,这里的对比并不是字符串对比,这里我们的关键字都是作为key存在的,效果就是 和key in obj一样的,都是对key进行哈希变换然后访问相应的地址!所以千万不要纠结对比一个字和对比4个字的差异,我们没对比字符串!

关于多关键词的匹配就说到这里了,优化版代码我就不贴了,因为一般也用不到。码字不易,随手点赞哈!

更多关于前端开发的信息,请关注WeX5微信公众号!

原创文章,转载请注明出处!本文链接:http://www.wex5.com/openway_wordsmatch/

1秒50万字!js实现关键词匹配相关推荐

  1. 「万字总结」熬夜总结50个JS的高级知识点,全都会你就是神

    前言 大家好,我是林三心,基础是进阶的前提,上一篇,我给大家分享了我这一年来平时记录的工作中碰到的50个JS基础知识点,今天就给大家分享一下,我这一年来,工作中遇到的50个JS高级知识点吧!!! 知识 ...

  2. 二部图最大权匹配_12,百度竞价推广账户搭建,关键词匹配-网络运营文字版

    视频版在https://www.toutiao.com/c/user/5729041189/#mid=6430967986 视频里面 第二部,搭建账户 搜索推广的账户结构由账户.推广计划.推广单元和关 ...

  3. 关键词匹配(Ac自动机模板题)

    2772: 关键词匹配 Time Limit: 1 Sec  Memory Limit: 128 MB Submit: 10  Solved: 4 [Submit][Status][Web Board ...

  4. 搜索不包含关键词_sem竞价代运营丨做竞价推广,关键词匹配才是重点

    竞价推广初期,很多企业都会遇到同样的问题,那就是如何设置关键词匹配模式?各种匹配模式下关键词是如何对应的? 简单来说,在用户进行搜索时,根据搜索习惯的不同,系统会自动挑选对应关键词,将推广结果展现给用 ...

  5. 手把手教你做关键词匹配项目(搜索引擎)---- 第九天

    第九天 回顾: 8. 手把手教你做关键词匹配项目(搜索引擎)---- 第八天 7. 手把手教你做关键词匹配项目(搜索引擎)---- 第七天 6. 手把手教你做关键词匹配项目(搜索引擎)---- 第六天 ...

  6. python关键字匹配_python通过BF算法实现关键词匹配的方法

    本文实例讲述了python通过BF算法实现关键词匹配的方法.分享给大家供大家参考.具体实现方法如下: #!/usr/bin/python # -*- coding: UTF-8 # filename ...

  7. 手把手教你做关键词匹配项目(搜索引擎)---- 第二十一天

    客串:屌丝的坑人表单神器.数据库那点事儿 面向对象升华:面向对象的认识----新生的初识.面向对象的番外----思想的梦游篇(1).面向对象的认识---如何找出类 负载均衡:负载均衡----概念认识篇 ...

  8. python问题关键词匹配算法_python通过BF算法实现关键词匹配的方法

    本文实例讲述了python通过BF算法实现关键词匹配的方法.分享给大家供大家参考.具体实现方法如下: #!/usr/bin/python # -*- coding: UTF-8 # filename ...

  9. 正则匹配不包含某字符串_如何替换JS字符串中匹配到多处中某一指定节点?

    来源 | https://www.cnblogs.com/class1/p/14273231.html 问题先行,要求搜索关键字,匹配到四处,那我鼠标放在第二处,我想把它变个颜色,该怎么实现呢?截图如 ...

最新文章

  1. 大文件分片上传前端框架_基于Node.js的大文件分片上传
  2. 使用UltraEdit来拷贝粘贴二进制
  3. c# 多线程异步demo
  4. 要求将数组中的0项去掉,将不为0的值存入一个新的数组,
  5. Annotation 使用备忘2
  6. photoshop常用快捷键大全
  7. chromium 一些设置 --插件安装
  8. python 代码命令大全
  9. rost反剽窃检测系统_Docker容器环境检测方法【代码】
  10. ansys workbench 静力结构分析 高阶教程
  11. 原生JS实现动态表格的生成
  12. vs2005创建智能设备IE脚本错误
  13. 从外网到域控(vulnstack靶机实战一)
  14. 服务器硬件配置及RAID配置操作
  15. 如何使用Sketch绘制半个圆角矩形
  16. phpmyadmin CVE-2016-6617 SQL注入漏洞
  17. 2014年国务院批准放假调休日期的具体安排通知
  18. 每次合上笔记本总是进入待机或休眠
  19. yolov5手动标注的xml转txt
  20. Node.js安装配置

热门文章

  1. 华为鸿蒙OS摄像头,首款华为鸿蒙OS摄像头开售:分布式看家新神器 无需SD卡
  2. 计算机专业硕士学费,2015计算机工程硕士专业学费汇总
  3. Android中第三方SDK集成之ZXing二维码扫一扫集成指南
  4. 她是直播聊学习成“网红”的北大博士!毕业后入职民办学校,年薪70万
  5. Zadig 构建效率提升 40% 背后的实践思路
  6. 提高电脑性能增加fps的方法
  7. Win10系统解决Windows Defender自动删除文件的问题
  8. 林斯坦 小学生学计算机,技术支持下的小学生数学有意义学习研究.doc
  9. 第六周:整数转罗马数字
  10. Vue源码翻译之渲染逻辑链