最近在某微信技术群,有人问到如何生成5位唯一数字+字母字符串的算法,要保证生成的字符串唯一,且字符串内部也要唯一。

  怎么样,这个需求是不是很简单,也有点特殊呢?简单是指需求简单,特殊是指,字符串长度要求是5位,而不是更长。根据群里的讨论,有以下解决方案:

  • UUID生成随机字符串,并控制字符串长度。估计这个群友是指UUID.randomUUID().toString.substring(0,5) 吧,不过这样是没法达到唯一的。也就是说每次生成的字符串可能重复。
  • apache的StringUtils。我在org.apache.commons.lang3.StringUtils这个类里面没有找到相关的函数。
  • 雪花算法。毫无疑问,雪花算法是可以的,不过没法保证是5位。或许这位兄台的需求有点奇葩了,还必须要求5位。
  • 内存表或数据库表。其实我是比较赞同这种方案的,一共就5位字符和数字,排列组合一下,不考虑内部唯一,一共就pow(36,5).=60466176。6千万数据保存到内存中去,用的时候取一条,用完为止。如果考虑内部唯一,那就是36*35*34*33*32=45239040,4千万的表。不过从表里面去,会有并发的问题。

  其实群友给的方案都是不错的,除了那个StringUtils,但我思考这个问题就比较简单、粗暴了。一共5位长度,要求字符串+数字,也就4千万的事儿,4千万很大妈?说实话,真不大。那怎么搞的?计数器就可以了!

  啥 ?计数器,你确定没说错?嗯,没说错。我刚提出这个方案的时候,就有脑残、喷子开始无脑的怼我了,不等我说完就开始怼我了。那计数器真的就不行吗?

  其实我个人比较倾向于上面的第五种方案,也就是把所有可用的字符串算出来,然后再取就行了。那么这样做有一个不好的地方就是,比较占用内存,大概215MB,说实话吧,这215MB也不算大,如果是我直接就用内存表了。不过还能不能优化一下内存空间?答案是能,那就是用计数器。

  其实我们在生成这4千万的字符串的时候,就是用计数器算的啊,就是从0到36^5,把数字转换成36进制,然后判断是否有重复字符串,这个过程就用了计数器。那能不能用计数器动态计算下一个符合条件的字符串呢?当然可以了啊。

object LimitStringIdGenerator{
private [idgenerator] val MaxStringLength = 5
private [idgenerator] val MaxNumberOfString = math.pow(36,5).toInt
private [idgenerator] def to36String(num:Int):String = BigInt(num).toString(36)
private [idgenerator] def notHasSameChar(charSequence: String):Boolean = charSequence.toCharArray match {
case Array(a,b,c,d,e) if ! ( a == b || a == c || a == d || a == e || b ==c || b == d || b ==e || c ==d || c ==e || d == e) => true
case Array(a,b,c,d) if ! ( a == b || a == c || a == d || b ==c || b == d || c ==d) => true
case Array(a,b,c) if ! ( a == b || a == c || b == c ) => true
case Array(a,b) if ! ( a == b ) => true
case _ => false
}
}
class LimitStringIdGenerator extends IDGenerator {
import LimitStringIdGenerator._
private var start = 0
override def nextId:Int = {
var nextId = start + 1
while(!notHasSameChar(to36String(nextId)) && nextId < MaxNumberOfString){
nextId = nextId + 1
} nextId
} override def setStart(start: Int): Unit = {
this.start = start
}
}

  上面是可执行的代码,非常简单,就是用一个nextId做计数器,依次递增,找到下一个符合条件的字符串。其实上面的代码还是可以优化一下的,为啥呢?因为有时候计数器的步长不一定是1,此话怎讲呢?

  我们来看看当计数器的值是01000时,步长是1会造成很大浪费,因为下一个可用的字符串是01234;当记数字的值是11000时,下一个可用的字符串值是12345,如果步长为1也会造成很大的浪费。

  那有人会问了,并发时候怎么办呢?如果停机再启动怎么办呢?嗯,这两个问题问的非常好,不过就有点刁难的意味了,为啥呢?因为楼主只是需要一个算法来获取5位唯一字符串而已,而且数据量不大。但我还是很喜欢这两个问题的。

  我们先来看并发的问题。为啥需要并发呢?有时候是效率的考虑,并发计算会稍微快点;还有时候是多线程导致的;当然还有其他原因。

  其实上面的算法效率已经非常高了,为啥?因为就是对变量自增,然后做个判断而已。经测试,把36^5月6千1百万的数据计算一遍,大概28秒,计算下一个可用的字符串,最长耗时大概400毫秒。那多线程的时候怎么办呢?那就用AtomicInteger来替换呗,应该可以保证线程安全。

  那停机再启动怎么办?其实吧,你停机的时候把nextId保存到数据库、缓存或者其他第三方存储,不就好了?啥,你说程序是异常宕机,没有保存?那就根据生成的最后一个字符串反推一下当前计数器的值喽。啥?你生成的字符串没保存?那你用来干啥呢?

  当然还是可以分布式的,就是有多个节点,每个节点都只从某个值开始计算下一个可用字符串,不过也没啥必要,一共4千万字符串,很快就会耗尽了。

  脑残粉和喷子就不要留言了,当然如果你有更好的方案,还是可以发个博客怼一怼的,如果没有那就闭嘴。

  

5位ID生成方案的更多相关文章

  1. 【php】mysql全局ID生成方案

    生产系统随着业务增长总会经历一个业务量由小变大的过程,可扩展性是考量数据库系统高可用性的一个重要指标;在单表/数据库数据量过大,更新量不断飙涨时,MySQL DBA往往会对业务系统提出sharding ...

  2. MySQL分库分表环境下全局ID生成方案 转

    在大型互联网应用中,随着用户数的增加,为了提高应用的性能,我们经常需要对数据库进行分库分表操作.在单表时代,我们可以完全依赖于数据库的自增ID来唯一标识一个用户或数据对象.但是当我们对数据库进行了分库 ...

  3. MySQL分库分表环境下全局ID生成方案

    在大型互联网应用中,随着用户数的增加,为了提高应用的性能,我们经常需要对数据库进行分库分表操作.在单表时代,我们可以完全依赖于数据库的自增ID来唯一标识一个用户或数据对象.但是当我们对数据库进行了分库 ...

  4. 【转】MySQL分库分表环境下全局ID生成方案

    转载一篇博客,里面有很多的知识和思想值得我们去思考. —————————————————————————————————————————————————————————————————————— 在大 ...

  5. 分布式唯一ID生成方案是什么样的?(转)

    一.前言 分布式系统中我们会对一些数据量大的业务进行分拆,如:用户表,订单表.因为数据量巨大一张表无法承接,就会对其进行分库分表. 但一旦涉及到分库分表,就会引申出分布式系统中唯一主键ID的生成问题, ...

  6. 基于数据库构建分布式的ID生成方案

    在分布式系统中,生成全局唯一ID,有很多种方案,但是在这多种方案中,每种方案都有有缺点,下面我们之针对通过常用数据库来生成分布式ID的方案,其它方法会在其它文中讨论: 1,RDBMS生成ID: 这里我 ...

  7. 搞懂分布式技术12:分布式ID生成方案

    搞懂分布式技术12:分布式ID生成方案 ## 转自: 58沈剑 架构师之路 2017-06-25 一.需求缘起 几乎所有的业务系统,都有生成一个唯一记录标识的需求,例如: 消息标识:message-i ...

  8. 【系统设计】分布式唯一ID生成方案总结

    目录 分布式系统中唯一ID生成方案 1. 唯一ID简介 2. 全局ID常见生成方案 2.1 UUID生成 2.2 数据库生成 2.3 Redis生成 2.4 利用zookeeper生成 2.5 雪花算 ...

  9. 分布式id生成方案总结

    本文已经收录自 JavaGuide (60k+ Star[Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识.) 本文授权转载自:https://juejin.im/post/ ...

随机推荐

  1. P1918 保龄球 洛谷

    https://www.luogu.org/problem/show?pid=1918 题目描述 DL 算缘分算得很烦闷,所以常常到体育馆去打保龄球解闷.因为他保龄球已经打了几十年了,所以技术上不成问 ...

  2. [Bzoj4260]Codechef REBXOR(trie树)

    4260: Codechef REBXOR Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 1534  Solved: 669[Submit][Stat ...

  3. Excel中INDEX函数的使用

    1 函数INDEX( )有两种形式: 数组形式——INDEX(array,row_num,column_num)和 引用形式——INDEX(reference,row_num,column_num,a ...

  4. 常见Python运行时错误

    1)忘记在 if , elif , else , for , while , class ,def 声明末尾添加 :(导致 “SyntaxError :invalid syntax”) 该错误将发生在 ...

  5. 9.Laravel5学习笔记:在laravel中注冊自己的服务到容器中

    问题描写叙述 或许标题写的不够清楚.实际情况是,在我使用laravel的过程中.须要将自己的一个类,通过服务提供者注冊到IOC容器中,可是在实际操作过程中.出现了下面错误: Unresolvable ...

  6. javascript是什么?

    JavaScript 是脚本语言 JavaScript 是一种轻量级的编程语言. JavaScript 是可插入 HTML 页面的编程代码. JavaScript 插入 HTML 页面后,可由全部的现 ...

  7. Navicat for MySQL出现#1045 错误怎么办

    #1045 - Access denied for user 'root'@'localhost' (using password: NO)这是因为你连接的时候没有密码或者密码没改对导致的.如下图所示 ...

  8. Android开发——本地验证码的简易实现

    0.  前言   验证码无处不在.有人问我,你知道达芬奇password以下是什么吗,对.答案就是达芬奇验证码. 验证码一个最基本的作用就是防止恶意暴力破解登录,防止不间断的登录尝试,事实上能够在se ...

  9. 说说Android应用的persistent属性(转)

    1 启动persistent应用 在Android系统中,有一种永久性应用.它们对应的AndroidManifest.xml文件里,会将persistent属性设为true,比如: <appli ...

  10. hdu 3853(数学期望入门)

    题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=3853 LOOPS Time Limit: 15000/5000 MS (Java/Others)    ...