以下是要求:
必须是字母数字,8-10 个字符,以便用户友好。这些将作为唯一键存储在数据库中。我使用 Guids 作为主键,因此使用 GUIds 生成这些唯一 Id 的选项会更可取。
我正在考虑采用 Guid 并转换为 8 个字符的唯一字符串的 base-n 转换器的行。
首选短的、轻量级的算法,因为它经常被调用。
8 characters - perfectly random - 36^8 = 2,821,109,907,456 combinations
10 characters - perfectly random - 36^10 = 3,656,158,440,062,976 combinations
GUID's - statistically unique* - 2^128 = 340,000,000,000,000,000,000,000,000,000,000,000,000 combinations
您的 GUID -> 字符转换问题;虽然您的 GUID 在统计上是唯一的,但通过采用任何子集,您可以减少随机性并增加冲突的机会。您当然不想创建非唯一的 SKU。
解决方案1:
使用与对象和业务规则相关的数据创建 SKU。
即,可能存在使对象唯一(自然键)的属性的小组合。结合自然键的元素,对它们进行编码和压缩以创建 SKU。通常,您只需要一个日期时间字段(即 CreationDate)和一些其他属性即可实现此目的。您在创建 sku 时可能有很多漏洞,但 sku 与您的用户更相关。
假设:
Wholesaler, product name, product version, sku
Amazon, IPod Nano, 2.2, AMIPDNN22
BestBuy, Vaio, 3.2, BEVAIO32
解决方案2:
一种方法,它保留一系列数字,然后继续按顺序释放它们,并且永远不会返回相同的数字两次。您仍然可以在该范围内留下漏洞。虽然您可能不需要生成足够多的 sku,但请确保您的要求允许这样做。
一种实现是key
在具有计数器的数据库中拥有一个表。计数器在事务中递增。重要的一点是,软件中的方法不是增加 1,而是抓取一个块。伪c#代码如下。
-- what the key table may look like
CREATE TABLE Keys(Name VARCHAR(10) primary key, NextID INT)
INSERT INTO Keys Values('sku',1)
// some elements of the class
public static SkuKeyGenerator
{
private static syncObject = new object();
private static int nextID = 0;
private static int maxID = 0;
private const int amountToReserve = 100;
public static int NextKey()
{
lock( syncObject )
{
if( nextID == maxID )
{
ReserveIds();
}
return nextID++;
}
}
private static void ReserveIds()
{
// pseudocode - in reality I'd do this with a stored procedure inside a transaction,
// We reserve some predefined number of keys from Keys where Name = 'sku'
// need to run the select and update in the same transaction because this isn't the only
// method that can use this table.
using( Transaction trans = new Transaction() ) // pseudocode.
{
int currentTableValue = db.Execute(trans, "SELECT NextID FROM Keys WHERE Name = 'sku'");
int newMaxID = currentTableValue + amountToReserve;
db.Execute(trans, "UPDATE Keys SET NextID = @1 WHERE Name = 'sku'", newMaxID);
trans.Commit();
nextID = currentTableValue;
maxID = newMaxID;
}
}
这里的想法是您保留足够的键,这样您的代码就不会经常进入数据库,因为获取键范围是一项昂贵的操作。您需要很好地了解需要保留的密钥数量,以平衡密钥丢失(应用程序重新启动)与过快耗尽密钥并返回数据库。这种简单的实现无法重用丢失的密钥。
因为这个实现依赖于数据库和事务,所以您可以让应用程序同时运行并且都生成唯一键,而无需经常访问数据库。
请注意,以上内容大致基于Patterns of Enterprise Application Architecture (Fowler)key table
第 222 页。该方法通常用于在不需要数据库标识列的情况下生成主键,但您可以看到它如何适应您的目的。
您可能会考虑以36为底,因为它可以处理字母和数字。考虑从你的集合中移除 I(眼睛)和 O(哦),这样它们就不会与 1(一)和 0(零)混淆。有些人可能也会抱怨 2 和 Z。
如果您正在寻找“用户友好”,您可能想尝试使用整个单词,而不是简单地将其设为短/字母数字,因此,类似于:
words = [s.strip().lower() for s in open('/usr/share/dict/canadian-english') if "'" not in s]
mod = len(words)
def main(script, guid):
guid = hash(guid)
print "+".join(words[(guid ** e) % mod] for e in (53, 61, 71))
if __name__ == "__main__":
import sys
main(*sys.argv)
产生如下输出:
oranjestad+compressing+wellspring
padlock+discommoded+blazons
pt+olenek+renews
这很有趣。否则,简单地获取 guid 的前 8-10 个字符或 guid 的 sha1/md5 哈希可能是您最好的选择。
可能工作的最简单的事情是每次需要值时递增的计数器。八个(左填充零)数字为您提供 1 亿个可能的值,从 00000000 到 99999999(尽管您可能会插入空格或连字符以提高可读性,如 000-000-00)。
如果您需要超过 1 亿个值,您可以增加长度或在其他位置使用字母。使用 A0A0A0A0 到 Z9Z9Z9Z9 可为您提供超过 45 亿个可能的值 (4,569,760,000)。采用长整数并产生这样的编码是很简单的代码(最右边的数字为 10,最右边的数字为 10,然后最右边的字母为 26,等等)如果你有内存要刻录,最快的方法是将计数器转换为 mod 260 数组,并使用每个 mod 260 值作为索引到两个字符串数组(“A0”、“A1”、“A2”等到“A9”、“ B0”、“B1”等到“Z9”)。
base 36 的问题(在另一个回复中提到)是您不仅要担心读者混淆相似字符(一与 I,零与 O,二与 Z,五与 S),还要担心组合可能被读者视为拼写令人反感或淫秽的单词或缩写的相邻字母。
您可能想尝试使用 CRC32 散列算法。CRC32 生成一个 8 个字符的字符串。