前言
短網址就是將一個長網址轉換成一個短網址,訪問短網址會重定向到原來的長網址,短網址會更利於傳播和推廣。微博就會將我們發的長網址轉換成短網址,

顯示效果

查看HTML源碼

微博將 https://www.cnblogs.com/strongmore/p/14520111.html 轉換成了http://t.cn/A6t8dUoR。
實現
通過發號策略,每次來一個長網址,發一個號,這里我們使用SnowFlake(雪花算法)實現發號,然后轉成62進制。
雪花算法
/**
* 分布式Id生成器-雪花算法
*/
public class SnowFlake {
/**
* 起始的時間戳
*/
private final static long START_STMP = 1480166465631L;
/**
* 每一部分占用的位數
*/
private final static long SEQUENCE_BIT = 12; //序列號占用的位數
private final static long MACHINE_BIT = 5; //機器標識占用的位數
private final static long DATACENTER_BIT = 5;//數據中心占用的位數
/**
* 每一部分的最大值
*/
private final static long MAX_DATACENTER_NUM = ~(-1L << DATACENTER_BIT);
private final static long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT);
private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
/**
* 每一部分向左的位移
*/
private final static long MACHINE_LEFT = SEQUENCE_BIT;
private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;
private long datacenterId; //數據中心
private long machineId; //機器標識
private long sequence = 0L; //序列號
private long lastStmp = -1L;//上一次時間戳
public SnowFlake(long datacenterId, long machineId) {
if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
throw new IllegalArgumentException(
"datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
}
if (machineId > MAX_MACHINE_NUM || machineId < 0) {
throw new IllegalArgumentException(
"machineId can't be greater than MAX_MACHINE_NUM or less than 0");
}
this.datacenterId = datacenterId;
this.machineId = machineId;
}
/**
* 產生下一個ID
*/
public synchronized long nextId() {
long currStmp = getNewstmp();
if (currStmp < lastStmp) {
throw new RuntimeException("Clock moved backwards. Refusing to generate id");
}
if (currStmp == lastStmp) {
//相同毫秒內,序列號自增
sequence = (sequence + 1) & MAX_SEQUENCE;
//同一毫秒的序列數已經達到最大4096
if (sequence == 0L) {
currStmp = getNextMill();
}
} else {
//不同毫秒內,序列號置為0
sequence = 0L;
}
lastStmp = currStmp;
return (currStmp - START_STMP) << TIMESTMP_LEFT //時間戳部分
| datacenterId << DATACENTER_LEFT //數據中心部分
| machineId << MACHINE_LEFT //機器標識部分
| sequence; //序列號部分
}
private long getNextMill() {
long mill = getNewstmp();
while (mill <= lastStmp) {
mill = getNewstmp();
}
return mill;
}
private long getNewstmp() {
return System.currentTimeMillis();
}
public static void main(String[] args) {
SnowFlake snowFlake = new SnowFlake(2, 3);
for (int i = 0; i < (1 << 12); i++) {
System.out.println(snowFlake.nextId());
}
}
}
轉62進制
/**
* 進制轉換工具,最大支持十進制和62進制的轉換
* 1、將十進制的數字轉換為指定進制的字符串;
* 2、將其它進制的數字(字符串形式)轉換為十進制的數字
*/
public class NumericConvertUtils {
/**
* 在進制表示中的字符集合,0-Z分別用於表示最大為62進制的符號表示
*/
private static final char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};
/**
* 將十進制的數字轉換為指定進制的字符串
*
* @param number 十進制的數字
* @param seed 指定的進制
* @return 指定進制的字符串
*/
public static String toOtherNumberSystem(long number, int seed) {
if (number < 0) {
number = ((long) 2 * 0x7fffffff) + number + 2;
}
char[] buf = new char[32];
int charPos = 32;
while ((number / seed) > 0) {
buf[--charPos] = digits[(int) (number % seed)];
number /= seed;
}
buf[--charPos] = digits[(int) (number % seed)];
return new String(buf, charPos, (32 - charPos));
}
/**
* 將其它進制的數字(字符串形式)轉換為十進制的數字
*
* @param number 其它進制的數字(字符串形式)
* @param seed 指定的進制,也就是參數str的原始進制
* @return 十進制的數字
*/
public static long toDecimalNumber(String number, int seed) {
char[] charBuf = number.toCharArray();
if (seed == 10) {
return Long.parseLong(number);
}
long result = 0, base = 1;
for (int i = charBuf.length - 1; i >= 0; i--) {
int index = 0;
for (int j = 0, length = digits.length; j < length; j++) {
//找到對應字符的下標,對應的下標才是具體的數值
if (digits[j] == charBuf[i]) {
index = j;
}
}
result += index * base;
base *= seed;
}
return result;
}
}
將一個long型數字轉成包含0-9,A-Z,a-z共62個字符的62進制字符串。將生成的字符串和短網址服務器域名連接即為最終的短網址,如https://t.cn/FWSc3ki8mY
public class Main {
public static void main(String[] args) {
SnowFlake snowFlake = new SnowFlake(0, 0);
for (int i = 0; i < 10; i++) {
System.out.println(NumericConvertUtils.toOtherNumberSystem(snowFlake.nextId(), 62));
}
}
}
輸出結果為
FWSc3ki8mY
FWSc3ki8mZ
FWSc3ki8n0
FWSc3ki8n1
FWSc3ki8n2
FWSc3ki8n3
FWSc3ki8n4
FWSc3ki8n5
FWSc3ki8n6
FWSc3ki8n7
整體流程
- 利用發號器創建短網址,保存長網址和短網址的映射關系到數據庫或Redis。
- 短網址服務器接收到請求,根據 FWSc3ki8mY 找到原來的長網址,返回302,告訴瀏覽器重定向到長網址。
待優化地方
現在的實現對於同一個長網址,每次創建的短網址也是不同的,如果每次創建前去數據庫查詢的話,效率太低,可以使用 LRU 緩存最近的N次映射結果,先查緩存,再查數據庫,兼顧了空間和性能。
