详细实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
/** * 较短的有序序列生成算法 * <p>1. 首先使用「类雪花算法」生成一个有序序列</p> * <ul> * <li>1 bit: 因为我们生成的 id 都是正数,所以第一个 bit 统一都是 0</li> * <li>42 bit:表示时间戳,单位是毫秒。42 bit可以表示的数字多达 2^42 – 1,也就是可以标识 2 ^ 42 – 1 个毫秒值,换算成年也就是从我们指定的起始日期开始的 139 年内的时间((2^42-1) / (3600*24*1000*366) ≈ 139)</li> * <li>10 bit:表示当前服务器id,代表的是这个服务最多可以部署在 2^10 台服务器上,也就是 1024 台服务器</li> * <li>10 bit:表示同一个毫秒内产生的不同序列,10 bit 可以代表的最大正整数是 2 ^ 10 = 1024,也就是可以用这个 10 bit 代表的数字来区分同一个毫秒内的 1024 个不同的 id</li> * </ul> * * <p>2. 将得到的 long 类型的序列号转化为62进制字符串(转换之后的字符串长度为10~11位)</p> * * <p>3. 最后将以上步骤得出的字符串补全为12位的字符串返回</p> * */ @Slf4j public class TidGenerator { /** * 起始的时间戳(2020-01-01 00:00:00 000) */ private final static long START_STAMP = 1577808000000L; /* ************ 每一部分占用的位数 *************** */ /** * 机器标识占用的位数 */ private final static long MACHINE_BIT = 10; /** * 序列号占用的位数 */ private final static long SEQUENCE_BIT = 10; /** * 每一部分的最大值 */ private final static long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT); private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT); /** * 每一部分向左的位移 */ private final static long MACHINE_LEFT = SEQUENCE_BIT; private final static long TIMESTAMP_LEFT = MACHINE_LEFT + MACHINE_BIT; /** * 机器标识 */ private long machineId; /** * 序列号 */ private long sequence = 0L; /** * 上一次生成id的时间戳 */ private long lastStamp = -1L; public TidGenerator(long machineId) { if (machineId > MAX_MACHINE_NUM || machineId < 0) { throw new IllegalArgumentException(“machineId can’t be greater than MAX_MACHINE_NUM or less than 0”); } this.machineId = machineId; } /** * 产生下一个TID */ public String nextTid(){ //1. 生成一个有序序列 long sequence = nextSequenceId(); //2. 转化为62进制 String str = ConversionUtils.decimalToSixtyTwo(sequence); //3. 补全12位并返回 return completeDigits(str); } /** * 产生下一个有序ID */ private synchronized long nextSequenceId() { long nowStamp = getCurrentMillis(); //检查是否出现了「时钟回拨」问题 nowStamp = checkClockMovedBackwards(nowStamp); if (nowStamp == lastStamp) { //相同毫秒内,序列号自增 sequence = (sequence + 1) & MAX_SEQUENCE; //同一毫秒的序列数已经达到最大 if (sequence == 0L) { nowStamp = getNextMillis(); } } else { //不同毫秒内,序列号置为0 sequence = 0L; } //记录最近一次生成id的时间戳,单位是毫秒 lastStamp = nowStamp; //生成一个 64bit 的id(时间戳部分 + 机器标识部分 + 序列号部分) return (nowStamp – START_STAMP) << TIMESTAMP_LEFT | machineId << MACHINE_LEFT | sequence; } /** * 获取下一毫秒值 */ private long getNextMillis() { long millis = getCurrentMillis(); while (millis <= lastStamp) { millis = getCurrentMillis(); } return millis; } /** * 获取当前毫秒值 */ private long getCurrentMillis() { return System.currentTimeMillis(); } /** * 检查是否出现了「时钟回拨」问题 */ private long checkClockMovedBackwards(long nowStamp){ if(nowStamp >= lastStamp){ return nowStamp; } //如果因为某些原因出现了「时钟回拨」问题 long diff = lastStamp – nowStamp; //如果时间回拨超过1秒钟,则对外抛出异常,否则暂停1秒钟后再次尝试获取 if(diff >= TimeUnit.SECONDS.toMillis(1)){ throw new RuntimeException(String.format(“Clock moved backwards. Refusing to generate id for %d milliseconds”, diff)); } try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { log.error(“An error occurred here”, e); throw new RuntimeException(“An error occurred here”, e); } nowStamp = getCurrentMillis(); if(nowStamp >= lastStamp){ return nowStamp; } //如果还出现这个问题,则抛出异常 throw new RuntimeException(String.format(“Clock moved backwards again. Refusing to generate id for %d milliseconds”, (lastStamp – nowStamp))); } /** * 补全字符串位数(最后返回12位) * @param str 原始字符串 * @return java.lang.String */ private String completeDigits(String str){ int targetDigits = 12; if(str.length() >= targetDigits){ return str; } int diff = targetDigits – str.length() – 1; //长度标识 + 1位的随机数(可能不存在) + 原递增序列 return ConversionUtils.decimalToSixtyTwo(str.length()) + RandomUtils.randomChars(diff, false) + str; } } |
上述代码中使用的「进制转换」工具和「随机数生成」工具分别是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
/** * 「十进制与62进制互相转换」的算法 * <table> * <tr> * <th>ASCII可显示字符</th><th>对应十进制数字范围</th> * </tr> * <tr> * <td>0-9</td><td>48~57</td> * </tr> * <tr> * <td>A-Z</td><td>65~90</td> * </tr> * <tr> * <td>a-z</td><td>97~122</td> * </tr> * <p>在本工具中,约定代表62进制的62位序列为以下格式:0-9A-Za-z</p> * */ public class ConversionUtils { /** * 十进制转62进制(仅限正整数) * @param num 十进制数字 * @return java.lang.String */ public static String decimalToSixtyTwo(long num){ if(num <= 0){ return “0”; } StringBuilder sb = new StringBuilder(); //余数 long remainder; while (num > 0){ remainder = num % 62; //0-9 if(remainder < 10){ sb.append((char)(‘0’ + remainder)); } //A-Z else if(remainder < 36){ sb.append((char)(‘A’ + remainder – 10)); } //a-z else{ sb.append((char)(‘a’ + remainder – 36)); } num = num / 62; } //因为在上面的循环过程中,后一次循环本应是计算出来的高位字符,但是却被我们放在字符串的最后面,因此最终结果需要再反转一次 return sb.reverse().toString(); } /** * 62进制转十进制 * @param numStr 62进制字符串 * @return java.lang.String */ public static long sixtyTwoToDecimal(String numStr){ //最后转换完成之后的十进制数字 long num = 0; //字符串中的具体某一个字符 int idx; for (int i = 0; i < numStr.length(); i++) { idx = numStr.charAt(numStr.length() – 1 – i); if(idx >= ‘a’){ //idx = ‘a’ + remainder – 36,于是可以推导出:remainder = idx + 36 – ‘a’ //num = remainder * 62^i num += (idx + 36 – ‘a’) * Math.pow(62, i); } else if(idx >= ‘A’){ //idx = ‘A’ + remainder – 10,于是可以推导出:remainder = idx + 10 – ‘A’ num += (idx + 10 – ‘A’) * Math.pow(62, i); }else{ //idx = ‘0’ + remainder,于是可以推导出:remainder = idx – ‘0’ num += (idx – ‘0’) * Math.pow(62, i); } } return num; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
import java.util.Random; /** * 随机数生成 * */ public class RandomUtils { private static final String SPECIAL_CHARS = “!@#$%^&*_=+-/”; /** * 查找一个char数组中还没有填充字符的位置 */ private static int nextIndex(char[] chars, Random rnd) { int index = rnd.nextInt(chars.length); while (chars[index] != 0) { index = rnd.nextInt(chars.length); } return index; } /** * 返回一个随机的特殊字符 */ private static char nextSpecialChar(Random rnd) { return SPECIAL_CHARS.charAt(rnd.nextInt(SPECIAL_CHARS.length())); } /** * 返回一个随机的大写字母 */ private static char nextUpperLetter(Random rnd) { return (char) (‘A’ + rnd.nextInt(26)); } /** * 返回一个随机的小写字母 */ private static char nextLowerLetter(Random rnd) { return (char) (‘a’ + rnd.nextInt(26)); } /** * 返回一个随机的数字 */ private static char nextNumLetter(Random rnd) { return (char) (‘0’ + rnd.nextInt(10)); } /** * 返回一个随机的字符 */ private static char nextChar(Random rnd) { switch (rnd.nextInt(3)) { case 0: return (char) (‘a’ + rnd.nextInt(26)); case 1: return (char) (‘A’ + rnd.nextInt(26)); default: return (char) (‘0’ + rnd.nextInt(10)); } } /** * 返回一个随机的字符(包含特殊字符) */ private static char nextCharWithSpecialChars(Random rnd) { switch (rnd.nextInt(4)) { case 0: return (char) (‘a’ + rnd.nextInt(26)); case 1: return (char) (‘A’ + rnd.nextInt(26)); case 2: return (char) (‘0’ + rnd.nextInt(10)); default: return SPECIAL_CHARS.charAt(rnd.nextInt(SPECIAL_CHARS.length())); } } /** * 生成指定位数的随机数 * @param length 长度 * @param containSpecialChars 是否包含特殊字符 */ public static String randomChars(int length, boolean containSpecialChars){ char[] chars = new char[length]; Random rnd = new Random(); //1. 填补空白位置的字符 for (int i = 0; i < length; i++) { if (chars[i] == 0) { chars[i] = (containSpecialChars ? nextCharWithSpecialChars(rnd) : nextChar(rnd)); } } //2. 返回结果 return new String(chars); } /** * 生成指定位数的随机密码 * @param length 长度 */ public static String randomPassword(int length) { if(length < 4){ return “”; } char[] chars = new char[length]; Random rnd = new Random(); //1. 至少生成一个大写字母、小写字母、特殊字符、数字 chars[nextIndex(chars, rnd)] = nextSpecialChar(rnd); chars[nextIndex(chars, rnd)] = nextUpperLetter(rnd); chars[nextIndex(chars, rnd)] = nextLowerLetter(rnd); chars[nextIndex(chars, rnd)] = nextNumLetter(rnd); //2. 填补其他位置的字符 for (int i = 0; i < length; i++) { if (chars[i] == 0) { chars[i] = nextCharWithSpecialChars(rnd); } } //3. 返回结果 return new String(chars); } } |
测试用例:
1 2 3 4 5 6 7 |
public static void main(String[] args) { TidGenerator generator = new TidGenerator(10); for (int i = 0; i < 10; i++) { System.out.println(generator.nextTid()); } } |
最后生成的序列类似下面这种:
1 2 3 4 5 6 7 8 9 10 |
Ae29SJNndem8 A929SJNnmSLA AU29SJNnqr7g AZ29SJNnqr7h A229SJNnqr7i A729SJNnqr7j AI29SJNnqr7k Ae29SJNnqr7l An29SJNnqr7m A329SJNnqr7n |
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/124466.html