一、介绍
在大规模的数据处理中,可能需要对海量的 URL 进行去重处理。例如,在爬虫系统、日志分析或实时推荐系统中,快速判断一个 URL 是否已经处理过至关重要。
常规的字符串比较方法在面对大量数据时性能较差,因此我们需要一种高效、可靠的方法来实现 URL 的去重。
二、方案设计
2.1 哈希函数的选择
MurmurHash3 是一种高性能、低碰撞率的哈希函数,适合用于非加密场景的哈希计算。相比 MD5、SHA 等加密哈希函数,MurmurHash3 速度更快,且足以满足我们的需求。
2.2 数据结构的选择
为了支持高并发和高性能,选择线程安全且性能优异的数据结构:
- ConcurrentHashMap:线程安全的哈希表,支持高并发读写操作。
- Caffeine Cache:高性能的缓存库,支持自动过期、基于大小的回收等特性。
三、代码实现
3.1 引入依赖
在项目的 pom.xml
文件中引入必要的依赖:
<dependencies>
<!-- Guava,用于 MurmurHash3 实现 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.3.1-jre</version>
</dependency>
<!-- Caffeine Cache,用于缓存哈希值 -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.9.3</version>
</dependency>
</dependencies>
3.2 实现哈希工具类
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import java.nio.charset.StandardCharsets;
public class HashUtil {
private static final HashFunction HASH_FUNCTION = Hashing.murmur3_128();
public static long hashUrl(String url) {
return HASH_FUNCTION.hashString(url, StandardCharsets.UTF_8).asLong();
}
}
3.3 实现去重逻辑
使用 Caffeine Cache 来存储哈希值,实现自动过期和高效的查重。
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;
public class UrlDeduplicator {
private Cache<Long, Boolean> urlCache;
public UrlDeduplicator(long expireDuration, TimeUnit timeUnit) {
urlCache = Caffeine.newBuilder()
.expireAfterWrite(expireDuration, timeUnit)
.build();
}
public boolean isDuplicate(String url) {
long hash = HashUtil.hashUrl(url);
Boolean existing = urlCache.getIfPresent(hash);
if (existing != null) {
return true;
} else {
urlCache.put(hash, Boolean.TRUE);
return false;
}
}
}
3.4 实现 URL 处理器
import java.util.List;
import java.util.concurrent.*;
public class UrlProcessor {
private UrlDeduplicator deduplicator;
private ExecutorService executor;
public UrlProcessor(int threadPoolSize, long expireDuration, TimeUnit timeUnit) {
this.deduplicator = new UrlDeduplicator(expireDuration, timeUnit);
this.executor = Executors.newFixedThreadPool(threadPoolSize);
}
public void processUrls(List<String> urls) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(urls.size());
for (String url : urls) {
executor.submit(() ->