n位邀请码生成问题

N位邀请码生成问题

1. 传统方式

采用Java库提供的基本Random类进行处理,考虑如下问题,生成n位邀请码,要求邀请码中的字符为0-9A-Z,这种方式只需要采用Random取n个0-35之间的随机数,然后分别与0-9A-Z进行对应。去重操作可以交给数据库来解决。

优点:
1. 实现简单
2. 对邀请码要求量小的情况下相当适用
缺点:
1. 后期去重十分不方便,极易出现重复情况

2. Redis解决

采用Redis解决邀请码问题,核心思想是采用Redis的位操作,以及Redis基本的哈希表数据结构做了很好的分桶操作。同样是上述问题,使用Redis处理基本思路如下:

  1. 在Redis中存储36的n次方个1bit组成的字符串,如果字符串过长,则对其进行分桶操作,将大value拆成小value。
  2. 采用lua脚本将bitpos和setbit进行结合,组成组合函数,函数的作用是输入x,取出距离x最近的1的index,并将其设置为0。返回index
  3. 使用Random函数先在总的桶中随机选取一个,然后再在桶内调用第二部的lua脚本。
  4. 将第二部的lua脚本中的返回值结合桶的index,计算为邀请码。

大致代码如下:

  1. 构造器初始化数据

    /**
    *@param length 邀请码长度
    *@save_key_prefix redis key的前缀
    */
    public CodeUtil(int length, String save_key_prefix) {
        if (length < MIN_LENGTH) {
            throw new RuntimeException();
        }
        this.save_key_format = save_key_prefix + "_%d";
        this.length = length;
        /* total用于记录邀请码可以出现的所有可能的情况 */
        this.total = new BigDecimal(1);
        for (int i = 0; i < length; i++) {
            this.total = this.total.multiply(BigDecimal.valueOf(CHAR_RESOURCE.length));
        }
        this.bulkLength = this.total.divide(new BigDecimal(BULK_SIZE)).intValue();
    }
    
  2. 在Redis中存储36的n次方个1bit组成的字符串

    /**
         * 初始化数据
         * */
        public void init(String addr, int port) {
            /*初始化Redis数据*/
            jedisPool = new JedisPool(addr, port);
            Jedis jedis = jedisPool.getResource();
            byte[] label = new byte[1024];
            /* Redis的一个key只存放1KB个1 */
            for (int i = 0; i < label.length; i++) {
                label[i] = (byte) 0xFF;
            }
            /* 填充Redis */
            for (int i = 0; i < this.bulkLength; i++) {
                String key = String.format(this.save_key_format, i);
                byte[] data = jedis.get(key.getBytes());
                if (data != null) {
                    continue;
                }
                jedis.set(key.getBytes(), label);
            }
            jedis.close();
        }
    
    
  3. 制作LUA脚本,使获取与设置统一成原子操作

    /* Lua脚本,用于读取并设置位 */
        private static final String script = "local index = redis.call('bitpos',KEYS[1],KEYS[2],KEYS[3]);\n" +
                "if index ~= -1 then\n" +
                "    local result = redis.call('setbit',KEYS[1],index,'0')\n" +
                "end\n" +
                "return index;";
    
  4. 创建code:

    	/**
         * 创建Code
         * */
        public String createCode() {
            Jedis jedis = jedisPool.getResource();
            while (true) {
                /* 先分桶,在获取随机数 */
                int bulkIndex = ThreadLocalRandom.current().nextInt(this.bulkLength);
                /* 再对桶内数据获取随机数 */
                int offset = ThreadLocalRandom.current().nextInt(1024);
                System.out.println("bulkIndex:"+bulkIndex);
                System.out.println("offset:"+offset);
                String key = String.format(this.save_key_format,bulkIndex);
                /* 调用lua脚本获取index*/
                Object result = jedis.eval(script,3,new String[]{key,"1",offset+""});
                if(!result.equals("-1")){
                	/* 根据index转换成code */
                    String code = intToCode(bulkIndex,offset);
                    jedis.close();
                    return code;
                }
            }
    
        }
    
        /**
         * 数字转Code
         * */
        public String intToCode(int bulkIndex, int offset) {
            BigDecimal bigDecimal = new BigDecimal(bulkIndex).multiply(BULK_SIZE_BIGDICIMAL).add(BigDecimal.valueOf(offset));
            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < this.length; i++) {
                BigDecimal[] result = bigDecimal.divideAndRemainder(new BigDecimal(36));
                int index = result[1].intValue();
                bigDecimal = result[0];
                stringBuilder.append(CHAR_RESOURCE[index]);
            }
            return stringBuilder.toString();
        }
    

整体代码如下:

package com.wdkid;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.math.BigDecimal;
import java.util.Scanner;
import java.util.concurrent.ThreadLocalRandom;

public class CodeUtil {

    private final static int BULK_SIZE = 1024 * 8;

    private final static BigDecimal BULK_SIZE_BIGDICIMAL = BigDecimal.valueOf(1024).multiply(BigDecimal.valueOf(8));

    /* 邀请码最小长度 */
    private int MIN_LENGTH = 4;

    /* 邀请码中出现的字符 */
    private static final String[] CHAR_RESOURCE = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
            "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
            "U", "V", "W", "X", "Y", "Z"
    };

    /* redis桶的个数 */
    private int bulkLength;

    /* 指定长度的邀请码最多需要记录的数字的个数即CHAR_RESOURCE.length的length次方 */
    private BigDecimal total;

    /* 邀请码长度 */
    private int length;

    /* 保存邀请码的key的格式 */
    private String save_key_format;

    private JedisPool jedisPool;

    /* Lua脚本,用于读取并设置位 */
    private static final String script = "local index = redis.call('bitpos',KEYS[1],KEYS[2],KEYS[3]);\n" +
            "if index ~= -1 then\n" +
            "    local result = redis.call('setbit',KEYS[1],index,'0')\n" +
            "end\n" +
            "return index;";

    public CodeUtil(int length, String save_key_prefix) {
        if (length < MIN_LENGTH) {
            throw new RuntimeException();
        }
        this.save_key_format = save_key_prefix + "_%d";
        this.length = length;
        this.total = new BigDecimal(1);
        for (int i = 0; i < length; i++) {
            this.total = this.total.multiply(BigDecimal.valueOf(CHAR_RESOURCE.length));
        }
        this.bulkLength = this.total.divide(new BigDecimal(BULK_SIZE)).intValue();
    }

    /**
     * 初始化数据
     * */
    public void init(String addr, int port) {
        /*初始化Redis数据*/
        jedisPool = new JedisPool(addr, port);
        Jedis jedis = jedisPool.getResource();
        byte[] label = new byte[1024];
        for (int i = 0; i < label.length; i++) {
            label[i] = (byte) 0xFF;
        }
        for (int i = 0; i < this.bulkLength; i++) {
            String key = String.format(this.save_key_format, i);
            byte[] data = jedis.get(key.getBytes());
            if (data != null) {
                continue;
            }
            jedis.set(key.getBytes(), label);
        }
        jedis.close();
    }

    /**
     * 创建Code
     * */
    public String createCode() {
        Jedis jedis = jedisPool.getResource();
        while (true) {
            /* 先分桶,在获取随机数 */
            int bulkIndex = ThreadLocalRandom.current().nextInt(this.bulkLength);
            int offset = ThreadLocalRandom.current().nextInt(1024);
            System.out.println("bulkIndex:"+bulkIndex);
            System.out.println("offset:"+offset);
            String key = String.format(this.save_key_format,bulkIndex);
            Object result = jedis.eval(script,3,new String[]{key,"1",offset+""});
            if(!result.equals("-1")){
                String code = intToCode(bulkIndex,offset);
                jedis.close();
                return code;
            }
        }

    }

    /**
     * 数字转Code
     * */
    public String intToCode(int bulkIndex, int offset) {
        BigDecimal bigDecimal = new BigDecimal(bulkIndex).multiply(BULK_SIZE_BIGDICIMAL).add(BigDecimal.valueOf(offset));
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < this.length; i++) {
            BigDecimal[] result = bigDecimal.divideAndRemainder(new BigDecimal(36));
            int index = result[1].intValue();
            bigDecimal = result[0];
            stringBuilder.append(CHAR_RESOURCE[index]);
        }
        return stringBuilder.toString();
    }


    public static void main(String[] args) {
        CodeUtil codeUtil = new CodeUtil(4, "code");
        codeUtil.init("127.0.0.1", 6379);
        Scanner scanner = new Scanner(System.in);
        for (int i = 0;i < 5000;i ++){
            System.out.println("Please enter the new line:");
            scanner.nextLine();
            System.out.println(codeUtil.createCode());
        }
    }

}

优缺点分析以及优化步骤待明天补充。

更多笔记请查看

https://github.com/Math312/Notes.git

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值