这两天在写一个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会有大量的插入和删除操作,我认为对集合进行大量的插入删除性能会比较低,为了测试集合的插入删除性能对比队列低多少,我写了一个简单的并发测试:
- 
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的实现。
 
复制代码 【Java 集合与队列的插入、删除在并发下的性能比较】
原文:http://blog.csdn.net/u014714340/article/details/45337843