Hashids是一个将数字转化为长度较短、唯一且不连续的值的库。特点是:
进制转换:将10进制的整数转化为 62 进制(26个字母大小写+10个数字),可扩展为任意进制。
private static String hash(long input, String alphabet) {
String hash = "";
final int alphabetLen = alphabet.length();
do {
final int index = (int) (input % alphabetLen);
if (index >= 0 && index < alphabet.length()) {
hash = alphabet.charAt(index) + hash;
}
input /= alphabetLen;
} while (input > 0);
return hash;
}
Fisher-Yates Shuffle算法
其基本思想就是从原始数组中随机取一个之前没取过的数字到新的数组中,具体如下:
Hashids 使用了 Fisher–Yates 洗牌算法的变种。从原始数组中随机取一个之前没取过的数字与当前数组最后一个元素交换位置。
private static Random random = new Random();
private static String shuffle(String alphabet) {
char[] shuffle = alphabet.toCharArray();
for (int i = shuffle.length - 1; i >= 1; i--) {
int j = random.nextInt(i+1);
char tmp = shuffle[i];
shuffle[i] = shuffle[j];
shuffle[j] = tmp;
}
return new String(shuffle);
}
<dependency>
<groupId>org.hashids</groupId>
<artifactId>hashids</artifactId>
<version>1.0.3</version>
</dependency>
代码解析
import java.util.ArrayList;
public class Hashids {
// Hashids 能够加密的最大值
public static final long MAX_NUMBER = 9007199254740992L;
// 默认编解码字符串
private static final String DEFAULT_ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
// 默认分隔符
private static final String DEFAULT_SEPS = "cfhistuCFHISTU";
// 默认盐值
private static final String DEFAULT_SALT = "";
// 默认最小加密后字符串的长度
private static final int DEFAULT_MIN_HASH_LENGTH = 0;
// 最小编解码字符串
private static final int MIN_ALPHABET_LENGTH = 16;
//
private static final double SEP_DIV = 3.5;
//
private static final int GUARD_DIV = 12;
// 编解码盐值
private final String salt;
// 编码后最小的字符长度
private final int minHashLength;
// 编解码字符串
private final String alphabet;
// 多个数字编解码的分界符
private final String seps;
// 补齐至 minHashLength 长度添加的字符列表
private final String guards;
// 以上 3 个字符串中的字符是互不包含关系
public Hashids() {
this(DEFAULT_SALT);
}
public Hashids(String salt) {
this(salt, 0);
}
public Hashids(String salt, int minHashLength) {
this(salt, minHashLength, DEFAULT_ALPHABET);
}
public Hashids(String salt, int minHashLength, String alphabet) {
this.salt = salt != null ? salt : DEFAULT_SALT;
this.minHashLength = minHashLength > 0 ? minHashLength : DEFAULT_MIN_HASH_LENGTH;
// alphabet 中的字符需要唯一
final StringBuilder uniqueAlphabet = new StringBuilder();
for (int i = 0; i < alphabet.length(); i++) {
if (uniqueAlphabet.indexOf(String.valueOf(alphabet.charAt(i))) == -1) {
uniqueAlphabet.append(alphabet.charAt(i));
}
}
alphabet = uniqueAlphabet.toString();
// 最小长度检查
if (alphabet.length() < MIN_ALPHABET_LENGTH) {
throw new IllegalArgumentException(
"alphabet must contain at least " + MIN_ALPHABET_LENGTH + " unique characters");
}
// 编解码表不能含有空格
if (alphabet.contains(" ")) {
throw new IllegalArgumentException("alphabet cannot contains spaces");
}
// seps 只能包含 alphabet 中的字符
// alphabet 不能包含 seps 中的字符
String seps = DEFAULT_SEPS;
for (int i = 0; i < seps.length(); i++) {
final int j = alphabet.indexOf(seps.charAt(i));
if (j == -1) {
seps = seps.substring(0, i) + " " + seps.substring(i + 1);
} else {
alphabet = alphabet.substring(0, j) + " " + alphabet.substring(j + 1);
}
}
alphabet = alphabet.replaceAll("\\s+", "");
seps = seps.replaceAll("\\s+", "");
// 根据 salt 打乱默认 seps 的顺序
seps = Hashids.consistentShuffle(seps, this.salt);
if ((seps.isEmpty()) || (((float) alphabet.length() / seps.length()) > SEP_DIV)) {
int seps_len = (int) Math.ceil(alphabet.length() / SEP_DIV);
if (seps_len == 1) {
seps_len++;
}
if (seps_len > seps.length()) {
final int diff = seps_len - seps.length();
seps += alphabet.substring(0, diff);
alphabet = alphabet.substring(diff);
} else {
seps = seps.substring(0, seps_len);
}
}
// 根据 salt 打乱编解码表 alphabet 的顺序
alphabet = Hashids.consistentShuffle(alphabet, this.salt);
// use double to round up
final int guardCount = (int) Math.ceil((double) alphabet.length() / GUARD_DIV);
String guards;
if (alphabet.length() < 3) {
guards = seps.substring(0, guardCount);
seps = seps.substring(guardCount);
} else {
guards = alphabet.substring(0, guardCount);
alphabet = alphabet.substring(guardCount);
}
this.guards = guards;
this.alphabet = alphabet;
this.seps = seps;
}
// 将数字加密为字符串
public String encode(long... numbers) {
if (numbers.length == 0) {
return "";
}
for (final long number : numbers) {
// 只能加解密非负整数
if (number < 0) {
return "";
}
if (number > MAX_NUMBER) {
throw new IllegalArgumentException("number can not be greater than " + MAX_NUMBER + "L");
}
}
return this._encode(numbers);
}
// 将字符串解密为数字
public long[] decode(String hash) {
if (hash.isEmpty()) {
return new long[0];
}
String validChars = this.alphabet + this.guards + this.seps;
for (int i = 0; i < hash.length(); i++) {
if (validChars.indexOf(hash.charAt(i)) == -1) {
return new long[0];
}
}
return this._decode(hash, this.alphabet);
}
private String _encode(long... numbers) {
long numberHashInt = 0;
for (int i = 0; i < numbers.length; i++) {
numberHashInt += (numbers[i] % (i + 100));
}
String alphabet = this.alphabet;
// 第一位不参与编解码
final char ret = alphabet.charAt((int) (numberHashInt % alphabet.length()));
long num;
long sepsIndex, guardIndex;
String buffer;
final StringBuilder ret_strB = new StringBuilder(this.minHashLength);
ret_strB.append(ret);
char guard;
for (int i = 0; i < numbers.length; i++) {
num = numbers[i];
buffer = ret + this.salt + alphabet;
alphabet = Hashids.consistentShuffle(alphabet, buffer.substring(0, alphabet.length()));
final String last = Hashids.hash(num, alphabet);
ret_strB.append(last);
// 不是最后一个待加密数字,增加分隔符
// 按照一定规则在 seps 选择一位分隔符,如何选择无所谓,因为 seps 不存在于 alphabet
if (i + 1 < numbers.length) {
if (last.length() > 0) {
num %= (last.charAt(0) + i);
sepsIndex = (int) (num % this.seps.length());
} else {
sepsIndex = 0;
}
ret_strB.append(this.seps.charAt((int) sepsIndex));
}
}
// 小于最小长度 minHashLength 则进行扩充
String ret_str = ret_strB.toString();
// 扩充两位
if (ret_str.length() < this.minHashLength) {
guardIndex = (numberHashInt + (ret_str.charAt(0))) % this.guards.length();
guard = this.guards.charAt((int) guardIndex);
ret_str = guard + ret_str;
if (ret_str.length() < this.minHashLength) {
guardIndex = (numberHashInt + (ret_str.charAt(2))) % this.guards.length();
guard = this.guards.charAt((int) guardIndex);
ret_str += guard;
}
}
final int halfLen = alphabet.length() / 2;
while (ret_str.length() < this.minHashLength) {
alphabet = Hashids.consistentShuffle(alphabet, alphabet);
// 使用编解码字符串填充,可见上述填充的两位前后都是冗余字段
ret_str = alphabet.substring(halfLen) + ret_str + alphabet.substring(0, halfLen);
final int excess = ret_str.length() - this.minHashLength;
// 长度长于 minHashLength 限制
if (excess > 0) {
final int start_pos = excess / 2;
ret_str = ret_str.substring(start_pos, start_pos + this.minHashLength);
}
}
return ret_str;
}
private long[] _decode(String hash, String alphabet) {
final ArrayList<Long> ret = new ArrayList<Long>();
int i = 0;
final String regexp = "[" + this.guards + "]";
String hashBreakdown = hash.replaceAll(regexp, " ");
String[] hashArray = hashBreakdown.split(" ");
// 见下面代码说明
if (hashArray.length == 3 || hashArray.length == 2) {
i = 1;
}
if (hashArray.length > 0) {
hashBreakdown = hashArray[i];
if (!hashBreakdown.isEmpty()) {
// 首位无用字段
final char lottery = hashBreakdown.charAt(0);
hashBreakdown = hashBreakdown.substring(1);
hashBreakdown = hashBreakdown.replaceAll("[" + this.seps + "]", " ");
hashArray = hashBreakdown.split(" ");
String subHash, buffer;
for (final String aHashArray : hashArray) {
subHash = aHashArray;
buffer = lottery + this.salt + alphabet;
alphabet = Hashids.consistentShuffle(alphabet, buffer.substring(0, alphabet.length()));
ret.add(Hashids.unhash(subHash, alphabet));
}
}
}
// transform from List to long[]
long[] arr = new long[ret.size()];
for (int k = 0; k < arr.length; k++) {
arr[k] = ret.get(k);
}
// 加密校验
if (!this.encode(arr).equals(hash)) {
arr = new long[0];
}
return arr;
}
private static String consistentShuffle(String alphabet, String salt) {
if (salt.length() <= 0) {
return alphabet;
}
int asc_val, j;
final char[] tmpArr = alphabet.toCharArray();
for (int i = tmpArr.length - 1, v = 0, p = 0; i > 0; i--, v++) {
v %= salt.length();
asc_val = salt.charAt(v);
p += asc_val;
j = (asc_val + v + p) % i;
final char tmp = tmpArr[j];
tmpArr[j] = tmpArr[i];
tmpArr[i] = tmp;
}
return new String(tmpArr);
}
/**
* 将整数转换为 alphabet.length() 进制
*
* @param input
* @param alphabet
* @return
*/
private static String hash(long input, String alphabet) {
String hash = "";
final int alphabetLen = alphabet.length();
do {
final int index = (int) (input % alphabetLen);
if (index >= 0 && index < alphabet.length()) {
hash = alphabet.charAt(index) + hash;
}
input /= alphabetLen;
} while (input > 0);
return hash;
}
/**
* 将 alphabet.length() 进制转换为整数
*
* @param input
* @param alphabet
* @return
*/
private static Long unhash(String input, String alphabet) {
long number = 0, pos;
for (int i = 0; i < input.length(); i++) {
pos = alphabet.indexOf(input.charAt(i));
number = number * alphabet.length() + pos;
}
return number;
}
}
@Test
public void test5() {
String str = "456a123b456";
String s = str.replaceAll("[abc]", " ");
System.out.println(s);
System.out.println(Arrays.toString(s.split(" ")));
}
// 456 123 456
// [456, 123, 456]
https://hashids.org/java/
https://blog.csdn.net/qq_26399665/article/details/79831490