首页 > 代码库 > Java 集合与队列的插入、删除在并发下的性能比较

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 + " :\t" + 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