1.Redis自动补全功能介绍:

​ Redis可以帮我们实现很多种功能,今天这里着重介绍的是Redis的自动补全功能的实现.我们使用有序集合,并score都为0,这样就按元素值的字典序排序.然后我们可以根据排序号的字符,进行添加前缀和后缀的方式,找到我们想要的区间内容.下面介绍一个简单的Zset的排序内容和思路,以便后续的理解:

名称为redis_concat的Zset集合元素如下:

编号 数值 分值
1 a 0
2 ab 0
3 abcd 0
4 abef 0
5 hjk 0
6 dbfgll 0
7 efhuo 0
8 iop 0
9 lkj 0
10 ghu 0

​ 当所有的数值分值为0的时候,Zset会按照字典升序排列,这里我们如果需要查找上面的a,就应该能找出[ a, ab,abcd,abef]这四个元素,查找上面的ab,就应该能找出[ab,abcd,abef]这三个元素,其他同理.这个时候我们只要想办法在这个搜索条件查找元素的前面后最后都筛选出想要的数据即可:

  • Ascii码里小写字母a的前面是`,z的后面是{
  • 于是我们查找ab匹配的元素,插入 aa{ 和 ab{ 即可( 或者" ab` "和" ab{ " )
  • 找到aa{ 和 ab{ 的下标,通过Zrange()得出相关区间的内容
  • 如果是中文,建议全部将支付转为16进制字符来进行存储,取出时候再转码

2.相关Demo分享

​ 基于此本人建立了一个前后端分离的利用Redis自动补全联系人姓名的项目,前端采用的是Vue,后端采用Java的Spring框架,这个示例功能单一,有好的建议和想法都可以给我留言评论,多加以改进,另外项目GitHub地址在文末,喜欢请关注.下面是项目的简单演示:

项目结构如下:
  1. ├─src
  2. └─main
  3. ├─java
  4. └─com
  5. └─home
  6. ├─config
  7. ├─constants
  8. ├─controller
  9. ├─mapper
  10. ├─page
  11. ├─pojo
  12. └─service
  13. └─impl
  14. ├─resources
  15. ├─mapper
  16. └─properties
  17. └─webapp
  18. └─WEB-INF
  19. └─views
  20. └─vue
  21. └─target
  22. ├─classes
  23. ├─com
  24. └─home
  25. ├─config
  26. ├─constants
  27. ├─controller
  28. ├─mapper
  29. ├─page
  30. ├─pojo
  31. └─service
  32. └─impl
  33. ├─mapper
  34. └─properties
  35. ├─generated-sources
  36. └─annotations
  37. ├─qfang-agent-online-mass-client
  38. ├─META-INF
  39. └─WEB-INF
  40. ├─classes
  41. ├─com
  42. └─home
  43. ├─controller
  44. ├─mapper
  45. ├─pojo
  46. └─service
  47. └─impl
  48. └─mapper
  49. └─lib
  50. └─redis-web-1.0-SNAPSHOT
  51. ├─META-INF
  52. └─WEB-INF
  53. ├─classes
  54. ├─com
  55. └─home
  56. ├─config
  57. ├─constants
  58. ├─controller
  59. ├─mapper
  60. ├─page
  61. ├─pojo
  62. └─service
  63. └─impl
  64. ├─mapper
  65. └─properties
  66. ├─lib
  67. └─views
Vue的构建步骤:
  1. # install dependencies
  2. npm install
  3. # serve with hot reload at localhost:8080
  4. npm run dev
  5. # build for production with minification
  6. npm run build
  7. # build for production and view the bundle analyzer report
  8. npm run build --report
  9. # run unit tests
  10. npm run unit
  11. # run e2e tests
  12. npm run e2e
  13. # run all tests
  14. npm test
Java_Service中相关的方法:
  • 1.分页获取前100条数据,如果Redis中不存该联系人在就放入redis中
  • 2.放入前使用 unicode编码,位于coding方法中,取出相关的数据后记得使用decoding方法解码
  • 3.获得相关数据后删除放入的前缀和后缀,这里都加了UUID,防止有相同的查询带有前后缀的数据被误删(如查找 ab ,数据中本身就含有 ab{ 等)
  • 4.获得前5条或者前10条相关匹配的数据给前台(这里自定义即可,查看注释地方)
相关类详情:
  1. package com.home.service.impl;
  2. import com.home.constants.RedisExpireUtil;
  3. import com.home.mapper.ContactMapper;
  4. import com.home.page.PageObject;
  5. import com.home.page.PageResult;
  6. import com.home.pojo.Contact;
  7. import com.home.service.ContactService;
  8. import org.apache.commons.lang3.StringUtils;
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.stereotype.Service;
  11. import redis.clients.jedis.Jedis;
  12. import redis.clients.jedis.JedisPool;
  13. import java.util.*;
  14. /**
  15. * 改造为中英文皆可匹配
  16. * 这里全部转为16进制存入到redis中
  17. */
  18. @Service
  19. public class ContactServiceImpl implements ContactService {
  20. @Autowired
  21. private ContactMapper contactMapper;
  22. @Autowired
  23. private JedisPool jedisPool;
  24. private static final String REDIS_CONCAT = "redis_concat";
  25. private static final String VALID_CHARACTERS = "0123456789abcdefg";
  26. @Override
  27. public Contact selectByPrimaryKey(int i) {
  28. return contactMapper.selectByPrimaryKey(i);
  29. }
  30. /**
  31. * 获取相应的条数的数据
  32. */
  33. @Override
  34. public PageResult getDateSplitByNum(PageObject pageObject) {
  35. // 获取总条数
  36. int totalCount = contactMapper.getAllCount();
  37. List<Contact> contactList = contactMapper.selectByPageObject(pageObject);
  38. return new PageResult(contactList, totalCount, pageObject);
  39. }
  40. @Override
  41. public List<String> getRelatedWord(String name) {
  42. if (StringUtils.isBlank(name)) {
  43. return null;
  44. }
  45. Jedis jedis = jedisPool.getResource();
  46. if (jedis.exists(REDIS_CONCAT)) {
  47. // 拼接字段
  48. String[] prefixRange = findPrefixRange(coding(name));
  49. // 放入到redis中
  50. List<String> strFinds = putIntoRedisAndFind(prefixRange);
  51. return strFinds;
  52. } else {
  53. // 从数据库放入
  54. transDBToRedis(1, 100);
  55. // 拼接字段
  56. String[] prefixRange = findPrefixRange(name);
  57. // 放入到redis中
  58. List<String> strFinds = putIntoRedisAndFind(prefixRange);
  59. return strFinds;
  60. }
  61. }
  62. /**
  63. * 把数据放入到redis
  64. *
  65. * @return
  66. */
  67. public void transDBToRedis(int currentPage, int pageSize) {
  68. Jedis jedis = jedisPool.getResource();
  69. PageObject po = new PageObject();
  70. po.setCurrentPage(currentPage);
  71. po.setPageSize(pageSize);
  72. // 1.取出前100条数据
  73. PageResult pageResult = getDateSplitByNum(po);
  74. List<Contact> contactList = (List<Contact>) pageResult.getData();
  75. // 2.放入前100条数据到redis
  76. for (Contact contact : contactList) {
  77. String contactName = coding(contact.getContactName());
  78. if (jedis.zrank(REDIS_CONCAT, contactName) == null) {
  79. jedis.zadd(REDIS_CONCAT, 0D, contactName);
  80. }
  81. }
  82. if (jedis.ttl(REDIS_CONCAT).intValue() == RedisExpireUtil.NEVER_EXPIRE) {
  83. // 放入redis有效期一小时
  84. jedis.expire(REDIS_CONCAT, RedisExpireUtil.ONE_HOUR);
  85. }
  86. }
  87. /**
  88. * 将相关的参数放入redis中
  89. *
  90. * @param prefixRange
  91. */
  92. private List<String> putIntoRedisAndFind(String[] prefixRange) {
  93. Jedis jedis = jedisPool.getResource();
  94. String uuid = UUID.randomUUID().toString().replaceAll("-", "");
  95. List<String> list = new ArrayList();
  96. try {
  97. jedis.watch(REDIS_CONCAT);
  98. String start = prefixRange[0] + uuid;
  99. String end = prefixRange[1] + uuid;
  100. // 1.放入redis
  101. jedis.zadd(REDIS_CONCAT, 0, start);
  102. jedis.zadd(REDIS_CONCAT, 0, end);
  103. // 2.得到索引的位置
  104. int begin_index = jedis.zrank(REDIS_CONCAT, start).intValue();
  105. int end_index = jedis.zrank(REDIS_CONCAT, end).intValue();
  106. // 3.删除这两个放入的值
  107. jedis.zrem(REDIS_CONCAT, start);
  108. jedis.zrem(REDIS_CONCAT, end);
  109. // 3.因为最多展示5个,所以计算出结束为止
  110. int erange = Math.min(begin_index + 4, end_index - 2);
  111. if(begin_index>erange){
  112. return null;
  113. }
  114. // 4.获得其中的值
  115. Set<String> zrange = jedis.zrange(REDIS_CONCAT, begin_index, erange);
  116. if (zrange == null) {
  117. return null;
  118. }
  119. list.addAll(zrange);
  120. ListIterator<String> it = list.listIterator();
  121. while (it.hasNext()) {
  122. String next = it.next();
  123. if (next.indexOf("g") != -1) {
  124. it.remove();
  125. } else {
  126. it.set(decoding(next));//把16进制字符串转换回来
  127. }
  128. }
  129. } catch (Exception e) {
  130. e.printStackTrace();
  131. } finally {
  132. jedis.unwatch();
  133. }
  134. return list;
  135. }
  136. // 这个方法仅仅适用于匹配英文字符
  137. // public String[] findPrefixRange(String prefix) {
  138. // String start = prefix + '`';
  139. // String end = prefix + '{';
  140. // System.out.println(prefix + " " + start + "---" + end);
  141. // return new String[]{start, end};
  142. // }
  143. //unicode编码
  144. private String coding(String s) {
  145. char[] chars = s.toCharArray();
  146. StringBuffer buffer = new StringBuffer();
  147. for (char aChar : chars) {
  148. String s1 = Integer.toString(aChar, 16);
  149. buffer.append("-" + s1);
  150. }
  151. String encoding = buffer.toString();
  152. return encoding;
  153. }
  154. //unicode解码
  155. private String decoding(String s) {
  156. String[] split = s.split("-");
  157. StringBuffer buffer = new StringBuffer();
  158. for (String s1 : split) {
  159. if (!s1.trim().equals("")) {
  160. char i = (char) Integer.parseInt(s1, 16);
  161. buffer.append(i);
  162. }
  163. }
  164. return buffer.toString();
  165. }
  166. private String[] findPrefixRange(String prefix) {
  167. //查找出前缀字符串最后一个字符在列表中的位置
  168. int posn = VALID_CHARACTERS.indexOf(prefix.charAt(prefix.length() - 1));
  169. //找出前驱字符
  170. char suffix = VALID_CHARACTERS.charAt(posn > 0 ? posn - 1 : 0);
  171. //生成前缀字符串的前驱字符串
  172. String start = prefix.substring(0, prefix.length() - 1) + suffix + 'g';
  173. //生成前缀字符串的后继字符串
  174. String end = prefix + 'g';
  175. return new String[]{start, end};
  176. }
  177. }

3.项目git地址

(喜欢记得点星支持哦,谢谢!)

https://github.com/fengcharly/redis-auto-complete

使用Redis实现中英文自动补全功能详解的更多相关文章

  1. gocode+auto-complete搭建emacs的go语言自动补全功能

    上篇随笔记录了在emacs中使用go-mode和goflymake搭建了go语言的简单编程环境(推送门),今天来记录一下使用gocode+auto-complete配置emacs中go语言的自动补全功 ...

  2. notepad++代码自动补全功能

    可以代码自动补全功能,默认他是没有开启这个功能的,在首选项->备份与自动完成 里面有自动完成这一个设置,可以设置单词补全,也可以设置函数补全,这样写代码就快多了

  3. Eclipse自动补全功能和自动生成作者、日期注释等功能设置

    修改作者.日期注释格式:打开Windows->Preferences->Java->Code Style->Code Templates,点击右边窗口中的Comments,可以 ...

  4. Eclipse自动补全功能轻松设置 || 不需要修改编辑任何文件

    本文介绍如何设置Eclipse代码自动补全功能.轻松实现输入任意字母均可出现代码补全提示框.   Eclipse代码自动补全功能默认只包括 点"."  ,即只有输入”." ...

  5. 【Qt编程】基于Qt的词典开发系列<十四>自动补全功能

    最近写了一个查单词的类似有道词典的软件,里面就有一个自动补全功能(即当你输入一个字母时,就会出现几个候选项).这个自动补全功能十分常见,百度搜索关键词时就会出现.不过它们这些补全功能都是与你输入的进行 ...

  6. Eclipse使用技巧 - 2. Eclipse自动补全功能轻松设置

    本文介绍如何设置Eclipse代码自动补全功能.轻松实现输入任意字母均可出现代码补全提示框. Eclipse代码自动补全功能默认只包括 点”.” ,即只有输入”.”后才出现自动补全的提示框.想要自动补 ...

  7. 如何为 .NET Core CLI 启用 TAB 自动补全功能

    如何为 .NET Core CLI 启用 TAB 自动补全功能 Intro 在 Linux 下经常可以发现有些目录/文件名,以及有些工具可以命令输入几个字母之后按 TAB 自动补全,最近发现其实 do ...

  8. jquery的输入框自动补全功能+ajax

    jquery的输入框自动补全功能+ajax 2017年05月10日 18:51:39 辣姐什么鬼 阅读数:1461 标签: web前端 更多 个人分类: web前端   内容参考网友文章写成,原博的链 ...

  9. 解决VS Code开发Python3语言自动补全功能不带括号的问题

    Visual Studio Code(以下简称VS Code)用来开发Python3,还是很便利的,本身这个IDE就是轻量级的,才几十兆大小,通过安装插件的方式支持各种语言的开发.界面也美美哒,可以在 ...

随机推荐

  1. element-ui Rate组件源码分析整理笔记(十三)

    Rate组件源码比较简单,有添加部分注释 main.vue <template> <!--valuenow当前的评分 valuetext当前显示的文本--> <div c ...

  2. MQTT实战2 - 使用MQTTnet实现mqtt通信

    MQTT实战1 - 使用Apache Apollo代理服务器实现mqtt通信 MQTT实战2 - 使用MQTTnet实现mqtt通信 源码下载 -> 提取码  QQ:505645074 MQTT ...

  3. Linux Tools 之 iostat 工具总结

    iostat是Linux中被用来监控系统的I/O设备活动情况的工具,是input/output statistics的缩写.它可以生成三种类型的报告: CPU利用率报告 设备利用率报告 网络文件系统报 ...

  4. Centos7安装宝塔控制面板

    目录 宝塔面板安装和使用图文教程 1,通过ssh工具登录服务器 2,安装宝塔面板 2,登录宝塔面板 3,设置宝塔面板 3.1,首先我们进入面板设置 3.2,更改面板端口 3.3,绑定域名 3.4,绑定 ...

  5. [Python]使用生成器来简化代码

    原本只是大概知道生成器是什么,但一直不知道怎么用,或是什么情景下用,后来才发现: 在需要一边读数据一边处理任务时,如果直接为每个任务都写一个函数,那么读数据的部分就要在每个函数都重复一遍 直接将所有任 ...

  6. P4677 山区建小学|区间dp

    P4677 山区建小学 题目描述 政府在某山区修建了一条道路,恰好穿越总共nn个村庄的每个村庄一次,没有回路或交叉,任意两个村庄只能通过这条路来往.已知任意两个相邻的村庄之间的距离为di 为了提高山区 ...

  7. Celery详解(2)

    除了redis,还可以使用另外一个神器----Celery.Celery是一个异步任务的调度工具. Celery是Distributed Task Queue,分布式任务队列,分布式决定了可以有多个w ...

  8. 201871010102-常龙龙《面向对象程序设计(java)》第十周学习总结

    项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 https://www.cnblogs.com/nwnu-daizh/p ...

  9. LeetCode 154. Find Minimum in Rotated Sorted Array II寻找旋转排序数组中的最小值 II (C++)

    题目: Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand. ( ...

  10. 树莓派跑yolo

    https://blog.csdn.net/u011304078/article/details/85772764 https://blog.csdn.net/weixin_41665225/arti ...