关键字:十进制、十六进制、进制转换、短连接
1、背景
有时候,比如我们的链接可能太长了,此时可以考虑用短连接的方式来,比如链接
https://www.suibibk.com/blog/579412311547052032/555875030676799488/611887075330883584
这种链接生成二维码后,密度太高,有点都不方便,此时我们可以将其用hash后产生一个摘要,然后同时用雪花算法生成一个19位的数字,对生成的十九位的数字进行64进制转换(这里建议62进制,因为64进制会加上+,-这些特殊字符,链接中不友好),然后和原url一起放入数据库中。那么页面访问就可以是
生成的二维码密度也比较低啦。用户访问就直接访问上面的链接,然后后台根据JcEurLBHed去数据库找出原链接重定向即可。
2、原理:
好了,其实原理很简单,主要是我们得有十进制十六进制的转换工具类,其实只要知道原理就很好写了,原理可以参考我的博文:进制转换详解
X进制转十进制:
X进制数从低位到高位(即从右往左)计算,第0位的权值是X的0次方,第1位的权值是X的1次方,第2位的权值是X的2次方,依次递增下去,把最后的结果相加的值就是十进制的值了。
十进制转X进制:
除X取余法,即每次将整数部分除以X,余数为该位权上的数,而商继续除以X,余数又为上一个位权上的数,这个步骤一直持续下去,直到商为0为止,最后读数时候,从最后一个余数起,一直到最前面的一个余数。
3、实现
首先我们工具类中会提供两个功能,一个是十进制转六十四进制,一个是六十四进制转十进制,我们先来看十进制转六十四进制:
用上面的除64取余,然后直到商为止,那么伪代码可以是如下所示:
StringBuilder buf = new StringBuilder();
while (number != 0) {
//获取余数
buf.append(除64获取余数);
//剩下的值
number除64
}
//因为是从最后开始读的,所以这里要反转
return buf.reverse().toString();
如果是六十四进制转十进制,那么就用上面的进制转换原理。我们只需要对六十四进制的字符串从最右边开始一个个处理即可。比如64进制的字符串rtyu0u那么值应该是
r*64^5+t*64^4+y*64^3+u*64^2+0*64^1+u*64^0
那么关键的一部是我们需要获取到r,t,y,u,0,u对应在64进制表对应的值是多少,比如下面是64进制表:
0 1 2 3 4 5 6 7 8 9 a(10) b(11) c(12) d(13) e(14) f(15) g(16) h(17) i(18) j(19) k(20) l(21) m(22) n(23) o(24) p(25) q(26) r(27) s(28) t(29) u(30) v(31) w(32) x(33) y(34) z(35) A(36) B(37) C(38) D(39) E(40) F(41) G(42) H(43) I(44) J(45) K(46) L(47) M(48) N(49) O(50) P(51) Q(52) R(53) S(54) T(55) U(56) V(57) W(58) X(59) Y(60) Z(61) +(62) /(63)
很明显r是27,t是29,y是34,u是30,那么rtyu0u转换成十进制的值是:29486604318。也就是我们只需要确定u这些在定义表中对应的位置就是他们的值啦。方法如下,因为参照ASCII码表可以知道,上面的字母和数字都在ASCII码表中对应有数字,可以查看:Java的char自动转int的转换原理,也就是比如我们的字母u,就在ASCII表中对应的数字是:117,它在64进制对应的是30,因此,我们只需要构造类似a[117]=30的数组即可。
源码如下:
public class ConversionUtil {
//这里最后一位用-比较好,因为/比较特殊
private static final String baseDigits = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+-";
private static final int BASE = baseDigits.length();
//通过余数获取对应的64进制表示
private static final char[] digitsChar = baseDigits.toCharArray();
//这里预留了足够的空位122位
private static final int FAST_SIZE = 'z';
//这个是为了存放字母对应的值,比如-对应63,但是-是45,也就是 digitsIndex[45]=63
//[digitsChar[-]会自动转变成45,这样子十六进制转十进制,就可以获取到前面的数字了。
private static final int[] digitsIndex = new int[FAST_SIZE + 1];
static {
for (int i = 0; i < FAST_SIZE; i++) {
digitsIndex[i] = -1;
}
//构造:a[117]=30这样的数组
for (int i = 0; i < BASE; i++) {
digitsIndex[digitsChar[i]] = i;
}
}
//64进制转十进制
public static long decode(String s) {
long result = 0L;
long multiplier = 1;
for (int pos = s.length() - 1; pos >= 0; pos--) {
int index = getIndex(s, pos);
result += index * multiplier;
multiplier *= BASE;
}
return result;
}
//十进制转64进制
public static String encode(long number) {
if (number < 0)
System.out.println("Number(Base64) must be positive: " + number);
if (number == 0)
return "0";
StringBuilder buf = new StringBuilder();
while (number != 0) {
//获取余数
buf.append(digitsChar[(int) (number % BASE)]);
//剩下的值
number /= BASE;
}
//反转
return buf.reverse().toString();
}
//获取对应的的64进制的值
private static int getIndex(String s, int pos) {
char c = s.charAt(pos);
if (c > FAST_SIZE) {
System.out.println("Unknow character for Base64: " + s);
}
int index = digitsIndex[c];
if (index == -1) {
System.out.println("Unknow character for Base64: " + s);
}
return index;
}
}
如果是生成短连接,建议除开+,-。