redis-缓存设计-搜索前缀匹配
说明
录入:
是将录入字符的String 的各个char 的ASCII码转为16进制 在通过-拼接起来,通过zadd新增 score设置为0 则会通过value 16进制进行排序
查找
将查找的字符转换为16进制通过-拼接
start计算:通过匹配字符16进制最后以为进1算出起始16进制 再+g 包括所有起始范围
end计算:通过匹配字符16进制+g 包括所有范围
然后zadd临时加入到redis 默认通过value排序则将匹配字符大概包裹起来
然后通过2个临时数据获得rank 再根据起始和结zrank获得数据 过滤掉多余的 再讲16进制转换为字符 返回
录入数据
//unicode编码 private String coding(String s) { char[] chars = s.toCharArray(); StringBuffer buffer = new StringBuffer(); for (char aChar : chars) { //通过字符对应的ASCII码转换为16进制再根据-拼接起来 -4f60-7231-5e7f-5dde-30 String s1 = Integer.toString(aChar, 16); buffer.append("-" + s1); } String encoding = buffer.toString(); return encoding; } //添加数据 public long add(Jedis con, String member, String key) { //进行编码 存储的是 每个字符进行ASCII码转换的16进制 String coding = coding(member); //如果score为0 则会根据value来进行排序 Long zadd = con.zadd(key, 0, coding); return zadd; }
匹配数据
/** * unicode解码 字符通过16进制转换为ASCII码 即可得到对应字符 * * @param s * @return */ private String decoding(String s) { String[] split = s.split("-"); StringBuffer buffer = new StringBuffer(); for (String s1 : split) { if (!s1.trim().equals("")) { //将16进制 转换成ASCII码 ASCII码对应的字符 char i = (char) Integer.parseInt(s1, 16); buffer.append(i); } } return buffer.toString(); } /** * 16进制 * 二进制与16进制对应 0-0 1-1 2-2 3-3 4-4 5-5- 6-6 7-7 8-8 9-9 10-a 11-b 12-c 13-d 14-e 15-f 16-g */ private static final String VALID_CHARACTERS = "0123456789abcdefg"; 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 = null; String end = null; //针对于单纯查0的 没有前置了 所以不用拼g if (posn == 0) { start = prefix; end = prefix + "g";//end拼g 把范围内的全查出来 } else { start = prefix.substring(0, prefix.length() - 1) + suffix + 'g'; //生成前缀字符串的前驱字符串 end = prefix + 'g'; // 防止多个群成员可以同时操作有序集合,将相同的前驱字符串和后继字符串插入有序集合//生成前缀字符串的后继字符串 String identifier = UUID.randomUUID().toString(); start += identifier; end += identifier; } return new String[]{start, end}; } //查找数据 public List<String> find(Jedis con, String member, String key) { List<String> list = new ArrayList<>(); member = coding(member);//把输入的字符转换成16进制字符串,因为redis里面存的是每个字符对应的16进制字符串 String[] range = findPrefixRange(member); String start = range[0]; String end = range[1]; //往zset插入起始和结束的字符,将匹配的结果的数据包起来 con.zadd(key, 0, start); con.zadd(key, 0, end); while (true) { con.watch(key); //根据插入包起来的数据rank拿出启始和结束的字符 int sindex = con.zrank(key, start).intValue(); int eindex = con.zrank(key, end).intValue(); //找出两个插入元素的位置 int erange = Math.min(sindex + 9, eindex - 2); //因为最多展示10个,所以计算出结束为止 Transaction transaction = con.multi(); transaction.zrem(key, start); transaction.zrem(key, end); transaction.zrange(key, sindex, erange); List<Object> results = transaction.exec(); if (results != null) { Set<String> set = (Set<String>) results.get(results.size() - 1); list.addAll(set); break; } } ListIterator<String> iterator = list.listIterator(); // 这里过滤多个成员添加前驱字符串和后继字符串引起的不符合的数据 while (iterator.hasNext()) { String string = iterator.next(); if (string.indexOf("g") != -1) { iterator.remove(); } else { iterator.set(decoding(string));//把16进制字符串转换回来 } } return list; }
测试
public static void main(String[] args) throws Exception { Jedis conn = new Jedis("127.0.0.1", 6379); conn.flushDB(); AutoComplete auto_complete = new AutoComplete(); for (int i = 0; i < 1; i++) { auto_complete.add(conn, "你爱广州" + i, "test1"); } auto_complete.add(conn, "我爱广州", "test1"); auto_complete.add(conn, "我爱成都", "test1"); auto_complete.add(conn, "你好啊", "test1"); auto_complete.add(conn, "aaabbb", "test1"); auto_complete.add(conn, "ac", "test1"); auto_complete.add(conn, "01", "test1"); auto_complete.add(conn, "02", "test1"); auto_complete.add(conn, "11", "test1"); List<String> numbers = auto_complete.find(conn, "我爱成", "test1"); System.out.println(JSON.toJSONString(numbers)); }
打印
["我爱成都"]
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!