Redis实现AutoComplete

使用redis的有序集合实现前缀的autocomplete。
1.首先把数据存入redis有续集,
原文件格式如下:


存入redis中格式,完整的名字有*结尾:

11609) "y"
11610) "ya"
11611) "yal"
11612) "yalo"
11613) "yalon"
11614) "yalond"
11615) "yalonda*"
11616) "yas"
11617) "yasm"
11618) "yasme"
11619) "yasmee"
11620) "yasmeen*"
11621) "yasmi"
11622) "yasmin*"
11623) "ye"
11624) "yel"
11625) "yele"
11626) "yelen"
11627) "yelena*"
11628) "yet"
11629) "yett"
11630) "yetta*"
11631) "yetti"
11632) "yettie*"
11633) "yetty*"

2.根据输入的前缀,设置开始查找的起始位置

127.0.0.1:6379> zrank test y
(integer) 11608
127.0.0.1:6379> 

如y开头,从11608开始查找。

3.从起始位置分批(循环)向后查询(zrange),将符合前缀且*结尾的词放入返回列表,直至当列表size大于所需要的数量(maxNeeded)或不符合所输入的前缀时,返回列表。
用jedis实现:

package com.zzhblh.sample;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Pipeline;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

public class testJedisAutocomplete {

    private final Jedis redis;
    private final Pipeline p;
    private final String redisKey;
    private long start,end;

    testJedisAutocomplete(final Jedis redis, final Pipeline p, final String redisKey, final URL dictionaryUrl, final boolean populate,
                 final boolean clear) throws IOException {
        this.redis = redis;
        this.p = p;
        this.redisKey = redisKey;

        if(clear) {
            // Truncate existing Dictionary
            clearDictionary();
        }
        if(populate) {
            start = System.currentTimeMillis();
            // Load the entries from dictionaryUrl
            loadDictionary(dictionaryUrl);
            end = System.currentTimeMillis();
            System.out.println("add with pipeline used [" + (end - start) / 1000 + "] seconds ..");
            p.close();
        }
    }

    public List<String> complete(final String prefix, final int count) {
        // 1. Find the position of the prefix in the sorted set in Redis
        if(null == prefix) {
            return Collections.emptyList();
        }
        int prefixLength = prefix.length();
        int start = redis.zrank(redisKey, prefix).intValue();
        if(start < 0 || prefixLength == 0) {
            return Collections.emptyList();
        }
        List<String> results = new ArrayList<String>();
        int found = 0, rangeLength = 50, maxNeeded = count;
        while(found < maxNeeded) {
            Set<String> rangeResults = redis.zrange(redisKey, start, start + rangeLength - 1);
            start += rangeLength;
            if(rangeResults.isEmpty()) {
                break;
            }
            for(final String entry : rangeResults) {
                int minLength = Math.min(entry.length(), prefixLength);
                if(!entry.substring(0, minLength).equalsIgnoreCase(prefix.substring(0, minLength))) {
                    return results;
                }
                if(entry.endsWith("*") && found < maxNeeded) {
                    results.add(entry.substring(0, entry.length() - 1));
                    found = results.size();
                }
            }
        }
        return results;
    }

    private void clearDictionary() {
        redis.del(redisKey);
    }


    private void loadDictionary(final URL dictionaryUrl) throws IOException {
        InputStreamReader inputStreamReader = new InputStreamReader(dictionaryUrl.openStream());
        BufferedReader reader = new BufferedReader(inputStreamReader);
        String word;
        while((word = reader.readLine()) != null) {
            word = word.trim();
            // Add the word if the word does not start with #
            if(!word.isEmpty() && !word.startsWith("#")) {
                addWord(word);
            }
        }
        reader.close();
        inputStreamReader.close();
    }

    private void addWord(final String word) {
        // Add all the possible prefixes of the given word and also the given
        // word with a * suffix.
        p.zadd(redisKey, 0, word + "*");
        for(int index = 1, total = word.length(); index < total; index++) {
            p.zadd(redisKey, 0, word.substring(0, index));
        }

    }

    public static class Builder {

        private final Jedis redis;
        private final Pipeline p;
        private final String redisKey;
        private final URL dictionaryUrl;
        private boolean clear = true;
        private boolean populate = true;

        public Builder(final Jedis redis, final  Pipeline p, final String redisKey, final URL dictionaryUrl) {
            this.redis = redis;
            this.p = p;
            this.redisKey = redisKey;
            this.dictionaryUrl = dictionaryUrl;
        }

        public Builder clear(final boolean clear) {
            this.clear = clear;
            this.populate = true;
            return this;
        }

        public Builder populate(final boolean populate) {
            this.populate = populate;
            return this;
        }

        public testJedisAutocomplete build() throws IOException {
            return new testJedisAutocomplete(redis, p, redisKey, dictionaryUrl, populate, clear);
        }

    }

    public static void main(final String[] args) throws Exception {
        JedisPoolConfig config = new JedisPoolConfig();
        JedisPool pool = new JedisPool(config, "107.170.248.158", 6379);
        Jedis redis = pool.getResource();
        redis.auth("123");
        Pipeline p = redis.pipelined();
        URL url = new URL("http://antirez.com/misc/female-names.txt");

        testJedisAutocomplete autoComplete = new testJedisAutocomplete.Builder(redis, p, "test", url).clear(true).populate(true).build();
        System.out.println(autoComplete.complete("y", 80));
    }

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • 本文将从Redis的基本特性入手,通过讲述Redis的数据结构和主要命令对Redis的基本能力进行直观介绍。之后概...
    kelgon阅读 61,116评论 24 626
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,591评论 18 139
  • 转载:Redis 宝典 | 基础、高级特性与性能调优 本文由 DevOpsDays 本文由简书作者kelgon供稿...
    meng_philip123阅读 3,112评论 1 34
  • 生活远比自己想象的要精彩,未来也充满了惊喜与意外,这都是我们无法琢磨透的…… 休学后的我在外地一家餐厅打工,那...
    旅玲阅读 205评论 0 0
  • 时间过得很快,一转眼的时间寒假结束了,报名的拓展写作班也进入了尾声。 能够报这个班还蛮幸运的,因为从前的我就知道些...
    shakalaka都被用了阅读 189评论 0 0