文档章节

java单机生成不重复增长15位流水号方式

北极的晓风
 北极的晓风
发布于 2014/11/23 19:00
字数 1323
阅读 555
收藏 1

开发中,经常需要获取一些流水号,或称为ID,这些ID经常无业务意义,仅仅是为了区分不同的实体或数据库中的记录。UUID提供了一种非常好的策略,考虑到了时间、机器ID(涉及CPU编号、网卡MAC等等物理信息)、随机值等信息,能生成在时空中“唯一”的字符串,需要注意,此唯一并不是绝对唯一,如果不考虑主动造假和作弊,目前能保证唯一。

在具体的开发中,UUID生成的UUID字符串过长,不太适合业务场景。而且如果是单机场景,还有点浪费。

我在开发过程中,使用了这样一种方法,来解决唯一ID的生成,又不至于太长。

1)获取系统当前时间的毫秒值,系统一般返回的是Long型,将该值转换成16进制,目的是为了缩小生成结果字符串的长度。

2)使用一个全局int变量,每次获取唯一ID时,该变量自增 1,并追加到第一步中的字符串后面。如果想保持获取到的字符串长度一致,可以补0字符串。

在上面所述的方案中,为了在单位时间内生成的唯一ID尽量多,将自增的int值转换成36进制(10位数字+26位字母),这样就使单位毫秒内生成的唯一ID数量在36的N次方个。N=打算生成多少位字符串。如果打算生成15位唯一ID,则当前时间会占去11位(16进制的字符形式),4位36进制字符,大约在160万个。也就是说,1 毫秒内生成 160万个字符不重复。

此种方案主要的核心是:如何保证全局共享的int自增数值在多线程环境下,能原子性的自增呢?答案是 同步控制 或者 AtomicInteger类。

简单的实现和测试类如下:

思路如下:1)main方法中启动了20个线程,每个线程循环获取100个字符串,并将字符串放置入 全局的hashSet中,最后打印出hashSet的长度。如果恰好是2000,则证明没问题,否则存在问题;2)生成15位字符串的方法是 获取当前时间毫秒值,并转换成16进制,然后使用AtomicInteger的自增并获取的原子方法。最后补全位数15位。

import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 
 * 流水号生成器
 * 
 * @project 
 * @version v1.0
 * @author wangboak (wangboak@126.com)
 * @createDate 2014年10月7日 下午2:01:00
 * @company (C) Copyright 迅谨(北京)科技有限公司 2014-2114<br/>
 * @since JDK 1.7
 *
 */
public class SNUtil {

	private final static String str62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
	private final static int pixLen = 36;
	private static volatile int pixOne = 0;
	private static volatile int pixTwo = 0;
	private static volatile int pixThree = 0;
	private static volatile int pixFour = 0;

	private static final AtomicInteger ATOM_INT = new AtomicInteger(0);
	private static final int MAX_36 = 36 * 36 * 36 * 36;

	/**
	 * 生成短时间内不会重复的长度为15位的字符串。<br/>
	 * 生成策略为获取自1970年1月1日零时零分零秒至当前时间的毫秒数的16进制字符串值,该字符串值为11位<br/>
	 * 并追加四位"0-z"的自增字符串.<br/>
	 * 如果系统时间设置为大于<b>2304-6-27 7:00:26<b/>的时间,将会报错!<br/>
	 * 由于系统返回的毫秒数与操作系统关系很大,所以本方法并不准确。本方法可以保证在系统返回的一个毫秒数内生成36的4次方个(1679616)ID不重复。<br/>
	 * 经过测试:该方法效率 比 create15_2 方法快一倍
	 * @return 15位短时间不会重复的字符串。<br/>
	 * @author wangbo
	 * @since JDK1.6
	 */
	public final static synchronized String create15() {
		StringBuilder sb = new StringBuilder(15);// 创建一个StringBuilder
		sb.append(Long.toHexString(System.currentTimeMillis()));// 先添加当前时间的毫秒值的16进制
		pixFour++;
		if (pixFour == pixLen) {
			pixFour = 0;
			pixThree++;
			if (pixThree == pixLen) {
				pixThree = 0;
				pixTwo++;
				if (pixTwo == pixLen) {
					pixTwo = 0;
					pixOne++;
					if (pixOne == pixLen) {
						pixOne = 0;
					}
				}
			}
		}
		return sb.append(str62.charAt(pixOne)).append(str62.charAt(pixTwo)).append(str62.charAt(pixThree)).append(str62.charAt(pixFour))
				.toString();
	}

	@SuppressWarnings("unused")
	private final static String create15_2() {
		StringBuilder sb = new StringBuilder(15);
		sb.append(Long.toHexString(System.currentTimeMillis()));
		ATOM_INT.compareAndSet(MAX_36, 0);
		String str = longTo36(ATOM_INT.incrementAndGet());
		if (str.length() == 1) {
			sb.append("000").append(str);
		} else if (str.length() == 2) {
			sb.append("00").append(str);
		} else if (str.length() == 3) {
			sb.append("0").append(str);
		} else {
			sb.append(str);
		}
		return sb.toString();
	}

	/**
	 * 10进制转任意进制
	 * @param num Long型值
	 * @param base 转换的进制
	 * @return 任意进制的字符形式
	 */
	private static final String ten2Any(long num, int base) {
		StringBuilder sb = new StringBuilder(7);
		while (num != 0) {
			sb.append(str62.charAt((int) (num % base)));
			num /= base;
		}
		return sb.reverse().toString();
	}

	/**
	 * 将一个Long 值 转换为 62进制
	 * @param num
	 * @return 
	 */
	public static final String longTo62(long num) {
		return ten2Any(num, 62);
	}

	private static final String longTo36(long num) {
		return ten2Any(num, 36);
	}
}


© 著作权归作者所有

共有 人打赏支持
北极的晓风
粉丝 3
博文 8
码字总数 3730
作品 0
海淀
项目经理
私信 提问
加载中

评论(2)

北极的晓风
北极的晓风

引用来自“Anger_Coder”的评论

你可以不加锁呀,你让所有方法顺序执行就好了,不用多线程呀!
可是在真实的环境中,是多线程情况。得保证在多线程情况下 获得唯一的ID。问题已经找见了,问题出在测试使用的集合HashSet,该类不是线程安全的,其add方法不保证线程安全。所以测试结果错误。
Anger_Coder
Anger_Coder
你可以不加锁呀,你让所有方法顺序执行就好了,不用多线程呀!
数据库中间件 Sharding-JDBC 源码分析 —— 分布式主键

概述 本文分享 Sharding-JDBC 分布式主键实现。 给大家推荐一个程序员学习扣群:854818273。群里有分享的视频,还有思维导图 群公告有视频,都是干货的,你可以下载来看。主要分享分布式架构...

java知识分子
10/26
0
0
java js 身份证号码处理

使用java将20年代的中国15位身份证号转换为18位的身份证号。 使用JavaScript将20年代的中国15位身份证号转换为18位的身份证号。 附一个java编写的身份证号码处理工具类IdCardUtil:...

Arthur126
2015/08/21
0
0
海量连接服务端jvm参数调优杂记

应用:shark-新美大移动端网络优化(每日接受移动端请求约150亿) 应用特点: qps比较高,新生代增长飞快 用户的连接需要维持一段时间 单机需要维持海量连接,几十万的级别 以上三个特点导致有...

闪电侠_
07/27
0
0
[XMPP]我是怎么通过直接操作数据来为Openfire注册新用户的

众所周知,Openfire的注册方式一般有三种: 1.带内注册 ---- In-Band Registration. 即客户端通过匿名方式与Openfire 服务器端建立连接并验证,然后发起注册节点XML流,以XMPPStream的方法直接...

长平狐
2013/09/17
7.6K
1
大四Java复习笔记之Java基础

一、static和final 学习Java那么久,好像自己就没有怎么用过final,所以对fianl的理解不够。final不但出现在变量的修饰里面,还可以出现在方法和类的修饰。final类不能被继承,因此final类的...

lgl梁
2014/11/17
0
2

没有更多内容

加载失败,请刷新页面

加载更多

docker搞个wordpress

1.先把wordpress的镜像下载下来 docker pull wordpress 2.下载mysql docker pull mysql:lastest 3.启动mysql docker run --name blog -e root -d mysql:5.7 docker run --name some-mysql -e......

无极之岚
4分钟前
0
0
【宇润日常疯测-005】PHP 中的 clone 和 new 性能比较

clone和new本不应该放在一起比较,它们的作用是不同的。但可能有一些场景下,可以用clone也可以用new,那么这时候我们选哪个呢? 我编写了两个测试,第一个是声明一个空类,第二个是带构造方...

宇润
5分钟前
0
1
点击按钮弹出类似IOS 底部 dialog

implementation 'com.baoyz.actionsheet:library:1.1.7' 然后设置按钮点击监听,,调用下列代码即可 ActionSheet.createBuilder(this, getSupportFragmentManager()) ......

lanyu96
8分钟前
0
0
专访阿里云专有云马劲,一个理性的理想主义者

“我的故事都是和团队技术相关的,自己还真没有什么引人入胜的故事。”当马劲被问到能不能多分享些个人经历故事时他笑着说,我们就干脆怀着好奇聊了聊他和阿里云专有云一路走来的故事。 马劲...

阿里云官方博客
40分钟前
1
0
java环形缓冲区

import java.util.ArrayList;import java.util.List;/** * * 环形缓冲区<br/> * 一. 写数据:<br/> * 1. push: 当数据已写满时返回false,否则可以正常写入返回true<br/>......

whoisliang
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部