Redis实现自动补全

本文可作为redis in action第六章的读书笔记

首先,数据库里有 abc,abks,pskm,aspqbmc,而自动补全,至少有两种:

1 前缀补全
  例如我输入'ab',给我返回abc与abks
2 随机补全
  例如我输入'm p'给我返回pskm,aspqbmc


前缀补全

ok,咱们先说这个前缀补全
如果数据量不大的话,java的String类型有startWith方法,直接遍历调用startWith方法即可
如果数据量大的话,怎么办?
使用redis,zset
具体怎么办呢?
首先我们知道zset里面存放的是member-score的值对,默认score从小到大排序,如果score相等,那么安装member排序。
然后我们把abc,abks,pskm,aspqbmc放入zset,score都是0
那么zset里的排序情况如下:
abc------0
abks-----0
aspqbmc--0
pskm-----0
我们要匹配abk,如果我们能知道所有能匹配abk的member在zset里的起始位置,那就好办了。
下面的问题就是我怎么知道能匹配abk的字符都有什么,都在zset的什么位置呢?
我们知道在ascii码里小写字母a的前面是',z的后面是{
那么能匹配abk的字符串肯定就在abj{和abk{之间
能匹配abka的字符就肯定在abk`{和abka{之间
那剩下的就简单了,我们吧起始字符串都插入zset,然后获得startIndex和endIndex,再从startIndex和endIndex中取出所有的member,这不就是最后的答案么?当然,最后还得再从库里删除插入的那两个字符串
具体代码
    public 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 + '{';
        String end = prefix + '{';
        System.out.println(prefix+"  "+start+"---"+end);
        return new String[]{start, end};
    }
如果prefix给定abka,那么上面的方法就打印出abka  abk`{---abka{
 
    然后开始查找
     @SuppressWarnings("unchecked")
    public Set<String> autocompleteOnPrefix(Jedis conn, String guild, String prefix) {
        String[] range = findPrefixRange(prefix);
        String start = range[0];
        String end = range[1];
        
        String identifier = UUID.randomUUID().toString();
        start += identifier; //1加这个东西 干什么?
        end += identifier;
        String zsetName = "members:" + guild;


        conn.zadd(zsetName, 0, start);
        conn.zadd(zsetName, 0, end);
        


        Set<String> items = null;
        while (true){   //2为什么要循环?
            conn.watch(zsetName);   //3为什么要watch
            int sindex = conn.zrank(zsetName, start).intValue();
            int eindex = conn.zrank(zsetName, end).intValue();
            int erange = Math.min(sindex + 9, eindex - 2);//一次只找出10个


            Transaction trans = conn.multi();
            trans.zrem(zsetName, start);
            trans.zrem(zsetName, end);
            trans.zrange(zsetName, sindex, erange);
            List<Object> results = trans.exec();
            
            if (results != null){
                items = (Set<String>)results.get(results.size() - 1);
                break; //4为什么要退出?
            }
        }


	//剔除开始加入的那两个标记
        for (Iterator<String> iterator = items.iterator(); iterator.hasNext(); ){
            if (iterator.next().indexOf('{') != -1){
                iterator.remove();
            }
        }
        return items;
    }
读到上面的代码,肯定会有几个问题
咱们一个一个说
标识1的前面,就是获得那两个起始与结尾的字符串,很容易理解
那为什么要给后面加一个uuid呢?
如果得到的一个起始字符串是abk{,但是我们的数据库里本身就有一个abk{,最后怎么删除呀?
表示3 那边为什么要加watch呀
这个还用说么,我看的时候,你就别看了
关于redis的事务问题,大家看http://www.cnblogs.com/huangxincheng/archive/2015/11/24/4991096.html
标识2 while循环,你明白表示3就明白标识2了,如果我查的时候,有别的客户端也在查,那我就退出重来么
表示4 退出 好理解


在负载很大的情况下,watch那边就会经常重试,下一节,我们试着用锁来替换watch
另外如果我要支持汉字怎么办?用空代替',用编码支持的最大值来代替{就OK了。


随机补全

这位大哥写的很好,我喜欢

http://blog.csdn.net/u011250882/article/details/47301467


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值