redis-快取設計-字首匹配
阿新 • • 發佈:2020-07-24
說明
錄入:
是將錄入字元的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)); }
列印
["我愛成都"]