1、背景

最近我的自动化测试平台(PostGirl)上有一个小需求:
用户在知识库的搜索框输入关键字,下方自动显示出以该关键字开头的词汇。实现效果类似百度的联想搜索(见下图)。

2、方案一

开始我的实现思路是使用redis的zset来实现。通过zadd添加元素。搜索的时候使用zrank获取到关键字的位置,然后通过zrange 得到所有以关键字开头的词汇,最后进行展示。
核心代码如下:

 // 1、将关键字存储到redis中,用于后续输入联想功能jedis.zadd("search",0,word.getName());// 2、查询出关键字处于有序集合的位置Long wordIndex = resource.zrank("search", keyWord);
// 3、获取指定起始位置到末尾的所有值 Set<String> results = resource.zrange("search", wordIndex, -1L);for (String result: results) {if (result.startsWith(keyWord)) {reustlts.add(result); // 这个里面存储了所有以指定关键字开头的词汇。}}

但是,使用redis实现有个缺点,在zset中获取以某个字开头的位置很好确定,但是结尾不好确定,容易把不相关的内容搜索出来,感觉不怎么好处理。

方案二

在内存中维护一颗字典树,每次插入关键字后将字典树序列化为json字符保存到数据库。同时更新字典树对象。当重启的时候,将数据库中的json字符串查询出来然后实例化为对象(这一步在启动时加载效果更好)。
代码实现如下:

package com.cz.commons.search;import com.cz.commons.holder.Holder;
import com.google.gson.Gson;import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** 字典树,用于搜索联想* @program: PostGirl-panent* @description: Trie* @author: Cheng Zhi* @create: 2021-07-10 12:36**/
public class Trie implements Serializable {public static Trie trie;public static TrieNode root ;public Trie() {if (root == null) {root = new TrieNode();}}public static Trie getTrie() {if (trie == null) {trie = new Trie();}return trie;}static class TrieNode {// 标识是否为终节点Boolean isEnd;// 多少个路线通过该节点Integer num;// 保存所有儿子节点Map<String,TrieNode> sonMap;// 标记是否存在儿子节点Boolean hasSon;TrieNode() {isEnd = false;hasSon = false;num = 1;sonMap = new HashMap<String, TrieNode>();}}/*** 插入字典树* @param sentence*/public void insert(String sentence) {TrieNode node = root;for (int i=0; i<sentence.length(); i++) {String word = String.valueOf(sentence.charAt(i));// 如果树中不存在该字if (!node.sonMap.containsKey(word)) {node.sonMap.put(word,new TrieNode());node.hasSon = true;} else {node.num ++;}node = node.sonMap.get(word);}node.isEnd = true;}/*** 在树中查找* @param sentence* @return*/public boolean search(String sentence) {TrieNode node = root;for (int i=0; i<sentence.length(); i++) {String word = String.valueOf(sentence.charAt(i));if (node.sonMap.containsKey(word)) {node = node.sonMap.get(word);} else {return false;}}return node.isEnd;}/*** 获取所有以入参关键字开头的词汇* @param sentence* @return*/public List<String> searchList(String sentence) {List<String> resultList = new ArrayList<>();TrieNode node = root;StringBuilder prefix = new StringBuilder();String[] strings = new String[20];int index = 0;Holder<Integer> holder = new Holder<>(index);for (int i=0; i<sentence.length(); i++) {String word = String.valueOf(sentence.charAt(i));// 如果树中不包含关键字,直接返回if (!node.sonMap.containsKey(word)) {return resultList;} else {node = node.sonMap.get(word);}prefix.append(word);// 判断如果是一句话中的最后一个字符if (i == sentence.length() -1) {final String finalSb = prefix.toString();recursion(node, resultList, holder, prefix.toString());}}return resultList;}/*** 递归获取字典树的树枝。* @param node* @return*/private String recursion(TrieNode node, List<String> strings, Holder<Integer> holder, String sb) {String prefix = sb;if (node.hasSon) {for (String key : node.sonMap.keySet()) {if (node.sonMap.get(key).isEnd) {//strings[holder.getValue()] = sb+key;strings.add(sb+key);}holder.setValue(holder.getValue() + 1);}for (String key : node.sonMap.keySet()) {holder.setValue(holder.getValue() + 1);recursion(node.sonMap.get(key),strings,holder, sb+key);}}return sb.toString();}/*** 为Trie内部类TrieNode初始化值* @param jsonObject* @return*/public Trie setTrieNode(String jsonObject) {TrieNode trieNode = mapToObject(jsonToMap(jsonObject));Trie trie = getTrie();trie.root = trieNode;return trie;}/*** 将json转化为Map* @param jsonStr* @return*/public Map<?, ?> jsonToMap(String jsonStr) {Map<?, ?> ObjectMap = null;Gson gson = new Gson();java.lang.reflect.Type type = new com.google.gson.reflect.TypeToken<Map<?,?>>() {}.getType();ObjectMap = gson.fromJson(jsonStr, type);return ObjectMap;}/*** 将map转化为java对象* @param map* @return*/public TrieNode mapToObject(Map<?, ?> map) {TrieNode trieNode = new TrieNode();Double doubleNum = (Double) map.get("num");trieNode.num = doubleNum.intValue();;trieNode.isEnd = (Boolean) map.get("isEnd");trieNode.hasSon = (Boolean) map.get("hasSon");Map<?,?> sonMap = (Map<?, ?>) map.get("sonMap");Map<String, TrieNode> newMap = new HashMap<>();for (Object key : sonMap.keySet()) {newMap.put((String) key, mapToObject((Map<?, ?>) sonMap.get(key)));}trieNode.sonMap = newMap;return trieNode;}public static void main(String[] args) {Trie chTrie = new Trie();Gson gson = new Gson();String str = "这是一坨,这是,这是一箱,测试,这是个好孩子,这测试,这是行,这是好,这是啥,这是朱,这是猪啊啊啊啊啊啊啊啊";String[] splits = str.split(",");for (String split : splits) {chTrie.insert(split);}String strs = "这是";System.out.println(chTrie.searchList(strs));}
}

效果


思考

这样做,数据量小貌似没有问题,但是,如果数据量相当大的话,那么这颗树将会很大,使用数据库操作必定会很慢。还得再想想怎么搞。。。

补充

好多朋友询问Holder是什么?holder是一个用来做数据传递的载体,可以理解为是一个容器。
下面是源码:

package pers.cz.commons.holder;/*** @program: netDisc  使用引用传递的方式实现不可变类的传递:例如int类型的传递* @description: Holder* @author: Cheng Zhi* @create: 2020-12-05 13:40**/
public class Holder<T> implements IHolder {private T value;private IHolder pipeHolder;public T getValue() {return value;}public void setValue(T value) {this.value = value;}public Holder(T value) {this.value = value;}public Holder() {}@Overridepublic String toString() {return "Holder{" +"value=" + value +", pipeHolder=" + pipeHolder +'}';}@Overridepublic Object get() {return null;}@Overridepublic void set(Object obj) {}/*** 测试方法* @param args*/public static void main(String[] args) {// doing();String name  = "cz";Holder holder = new Holder(name);change(holder);System.out.println(holder.getValue());Integer a = 5;Holder holder1 = new Holder(a);changeInt(holder1);System.out.println(holder1.getValue());}private static void changeInt(Holder holder) {Integer a = (Integer) holder.getValue();holder.setValue( a+1);}private static void change(Holder holder) {holder.setValue("cz666");}
}
package pers.cz.commons.holder;import java.io.Serializable;public interface IHolder extends Serializable {public abstract Object get();public abstract void set(Object obj);}

联想搜索实现方案(java基于字典树算法的实现方式)相关推荐

  1. php 实现联想式 搜索,PHP实现搜索联想功能(基于字典树算法)

    搜索联想功能是各大搜索引擎具备的基础功能,如下图所示,这个功能简化了用户的输入行为,并且能够给用户推荐热门的搜索词,下面我们来讲一下如何用php实现搜索联想的功能. 实现原理 搜索联想功能拆解一下由两 ...

  2. es keyword和text的区别以及联想词实现方案

    keyword和text的区别 相同 支持模糊查询和精确查询 不同 分词 keyword 不分词 text 分词 聚合查询 keyword 支持 text 不支持 联想词实现方案 内容 基于es的ke ...

  3. 基于java基于javaweb的管理系统设计与实现怎样选题思路分享

    首先简单介绍一下自己,,MY IS一名勤奋的程序员JAVA 开发工程师,目前在一家 IT 互联网公司,主要负责公司搬砖的后端开发工作,除了我,团队里都是多年开发经验的老师,累积丰富开发经验,擅长JAV ...

  4. Elasticsearch5.3.1 IK分词,同义词/联想搜索设置

    本文主要是记录Elasticsearch5.3.1 IK分词,同义词/联想搜索设置,本来是要写fscrawler的多种格式(html,pdf,word...)数据导入的,但是IK分词和同义词配置还是折 ...

  5. java基于Springboot+vue的文体文具销售商城网站 elementui

    在现实生活中,不论是学生还是工作人员,都需要对你的文具进行书写完成相应的学业以及工作.传统的文具购买方式都是到文具店购买.是到文具店购买对应的文具也有一定的弊端,一方面是文具店,因为成本的原因商品并没 ...

  6. 路由选路java,java基于蚁群算法路由选择可视化动态模拟-开题报告

    <java基于蚁群算法路由选择可视化动态模拟-开题报告>由会员分享,可在线阅读,更多相关<java基于蚁群算法路由选择可视化动态模拟-开题报告(3页珍藏版)>请在人人文库网上搜 ...

  7. atitit.跨架构 bs cs解决方案. 自定义web服务器的实现方案 java .net jetty  HttpListener...

    atitit.跨架构 bs cs解决方案. 自定义web服务器的实现方案 java .net jetty  HttpListener 1. 自定义web服务器的实现方案,基于原始socket vs   ...

  8. Java基于opencv实现图像数字识别(一),java开发面试笔试题

    我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家. 扫描二维码或搜索下图红色VX号,加VX好友,拉你进[程序员面试学习交流群]免费领取.也欢迎各位一起 ...

  9. geohash美团附近酒店搜索-技术方案

    美团附近酒店搜索-技术方案 自Redis 3.2开始,Redis基于geohash和zset提供了地理位置相关功能. 什么是Geohash Geohash是一种地址编码,它能把二维的经纬度编码成一维的 ...

最新文章

  1. linux运维/自动化开发__目录
  2. Js 对小数的处理(科学计数法 , 显示精度)
  3. libuv / 编译 libuv 1.30 过程说明(静态库)
  4. Boost:ping的测试程序
  5. tf.placeholder函数的用法
  6. springBoot跨域解决
  7. respond java 使用_java – 使用android问题的HttpResponse:执行总...
  8. Windows 7 SP1意味着XP降级的结束
  9. 中文搜索引擎技术揭密-网络蜘蛛
  10. php对联广告代码,网站漂浮对联广告代码
  11. python 数学公式显示_python 数学公式·
  12. bootstrap日期插件daterangepicker的使用
  13. 为了自己的梦想而努力
  14. 自学云计算进度(2)-Linux基础命令
  15. 浏览器显示“您与此网站之间建立的连接不安全”的解决方案
  16. 第14节 三个败家子(14)——在辉煌中走向深渊
  17. Android存储子系统流程--vold
  18. 【Android归纳】基于XListView的下拉刷新、上拉加载更多的控件分析
  19. PHP/Golang实现—数据结构之顺序栈
  20. @程序员,你需要点金融常识

热门文章

  1. zzuli 1787: 生化危机 (BFS)
  2. 基于ymir2.0版本搭建本地化调试的环境
  3. 居家隔离没事干?进来秒变算法工程师
  4. 关于java开发mc模组出现的问题
  5. ACWing算法提高课 友好城市
  6. TransRepair:自动测试及修复神经网络翻译模型的不一致性问题
  7. 关于理财年轻人正在跑步入场
  8. 【多输入模型 Multiple-Dimension 数学原理分析以及源码详解 深度学习 Pytorch笔记 B站刘二大人 (6/10)】
  9. [转]数据分析与处理之二:Leveldb 实现原理
  10. 【续】DeepMind项目取得新进展:一种改进机器人学习系统的新思路