原理介绍
此流水号构成:日期+Long类型的值 组成的一个一长串数字,形如2020010419492195304210432。很显然前面是日期数据,后面的一长串就蕴含了不少的含义:当前秒数、商家ID(也可以是你其余的业务数据)、机器ID、一串随机码等等
各部分介绍:
- 第一部分为当前时间的毫秒值。最大999,所以占10位
- 第二部分为:serviceType表示业务类型。比如订单号、操作流水号、消费流水号等等。最大值定为30,足够用了吧。占5位
- 第三部分为:shortParam,表示用户自定义的短参数。可以放置比如订单类型、操作类型等等类别参数。最大值定为30,肯定也是足够用了的。占5位
- 第四部分为:longParam,同上。用户一般可防止id参数,如用户id、商家id等等,最大支持9.9999亿。绝大多数足够用了,占30位
- 第五部分:剩余的位数交给随机数,随机生成一个数,占满剩余位数。一般至少有15位剩余,所以能支持2的15次方的并发,也是足够用了的
- 最后,在上面的long值前面加上日期时间(年月日时分秒)
上源码
Tips:此源码为本人自己编写,自测了多种情况,若各位使用中有更好的建议,欢迎留言
/** * 通过移位算法 生成流水号 * <p> * --> 通用版本(其实各位可以针对具体场景 给出定制化版本 没关系的) * (最直接的方式就是日期+主机Id+随机字符串来拼接一个流水号) * * @author yourBatman */ public class SerialNumberUtil { //采用long值存储 一共63位 private static final int BIT_COUNT = 63; //各个部分占的最大位数(为了减轻负担,时分秒都放到前面去 不要占用long的位数了 但是毫秒我隐藏起来,方便查问题) //毫秒值最大为999(1111100111)占10位 private static final int SHIFTS_FOR_MILLS = 10; //下面是各部分的业务位数(各位根据自己不同的业务需求 自己定制) //serviceType占位 private static final int SHIFTS_FOR_SERVICETYPE = 5; //shortParam占位 private static final int SHIFTS_FOR_SHORTPARAM = 5; private static final int SHIFTS_FOR_LONGPARAM = 30; /// //最后的随机数 占满剩余位数 private static final int SHIFTS_FOR_RANDOMNUM = BIT_COUNT - SHIFTS_FOR_MILLS - SHIFTS_FOR_SERVICETYPE - SHIFTS_FOR_SHORTPARAM - SHIFTS_FOR_LONGPARAM; //掩码 用于辅助萃取出数据 此技巧特别巧妙 private static final long MASK_FOR_MILLS = (1 << SHIFTS_FOR_MILLS) - 1; private static final long MASK_FOR_SERVICETYPE = (1 << SHIFTS_FOR_SERVICETYPE) - 1; private static final long MASK_FOR_SHORTPARAM = (1 << SHIFTS_FOR_SHORTPARAM) - 1; private static final long MASK_FOR_LONGPARAM = (1 << SHIFTS_FOR_LONGPARAM) - 1; //时间模版 private static final String DATE_PATTERN = "yyyyMMddHHmmss"; /** * 生成流水号 若需要隐藏跟多的参数进来,可以加传参。如订单类型(订单id就没啥必要了)等等 * * @param serviceType 业务类型,比如订单号、消费流水号、操作流水号等等 请保持一个公司内不要重复 * 最大值:30(11110) 占5位 * @param shortParam 短参数 不具体定义什么 一般用于表示类型。如这表示订单流水号,这里可以表示订单类型 * 最大值:30(11110) 占5位 * @param longParam 长参数,一般用于记录id参数什么的,比如是订单的话,这里可以表示商户ID(商户一般不会非常多吧) * 最大值:999999999(101111101011110000011111111) 占30位 表示9.999亿的数据 相信作为id的话,一般都超不过这个数值吧 * @return 流水号 年月日时分秒+long类型的数字 = string串 */ public static String genSerialNum(long serviceType, long shortParam, long longParam) { if (serviceType > 30) { throw new RuntimeException("the max value of 'serviceType' is 30"); } if (shortParam > 30) { throw new RuntimeException("the max value of 'shortParam' is 30"); } if (longParam > 99999999) { throw new RuntimeException("the max value of 'longParam' is 99999999"); } //放置毫秒值 long mills = LocalTime.now().getNano() / 1000000; //备注 此处一定要是long类型 否则会按照int的32位去移位 long millsShift = mills << (BIT_COUNT - SHIFTS_FOR_MILLS); //放置serviceType long serviceTypeShift = serviceType << (BIT_COUNT - SHIFTS_FOR_MILLS - SHIFTS_FOR_SERVICETYPE); //放置shortParam long shortParamShift = shortParam << (BIT_COUNT - SHIFTS_FOR_MILLS - SHIFTS_FOR_SERVICETYPE - SHIFTS_FOR_SHORTPARAM); //放置longParam long longParamShift = longParam << (BIT_COUNT - SHIFTS_FOR_MILLS - SHIFTS_FOR_SERVICETYPE - SHIFTS_FOR_SHORTPARAM - SHIFTS_FOR_LONGPARAM); //生成一个指定位数(二进制位数)的随机数 最后一个 不需要左移了 因为长度就是自己 long randomShift = getBinaryRandom(SHIFTS_FOR_RANDOMNUM); //拼接各个部分 long finalNum = millsShift | serviceTypeShift | shortParamShift | longParamShift | randomShift; //最后前面拼接上年月日时分秒 返回出去 return LocalDateTime.now().format(DateTimeFormatter.ofPattern(DATE_PATTERN)) + finalNum; } /** * 拿到指定位数的 首位数字不为0的位数,最终以十进制数返回出来 * * @param count 需要的总位数 总位数不允许超过63 * @return binary random */ private static long getBinaryRandom(int count) { StringBuffer sb = new StringBuffer(); String str = "01"; //采用ThreadLocalRandom 生成随机数 避免多线程问题 ThreadLocalRandom r = ThreadLocalRandom.current(); for (int i = 0; i < count; i++) { int num = r.nextInt(str.length()); char c = str.charAt(num); while (c == '0') { //确保第一个是不为0数字 否则一直循环去获取 if (i != 0) { break; } else { num = r.nextInt(str.length()); c = str.charAt(num); } } sb.append(c); } return Long.valueOf(sb.toString(), 2); } //===============================提供便捷获取各个部分的工具方法=================================== /** * 从序列号拿到日期 并且格式化为LocalDateTime格式 * * @param serialNumber 流水号 * @return 日期时间 */ public static LocalDateTime getDate(String serialNumber) { String dateStr = serialNumber.substring(0, DATE_PATTERN.length()); return LocalDateTime.parse(dateStr, DateTimeFormatter.ofPattern(DATE_PATTERN)); } /** * 拿到毫秒数:是多少毫秒 * * @param serialNumber 流水号 * @return 毫秒数 */ public static long getMills(String serialNumber) { return getLongSerialNumber(serialNumber) >> (BIT_COUNT - SHIFTS_FOR_MILLS) & MASK_FOR_MILLS; } /** * 拿到 serviceType * * @param serialNumber 流水号 * @return serviceType */ public static long getServiceType(String serialNumber) { return getLongSerialNumber(serialNumber) >> (BIT_COUNT - SHIFTS_FOR_MILLS - SHIFTS_FOR_SERVICETYPE) & MASK_FOR_SERVICETYPE; } /** * 拿到 shortParam * * @param serialNumber 流水号 * @return shortParam */ public static long getShortParam(String serialNumber) { return getLongSerialNumber(serialNumber) >> (BIT_COUNT - SHIFTS_FOR_MILLS - SHIFTS_FOR_SERVICETYPE - SHIFTS_FOR_SHORTPARAM) & MASK_FOR_SHORTPARAM; } /** * 拿到 longParam * * @param serialNumber 流水号 * @return longParam */ public static long getLongParam(String serialNumber) { return getLongSerialNumber(serialNumber) >> (BIT_COUNT - SHIFTS_FOR_MILLS - SHIFTS_FOR_SERVICETYPE - SHIFTS_FOR_SHORTPARAM - SHIFTS_FOR_LONGPARAM) & MASK_FOR_LONGPARAM; } //把日期前缀去掉 private static long getLongSerialNumber(String serialNumber) { return Long.parseLong(serialNumber.substring(DATE_PATTERN.length())); } //================================================================== /** * 提供测试的Main方法 * * @param args the input arguments */ public static void main(String[] args) { String serialNum = genSerialNum(1, 2, 300); System.out.println(serialNum); //20181121173040299068801480344 //拿long型的值 System.out.println(getLongSerialNumber(serialNum)); //299068801480344 System.out.println(Long.toBinaryString(getLongSerialNumber(serialNum))); //拿到日期时间 System.out.println(getDate(serialNum)); //2018-11-21T17:30:40 //拿毫秒值 System.out.println((LocalTime.now().getNano() / 1000000)); System.out.println(getMills(serialNum)); //拿到serviceType System.out.println(getServiceType(serialNum)); //1 //拿到shortParam System.out.println(getShortParam(serialNum)); //2 //拿到longParam System.out.println(getLongParam(serialNum)); //300 } }
总结
到这里Java中的位运算这块就算聊完。在实际工作中,如果只是为了数字的计算(不是运算),是不建议使用位运算符的,毕竟人能读懂比机器能读懂更重要。在一些特殊的场景:比如N多状态的控制、对效率有极致要求的情况下,或许位运算能给与你帮助,所以希望此文能帮助到你,这边是它最大的意义~
当然,若你有些自己的想法或者对本文感兴趣,可以私信我 or 左边扫码加我好友来一起探讨和交流学习,共同进步。