聊聊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 左边扫码加我好友来一起探讨和交流学习,共同进步。

相关文章
|
1月前
|
IDE Java 编译器
Java“找不到符号” 错误怎么查找解决
“找不到符号”是Java编程中常见的编译错误,通常表明代码试图访问未声明或不可见的符号(如类、方法或变量)。解决此问题需检查拼写、导入包是否正确及作用域是否合适。确保使用正确的类路径和库,可有效避免此类错误。若问题依旧,查阅官方文档或使用调试工具定位错误亦为良策。
1244 10
|
2月前
|
IDE Java 编译器
lombok编译遇到“找不到符号的问题”
【9月更文挑战第18天】当使用 Lombok 遇到 “找不到符号” 的问题时,可能是由于 Lombok 未正确安装、编译器不支持、IDE 配置不当或项目构建工具配置错误。解决方法包括确认 Lombok 安装、编译器支持,配置 IDE 和检查构建工具配置。通过这些步骤通常可解决问题,若问题仍存在,建议检查项目配置和依赖,或查看日志获取更多信息。
557 2
|
3月前
|
Java
java:找不到符号
这篇文章讨论了Java编程中常见的错误信息 "找不到符号:类 entity",并提供了解决这个问题的一些方法和建议。
|
3月前
|
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】(中)
|
6天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
15天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
3天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
16 9
|
6天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####