聊聊Java中的位运算:与、或、非、异或、左移、右移、无符号右移【小家Java】(下)

简介: 聊聊Java中的位运算:与、或、非、异或、左移、右移、无符号右移【小家Java】(下)

原理介绍


此流水号构成:日期+Long类型的值 组成的一个一长串数字,形如2020010419492195304210432。很显然前面是日期数据,后面的一长串就蕴含了不少的含义:当前秒数、商家ID(也可以是你其余的业务数据)、机器ID、一串随机码等等


各部分介绍:


  1. 第一部分为当前时间的毫秒值。最大999,所以占10位
  2. 第二部分为:serviceType表示业务类型。比如订单号、操作流水号、消费流水号等等。最大值定为30,足够用了吧。占5位
  3. 第三部分为:shortParam,表示用户自定义的短参数。可以放置比如订单类型、操作类型等等类别参数。最大值定为30,肯定也是足够用了的。占5位
  4. 第四部分为:longParam,同上。用户一般可防止id参数,如用户id、商家id等等,最大支持9.9999亿。绝大多数足够用了,占30位
  5. 第五部分:剩余的位数交给随机数,随机生成一个数,占满剩余位数。一般至少有15位剩余,所以能支持2的15次方的并发,也是足够用了的
  6. 最后,在上面的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 左边扫码加我好友来一起探讨和交流学习,共同进步。

相关文章
|
2月前
|
IDE Java 编译器
Java“找不到符号” 错误怎么查找解决
“找不到符号”是Java编程中常见的编译错误,通常表明代码试图访问未声明或不可见的符号(如类、方法或变量)。解决此问题需检查拼写、导入包是否正确及作用域是否合适。确保使用正确的类路径和库,可有效避免此类错误。若问题依旧,查阅官方文档或使用调试工具定位错误亦为良策。
1755 10
|
3月前
|
IDE Java 编译器
lombok编译遇到“找不到符号的问题”
【9月更文挑战第18天】当使用 Lombok 遇到 “找不到符号” 的问题时,可能是由于 Lombok 未正确安装、编译器不支持、IDE 配置不当或项目构建工具配置错误。解决方法包括确认 Lombok 安装、编译器支持,配置 IDE 和检查构建工具配置。通过这些步骤通常可解决问题,若问题仍存在,建议检查项目配置和依赖,或查看日志获取更多信息。
958 2
|
4月前
|
Java
java:找不到符号
这篇文章讨论了Java编程中常见的错误信息 "找不到符号:类 entity",并提供了解决这个问题的一些方法和建议。
|
4月前
|
Java
成功解决:java: 找不到符号 符号: 方法 getSort() 位置: 类型为com.atguigu.gulimall.product.entity.CategoryEntity的变量 menu1
这篇文章讨论了Java中遇到的一个常见错误:"java: 找不到符号 符号: 方法 getSort() 位置: 类型为com.atguigu.gulimall.product.entity.CategoryEntity的变量 menu1",即在尝试调用一个不存在的方法时出现的问题,并提供了相应的解决方法。
|
JSON 算法 Java
聊聊Java中的位运算:与、或、非、异或、左移、右移、无符号右移【小家Java】(上)
聊聊Java中的位运算:与、或、非、异或、左移、右移、无符号右移【小家Java】(上)
聊聊Java中的位运算:与、或、非、异或、左移、右移、无符号右移【小家Java】(上)
|
运维 Java 关系型数据库
聊聊Java中的位运算:与、或、非、异或、左移、右移、无符号右移【小家Java】(中)
聊聊Java中的位运算:与、或、非、异或、左移、右移、无符号右移【小家Java】(中)
|
17天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
8天前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
3天前
|
监控 Java 开发者
深入理解Java中的线程池实现原理及其性能优化####
本文旨在揭示Java中线程池的核心工作机制,通过剖析其背后的设计思想与实现细节,为读者提供一份详尽的线程池性能优化指南。不同于传统的技术教程,本文将采用一种互动式探索的方式,带领大家从理论到实践,逐步揭开线程池高效管理线程资源的奥秘。无论你是Java并发编程的初学者,还是寻求性能调优技巧的资深开发者,都能在本文中找到有价值的内容。 ####
|
8天前
|
安全 Java 开发者
Java中的多线程编程:从基础到实践
本文深入探讨了Java多线程编程的核心概念和实践技巧,旨在帮助读者理解多线程的工作原理,掌握线程的创建、管理和同步机制。通过具体示例和最佳实践,本文展示了如何在Java应用中有效地利用多线程技术,提高程序性能和响应速度。
32 1