本文目录导读:

我来提供一个完整的Java短链接生成案例,包含多种实现方案。
基于哈希+Base62编码(最常用)
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class ShortUrlGenerator {
// 62个字符的字符表
private static final String BASE62_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
private static final int BASE = 62;
// 存储在内存中的映射(实际应用中用数据库/Redis)
private static Map<String, String> shortToLongMap = new HashMap<>();
private static Map<String, String> longToShortMap = new HashMap<>();
/**
* 方法1:基于MD5哈希生成短链接
*/
public static String generateByMD5(String longUrl, String salt) {
try {
// 添加随机盐值避免碰撞
String input = longUrl + salt;
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] digest = md5.digest(input.getBytes());
// 取前8个字节
StringBuilder hexString = new StringBuilder();
for (int i = 0; i < 8; i++) {
hexString.append(String.format("%02x", digest[i]));
}
// 将16进制转成62进制
String hexStringStr = hexString.toString();
StringBuilder shortUrl = new StringBuilder();
for (int i = 0; i < 6; i++) { // 生成6位短码
shortUrl.append(hexToChar(hexStringStr.substring(i * 2, i * 2 + 2)));
}
return shortUrl.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("MD5 algorithm not found", e);
}
}
/**
* 方法2:基于UUID生成
*/
public static String generateByUUID() {
return UUID.randomUUID().toString().replace("-", "")
.substring(0, 8) // 取8位
.toUpperCase();
}
/**
* 方法3:基于自增ID+Base62编码
*/
private static long counter = 1000000000L; // 模拟自增ID
public static synchronized String generateByIncrement() {
return convertToBase62(counter++);
}
/**
* 方法4:基于32位整数+Base62编码
*/
public static String generateByRandom() {
int randomInt = Math.abs(UUID.randomUUID().hashCode());
return convertToBase62(randomInt);
}
/**
* 将10进制转换为62进制
*/
private static String convertToBase62(long number) {
if (number == 0) {
return "0";
}
StringBuilder result = new StringBuilder();
while (number > 0) {
int remainder = (int) (number % BASE);
result.insert(0, BASE62_CHARS.charAt(remainder));
number /= BASE;
}
return result.toString();
}
/**
* 将16进制字符串转换为字符
*/
private static char hexToChar(String hex) {
int num = Integer.parseInt(hex, 16);
return BASE62_CHARS.charAt(num % BASE);
}
/**
* 保存映射关系
*/
public static void saveMapping(String shortCode, String longUrl) {
shortToLongMap.put(shortCode, longUrl);
longToShortMap.put(longUrl, shortCode);
}
/**
* 根据短码获取长URL
*/
public static String getLongUrl(String shortCode) {
return shortToLongMap.get(shortCode);
}
public static void main(String[] args) {
String longUrl = "https://www.example.com/blog/article/123456789?page=1&size=10";
// 测试各种生成方式
System.out.println("原始URL: " + longUrl);
System.out.println("长度: " + longUrl.length());
// 1. MD5方式
String md5ShortCode = generateByMD5(longUrl, "salt123");
System.out.println("\nMD5方式生成: " + md5ShortCode);
System.out.println("长度: " + md5ShortCode.length());
saveMapping(md5ShortCode, longUrl);
// 2. UUID方式
String uuidShortCode = generateByUUID();
System.out.println("\nUUID方式生成: " + uuidShortCode);
System.out.println("长度: " + uuidShortCode.length());
saveMapping(uuidShortCode, longUrl);
// 3. 自增ID方式
String incrementShortCode = generateByIncrement();
System.out.println("\n自增ID方式生成: " + incrementShortCode);
System.out.println("长度: " + incrementShortCode.length());
saveMapping(incrementShortCode, longUrl);
// 4. 随机数方式
String randomShortCode = generateByRandom();
System.out.println("\n随机数方式生成: " + randomShortCode);
System.out.println("长度: " + randomShortCode.length());
saveMapping(randomShortCode, longUrl);
// 5. 完整的短链接
String domain = "https://short.link/";
System.out.println("\n完整短链接示例: " + domain + md5ShortCode);
// 6. 验证反向解析
System.out.println("\n反向解析测试:");
System.out.println("短码 " + md5ShortCode + " -> " + getLongUrl(md5ShortCode));
// 7. 批量生成测试
System.out.println("\n批量生成测试:");
for (int i = 0; i < 10; i++) {
String code = generateByIncrement();
System.out.println("生成短码 " + (i + 1) + ": " + code);
}
}
}
完整的短链接服务类
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
/**
* 完整的短链接服务类
*/
public class ShortUrlService {
// 基础配置
private static final String DOMAIN = "https://short.url/";
private static final int SHORT_CODE_LENGTH = 8;
private static final long EXPIRATION_TIME = 30L * 24 * 60 * 60 * 1000; // 30天有效
// 字符表
private static final String ALPHABET = "BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789";
private static final int BASE = ALPHABET.length();
// 存储结构(实际应用使用Redis/数据库)
private static final Map<String, UrlEntry> urlStore = new HashMap<>();
// 防止重复生成的缓存
private static final Map<String, String> longUrlCache = new HashMap<>();
// URL实体类
public static class UrlEntry {
private final String longUrl;
private final long createdAt;
private final long expiresAt;
private long accessCount;
public UrlEntry(String longUrl) {
this.longUrl = longUrl;
this.createdAt = System.currentTimeMillis();
this.expiresAt = this.createdAt + EXPIRATION_TIME;
this.accessCount = 0;
}
public String getLongUrl() {
return longUrl;
}
public long getCreatedAt() {
return createdAt;
}
public long getExpiresAt() {
return expiresAt;
}
public void incrementAccessCount() {
this.accessCount++;
}
public long getAccessCount() {
return accessCount;
}
public boolean isExpired() {
return System.currentTimeMillis() > expiresAt;
}
@Override
public String toString() {
return String.format("UrlEntry{longUrl='%s', createdAt=%s, expiresAt=%s, accessCount=%d}",
longUrl, new Date(createdAt), new Date(expiresAt), accessCount);
}
}
/**
* 生成短链接(完整流程)
*/
public String createShortUrl(String longUrl) {
// 检查是否已存在
String existingCode = longUrlCache.get(longUrl);
if (existingCode != null) {
UrlEntry entry = urlStore.get(existingCode);
if (entry != null && !entry.isExpired()) {
return DOMAIN + existingCode;
}
}
// 生成唯一短码
String shortCode;
int attempts = 0;
do {
shortCode = generateUniqueCode();
attempts++;
} while (urlStore.containsKey(shortCode) && attempts < 10);
// 保存映射
UrlEntry entry = new UrlEntry(longUrl);
urlStore.put(shortCode, entry);
longUrlCache.put(longUrl, shortCode);
return DOMAIN + shortCode;
}
/**
* 生成唯一短码
*/
private String generateUniqueCode() {
// 方法1:基于时间戳
long timestamp = System.nanoTime();
String timeBase62 = base62Encode(timestamp);
// 方法2:加入随机数
int random = ThreadLocalRandom.current().nextInt(0, BASE * BASE);
String randomBase62 = base62Encode(random);
// 组合并调整长度
String combined = timeBase62 + randomBase62;
// 如果太长,取前N位
if (combined.length() > SHORT_CODE_LENGTH) {
combined = combined.substring(0, SHORT_CODE_LENGTH);
}
// 如果太短,填充
while (combined.length() < SHORT_CODE_LENGTH) {
combined += base62Encode(ThreadLocalRandom.current().nextInt(BASE));
}
return combined;
}
/**
* Base62编码
*/
private String base62Encode(long number) {
if (number == 0) {
return String.valueOf(ALPHABET.charAt(0));
}
StringBuilder sb = new StringBuilder();
while (number > 0) {
int mod = (int) (number % BASE);
sb.append(ALPHABET.charAt(mod));
number /= BASE;
}
return sb.reverse().toString();
}
/**
* 获取原始URL
*/
public String getOriginalUrl(String shortCode) {
if (shortCode != null && shortCode.startsWith(DOMAIN)) {
shortCode = shortCode.substring(DOMAIN.length());
}
UrlEntry entry = urlStore.get(shortCode);
if (entry == null) {
return null;
}
if (entry.isExpired()) {
urlStore.remove(shortCode);
longUrlCache.remove(entry.getLongUrl());
return null;
}
entry.incrementAccessCount();
return entry.getLongUrl();
}
/**
* 删除短链接
*/
public boolean deleteShortUrl(String shortCode) {
if (shortCode != null && shortCode.startsWith(DOMAIN)) {
shortCode = shortCode.substring(DOMAIN.length());
}
UrlEntry entry = urlStore.remove(shortCode);
if (entry != null) {
longUrlCache.remove(entry.getLongUrl());
return true;
}
return false;
}
/**
* 获取统计数据
*/
public UrlEntry getStats(String shortCode) {
if (shortCode != null && shortCode.startsWith(DOMAIN)) {
shortCode = shortCode.substring(DOMAIN.length());
}
return urlStore.get(shortCode);
}
/**
* 清理过期链接
*/
public int cleanExpiredUrls() {
int count = 0;
for (Map.Entry<String, UrlEntry> entry : urlStore.entrySet()) {
if (entry.getValue().isExpired()) {
urlStore.remove(entry.getKey());
longUrlCache.remove(entry.getValue().getLongUrl());
count++;
}
}
return count;
}
/**
* 生成二维码(如果需要的功能)
*/
public void generateQRCode(String shortUrl) {
// 使用zxing库等生成二维码
// 这里只是示例,实际实现需要添加依赖
System.out.println("生成二维码: " + shortUrl);
}
// 测试主方法
public static void main(String[] args) {
ShortUrlService service = new ShortUrlService();
// 测试用例
String[] urls = {
"https://www.google.com/search?q=java+short+url",
"https://stackoverflow.com/questions/12345678",
"https://github.com/username/repository",
"https://www.baidu.com/s?wd=短链接",
"https://example.com/product/12345?ref=promo&utm_source=test"
};
System.out.println("=== 短链接生成测试 ===\n");
for (String url : urls) {
String shortUrl = service.createShortUrl(url);
System.out.println("原始URL: " + url);
System.out.println("短链接: " + shortUrl);
// 解析测试
String code = shortUrl.replace(DOMAIN, "");
String original = service.getOriginalUrl(code);
System.out.println("解析URL: " + original);
System.out.println("解析成功: " + Objects.equals(url, original));
// 测试重复创建
String shortUrl2 = service.createShortUrl(url);
System.out.println("重复创建: " + shortUrl2);
System.out.println("是否相同: " + shortUrl.equals(shortUrl2));
System.out.println("------------------------\n");
}
// 批量生成测试
System.out.println("=== 并发批量生成测试 ===");
service.testConcurrentCalls();
// 统计信息
System.out.println("\n=== 统计信息 ===");
for (String url : urls) {
String shortUrl = service.createShortUrl(url);
String code = shortUrl.replace(DOMAIN, "");
UrlEntry stats = service.getStats(code);
if (stats != null) {
System.out.println("URL: " + stats.getLongUrl());
System.out.println("创建时间: " + new Date(stats.getCreatedAt()));
System.out.println("过期时间: " + new Date(stats.getExpiresAt()));
System.out.println("访问次数: " + stats.getAccessCount());
System.out.println("------------------------");
}
}
}
/**
* 并发测试
*/
private void testConcurrentCalls() {
Runnable task = () -> {
for (int i = 0; i < 10; i++) {
String url = "https://example.com/concurrent/" +
Thread.currentThread().getName() + "/" + i;
String shortUrl = createShortUrl(url);
System.out.println("线程 " + Thread.currentThread().getName() +
" 生成了: " + shortUrl);
}
};
Thread[] threads = new Thread[5];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(task, "Thread-" + (i + 1));
threads[i].start();
}
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
使用外部库(Guava)
import com.google.common.hash.Hashing;
import java.nio.charset.StandardCharsets;
public class GuavaShortUrlGenerator {
private static final String DOMAIN = "https://short.url/";
private static final String ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
private static final int BASE = ALPHABET.length();
/**
* 使用Guava的MurmurHash算法
*/
public static String generateShortCode(String longUrl) {
// 计算哈希值
long hash = Hashing.murmur3_32_fixed() // 或使用 murmur3_128
.hashString(longUrl, StandardCharsets.UTF_8)
.asInt();
// 转换为正数
long positiveHash = hash & Long.MAX_VALUE;
// Base62编码
StringBuilder shortCode = new StringBuilder();
while (positiveHash > 0) {
int index = (int) (positiveHash % BASE);
shortCode.insert(0, ALPHABET.charAt(index));
positiveHash /= BASE;
}
// 确保至少6位
while (shortCode.length() < 6) {
shortCode.insert(0, ALPHABET.charAt(0));
}
return shortCode.toString();
}
public static void main(String[] args) {
String[] urls = {
"https://www.google.com/search?q=java",
"https://stackoverflow.com/questions",
"https://github.com/username"
};
for (String url : urls) {
String code = generateShortCode(url);
System.out.println("URL: " + url);
System.out.println("短码: " + code);
System.out.println("短链接: " + DOMAIN + code);
System.out.println("长度: " + code.length());
System.out.println("------------------------");
}
}
}
使用说明
添加依赖(如果使用方案三)
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>
生产环境建议
- 使用Redis存储映射关系
- 考虑使用分布式ID生成器(如Snowflake)
- 添加缓存层(如Caffeine)
- 实现数据库持久化
- 设置过期时间
- 添加访问统计
性能优化建议
- 预生成ID池
- 使用连接池
- 批量操作
- 异步处理
这个案例提供了多种生成方式和完整的服务实现,可以根据实际需求选择使用。