ID生成(二)SequenceUtil

/**
 * 根据时间戳生产Long型唯一ID 工具类
 *
 * */
public class SequenceUtil {

    /*** 机 器 Id */
    private static long workerId = 0;
    /*** 数 据 中 心 */
    private static long centerId = 0;
    /*** 毫 秒 内 序 列 */
    private static long sequence = 0L;
    /*** 上 次 Id 生 成 的 时 间 戳 */
    private static long lastTimestamp = -1L;
    /** 机 器 编 号 所 占 位 数 */
    private static final long workerIdBits = 5L;
    /** 数 据 标 识 所 占 位 数 */
    private static final long centerIdBits = 5L;
    /** 开 始 时 间 戳 */
    private static final long poc = 1288834974657L;
    /** 序 列 在 Id 中 所 占 的 位 数 */
    private static final long sequenceBits = 12L;
    /** 为 算 法 提 供 可 用 配 置 */
    private static final long workerIdShift = sequenceBits;
    private static final long maxWorkerId = -1L ^ (-1L << workerIdBits);
    private static final long maxCenterId = -1L ^ (-1L << centerIdBits);
    private static final long centerIdShift = sequenceBits + workerIdBits;
    private static final long timestampLeftShift = sequenceBits + workerIdBits + centerIdBits;
    private static final long sequenceMask = -1L ^ (-1L << sequenceBits);
    public static synchronized long makeSequence() throws Exception{
        long timestamp = timeGen();
        // 当 前 时 间 小 于 上 次 Id 生 成 时 间 ,说 明 系 统 时 钟 回 退, 应 会 抛 出 异 常
        if (timestamp < lastTimestamp) {
            // 服 务 器 时 钟 被 调 整 了, Sequence 生 成 器 停 止 服 务
            throw new Exception(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }
        // 如 果 是 同 一 时 间 生 成,则 进 行 毫 秒 内 序 列
        if (lastTimestamp == timestamp) {
            // 每 次 加 +
            sequence = (sequence + 1) & sequenceMask;
            // 毫 秒 内 序 列 溢 出
            if (sequence == 0) {
                // 阻塞到下一个毫秒,获取新的时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }
        // 暂 存 当 前 时 间 戳 , 为 下 次 使 用  提 供 依 据
        lastTimestamp = timestamp;
        // 雪花算法核心
        return ((timestamp - poc) << timestampLeftShift) | (centerId << centerIdShift) | (workerId << workerIdShift) | sequence;
    }

    /**
     * 获 取 下 一 个 Id
     * */
    public static long makeId(){
        try {
            return makeSequence();
        }catch (Exception e){
            e.printStackTrace();
        }
        return -1;
    }

    /**
     * 获 取 下 一 个 Id ( String 类型 )
     * */
    public static String makeStringId(){
        return String.valueOf(makeId()) ;
    }

    public static List<String> makeStringIds(int size){

        List<String> ids = new ArrayList<String>();
        for (int i = 0;i<size;i++){
            ids.add(makeStringId());
        }
        return ids;
    }

    /**
     * 根 据 一 定 数 量 的 Id
     * */
    public Set<Long> makeId(int initSize) throws Exception{
        Set<Long> ids = new HashSet<Long>(initSize);
        for (long current = 0; current < initSize; current++){
            ids.add(makeId());
        }
        return ids;
    }

    /**
     * 时 间 戳 比 对
     * */
    protected static long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 当 前 系 统 时 间 毫 秒
     * */
    protected static long timeGen() {

        return System.currentTimeMillis();
    }
}
### Java实现自动编号功能 在Java中,可以通过多种方式来实现自动编号功能。以下是基于引用内容以及常见实践的一种解决方案。 #### 方法一:使用静态变量实现简单自增编号 如果只需要在一个程序运行期间生成连续的编号,则可以利用`static`关键字定义一个全局计数器: ```java public class NumberGenerator { private static int counter = 0; public synchronized static int getNextNumber() { return ++counter; } public synchronized static String getNextCustomNumber(String prefix) { return prefix + getNextNumber(); } } ``` 调用此方法时可以直接获取递增的整数值或者带有前缀的字符串形式编号[^1]。 #### 方法:结合数据库记录状态 当需要跨多次应用程序启动保持一致性时(例如Web应用),建议将当前最大值保存至持久化存储介质如关系型数据库表字段里。每次请求新ID之前先读取最新一条记录的最大id再加上指定步长即可得到下一个可用唯一标识符[^3]。 下面展示了一个模拟函数,该函数接受来自查询结果集中的已存在条目数目作为参数,并返回格式化的年度月份加流水号组合而成的结果串: ```java public class SequenceUtil { /** * 根据传入的选择项计算主题编号 * * @param select 数据库中已有数据的数量 * @return 年月日加上三位补零后的序列码组成的字符串 */ public static String generateSubjectNo(long select){ long nextSequnce = select + 1 ; String seqStr = String.format("%03d",nextSequnce); Calendar cal = GregorianCalendar.getInstance(); StringBuilder sbuilder = new StringBuilder(); sbuilder.append(cal.get(Calendar.YEAR)) .append(String.format("%02d",(cal.get(Calendar.MONTH)+1))) .append(String.format("%02d",cal.get(Calendar.DAY_OF_MONTH))) .append("-").append(seqStr); return sbuilder.toString(); } // 测试代码片段 public static void main(String[]args){ System.out.println(generateSubjectNo(9));// 输出类似于"20230801-010" } } ``` 以上两种方案各有优劣,在实际项目选型过程中需综合考虑具体业务需求和技术约束条件等因素决定采用哪种策略更为合适。 ### 注意事项 - 对于并发环境下的安全性问题,务必采取适当措施防止竞争状况发生,比如运用锁机制或是原子操作类。 - 如果涉及到分布式系统架构下的一致性保障,则可能还需要额外引入诸如UUID算法或者其他专门设计用于解决此类难题的技术手段。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

必成公

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值