首页 > 编程语言 >TimeId 基于时间戳的自增ID算法(Java版)

TimeId 基于时间戳的自增ID算法(Java版)

时间:2023-08-09 12:56:28浏览次数:50  
标签:Java int private final TimeId static ID

常用的全局唯一ID算法

1、UUID

首先是大名鼎鼎的 UUID,UUID 是通用唯一识别码(Universally Unique Identifier)的缩写。
UUID是一个128比特的数值,是基于当前时间、计数器(counter)和硬件标识(通常为无线网卡的MAC地址)等数据计算生成的。
虽然 UUID 碰撞几率不为零,但它足够接近于零,可以忽略不计,可以认为UUID能够保证全局唯一。
但是 UUID 有一些缺点,首先他相对比较长(36个字符,去掉多余的“-”后依旧有32个字符),另外UUID一般是无序的(默认的版本4)。

2、SnowFlake

SnowFlake 雪花算法是 Twitter 开源的分布式 ID 生成算法,其具有简洁、高性能、低延迟、ID 按时间趋势有序等特点。
雪花算法生成后是一个 64bit 的 Long 型的数值,其中有1位标识,41位时间截,10位的数据机器位,12位毫秒内的计数。
0 | timestamp (41 bits) | node ID (10 bits) | sequence number (12 bits)
因为第一位标识符是 0(保证long是一个正整数),后面就是时间戳,所以整个ID基本保持了自增。
雪花算法较高的性能和吞吐量,生成时不依赖于数据库,完全在内存中生成,每秒能够产生26万ID左右,能够满足绝大多数高并发场景下的互联网应用的要求,并且因为其自增的特性,数据库索引效率也很高。(在分布式系统多节点的情况下,所有节点的时钟并不能保证不完全同步,所以有可能会出现不是全局递增的情况,但是总体是递增的。)
默认的雪花算法41位时间戳可以使用69年,如果时间戳从2015年开始的,到了2084年会出现ID冲突问题。

3、NanoID

NanoID 是一个用于生成小型、安全且唯一标识符(ID)的 JavaScript 库(目前也有其他语言版本的实现)。它专门设计用于在不同的应用场景中生成短、易于处理的标识符,例如用作数据库记录的主键、URL 缩短服务的短链接标识等。
NanoID 它通过使用高质量的随机数生成算法,能够确保生成的标识符是唯一的并且难以预测。NanoID 与 UUID v4 (基于随机) 相当,它们在 ID 中有相似数量的随机位 (NanoID 为126,UUID 为122),因此它们的冲突概率相似。

NanoID 比 UUID 更加紧凑,使用更大的字母表(A-Za-z0-9_-)。 因此,ID 大小从36个符号减少到21个符号。NanoID使用URL友好字符(A-Za-z0-9_-)。非常适合web应用程序中的唯一标识符。
默认的 NanoID 中包含了字母的大小写,在大小写不敏感的情况下(例如对属性大小写不敏感的数据库),冲突概率会增加,但是可以通过自定义字母表以及增加长度,对该问题进行优化。
此外,NanoID生成是无序的,对聚类索引的数据列(B-TREE索引列)并不友好。

TimeId 算法实现

TimeId 是一种时间序列ID算法(20位字符串),其组成结构如下:
时间戳 + 循环计数 + 随机数 + 网络地址 + 进程号
优势:
1、TimeId 是一种时间戳算法,所以保证了其数值整体有序的(有序但不连贯),数据库索引效率也很高。
2、与 SnowflakeId 算法相比,TimeId 的冲突的概率比SnowflakeId低,也没有69年的限制。
3、与 UUID 相比,TimeId 大小从36个符号减少到20个符号。
缺点:
1、因为 TimeId 是一个字符串,相对 SnowflakeId(长整型)会占用更大的空间,但是依旧比 UUID 紧凑。
2、安全性上与 UUID v1 (根据时间和节点生成)类似,使用了节点ID来确保唯一性,雪花算法也存在类似问题。

** 以下是 TimeId 的实现(JAVA版本)**

注:这个算法网络地址获取部分只考虑了IPv4,如果需要IPv6,请自己修改 isValidIPv4 方法。

import java.lang.management.ManagementFactory;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.security.SecureRandom;
import java.util.Enumeration;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;

/**
 * 时间序列ID生成器(20位)<br>
 * 时间戳+循环计数+随机数+网络地址+进程号<br>
 * 优势:<br>
 * TimeId 是有序的(有序但不连贯)<br>
 * TimeId 使用了和 SnowflakeIdWorker 类似的时间戳算法,但是冲突的概率更低<br>
 * TimeId 与 UUID 相比,大小从36个符号减少到20个符号<br>
 * 劣势:<br>
 * TimeId 是字符串,相对 SnowflakeIdWorker( 长整型) 占用更大的空间,但是比UUID紧凑
 */
public class TimeId {

    private static final int RADIX36 = 36;
    private static final int TIMESTAMP_LENGTH = 9;
    private static final int COUNTER_LENGTH = 5;
    private static final int MAC_LENGTH = 2;
    private static final int PID_LENGTH = 1;
    private static final int RANDOM_LENGTH = 3;
    private static final int COUNTER_MOD = computeRadix36Mod(COUNTER_LENGTH);
    private static final int MAC_MOD = computeRadix36Mod(MAC_LENGTH);
    private static final int PID_MOD = computeRadix36Mod(PID_LENGTH);
    private static final int RANDOM_MOD = computeRadix36Mod(RANDOM_LENGTH);
    private static final char ZERO_CHAR = '0';

    private static final String LOCALHOST_IPV4 = "127.0.0.1";
    private static final String ANYHOST_IPV4 = "0.0.0.0";
    private static final String BROADCAST_IPV4 = "255.255.255.255";
    private static final Pattern IPV4_PATTERN = Pattern.compile(//
            "^(2(5[0-5]{1}|[0-4]\\d{1})|[0-1]?\\d{1,2})(\\.(2(5[0-5]{1}|[0-4]\\d{1})|[0-1]?\\d{1,2})){3}$"//
    );

    /** 循环序列号 */
    private static final AtomicInteger NEXT_COUNTER = new AtomicInteger(new SecureRandom().nextInt());

    /** 此类用来创建随机数的随机数生成器 */
    private static class Holder {
        static final SecureRandom NUMBER_GENERATOR = new SecureRandom();
        static final long MAC_VALUE = getMac();
        static final long PID_VALUE = getPid();
    }

    /**
     * 构造函数
     */
    protected TimeId() {

    }

    /**
     * 生成 TimeId
     * @return TimeId 字符串
     */
    public static String nextId() {
        StringBuilder buffer = new StringBuilder();
        append(buffer, timeGen(), TIMESTAMP_LENGTH);
        append(buffer, Math.abs(NEXT_COUNTER.getAndIncrement() % COUNTER_MOD), COUNTER_LENGTH);
        append(buffer, Math.abs(Holder.MAC_VALUE % MAC_MOD), MAC_LENGTH);
        append(buffer, Math.abs(Holder.PID_VALUE % PID_MOD), PID_LENGTH);
        append(buffer, Math.abs(Holder.NUMBER_GENERATOR.nextLong() % RANDOM_MOD), RANDOM_LENGTH);
        return buffer.toString();
    }

    /**
     * 将数值添加到字符串缓冲中
     * @param buffer 字符串缓冲中
     * @param value 数值
     * @param length 添加的位数
     */
    private static void append(StringBuilder buffer, long value, int length) {
        buffer.append(leftPad(Long.toString(value, RADIX36), length, ZERO_CHAR));
    }

    /**
     * 返回以毫秒为单位的当前时间
     * @return 当前时间(毫秒)
     */
    private static long timeGen() {
        return System.currentTimeMillis();
    }

    /**
     * 获得MAC值
     * @return MAC值
     */
    private static long getMac() {
        long mac = 0;
        try {
            byte[] address = getHardwareAddress();
            mac = new BigInteger(address).longValue();
        } catch (Exception | Error e) {
            mac = Holder.NUMBER_GENERATOR.nextLong();
        }
        return mac;
    }

    /**
     * 获得当前进程ID
     * @return 进程ID
     */
    private static long getPid() {
        long pid = 0;
        String name = ManagementFactory.getRuntimeMXBean().getName();
        try {
            pid = Long.parseLong(name.split("@")[0]);
        } catch (Exception | Error e) {
            pid = Holder.NUMBER_GENERATOR.nextLong();
        }
        return pid;
    }

    /**
     * 计算模数
     * @param length 长度
     * @return 进制模(32)
     */
    private static int computeRadix36Mod(int length) {
        int value = 1;
        for (int i = 0; i < length; i++) {
            value *= RADIX36;
        }
        return value - 1;
    }

    /**
     * 左填充指定字符的字符串.
     * @param cs 需要填充的字符串
     * @param size 要填充到的大小
     * @param padChar 要填充的字符
     * @return 左填充字符串或原始字符串(如果不需要填充)
     */
    private static String leftPad(final CharSequence cs, final int size, final char padChar) {
        if (cs == null) {
            return null;
        }
        final int pads = size - cs.length();
        if (pads <= 0) {
            return cs.toString();
        }
        StringBuilder builder = new StringBuilder(size);
        for (int i = 0; i < pads; i++) {
            builder.append(padChar);
        }
        builder.append(cs);
        return builder.toString();
    }

    /**
     * 获得本机 MAC地址
     * @return 本机MAC地址
     */
    private static final byte[] getHardwareAddress() {
        try {
            for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
                NetworkInterface networkInterface = en.nextElement();
                for (Enumeration<InetAddress> addrs = networkInterface.getInetAddresses(); addrs.hasMoreElements();) {
                    String ip = addrs.nextElement().getHostAddress();
                    if (isValidIPv4(ip)) {
                        return networkInterface.getHardwareAddress();
                    }
                }
            }
        } catch (SocketException e) {
            // Ignore
        }
        return new byte[0];
    }

    /**
     * 判断是否是有效IPv4地址
     * @param ip IP地址
     * @return 如果是有效IPv4地址返回true,否则返回false
     */
    private static boolean isValidIPv4(String ip) {
        return (ip != null //
                && !ANYHOST_IPV4.equals(ip) //
                && !LOCALHOST_IPV4.equals(ip) //
                && !BROADCAST_IPV4.equals(ip) //
                && IPV4_PATTERN.matcher(ip).matches());
    }
}

标签:Java,int,private,final,TimeId,static,ID
From: https://www.cnblogs.com/relucent/p/17616569.html

相关文章

  • vue import 调用方法 Import是javascript中的一种模块加载方式,在Vue中也可以使用impor
    vueimport调用方法Import是javascript中的一种模块加载方式,在Vue中也可以使用import来加载组件、库或其他模块。使用import语句,可以将需要的模块导入到当前模块的作用域中,以使其可用于当前模块内的执行。原文链接:https://www.yzktw.com.cn/post/1248672.htmlImport是javascri......
  • java笔记_12_自定义注解
    1、@interface用于声明注解,参数只用八种基本数据类型和四种数据类型(基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和String,Enum,Class,annotations),如果只有一个参数成员,最好把参数名称设为"value"2、@Target说明了Annotation所修饰的对象范围,......
  • java XSSFWorkbook excel 公式计算
    excel公式计算//创建一个工作薄XSSFWorkbookworkbook=newXSSFWorkbook();//如果是最后一列添加一个求和计算,将结果放到同一列最后一个。dataLists数据列表XSSFSheetsheet=workbook.getSheet(replaceSpecStr(sheetNames.get(0)));Rowrow......
  • JavaSE概览
    一、JavaSE基础day01#day011.Java语言发展史2.Java语言跨平台原理3.JRE和JDK4.常用DOS命令5.HelloWorld案例6.注释7.关键字8.常量9.数据类型10.变量11.变量使用的注意事项12.标识符13.类型转换day02#day021.算数运算符2.字符的+操作3.字......
  • idea的vim配置
    idea的vim配置"================================================================================================"=Extensions====================================="===========================================================================......
  • [Android] wifi管理之WifiManager
    1.WifiManager简介WifiManager是Android系统中负责管理WiFi网络的一个重要服务。它提供了一系列方法,允许开发者搜索、连接、保存、删除WiFi网络,还可以获取当前WiFi的连接状态、信号强度等信息。主要功能:连接到一个特定的WiFi网络:你可以使用WifiManager的addNetwork(WifiConfigurat......
  • Android TTS学习——继续爱的表白(转)
    一. 简单介绍在上一篇里 我们讲到了TTS 最主要的一个APIpublicintspeak(String   text,intqueueMode,   HashMap<String,   String>params)其中我们介绍了前两个参数,第三个参数设置了null而且我们在介绍AndroidTTS 提供的功能时,说到TTS 提供了两个接口,第......
  • Android TTS学习——用五种外语说出“我爱你”(转)
    一. 简单介绍在上一篇里我们简单的介绍了Android里的TTS功能并实现了一个最简单的Demo例子--AndroidTTSDemoFirst,在这篇文章中我们将具体介绍用到的TTS API,并给上一个Demo增加语言选择功能,可以使用TTS引擎支持的 English、 French 、 German 、 Italian 和 Spanish 这 ......
  • Android TTS学习——保存对你的喜欢(转)
    一. 简单介绍在上一篇里我们介绍了TTS提供的接口 OnUtteranceCompletedListener 的使用,这个接口的作用是监听语音片段的朗读,并在语音片段朗读结束后调用其定义的回调函数,在回调函数里可以进行需要的操作。在这一篇里我们介绍一下TTS提供的另一个有用的功能,把合成的语音以音频文......
  • JavaSE--多态在开发中的作用
    一、多态在开发中的作用  1、降低程序的耦合度,提高程序的扩展力  publicclassMaster(){    publicvoidfeed(Dogd){}    publicvoidfeed(Dogd){}  }  以上代码中:Master和Dog、Cat关系紧密,耦合度高,导致扩展力很差  publicclassMaster(){   ......