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实现中英文自动补全功能详解的更多相关文章

  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. 微信小程序 自定义顶部状态栏

    1>项目的结构如下: 2>组件的index.wxml代码如下: <!--没有按钮的情况--> <view class="custom flex_center&q ...

  2. JS初始

      简单只和复杂值的区别 1.简单值 简单值表示JS中可用的数据或信息的最底层简单形式. 注:简单之不可被细化. 也就是说,数字是数字,字符是字符,布尔值是true或false,null和undefi ...

  3. Rust中的结构体及方法语法

    这个可以和类作比较,或是go当中的方法比较. #[derive(Debug)] struct Rectangle { width: u32, height: u32, } impl Rectangle ...

  4. 2019第一期《python测试开发》课程,10月13号开学

    2019第一期<python测试开发>课程,10月13号开学! 主讲老师:上海-悠悠 上课方式:QQ群视频在线教学,方便交流 本期上课时间:10月13号-12月8号,每周六.周日晚上20: ...

  5. flask 基础2

    一.装饰器的坑 在使用装饰器函数时候,当一个装饰器装饰多个函数的时候,会由于内存地址相同时发生报错,因为装饰的都是一个函数 所以就需要引入 import functools  重新定义每一个函数的名称 ...

  6. 2015年第六届蓝桥杯C/C++程序设计本科B组决赛 ——居民集会(编程大题)

    标题:居民集会 蓝桥村的居民都生活在一条公路的边上,公路的长度为L,每户家庭的 位置都用这户家庭到公路的起点的距离来计算,第i户家庭距起点的距 离为di.每年,蓝桥村都要举行一次集会.今年,由于村里的 ...

  7. zz2019年主动学习有哪些进展?答案在这三篇论文里

    2019年主动学习有哪些进展?答案在这三篇论文里 目前推广应用的机器学习方法或模型主要解决分类问题,即给定一组数据(文本.图像.视频等),判断数据类别或将同类数据归类等,训练过程依赖于已标注类别的训练 ...

  8. CF13B Letter A

    CF13B Letter A 洛谷传送门 题目描述 Little Petya learns how to write. The teacher gave pupils the task to writ ...

  9. Media Formatters(媒体格式化器)

    6.1.1 Internet的媒体类型 媒体类型,也叫做MIME类型,标识了数据的格式.在HTTP中,媒体类型描述了消息体的格式.一个媒体类型由两个字符串组成:类型和子类型.例如: text/html ...

  10. TensorFlow基础篇

    Tensor(张量)意味着N维数组,Flow(流)意味着基于数据流图的计算.TensorFlow的运行机制属于“定义”和“运行”相分离.模型的构建只是相当于定义了一个图结构(代表一个计算任务),图中有 ...