最近有一个需求要生成短券码,个数位或者比较少,主要用了两种雪花与UUID
因为需求特性,生成出来的UUID或者雪花ID都要经过一次高进制运算后缩短,UIID本身带有字符,所以不能参与进制即使只能做取模运算,所以容易重复;
基础 | 一百万 | 一千万 | 重复率 | 可能性 |
---|---|---|---|---|
雪花 | 2.748 | 23.275 | 秒级百万不重复 | 基本不会重复 |
UUID | 2.677 | 24.976 | 千万之一 | 概率挺高 |
因为要把雪花或者UUID生成出来的数据做一次缩短,这里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();
}
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;
}
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());
}
}
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–