• Java 集合与队列的插入、删除在并发下的性能比较


      这两天在写一个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会有大量的插入和删除操作,我认为对集合进行大量的插入删除性能会比较低,为了测试集合的插入删除性能对比队列低多少,我写了一个简单的并发测试:

      1 /**
      2  * 测试集合与队列的插入与读写性能
      3  * 
      4  * @author jiqunpeng@gmail.com
      5  * 
      6  */
      7 public class SetQueueTest {
      8     // 随即数构造器
      9     private static Random r = new Random(10);
     10     // 控制测试线程停止的原子变量
     11     private static AtomicBoolean stop = new AtomicBoolean(false);
     12 
     13     /***
     14      * 基类,供测试用
     15      * 
     16      * @author jiqunpeng@gmail.com
     17      * 
     18      */
     19     static abstract class Service {
     20         // 操作的计数器
     21         protected long count = 0;
     22 
     23         // 添加一堆元素,并去一个元素
     24         public abstract String addAndPick(List<String> elements);
     25 
     26         // 取一个元素
     27         public abstract String pickOne();
     28 
     29         /**
     30          * 打印操作次数
     31          */
     32         public void tell() {
     33             System.out.println(this + " :	" + count);
     34         }
     35     }
     36 
     37     /***
     38      * 采用TreeSet的集合工具
     39      * 
     40      * @author jiqunpeng@gmail.com
     41      * 
     42      */
     43     static class SetService extends Service {
     44         private TreeSet<String> set = new TreeSet<String>();
     45 
     46         @Override
     47         public synchronized String addAndPick(List<String> elements) {
     48             count++;
     49             set.addAll(elements);
     50             return set.pollFirst();
     51         }
     52 
     53         @Override
     54         public synchronized String pickOne() {
     55             count++;
     56             return set.pollFirst();
     57         }
     58 
     59     }
     60 
     61     /***
     62      * 采用LinkedList的队列工具
     63      * 
     64      * @author jiqunpeng@gmail.com
     65      * 
     66      */
     67     static class QueueService extends Service {
     68         private Queue<String> queue = new LinkedList<String>();
     69 
     70         @Override
     71         public synchronized String addAndPick(List<String> elements) {
     72             count++;
     73             queue.addAll(elements);
     74             return queue.poll();
     75         }
     76 
     77         @Override
     78         public synchronized String pickOne() {
     79             count++;
     80             return queue.poll();
     81         }
     82     }
     83 
     84     /***
     85      * 测试类
     86      * 
     87      * @author jiqunpeng@gmail.com
     88      * 
     89      */
     90     static class Tester implements Runnable {
     91         // 绑定要测试的工具对象
     92         private Service service;
     93 
     94         Tester(Service s) {
     95             this.service = s;
     96         }
     97 
     98         @Override
     99         public void run() {
    100             while (stop.get() == false) {
    101                 List<String> elements = new ArrayList<String>();
    102                 int len = r.nextInt(200) + 8;
    103                 for (int i = 0; i < len; i++) {
    104                     elements.add(String.valueOf(r.nextInt()));
    105                 }
    106                 service.addAndPick(elements);
    107                 for (int i = 0; i < 104; i++)
    108                     service.pickOne();
    109             }
    110         }
    111     }
    112 
    113     /***
    114      * 多线程方式,测试一个插入、删除工具
    115      * 
    116      * @param service
    117      * @param time
    118      * @param unit
    119      * @throws InterruptedException
    120      */
    121     private static void test(Service service, int time, TimeUnit unit)
    122             throws InterruptedException {
    123         ExecutorService execs = Executors.newCachedThreadPool();
    124         for (int i = 0; i < 20; i++) {
    125             execs.execute(new Tester(service));
    126         }
    127         execs.shutdown();
    128         unit.sleep(time);
    129         stop.compareAndSet(false, true);
    130         service.tell();
    131     }
    132 
    133     public static void main(String[] args) throws InterruptedException {
    134         Service setService = new SetService();
    135         test(setService, 5, TimeUnit.SECONDS);
    136         stop.compareAndSet(true, false);// 重置终止条件
    137         Service queueService = new QueueService();
    138         test(queueService, 5, TimeUnit.SECONDS);
    139     }
    140 }

      输出的结果如下:

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

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

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

  • 相关阅读:
    django 登入,注册,密码重置
    Django 通过 admin 操作 MySQL数据库 ,审核功能加储存记录
    Python Django 循环插入到 MongoDB数据库中
    运行Django找不到Windows“http://127.0.0.1:8000/”的问题
    大数据 ----- Linux(基础命令)
    大数据 ----- 虚拟机与SXhell6 的链接
    大数据 ----- 在Xshell6 安装Hadoop与JDK以及环境变量
    java day14第十四课 IO(输入、输出)流和JAVA目录操作(重中之重)
    13弹;集合中的工具类Collections和Arrays
    第3弹;流程控制
  • 原文地址:https://www.cnblogs.com/fengfenggirl/p/SetQueueTest.html
Copyright © 2020-2023  润新知