【生成有序序列】借鉴雪花算法实现的一种长度更短的有序序列生成算法

人生之路不会是一帆风顺的,我们会遇上顺境,也会遇上逆境,在所有成功路上折磨你的,背后都隐藏着激励你奋发向上的动机,人生没有如果,只有后果与结果,成熟,就是用微笑来面对一切小事。

导读:本篇文章讲解 【生成有序序列】借鉴雪花算法实现的一种长度更短的有序序列生成算法,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

详细实现:

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

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!