这两天在写一个java多线程的爬虫,以广度优先爬取网页,设置两个缓存:

  •   一个保存已经访问过的URL:vistedUrls
  •   一个保存没有访问过的URL:unVistedUrls

  需要爬取的数据量不大,对URL压缩后,可以把这两个数据结构都放入内存,vistedUrls很显然用HashSet<String>实现,因为已经访问的URL只会添加,不会删除和修改,使用HashSet可以高效判断一个URL是否已经访问。

  纠结unVistedUrls该用什么数据结构,如果用队列的话,并发情况下,队列中可能会用重复的URL,比如一个线程A爬了CSDN的一个URL1,另一个线程B爬了博客园的一个URL2,URL1和URL2的页面都有一个相同的出链URL3,线程A把URL3加入到unVistedUrls的队尾,等待下次爬取,但在URL3被爬取之前,线程B也把URL3加到队尾,这样队列中就有两个相同的URL,可能会导致重复爬取网页,当然可以通过其他方法来保证不会重复爬取。

  然后就想能否也用Set来保存未访问的URL,这样在添加新的URL时,自动去重处理了,能够有效保证不爬取重复网页。但是unVistedUrls会有大量的插入和删除操作,我认为对集合进行大量的插入删除性能会比较低,为了测试集合的插入删除性能对比队列低多少,我写了一个简单的并发测试:

 /**
* 测试集合与队列的插入与读写性能
*
* @author jiqunpeng@gmail.com
*
*/
public class SetQueueTest {
// 随即数构造器
private static Random r = new Random(10);
// 控制测试线程停止的原子变量
private static AtomicBoolean stop = new AtomicBoolean(false); /***
* 基类,供测试用
*
* @author jiqunpeng@gmail.com
*
*/
static abstract class Service {
// 操作的计数器
protected long count = 0; // 添加一堆元素,并去一个元素
public abstract String addAndPick(List<String> elements); // 取一个元素
public abstract String pickOne(); /**
* 打印操作次数
*/
public void tell() {
System.out.println(this + " :\t" + count);
}
} /***
* 采用TreeSet的集合工具
*
* @author jiqunpeng@gmail.com
*
*/
static class SetService extends Service {
private TreeSet<String> set = new TreeSet<String>(); @Override
public synchronized String addAndPick(List<String> elements) {
count++;
set.addAll(elements);
return set.pollFirst();
} @Override
public synchronized String pickOne() {
count++;
return set.pollFirst();
} } /***
* 采用LinkedList的队列工具
*
* @author jiqunpeng@gmail.com
*
*/
static class QueueService extends Service {
private Queue<String> queue = new LinkedList<String>(); @Override
public synchronized String addAndPick(List<String> elements) {
count++;
queue.addAll(elements);
return queue.poll();
} @Override
public synchronized String pickOne() {
count++;
return queue.poll();
}
} /***
* 测试类
*
* @author jiqunpeng@gmail.com
*
*/
static class Tester implements Runnable {
// 绑定要测试的工具对象
private Service service; Tester(Service s) {
this.service = s;
} @Override
public void run() {
while (stop.get() == false) {
List<String> elements = new ArrayList<String>();
int len = r.nextInt(200) + 8;
for (int i = 0; i < len; i++) {
elements.add(String.valueOf(r.nextInt()));
}
service.addAndPick(elements);
for (int i = 0; i < 104; i++)
service.pickOne();
}
}
} /***
* 多线程方式,测试一个插入、删除工具
*
* @param service
* @param time
* @param unit
* @throws InterruptedException
*/
private static void test(Service service, int time, TimeUnit unit)
throws InterruptedException {
ExecutorService execs = Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
execs.execute(new Tester(service));
}
execs.shutdown();
unit.sleep(time);
stop.compareAndSet(false, true);
service.tell();
} public static void main(String[] args) throws InterruptedException {
Service setService = new SetService();
test(setService, 5, TimeUnit.SECONDS);
stop.compareAndSet(true, false);// 重置终止条件
Service queueService = new QueueService();
test(queueService, 5, TimeUnit.SECONDS);
}
}

  输出的结果如下:

SetQueueTest$SetService@5e9de959 :      7149859
SetQueueTest$QueueService@11b343e0 : 24303408

  测试结果让我感到吃惊,TreeSet的插入删除效率确实比LinkedList低,20个线程跑了10秒,使用队列,插入删除24303408次,使用集合,插入删除7149859次。它们之间差距并不大,队列只比集合快2~3倍。属于同一个数量级。于是我这个小型的爬虫应该放心的选择用Set作为unVistedUrls的实现。

转载请注明出处www.cnblogs.com/fengfenggirl

Java 集合与队列的插入、删除在并发下的性能比较的更多相关文章

  1. Java集合--阻塞队列及各种实现的解析

    阻塞队列(Blocking Queue) 一.队列的定义 说的阻塞队列,就先了解下什么是队列,队列也是一种特殊的线性表结构,在线性表的基础上加了一条限制:那就是一端入队列,一端出队列,且需要遵循FIF ...

  2. Java 集合深入理解(10):Deque 双端队列

    点击查看 Java 集合框架深入理解 系列, - ( ゜- ゜)つロ 乾杯~ 什么是 Deque Deque 是 Double ended queue (双端队列) 的缩写,读音和 deck 一样,蛋 ...

  3. Java 集合深入理解(9):Queue 队列

    点击查看 Java 集合框架深入理解 系列, - ( ゜- ゜)つロ 乾杯~ 今天心情不太好,来学一下 List 吧! 什么是队列 队列是数据结构中比较重要的一种类型,它支持 FIFO,尾部添加.头部 ...

  4. Java多线程 阻塞队列和并发集合

    转载:大关的博客 Java多线程 阻塞队列和并发集合 本章主要探讨在多线程程序中与集合相关的内容.在多线程程序中,如果使用普通集合往往会造成数据错误,甚至造成程序崩溃.Java为多线程专门提供了特有的 ...

  5. java集合详解(附栈,队列)

    1 集合 1.1 为什么会出现集合框架 [1] 之前的数组作为容器时,不能自动拓容 [2] 数值在进行添加和删除操作时,需要开发者自己实现添加和删除. 1.2 Collection接口 1.2.1 C ...

  6. Java集合总结(一):列表和队列

    java中的具体容器类都不是从头构建的,他们都继承了一些抽象容器类.这些抽象容器类,提供了容器接口的部分实现,方便具体容器类在抽象类的基础上做具体实现.容器类和接口的关系架构图如下: 虚线框表示接口, ...

  7. 对JAVA集合进行遍历删除时务必要用迭代器

    java集合遍历删除的方法: 1.当然这种情况也是容易解决,实现方式就是讲遍历与移除操作分离,即在遍历的过程中,将需要移除的数据存放在另外一个集合当中,遍历结束之后,统一移除. 2.使用Iterato ...

  8. java集合循环删除

    java集合循环删除,java list集合操作,java循环.分享牛,分享牛原创.java集合删除方法. 2.6.1.第一种方式 list.add("1"); list.add( ...

  9. java集合遍历删除指定元素异常分析总结

    在使用集合的过程中,我们经常会有遍历集合元素,删除指定的元素的需求,而对于这种需求我们往往使用会犯些小错误,导致程序抛异常或者与预期结果不对,本人很早之前就遇到过这个坑,当时没注意总结,结果前段时间又 ...

随机推荐

  1. yii2 输出xml格式数据

    作者:白狼 出处:http://www.manks.top/yii2_xml_response.html.html本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文 ...

  2. yii2整合百度编辑器umeditor

    作者:白狼 出处:www.manks.top/article/yii2_umeditor 本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责 ...

  3. 什么是Java实例初始化块

    在本篇文章,我将会使用一个例子展示什么是实例变量初始化块,实例初始化块和静态初始化块,然后说明在Java中实例初始化块是如何工作的. 执行顺序 查看下面的代码,你知道哪个先执行吗? package s ...

  4. 在查询时将查询条件放入Session中,导出时直接根据qpniRGaFiler取查询条件即可

    在查询时将查询条件放入Session中,导出时直接根据qpniRGaFiler取查询条件即可

  5. eclipse js提醒报错

    在用eclipse开发项目时,有时候导入项目后,报错为 Problem Occurred: Errors occurred during the build.    Errors running bu ...

  6. NuGet学习笔记2——使用图形化界面打包自己的类库

    NuGet相对于我们最重要的功能是能够搭建自己的NuGet服务器,实现公司内部类库的轻松共享更新.在安装好NuGet扩展后,我们已经能够通过NuGet轻松下载自己需要的类库,下面来说一说如何将自己的项 ...

  7. sqlplus: error while loading shared libraries: /u01/app/lib/libclntsh.so.11.1

    成功安装了Oracle 11g后,使用sqlplus登录数据库时遇到下面错误: [oracle@DB-Server ~]$ sqlplus / as sysdba   sqlplus: error w ...

  8. SQLServer基本函数

    1.字符串函数  长度与分析用 datalength(Char_expr) 返回字符串包含字符数,但不包含后面的空格 substring(expression,start,length) 取子串 ri ...

  9. Storm系列(三):创建Maven项目打包提交wordcount到Storm集群

    在上一篇博客中,我们通过Storm.Net.Adapter创建了一个使用Csharp编写的Storm Topology - wordcount.本文将介绍如何编写Java端的程序以及如何发布到测试的S ...

  10. hw 要的是螺丝钉

    日前突然接到华为HR的电话,叫我去面试。本来我的工作和工资收入等各方面在本地也还算可以,没有想要跳槽。但是本着去看看有没有更好机会的想法就去了。  9:30到了现场后,在那里等了很久,一个考官上来问了 ...