URL 去重的 6 种方案!(附详细代码)

作者 | 王磊

来源 | Java中文社群(ID:javacn666)

转载请联系授权(微信ID:GG_Stone)

URL 去重在我们日常工作中和面试中很常遇到,比如这些:

可以看出,包括阿里,网易云、优酷、作业帮等知名互联网公司都出现过类似的面试题,而且和 URL 去重比较类似的,如 IP 黑/白名单判断等也经常出现在我们的工作中,所以我们本文就来“盘一盘”URL 去重的问题。

URL 去重思路

在不考虑业务场景和数据量的情况下,我们可以使用以下方案来实现 URL 的重复判断:

  1. 使用 Java 的 Set 集合,根据添加时的结果来判断 URL 是否重复(添加成功表示 URL 不重复);

  2. 使用 Redis 中的 Set 集合,根据添加时的结果来判断 URL 是否重复;

  3. 将 URL 都存储在数据库中,再通过 SQL 语句判断是否有重复的 URL;

  4. 把数据库中的 URL 一列设置为唯一索引,根据添加时的结果来判断 URL 是否重复;

  5. 使用 Guava 的布隆过滤器来实现 URL 判重;

  6. 使用 Redis 的布隆过滤器来实现 URL 判重。

以上方案的具体实现如下。

URL 去重实现方案

1.使用 Java 的 Set 集合判重

Set 集合天生具备不可重复性,使用它只能存储值不相同的元素,如果值相同添加就会失败,因此我们可以通过添加 Set 集合时的结果来判定 URL 是否重复,实现代码如下:

public class URLRepeat {// 待去重 URLpublic static final String[] URLS = {"www.apigo.cn","www.baidu.com","www.apigo.cn"};public static void main(String[] args) {Set<String> set = new HashSet();for (int i = 0; i < URLS.length; i++) {String url = URLS[i];boolean result = set.add(url);if (!result) {// 重复的 URLSystem.out.println("URL 已存在了:" + url);}}}
}

程序的执行结果为:

URL 已存在了:www.apigo.cn

从上述结果可以看出,使用 Set 集合可以实现 URL 的判重功能。

2.Redis Set 集合去重

使用 Redis 的 Set 集合的实现思路和 Java 中的 Set 集合思想思路是一致的,都是利用 Set 的不可重复性实现的,我们先使用 Redis 的客户端 redis-cli 来实现一下 URL 判重的示例:

从上述结果可以看出,当添加成功时表示 URL 没有重复,但添加失败时(结果为 0)表示此 URL 已经存在了。

我们再用代码的方式来实现一下 Redis 的 Set 去重,实现代码如下:

// 待去重 URL
public static final String[] URLS = {"www.apigo.cn","www.baidu.com","www.apigo.cn"
};@Autowired
RedisTemplate redisTemplate;@RequestMapping("/url")
public void urlRepeat() {for (int i = 0; i < URLS.length; i++) {String url = URLS[i];Long result = redisTemplate.opsForSet().add("urlrepeat", url);if (result == 0) {// 重复的 URLSystem.out.println("URL 已存在了:" + url);}}
}

以上程序的执行结果为:

URL 已存在了:www.apigo.cn

以上代码中我们借助了 Spring Data 中的 RedisTemplate 实现的,在 Spring Boot 项目中要使用 RedisTemplate 对象我们需要先引入 spring-boot-starter-data-redis 框架,配置信息如下:

<!-- 添加操作 RedisTemplate 引用 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

然后需要再项目中配置 Redis 的连接信息,在 application.properties 中配置如下内容:

spring.redis.host=127.0.0.1
spring.redis.port=6379
#spring.redis.password=123456 # Redis 服务器密码,有密码的话需要配置此项

经过以上两个步骤之后,我们就可以在 Spring Boot 的项目中正常的使用 RedisTemplate 对象来操作 Redis 了。


3.数据库去重

我们也可以借助数据库实现 URL 的重复判断,首先我们先来设计一张 URL 的存储表,如下图所示:

此表对应的 SQL 如下:

/*==============================================================*/
/* Table: urlinfo                                               */
/*==============================================================*/
create table urlinfo
(id                   int not null auto_increment,url                  varchar(1000),ctime                date,del                  boolean,primary key (id)
);/*==============================================================*/
/* Index: Index_url                                             */
/*==============================================================*/
create index Index_url on urlinfo
(url
);

其中 id 为自增的主键,而 url  字段设置为索引,设置索引可以加快查询的速度。

我们先在数据库中添加两条测试数据,如下图所示:


我们使用 SQL 语句查询,如下图所示:

如果结果大于 0 则表明已经有重复的 URL 了,否则表示没有重复的 URL。

4.唯一索引去重

我们也可以使用数据库的唯一索引来防止 URL 重复,它的实现思路和前面 Set 集合的思想思路非常像。

首先我们先为字段 URL 设置了唯一索引,然后再添加 URL 数据,如果能添加成功则表明 URL 不重复,反之则表示重复。

创建唯一索引的 SQL 实现如下:

create unique index Index_url on urlinfo
(url
);

5.Guava 布隆过滤器去重

布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。

布隆过滤器的核心实现是一个超大的位数组和几个哈希函数,假设位数组的长度为 m,哈希函数的个数为 k。


以上图为例,具体的操作流程:假设集合里面有 3 个元素 {x, y, z},哈希函数的个数为 3。首先将位数组进行初始化,将里面每个位都设置位 0。对于集合里面的每一个元素,将元素依次通过 3 个哈希函数进行映射,每次映射都会产生一个哈希值,这个值对应位数组上面的一个点,然后将位数组对应的位置标记为 1,查询 W 元素是否存在集合中的时候,同样的方法将 W 通过哈希映射到位数组上的 3 个点。如果 3 个点的其中有一个点不为 1,则可以判断该元素一定不存在集合中。反之,如果 3 个点都为 1,则该元素可能存在集合中。注意:此处不能判断该元素是否一定存在集合中,可能存在一定的误判率。可以从图中可以看到:假设某个元素通过映射对应下标为 4、5、6 这 3 个点。虽然这 3 个点都为 1,但是很明显这 3 个点是不同元素经过哈希得到的位置,因此这种情况说明元素虽然不在集合中,也可能对应的都是 1,这是误判率存在的原因。

我们可以借助 Google 提供的 Guava 框架来操作布隆过滤器,实现我们先在 pom.xml 中添加 Guava 的引用,配置如下:

<!-- 添加 Guava 框架 -->
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>28.2-jre</version>
</dependency>

URL 判重的实现代码:

public class URLRepeat {// 待去重 URLpublic static final String[] URLS = {"www.apigo.cn","www.baidu.com","www.apigo.cn"};public static void main(String[] args) {// 创建一个布隆过滤器BloomFilter<String> filter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()),10, // 期望处理的元素数量0.01); // 期望的误报概率for (int i = 0; i < URLS.length; i++) {String url = URLS[i];if (filter.mightContain(url)) {// 用重复的 URLSystem.out.println("URL 已存在了:" + url);} else {// 将 URL 存储在布隆过滤器中filter.put(url);}}}
}

以上程序的执行结果为:

URL 已存在了:www.apigo.cn

6.Redis 布隆过滤器去重

除了 Guava 的布隆过滤器,我们还可以使用 Redis 的布隆过滤器来实现 URL 判重。在使用之前,我们先要确保 Redis 服务器版本大于 4.0(此版本以上才支持布隆过滤器),并且开启了 Redis 布隆过滤器功能才能正常使用。

以 Docker 为例,我们来演示一下 Redis 布隆过滤器安装和开启,首先下载 Redis 的布隆过器,然后再在重启 Redis 服务时开启布隆过滤器,如下图所示:

布隆过滤器使用布隆过滤器正常开启之后,我们先用 Redis 的客户端 redis-cli 来实现一下布隆过滤器 URL 判重了,实现命令如下:

在 Redis 中,布隆过滤器的操作命令不多,主要包含以下几个:

  • bf.add 添加元素;

  • bf.exists 判断某个元素是否存在;

  • bf.madd 添加多个元素;

  • bf.mexists 判断多个元素是否存在;

  • bf.reserve 设置布隆过滤器的准确率。

接下来我们使用代码来演示一下 Redis 布隆过滤器的使用:

import redis.clients.jedis.Jedis;
import utils.JedisUtils;import java.util.Arrays;public class BloomExample {// 布隆过滤器 keyprivate static final String _KEY = "URLREPEAT_KEY";// 待去重 URLpublic static final String[] URLS = {"www.apigo.cn","www.baidu.com","www.apigo.cn"};public static void main(String[] args) {Jedis jedis = JedisUtils.getJedis();for (int i = 0; i < URLS.length; i++) {String url = URLS[i];boolean exists = bfExists(jedis, _KEY, url);if (exists) {// 重复的 URLSystem.out.println("URL 已存在了:" + url);} else {bfAdd(jedis, _KEY, url);}}}/*** 添加元素* @param jedis Redis 客户端* @param key   key* @param value value* @return boolean*/public static boolean bfAdd(Jedis jedis, String key, String value) {String luaStr = "return redis.call('bf.add', KEYS[1], KEYS[2])";Object result = jedis.eval(luaStr, Arrays.asList(key, value),Arrays.asList());if (result.equals(1L)) {return true;}return false;}/*** 查询元素是否存在* @param jedis Redis 客户端* @param key   key* @param value value* @return boolean*/public static boolean bfExists(Jedis jedis, String key, String value) {String luaStr = "return redis.call('bf.exists', KEYS[1], KEYS[2])";Object result = jedis.eval(luaStr, Arrays.asList(key, value),Arrays.asList());if (result.equals(1L)) {return true;}return false;}
}

以上程序的执行结果为:

URL 已存在了:www.apigo.cn

总结

本文介绍了 6 种 URL 去重的方案,其中 Redis Set、Redis 布隆过滤器、数据库和唯一索引这 4 种解决方案适用于分布式系统,如果是海量的分布式系统,建议使用 Redis 布隆过滤器来实现 URL 去重,如果是单机海量数据推荐使用 Guava 的布隆器来实现 URL 去重


往期推荐

多图证明,Java到底是值传递还是引用传递?


阿里为什么推荐使用LongAdder,而不是volatile?


磊哥工作十几年了,竟没有用过do-while!(文末送书)

关注下方二维码,收获更多干货!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/545872.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入了解INF

大家都知道&#xff0c;在“我的电脑”里有数也数不清的INF文件&#xff0c;但是却很少有人知道&#xff0c;INF文件是干什么用的。充其量&#xff0c;也仅仅停留在INF文件能够解开锁定的注册表这一感性的认识上&#xff0c;那么到底什么是INF文件&#xff0c;INF文件又能干些什…

Java字符串indexOf(int ch,int fromIndex)方法,带示例

字符串indexOf(int ch&#xff0c;int fromIndex)方法 (String indexOf(int ch, int fromIndex) Method) indexOf(int ch, int fromIndex) is a String method in Java and it is used to get the index of a specified character in the string from given fromIndex. That me…

阿里巴巴为什么禁止使用Apache Beanutils进行属性复制?

作者 l Hollis来源 l Hollis&#xff08;ID&#xff1a;hollischuang&#xff09;在日常开发中&#xff0c;我们经常需要给对象进行赋值&#xff0c;通常会调用其set/get方法&#xff0c;有些时候&#xff0c;如果我们要转换的两个对象之间属性大致相同&#xff0c;会考虑使用属…

台达A2-M伺服

地址: P3.00 从站地址0x01~0x7F【1~127】 P3.01 通讯速度UZYX【0403】 X:0【4800】1【9600】2【19200】3【38400】4【57600】5【115200】Z:0【125Kbit/s】1【250】2【500】3【750】4【1Mbit/s】 P3.02 通讯格式6【8N2】7【8E1】8【8O1】 P3.03 1通讯错误刹停…

关于数组首地址a、a+1、a[0]、a[0]+1、*a、*a、a+0的解析

有一个数组&#xff1a; int a[4]{1,2,3,4};例如&#xff0c;sizeof(a),很明显它的结果是16&#xff0c;这个a就表示的是整个数组的大小&#xff0c;那么有 a1: 表示数组a的第二个元素即a[1]的地址 a0&#xff1a; a[0]的地址 &a[0]: a[0]的地址 &a[0…

Intent, Bundle, ListView的简单使用

Intent, Bundle, ListView的使用 无参数的Activity跳转&#xff1a; intent.setClass(MainActivity.this, InformationActivity.class); startActivity(intent);当前Activity A 向下一个Activity B跳转并传递数据&#xff1a; Bundle bundle new Bundle(); bundle.putString(…

vb随机显示图片

根目录下建一个文件夹PIC&#xff0c;在PIC下存放5张图片&#xff0c;名称分虽是1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5然后添加一个timer1控件&#xff0c;一个image1控件&#xff0c;它的interval设5000&#xff08;即5秒&#xff09;写如下代码Private Sub…

正则数字字母下划线至少两种_8085微处理器中至少两个8位数字

正则数字字母下划线至少两种Problem statement: 问题陈述&#xff1a; To find minimum of two 8bit numberusing 8085 microprocessor. 使用8085微处理器查找最少两个8位数字。 Algorithm: 算法&#xff1a; Load the accumulator with the first data. 向累加器加载第一个…

字符串操作的12个小技巧!

字符串可以说是 Java 中最具有代表性的类了&#xff0c;似乎没有之一哈&#xff0c;这就好像直播界的李佳琪&#xff0c;脱口秀中的李诞&#xff0c;一等一的大哥地位。不得不承认&#xff0c;最近吐槽大会刷多了&#xff0c;脑子里全是那些段子&#xff0c;写文章都有点不由自…

关于二维数组取地址加以或减一解引用问题

int main() { int aa[2][5] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int *ptr1 (int *)(&aa 1); int *ptr2 (int *)(*(aa 1)); printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));system("pause");return 0; }很显然aa是一个二维数组&#xff0c;很多…

rand和srand的用法

首先我们要对rand&#xff06;srand有个总体的看法:srand初始化随机种子,rand产生随机数&#xff0c;下面将详细说明。rand&#xff08;产生随机数&#xff09;表头文件: #include定义函数 :int rand(void)函数说明 :因为rand的内部实现是用线性同余法做的&#xff0c;他不是真…

repeating 路由_CSS中带有示例的repeating-linear-gradient()函数

repeating 路由Introduction: 介绍&#xff1a; So far, we have learned so many functions but learning never gets enough, therefore as a good developer, we must learn as many functions as we can and know their behavior with the help of practical implementati…

万字详解|手撕 9大排序算法!

0. 前言大家好&#xff0c;我是多选参数的程序锅&#xff0c;一个正在捣鼓操作系统、学数据结构和算法以及 Java 的失业人员。数据结构和算法我已经学了有一段日子了&#xff0c;最近也开始在刷 LeetCode 上面的题目了&#xff0c;但是自己感觉在算法上还是 0 &#xff0c;还得…

INF文件修改注册表

INF是一个用于安装驱动程序的文件&#xff0c;是文本格式的。里面记录着驱动程序的所有相关资料&#xff0c;随便打开一个INF文件度可看到很多奇奇怪怪的东西&#xff0c;其实这一点也不奇怪&#xff0c;因为每一项都有特定的含义&#xff0c;比如从VERSION一项就可以看出支持的…

.Net判断一个对象是否为数值类型探讨总结(高营养含量,含最终代码及跑分)...

前一篇发出来后引发了积极的探讨&#xff0c;起到了抛砖引玉效果&#xff0c;感谢大家参与。 吐槽一下&#xff1a;这个问题比其看起来要难得多得多啊。 大家的讨论最终还是没有一个完全正确的答案&#xff0c;不过我根据讨论结果总结了一个差不多算是最终版的代码&#xff0c;…

ai怎么约束每个字的大小_人工智能的约束满意问题

ai怎么约束每个字的大小Constraint Satisfactory problems, as the name suggests are the problems which have some constraints which need to be satisfied while solving any problem. In simpler words, we can say that while solving any problem or changing any stat…

一个多月的时间,终于把这件事做完了!

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;关注我的小伙伴都知道&#xff0c;前段时间磊哥搞了一个免费的模拟面试&#xff0c;但因为工作和&#xff08;面试&#xff…

简易电子密码锁制作

看到电影里面的保险箱用到的密码锁&#xff0c;于是心血来潮动手做了一个简单的密码锁&#xff0c;其有3次输入的机会&#xff0c;全错的话便进入死循环&#xff0c;一直警报&#xff0c;任何操作都无效&#xff0c;除了复位操作哈。所需素材&#xff1a;51单片机、1602液晶、蜂…

漫画:什么是红黑树?(整合版)

前段时间&#xff0c;小灰发布了红黑树相关的文章&#xff0c;分成上下篇来讲解。这一次&#xff0c;小灰把两篇文章做了整合&#xff0c;并且修正了红黑树删除部分的图片错误&#xff0c;感谢大家的指正。————— 第二天 —————————————————二叉查找树&a…

PHP高并发高负载系统架构

2019独角兽企业重金招聘Python工程师标准>>> 一、高并发和高负载的约束条件 硬件部署操作系统Web 服务器PHPMySQL测试二、解决之道——硬件篇 处理能力的提升&#xff1a;部署多颗CPU&#xff0c;选择多核心、具备更高运算频率、更大高速缓存的CPU&#xff1b; 处理…