基於Redis實現聯想查找自動補全
-
本文的自動補全只指最前匹配
-
常用的方案有哪些?
- 利用數據庫的模塊匹配來做,利如mysql的like %這種方式來完成,雖然最前匹配能保證用到索引,但是效率不高。
- 利用搜索引擎,比如elasticsearch,sphinx 一般都用此方案
- 通過redis的有序集合來實現(本文)
一 補全原理
-
拆分詞,加入到有序集合,注意添加到redis時score都設置爲0,這些字符就會按照自然排序排好。
zadd demo 0 內容
-
利用zrank命令,定位關鍵字的位置索引,然後通過索引來獲取所有以關鍵字開頭的集合
zrank demo 關鍵字
-
通過zrange獲取數據
zrange demo 1 -1
二基於java實現
-
因爲redis中不能存中文,所以需要一個轉換方案,從新編解碼,爲了方便解碼我們把字符用“-”隔開,
//unicode編碼 private String coding(String s) { if (s==null){ return ""; } 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) { if (s==null){ return ""; } 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(); }
-
確定轉換過的順序,因爲16進制和連接符我們確定好順序字符標準串,然後拿前綴字符
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}; }
-
實現redis查找命令
@Autowired private IGlobalCache globalCache; @Autowired RedisTemplate redisTemplate; private static final String REDIS_SALE_CUSTOMER = "redis_customer"; private static final String VALID_CHARACTERS = "-0123456789abcdefg"; public List<String> getRelateCustomerWord(String name) { if (StringUtils.isBlank(name)) { return null; } if (globalCache.hasKey(REDIS_SALE_CUSTOMER)) { // 拼接字段 String[] prefixRange = findPrefixRange(coding(name)); // 放入到redis中 List<String> strFinds = autoFind(prefixRange); return strFinds; } } private List<String> autoFind(String[] prefixRange) { List<String> list = new ArrayList<>(); try { String uuid = UUID.randomUUID().toString().replaceAll("-", ""); // 防止多個羣成員可以同時操作有序集合,將相同的前驅字符串和後繼字符串插入有序集合 String start = prefixRange[0] + uuid; String end = prefixRange[1] + uuid; // 1.放入redis redisTemplate.opsForZSet().add(REDIS_SALE_CUSTOMER, start, 0); redisTemplate.opsForZSet().add(REDIS_SALE_CUSTOMER, end, 0); // 2.得到索引的位置 int begin_index = redisTemplate.opsForZSet().rank(REDIS_SALE_CUSTOMER, start).intValue(); int end_index = redisTemplate.opsForZSet().rank(REDIS_SALE_CUSTOMER, end).intValue(); // 3.因爲最多展示10個,所以計算出結束爲止 int erange = Math.min(begin_index + 9, end_index - 2); // 3.刪除這兩個放入的值 redisTemplate.opsForZSet().remove(REDIS_SALE_CUSTOMER, start); redisTemplate.opsForZSet().remove(REDIS_SALE_CUSTOMER, end); // 4.獲得其中的值 Set<String> zrange = redisTemplate.opsForZSet().range(REDIS_SALE_CUSTOMER, begin_index, erange); list.addAll(zrange); ListIterator<String> it = list.listIterator(); while (it.hasNext()) { String next = it.next(); if (next.indexOf("g") != -1) { it.remove(); } else { //把16進制字符串轉換回來 it.set(decoding(next)); } } } catch (Exception e) { e.printStackTrace(); } return list; }
-
實現redis添加命令
public boolean putCustomerDataToRedis(String keyword) { Boolean add = redisTemplate.opsForZSet().add(REDIS_SALE_CUSTOMER, coding(keyword), 0); return add; }
tips
- redis事務不支持集羣,所以上附代碼沒有添加事務,可以考慮基於java的鎖來實現對key的操作