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));
    }

打印

["我爱成都"]

posted @ 2020-07-24 11:57  意犹未尽  阅读(1411)  评论(0编辑  收藏  举报