这两天在写一个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会有大量的插入和caozuo.html" target="_blank">删除操作,我认为对集合进行大量的插入删除性能会比较低,为了测试集合的插入删除性能对比队列低多少,我写了一个简单的并发测试:
- package com.xwtech.uomp.base.action.handler.admin;
-
- public class asdsd {
- ? ? ? ???/**
- ? ? ? ???测试集合与队列的插入与读写性能? ?**/
- ? ? ? ?? ?public class SetQueueTest {? ?? ?// 随即数构造器
- ? ? ? ?? ??
- ? ? ? ? ? ? ? ?? ?private static Random r = new Random(10);? ? // 控制测试线程停止的原子变量
- ? ? ? ?? ??
- ? ? ? ? ? ? ? ?? ?private static AtomicBoolean stop = new AtomicBoolean(false);??12?
- ? ??
- ? ? ? ? ? ? ? ?? ?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);? ?? ???
- ? ? ? ?? ?? ?? ?? ? ? ? }? ??
- ? ? ? ?? ?? ?? ?}??
- ? ? ? ?? ?}
- ? ? ? ?? ???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();??
- ? ? ? ?? ?? ?? ?? ? ? ? }??
- ? ? ? ?? ???}??
- ? ? 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();? ?? ?? ?
- ? ? ? ?? ?? ?? ?? ? ? ???}? ?
- ? ? ? ?? ?? ?? ? }??
- ? ???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();? ?? ?
- ? ? ? ?? ?? ?? ?}? ?? ??
- ? ? ? ?? ?? ?? ?? ? ? ?? ?}? ??
- ? ? ? ?? ?? ?? ???}??
- ? ? ? ?? ?? ?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的实现。
复制代码