使用Redis实现中英文自动补全功能详解
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地址在文末,喜欢请关注.下面是项目的简单演示:
项目结构如下:
├─src
│ └─main
│ ├─java
│ │ └─com
│ │ └─home
│ │ ├─config
│ │ ├─constants
│ │ ├─controller
│ │ ├─mapper
│ │ ├─page
│ │ ├─pojo
│ │ └─service
│ │ └─impl
│ ├─resources
│ │ ├─mapper
│ │ └─properties
│ └─webapp
│ └─WEB-INF
│ └─views
│ └─vue
└─target
├─classes
│ ├─com
│ │ └─home
│ │ ├─config
│ │ ├─constants
│ │ ├─controller
│ │ ├─mapper
│ │ ├─page
│ │ ├─pojo
│ │ └─service
│ │ └─impl
│ ├─mapper
│ └─properties
├─generated-sources
│ └─annotations
├─qfang-agent-online-mass-client
│ ├─META-INF
│ └─WEB-INF
│ ├─classes
│ │ ├─com
│ │ │ └─home
│ │ │ ├─controller
│ │ │ ├─mapper
│ │ │ ├─pojo
│ │ │ └─service
│ │ │ └─impl
│ │ └─mapper
│ └─lib
└─redis-web-1.0-SNAPSHOT
├─META-INF
└─WEB-INF
├─classes
│ ├─com
│ │ └─home
│ │ ├─config
│ │ ├─constants
│ │ ├─controller
│ │ ├─mapper
│ │ ├─page
│ │ ├─pojo
│ │ └─service
│ │ └─impl
│ ├─mapper
│ └─properties
├─lib
└─views
Vue的构建步骤:
# install dependencies
npm install
# serve with hot reload at localhost:8080
npm run dev
# build for production with minification
npm run build
# build for production and view the bundle analyzer report
npm run build --report
# run unit tests
npm run unit
# run e2e tests
npm run e2e
# run all tests
npm test
Java_Service中相关的方法:
- 1.分页获取前100条数据,如果Redis中不存该联系人在就放入redis中
- 2.放入前使用 unicode编码,位于coding方法中,取出相关的数据后记得使用decoding方法解码
- 3.获得相关数据后删除放入的前缀和后缀,这里都加了UUID,防止有相同的查询带有前后缀的数据被误删(如查找 ab ,数据中本身就含有 ab{ 等)
- 4.获得前5条或者前10条相关匹配的数据给前台(这里自定义即可,查看注释地方)
相关类详情:
package com.home.service.impl;
import com.home.constants.RedisExpireUtil;
import com.home.mapper.ContactMapper;
import com.home.page.PageObject;
import com.home.page.PageResult;
import com.home.pojo.Contact;
import com.home.service.ContactService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.*;
/**
* 改造为中英文皆可匹配
* 这里全部转为16进制存入到redis中
*/
@Service
public class ContactServiceImpl implements ContactService {
@Autowired
private ContactMapper contactMapper;
@Autowired
private JedisPool jedisPool;
private static final String REDIS_CONCAT = "redis_concat";
private static final String VALID_CHARACTERS = "0123456789abcdefg";
@Override
public Contact selectByPrimaryKey(int i) {
return contactMapper.selectByPrimaryKey(i);
}
/**
* 获取相应的条数的数据
*/
@Override
public PageResult getDateSplitByNum(PageObject pageObject) {
// 获取总条数
int totalCount = contactMapper.getAllCount();
List<Contact> contactList = contactMapper.selectByPageObject(pageObject);
return new PageResult(contactList, totalCount, pageObject);
}
@Override
public List<String> getRelatedWord(String name) {
if (StringUtils.isBlank(name)) {
return null;
}
Jedis jedis = jedisPool.getResource();
if (jedis.exists(REDIS_CONCAT)) {
// 拼接字段
String[] prefixRange = findPrefixRange(coding(name));
// 放入到redis中
List<String> strFinds = putIntoRedisAndFind(prefixRange);
return strFinds;
} else {
// 从数据库放入
transDBToRedis(1, 100);
// 拼接字段
String[] prefixRange = findPrefixRange(name);
// 放入到redis中
List<String> strFinds = putIntoRedisAndFind(prefixRange);
return strFinds;
}
}
/**
* 把数据放入到redis
*
* @return
*/
public void transDBToRedis(int currentPage, int pageSize) {
Jedis jedis = jedisPool.getResource();
PageObject po = new PageObject();
po.setCurrentPage(currentPage);
po.setPageSize(pageSize);
// 1.取出前100条数据
PageResult pageResult = getDateSplitByNum(po);
List<Contact> contactList = (List<Contact>) pageResult.getData();
// 2.放入前100条数据到redis
for (Contact contact : contactList) {
String contactName = coding(contact.getContactName());
if (jedis.zrank(REDIS_CONCAT, contactName) == null) {
jedis.zadd(REDIS_CONCAT, 0D, contactName);
}
}
if (jedis.ttl(REDIS_CONCAT).intValue() == RedisExpireUtil.NEVER_EXPIRE) {
// 放入redis有效期一小时
jedis.expire(REDIS_CONCAT, RedisExpireUtil.ONE_HOUR);
}
}
/**
* 将相关的参数放入redis中
*
* @param prefixRange
*/
private List<String> putIntoRedisAndFind(String[] prefixRange) {
Jedis jedis = jedisPool.getResource();
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
List<String> list = new ArrayList();
try {
jedis.watch(REDIS_CONCAT);
String start = prefixRange[0] + uuid;
String end = prefixRange[1] + uuid;
// 1.放入redis
jedis.zadd(REDIS_CONCAT, 0, start);
jedis.zadd(REDIS_CONCAT, 0, end);
// 2.得到索引的位置
int begin_index = jedis.zrank(REDIS_CONCAT, start).intValue();
int end_index = jedis.zrank(REDIS_CONCAT, end).intValue();
// 3.删除这两个放入的值
jedis.zrem(REDIS_CONCAT, start);
jedis.zrem(REDIS_CONCAT, end);
// 3.因为最多展示5个,所以计算出结束为止
int erange = Math.min(begin_index + 4, end_index - 2);
if(begin_index>erange){
return null;
}
// 4.获得其中的值
Set<String> zrange = jedis.zrange(REDIS_CONCAT, begin_index, erange);
if (zrange == null) {
return null;
}
list.addAll(zrange);
ListIterator<String> it = list.listIterator();
while (it.hasNext()) {
String next = it.next();
if (next.indexOf("g") != -1) {
it.remove();
} else {
it.set(decoding(next));//把16进制字符串转换回来
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
jedis.unwatch();
}
return list;
}
// 这个方法仅仅适用于匹配英文字符
// public String[] findPrefixRange(String prefix) {
// String start = prefix + '`';
// String end = prefix + '{';
// System.out.println(prefix + " " + start + "---" + end);
// return new String[]{start, end};
// }
//unicode编码
private String coding(String s) {
char[] chars = s.toCharArray();
StringBuffer buffer = new StringBuffer();
for (char aChar : chars) {
String s1 = Integer.toString(aChar, 16);
buffer.append("-" + s1);
}
String encoding = buffer.toString();
return encoding;
}
//unicode解码
private String decoding(String s) {
String[] split = s.split("-");
StringBuffer buffer = new StringBuffer();
for (String s1 : split) {
if (!s1.trim().equals("")) {
char i = (char) Integer.parseInt(s1, 16);
buffer.append(i);
}
}
return buffer.toString();
}
private String[] findPrefixRange(String prefix) {
//查找出前缀字符串最后一个字符在列表中的位置
int posn = VALID_CHARACTERS.indexOf(prefix.charAt(prefix.length() - 1));
//找出前驱字符
char suffix = VALID_CHARACTERS.charAt(posn > 0 ? posn - 1 : 0);
//生成前缀字符串的前驱字符串
String start = prefix.substring(0, prefix.length() - 1) + suffix + 'g';
//生成前缀字符串的后继字符串
String end = prefix + 'g';
return new String[]{start, end};
}
}
3.项目git地址
(喜欢记得点星支持哦,谢谢!)
https://github.com/fengcharly/redis-auto-complete
使用Redis实现中英文自动补全功能详解的更多相关文章
- gocode+auto-complete搭建emacs的go语言自动补全功能
上篇随笔记录了在emacs中使用go-mode和goflymake搭建了go语言的简单编程环境(推送门),今天来记录一下使用gocode+auto-complete配置emacs中go语言的自动补全功 ...
- notepad++代码自动补全功能
可以代码自动补全功能,默认他是没有开启这个功能的,在首选项->备份与自动完成 里面有自动完成这一个设置,可以设置单词补全,也可以设置函数补全,这样写代码就快多了
- Eclipse自动补全功能和自动生成作者、日期注释等功能设置
修改作者.日期注释格式:打开Windows->Preferences->Java->Code Style->Code Templates,点击右边窗口中的Comments,可以 ...
- Eclipse自动补全功能轻松设置 || 不需要修改编辑任何文件
本文介绍如何设置Eclipse代码自动补全功能.轻松实现输入任意字母均可出现代码补全提示框. Eclipse代码自动补全功能默认只包括 点"." ,即只有输入”." ...
- 【Qt编程】基于Qt的词典开发系列<十四>自动补全功能
最近写了一个查单词的类似有道词典的软件,里面就有一个自动补全功能(即当你输入一个字母时,就会出现几个候选项).这个自动补全功能十分常见,百度搜索关键词时就会出现.不过它们这些补全功能都是与你输入的进行 ...
- Eclipse使用技巧 - 2. Eclipse自动补全功能轻松设置
本文介绍如何设置Eclipse代码自动补全功能.轻松实现输入任意字母均可出现代码补全提示框. Eclipse代码自动补全功能默认只包括 点”.” ,即只有输入”.”后才出现自动补全的提示框.想要自动补 ...
- 如何为 .NET Core CLI 启用 TAB 自动补全功能
如何为 .NET Core CLI 启用 TAB 自动补全功能 Intro 在 Linux 下经常可以发现有些目录/文件名,以及有些工具可以命令输入几个字母之后按 TAB 自动补全,最近发现其实 do ...
- jquery的输入框自动补全功能+ajax
jquery的输入框自动补全功能+ajax 2017年05月10日 18:51:39 辣姐什么鬼 阅读数:1461 标签: web前端 更多 个人分类: web前端 内容参考网友文章写成,原博的链 ...
- 解决VS Code开发Python3语言自动补全功能不带括号的问题
Visual Studio Code(以下简称VS Code)用来开发Python3,还是很便利的,本身这个IDE就是轻量级的,才几十兆大小,通过安装插件的方式支持各种语言的开发.界面也美美哒,可以在 ...
随机推荐
- iOS开发之--为UITextField监听数值变化的三种方法
项目中有个验证码输入直接验证跳转页面,用的RAC来监听textfield的输入值,如下: @weakify(self); [self.codeView.textField.rac_textSignal ...
- MYSQL练习随笔
解法练习 案例1.子查询练习 字段 说明film_id 电影idtitle 电影名称description 电影描述信息category_id 电影分类idname 电影分类名称last_update ...
- 过滤身份证号和grep复习
一.把身份证号过滤出来 我们还是从一道面试题说起. 请从test.txt文件当中过滤出正确的的身份证号码 [root@localhost test.dir]# cat test.txt 赵 37083 ...
- jenkins slave上执行脚本报错
jenkins slave上执行脚本报错 解决方法:在系统配置中设置shell execuate C:\Windows\system32\cmd.exe 保存即可
- shell 之while两种写法
1.while[] #!/bin/dash min= max= while [ $min -le $max ] do echo $min min=`` done 2. while(()) #!/bin ...
- JAVA并发-Condition
简介 在没有Lock之前,我们使用synchronized来控制同步,配合Object的wait().notify()系列方法可以实现等待/通知模式.在Java SE5后,Java提供了Lock接口, ...
- (day48作业)jQuery+Bootstrap练习题
目录 一.图书管理系统页面搭建 二.jQuery练习题 一.图书管理系统页面搭建 <!DOCTYPE html> <html lang="en"> < ...
- VIJOS-P1421 更换轮胎
JDOJ 1506: VIJOS-P1421 更换轮胎 https://neooj.com/oldoj/problem.php?id=1506 Description 经过数周的期待,比赛终于正式开始 ...
- myeclipse开发工具的简单使用
一.使用eclipse.myeclipse开发JAVA程序 将程序开发环境和调试环境集合在一起,提高开发效率 1.创建java项目2.创建程序包3.编写JAVA源程序4.运行JAVA程序 二.程序移植 ...
- requests--发送请求
前戏 使用python做接口自动化,必须用到requests这个库,功能非常的强大,能满足我们大部分的需求. 官网:https://2.python-requests.org//zh_CN/lates ...