券码生成-基于UUID与雪花

最近有一个需求要生成短券码,个数位或者比较少,主要用了两种雪花与UUID

测试

因为需求特性,生成出来的UUID或者雪花ID都要经过一次高进制运算后缩短,UIID本身带有字符,所以不能参与进制即使只能做取模运算,所以容易重复;

基础 一百万 一千万 重复率 可能性
雪花 2.748 23.275 秒级百万不重复 基本不会重复
UUID 2.677 24.976 千万之一 概率挺高

一、BASE

因为要把雪花或者UUID生成出来的数据做一次缩短,这里UUID采用的是取模映射字符,雪花采用的是转换高进制:

1.Base UUID

    private static final char[] BASE = new char[]{'H', 'V', 'E', '8', 'S', '2', 'D', 'Z', 'X', '9', 'C', '7', 'P',
            '5', 'I', 'K', '3', 'M', 'J', 'U', 'F', 'R', '4', 'W', 'Y', 'L', 'T', 'N', '6', 'B', 'G', 'Q'
            , 'q', 'w', 'e', 'r', 't', 'y', 'u', 'p', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'z', 'x', 'c', 'v', 'b', 'n', 'm'
    };

    private static String strToSplitHex16(String uuid) {
        StringBuilder shortBuffer = new StringBuilder();
        //我们这里想要保证券码为11位,所以32位uuid加了一位随机数,再分成11等份;(如果是8位券码,则32位uuid分成8等份进行计算即可)
        for (int i = 0; i < 8; i++) {
            //每次取4个
            String str = uuid.substring(i * 4, i * 4 + 4);
            //将19进制转为10进
            int x = Integer.parseInt(str, 16);
            //取模映射字符
            shortBuffer.append(BASE[x % BASE.length]);
        }
        return shortBuffer.toString();
    }

2.Base 雪花

    private static final char[] BASE = new char[]{'H', 'V', 'E', '8', 'S', '2', 'D', 'Z', 'X', '9', 'C', '7', 'P',
            '5', 'I', 'K', '3', 'M', 'J', 'U', 'F', 'R', '4', 'W', 'Y', 'L', 'T', 'N', '6', 'B', 'G', 'Q'
            , 'q', 'w', 'e', 'r', 't', 'y', 'u', 'p', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'z', 'x', 'c', 'v', 'b', 'n', 'm'
    };

 public static String idToCode() {
        //获取雪花id
        long id = nextId();
        char[] buf = new char[BIN_LEN];
        int charPos = BIN_LEN;

        // 将雪花id转换为高进制,这里是55进制
        while (id / BIN_LEN > 0) {
            int index = (int) (id % BIN_LEN);
            buf[--charPos] = BASE[index];
            id /= BIN_LEN;
        }

        buf[--charPos] = BASE[(int) (id % BIN_LEN)];
        // 将字符数组转化为字符串
        String result = new String(buf, charPos, BIN_LEN - charPos);

        // 长度不足指定长度则随机补全
        int len = result.length();
        if (len < CODE_LEN) {
            StringBuilder sb = new StringBuilder();
            sb.append(SUFFIX_CHAR);
            Random random = new Random();
            // 去除SUFFIX_CHAR本身占位之后需要补齐的位数
            for (int i = 0; i < CODE_LEN - len - 1; i++) {
                sb.append(BASE[random.nextInt(BIN_LEN)]);
            }

            result += sb.toString();
        }

        return result;
    }

二、完整代码

1.UUID


import org.junit.Test;
import org.springframework.util.StopWatch;

import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

public class UUIDCouponId {

    private static final char[] BASE = new char[]{'H', 'V', 'E', '8', 'S', '2', 'D', 'Z', 'X', '9', 'C', '7', 'P',
            '5', 'I', 'K', '3', 'M', 'J', 'U', 'F', 'R', '4', 'W', 'Y', 'L', 'T', 'N', '6', 'B', 'G', 'Q'
            , 'q', 'w', 'e', 'r', 't', 'y', 'u', 'p', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'z', 'x', 'c', 'v', 'b', 'n', 'm'
    };

    private static String strToSplitHex16(String uuid) {
        StringBuilder shortBuffer = new StringBuilder();
        //我们这里想要保证券码为11位,所以32位uuid加了一位随机数,再分成11等份;(如果是8位券码,则32位uuid分成8等份进行计算即可)
        for (int i = 0; i < 8; i++) {
            //每次取4个
            String str = uuid.substring(i * 4, i * 4 + 4);
            //将19进制转为10进
            int x = Integer.parseInt(str, 16);
            //取模映射字符
            shortBuffer.append(BASE[x % BASE.length]);
        }
        return shortBuffer.toString();
    }

    /**
     * 产生优惠券编码的方法
     *
     * @return
     */
    public static String generateCouponCode() {
        String uuid = UUID.randomUUID().toString().replaceAll("-", "");
        return strToSplitHex16(uuid);
    }

    @Test
    public void t1() {
        Set<String> hashSet = new HashSet<>(10000000);
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        /*基于uuid生成*/
        for (int i = 0; i < 100000000; i++) {
            //产生的券码
            String key = generateCouponCode();
            if (hashSet.contains(key)) {
                System.out.println(key);
                System.out.println("重复");
            } else {
                hashSet.add(key);
            }
        }
        stopWatch.stop();
        System.out.println(stopWatch.getTotalTimeMillis());
    }
}

2.雪花

StopWatch 是spring的计时器可以删了


import org.springframework.util.StopWatch;

import java.util.*;

public class SnowCouponId {

    /**
     * 自定义进制(0,1没有加入,容易与o,l混淆),数组顺序可进行调整增加反推难度,A用来补位因此此数组不包含A,共55个字符。
     */
    private static final char[] BASE = new char[]{'H', 'V', 'E', '8', 'S', '2', 'D', 'Z', 'X', '9', 'C', '7', 'P',
            '5', 'I', 'K', '3', 'M', 'J', 'U', 'F', 'R', '4', 'W', 'Y', 'L', 'T', 'N', '6', 'B', 'G', 'Q'
            , 'q', 'w', 'e', 'r', 't', 'y', 'u', 'p', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'z', 'x', 'c', 'v', 'b', 'n', 'm'
//            , '!', '@', '#', '$', '%', '&', '*', '(', ')', '_', '+', '='
//            , '/', '-', '+', ',', '.', '[', ']', ':', ';', '~'
    };

    /**
     * A补位字符,不能与自定义重复
     */
    private static final char SUFFIX_CHAR = 'A';

    /**
     * 进制长度
     */
    private static final int BIN_LEN = BASE.length;

    /**
     * 生成邀请码最小长度
     */
    private static final int CODE_LEN = 9;

    /**
     * 起始的时间戳
     */
    private final static long twepoch = 1557825652094L;

    /**
     * 每一部分占用的位数
     */
    private final static long workerIdBits = 5L;
    private final static long datacenterIdBits = 5L;
    private final static long sequenceBits = 12L;

    /**
     * 每一部分的最大值
     */
    private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
    private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    private final static long maxSequence = -1L ^ (-1L << sequenceBits);

    /**
     * 每一部分向左的位移
     */
    private final static long workerIdShift = sequenceBits;
    private final static long datacenterIdShift = sequenceBits + workerIdBits;
    private final static long timestampShift = sequenceBits + workerIdBits + datacenterIdBits;

    private static long datacenterId = 1; // 数据中心ID
    private final static long workerId = Long.valueOf(0L); // 机器ID
    private static long sequence = 0L; // 序列号
    private static long lastTimestamp = -1L; // 上一次时间戳

    public static synchronized long nextId() {
        long timestamp = timeGen();
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(String.format(
                    "Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }
        if (timestamp == lastTimestamp) {
            sequence = (sequence + 1) & maxSequence;
            if (sequence == 0L) {
                timestamp = tilNextMillis();
            }
        } else {
            sequence = 0L;
        }
        lastTimestamp = timestamp;

        return (timestamp - twepoch) << timestampShift // 时间戳部分
                | datacenterId << datacenterIdShift // 数据中心部分
                | workerId << workerIdShift // 机器标识部分
                | sequence; // 序列号部分
    }

    private static long tilNextMillis() {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    private static long timeGen() {
        return System.currentTimeMillis();
    }


    /**
     * ID转换为邀请码
     *
     * @return
     */
    public static String idToCode() {
        //获取雪花id
        long id = nextId();
        char[] buf = new char[BIN_LEN];
        int charPos = BIN_LEN;

        // 将雪花id转换为高进制,这里是55进制
        while (id / BIN_LEN > 0) {
            int index = (int) (id % BIN_LEN);
            buf[--charPos] = BASE[index];
            id /= BIN_LEN;
        }

        buf[--charPos] = BASE[(int) (id % BIN_LEN)];
        // 将字符数组转化为字符串
        String result = new String(buf, charPos, BIN_LEN - charPos);

        // 长度不足指定长度则随机补全
        int len = result.length();
        if (len < CODE_LEN) {
            StringBuilder sb = new StringBuilder();
            sb.append(SUFFIX_CHAR);
            Random random = new Random();
            // 去除SUFFIX_CHAR本身占位之后需要补齐的位数
            for (int i = 0; i < CODE_LEN - len - 1; i++) {
                sb.append(BASE[random.nextInt(BIN_LEN)]);
            }

            result += sb.toString();
        }

        return result;
    }

    /**
     * 邀请码解析出ID
* 基本操作思路恰好与idToCode反向操作。 * * @param code * @return */
public static Long codeToId(String code) { char[] charArray = code.toCharArray(); long result = 0L; for (int i = 0; i < charArray.length; i++) { int index = 0; for (int j = 0; j < BIN_LEN; j++) { if (charArray[i] == BASE[j]) { index = j; break; } } if (charArray[i] == SUFFIX_CHAR) { break; } if (i > 0) { result = result * BIN_LEN + index; } else { result = index; } } return result; } public static void main(String[] args) { HashMap<String, Object> stringHashMap = new HashMap<>(10000000); StopWatch stopWatch = new StopWatch(); stopWatch.start(); Object o = new Object(); for (long i = 1; i < 10000000; i++) { String s = idToCode(); if (stringHashMap.containsKey(s)) { System.out.println(i + "出现重复" + s); } stringHashMap.put(s, o); } stopWatch.stop(); System.out.println(stopWatch.getTotalTimeMillis()); } }

–eof–

你可能感兴趣的:(架构,java)