短网址原理解析,当我们在浏览器里输入 http://t.cn/RlB2PdD 时:
DNS首先解析获得 http://t.cn 的 IP 地址
当 DNS 获得 IP 地址以后(比如:74.125.225.72),会向这个地址发送 HTTP GET 请求,查询短码 RlB2PdD
http://t.cn 服务器会通过短码 RlB2PdD 获取对应的长 URL
请求通过 HTTP 301 转到对应的长 URL https://m.helijia.com 。
这里有个小的知识点,为什么要用 301 跳转而不是 302 呐?
301 是永久重定向,302 是临时重定向。短地址一经生成就不会变化,所以用 301 是符合 http 语义的。同时对服务器压力也会有一定减少。
但是如果使用了 301,我们就无法统计到短地址被点击的次数了。而这个点击次数是一个非常有意思的大数据分析数据源。能够分析出的东西非常非常多。所以选择302虽然会增加服务器压力,但是我想是一个更好的选择。
算法1:自增序列算法 也叫永不重复算法
设置 id 自增,一个 10进制 id 对应一个 62进制的数值,1对1,也就不会出现重复的情况。这个利用的就是低进制转化为高进制时,字符数会减少的特性。
短址的长度一般设为 6 位,而每一位是由 [a - z, A - Z, 0 - 9] 总共 62 个字母组成的,所以 6 位的话,总共会有 62^6 ~= 568亿种组合,基本上够用了。
算法2:md5算法
将长网址 md5 生成 32 位签名串,分为 4 段, 每段 8 个字节。对这四段循环处理, 取 8 个字节, 将他看成 16 进制串与 0x3fffffff(30位1) 与操作, 即超过 30 位的忽略处理
这 30 位分成 6 段, 每 5 位的数字作为字母表的索引取得特定字符, 依次进行获得 6 位字符串
总的 md5 串可以获得 4 个 6 位串,取里面的任意一个就可作为这个长 url 的短 url 地址
两种算法对比:
第一种算法的好处就是简单好理解,永不重复。但是短码的长度不固定,随着 id 变大从一位长度开始递增。如果非要让短码长度固定也可以就是让 id 从指定的数字开始递增就可以了。百度短网址用的这种算法。
第二种算法,存在碰撞(重复)的可能性,虽然几率很小。短码位数是比较固定的。不会从一位长度递增到多位的。据说微博使用的这种算法。
我使用的算法一。有一个不太好的地方就是出现的短码是有序的,可能会不安全。我的处理方式是构造 62进制的字母不要按顺序排列。
两种算法都在案例文件里,自己去看代码吧,都很简单
-----------------------------------------
Base62NumUtil.java
/**
* 10进制、62进制互转
* edited by zhibo on 2015/05/21.
*/
public class Base62NumUtil {
/**
* 初始化 62 进制数据,索引位置代表字符的数值,比如 A代表10,z代表61等。
* 只要这里的顺序打乱,生成的62进制数看起来就没有规律了,除非对方也有一个一模一样的被打乱的数组。
*/
private static String chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static int scale = 62;
/**
* 将数字转为62进制
*
* @param num Long 型数字
* @return 62进制字符串
*/
public static String encode(long num) {
StringBuilder sb = new StringBuilder();
int remainder = 0;
while (num > scale - 1) {
/**
* 对 scale 进行求余,然后将余数追加至 sb 中,由于是从末位开始追加的,因此最后需要反转(reverse)字符串
*/
remainder = Long.valueOf(num % scale).intValue();
sb.append(chars.charAt(remainder));
num = num / scale;
}
sb.append(chars.charAt(Long.valueOf(num).intValue()));
return sb.reverse().toString();
}
/**
* 62进制字符串转为数字
*
* @param str 编码后的62进制字符串
* @return 解码后的 10 进制字符串
*/
public static long decode(String str) {
/**
* 将 0 开头的字符串进行替换
*/
str = str.replace("^0*", "");
long num = 0;
int index = 0;
for (int i = 0; i < str.length(); i++) {
/**
* 查找字符的索引位置
*/
index = chars.indexOf(str.charAt(i));
/**
* 索引位置代表字符的数值
*/
num += (long) (index * (Math.pow(scale, str.length() - i - 1)));
}
return num;
}
/**
* 使用10进制转62进制
*
* @param args
*/
public static void main(String[] args) {
for (Long id = new Long("10227646862"); id < new Long("13227646962"); id++) {
String url = "http://t.cn/" + Base62NumUtil.encode(id);
System.out.println(url);
}
}
}
MD5ShortURL.java
public class MD5ShortURL {
public static void main(String[] args) {
String sLongUrl = "https://cn.bing.com/";
String[] aResult = shortUrl(sLongUrl);
// 打印出结果
for (int i = 0; i < aResult.length; i++) {
System.out.println(aResult[i]);
}
}
public static String[] shortUrl(String url) {
/**
* 可以自定义生成 MD5 加密字符传前的混合 KEY,其实就是md5加盐。
* 如果key值动态生成(比如jdk的UUID、雪花片算法),就可以避免同一个网址生成的短网址碰撞的问题。
*/
String key = "blacktv";
// 要使用生成 URL 的字符
String[] chars = new String[]{"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", "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"
};
//不带网址也能玩
String hex = MD5.encrypt(key + url); // 对传入网址进行 MD5 加密
String[] resUrl = new String[4];
//得到 4组短链接字符串
for (int i = 0; i < 4; i++) {
// 把加密字符按照 8 位一组 16 进制与 0x3FFFFFFF 进行位与运算
String sTempSubString = hex.substring(i * 8, i * 8 + 8);
// 这里需要使用 long 型来转换,因为 Inteper .parseInt() 只能处理 31 位 , 首位为符号位 , 如果不用 long ,则会越界
long lHexLong = 0x3FFFFFFF & Long.parseLong(sTempSubString, 16);
String outChars = "";
//循环获得每组6位的字符串
for (int j = 0; j < 6; j++) {
// 把得到的值与 0x0000003D 进行位与运算,取得字符数组 chars 索引
//(具体需要看chars数组的长度 以防下标溢出,注意起点为0)
long index = 0x0000003D & lHexLong;
// 把取得的字符相加
outChars += chars[(int) index];
// 每次循环按位右移 5 位
lHexLong = lHexLong >> 5;
}
// 把字符串存入对应索引的输出数组
resUrl[i] = outChars;
}
return resUrl;
}
}