业务系统经常需要生成各种唯一ID,想到UUID、雪花算法等;
UUID字符没有含义,掏出来给客户看,比较的很丑;
雪花算法是64位的,小业务用户感觉太长了,有点不满意;
琢磨了好些天,自己写了一个,完成初步测试没有重复;把代码贴出来请各位程序大佬指教;欢迎留意给出各种意见;
代码如下:
/** * @Author PeterShen * @Date 2022/9/17 9:32 * @Description 构建唯一ID:当前时间的秒数(12)+随机(2)+IP信息(6)+自增(1-7) * @Version 1.0 */ public class IdBuildHelper { /** * 计数的最大值 1千万(最大七位) * 防止计数爆掉 */ static final int MAX_COUNT = 9989999; static final Object lockObject = new Object(); /** * 随机数生成 */ static Random random = new Random(); static SimpleDateFormat formatter = new SimpleDateFormat("yyMMddHHmmss"); /** * 每秒自增计数计数器 */ static volatile AtomicInteger counter = new AtomicInteger(0); /** * 当前的秒数,必须存储为秒级,不能是毫秒级,防止频繁换秒操作 */ static volatile long currentSecond = CurrentTimeMillisClock.getInstance().now() /1000; //服务的端口 static int serverPort = 80; //服务的IP地址 static String serviceIp = null; /** * 根据日期构建一个唯一ID * 由日期(12位)+两位随机+六位IP信息+自增(1-7位) 总位数:21-27位 * @return */ public static String buildId() { long second; int i; synchronized (lockObject){ //加锁获取一份生成ID因子 second = currentSecond; i = counter.incrementAndGet(); } //根据当前日期判断是否需要换秒, Long integer = CurrentTimeMillisClock.getInstance().now() /1000; //为了避免重复,日期正常只能往前推进,如果改了过去的时间,一开始拒绝重置计数,直到计数达到最大值,不得已才重置 if (currentSecond < integer || counter.get() > MAX_COUNT) { synchronized (lockObject){ if (currentSecond < integer || counter.get() > MAX_COUNT) { //重置计数 counter.set(0); //换秒 if (currentSecond < integer) { currentSecond = integer; } else if (counter.get() > MAX_COUNT) { //计数满,强制换秒 currentSecond++; } } } } //根据因子生成 return formatter.format(second*1000) + getTowBitRandom() + getIpStr() + i; } /** * 获取两位随机数 * * @return */ private static String getTowBitRandom() { return String.format("%02d", random.nextInt(99)); } /** * 获取日期秒级(12位)字符串 * * @return */ private static String getDateSecondStr(long second) { return formatter.format(second*1000); } /** * 获取IP后面两段(6位) * 注意:这里有个假设,多个服务实例会不是在同一个网段 * * @return */ private static String getIpStr() { if (serviceIp == null) { try { InetAddress address = InetAddress.getLocalHost(); String ip = address.getHostAddress(); //获取IP后面两段 String[] split = ip.split("\\."); if (split.length > 0) { int n = Integer.valueOf(split[split.length - 2] + split[split.length - 1]); //与服务端口做异或运算,隐藏真实IP n = (n ^ serverPort) << 1; serviceIp = String.format("%06d", n); } else { serviceIp = ""; } } catch (UnknownHostException e) { serviceIp = ""; } } return serviceIp; } /** * 设置服务器执行端口:默认80 * 为了防止同一个IP 用不同端口启用多个实例,建议填入 */ public static void setServerPort(int port) { IdBuildHelper.serverPort = port; } }
对应的测试代码:
@Test void multiThreadTest() throws InterruptedException { Set<String> codeSet = new ConcurrentHashSet<>(5000000); Thread[] threads = new Thread[10]; for (int i = 0; i < 10; i++) { Thread t = new Thread(new Runnable() { @Override public void run() { System.out.println( Thread.currentThread().getName()+"开始执行"); for (int j = 0; j < 500000; j++) { String s = IdBuildHelper.buildId(); boolean add = codeSet.add(s); if(!add){ System.out.println("发生重复"+ s+":"+j); } } System.out.println( Thread.currentThread().getName()+"执行完成"); System.out.println("容器内容数:"+ codeSet.size()); } }); t.setName("线程"+i); threads[i] = t; } for (Thread thread : threads) { thread.start(); } for (Thread thread : threads) { thread.join(); } if(codeSet.size()<5000000){ System.out.println("生成异常"); } codeSet.clear(); }
感受:
几行简单的代码真的琢磨了好久,反复改了好多次;主要反复的点是在:1.多线程重复问题;2.生成性能问题;3.生成的ID合适规范性的问题;然后感觉自己能力有限,总是考虑不周到,需要继续学习;
如您在阅读时遇到任何疑问也欢迎留言,感觉这是个有趣的东西,所以贴出来,总之欢迎多交流。
标签:String,Thread,生成器,static,split,毛病,new,ID From: https://www.cnblogs.com/splyn/p/16853964.html