【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. package com.xwtech.uomp.base.action.handler.admin;
  2. public class asdsd {
  3. /**
  4. 测试集合与队列的插入与读写性能   **/
  5. public class SetQueueTest {      // 随即数构造器
  6. private static Random r = new Random(10);    // 控制测试线程停止的原子变量
  7. private static AtomicBoolean stop = new AtomicBoolean(false);  12
  8. static abstract class Service {
  9. // 操作的计数器
  10. protected long count = 0;
  11. // 添加一堆元素,并去一个元素
  12. public abstract String addAndPick(List<String> elements);
  13. // 取一个元素
  14. public abstract String pickOne();
  15. public void tell() {
  16. System.out.println(this + " :t" + count);
  17. }
  18. }
  19. }
  20. static class SetService extends Service {
  21. private TreeSet<String> set = new TreeSet<String>();
  22. @Override
  23. public synchronized String addAndPick(List<String> elements)
  24. {
  25. count++;
  26. set.addAll(elements);
  27. return set.pollFirst();
  28. }
  29. @Override
  30. public synchronized String pickOne()
  31. {
  32. count++;
  33. return set.pollFirst();
  34. }
  35. }
  36. static class QueueService extends Service {
  37. private Queue<String> queue = new LinkedList<String>();
  38. @Override
  39. public synchronized String addAndPick(List<String> elements)
  40. {             count++;
  41. queue.addAll(elements);
  42. return queue.poll();
  43. }
  44. @Override
  45. public synchronized String pickOne() {
  46. count++;
  47. return queue.poll();
  48. }
  49. }
  50. static class Tester implements Runnable {
  51. // 绑定要测试的工具对象
  52. private Service service;
  53. Tester(Service s) {
  54. this.service = s;
  55. }
  56. @Override
  57. public void run() {
  58. while (stop.get() == false) {
  59. List<String> elements = new ArrayList<String>();
  60. int len = r.nextInt(200) + 8;
  61. for (int i = 0; i < len; i++) {
  62. elements.add(String.valueOf(r.nextInt()));
  63. }
  64. service.addAndPick(elements);
  65. for (int i = 0; i < 104; i++)
  66. service.pickOne();
  67. }
  68. }
  69. }
  70. private static void test(Service service, int time, TimeUnit unit)
  71. throws InterruptedException
  72. {
  73. ExecutorService execs = Executors.newCachedThreadPool();
  74. for (int i = 0; i < 20; i++) {
  75. execs.execute(new Tester(service));
  76. }
  77. execs.shutdown();
  78. unit.sleep(time);
  79. stop.compareAndSet(false, true);
  80. service.tell();
  81. }
  82. public static void main(String[] args) throws InterruptedException
  83. {
  84. Service setService = new SetService();
  85. test(setService, 5, TimeUnit.SECONDS);
  86. stop.compareAndSet(true, false);// 重置终止条件
  87. Service queueService = new QueueService();
  88. test(queueService, 5, TimeUnit.SECONDS);
  89. }
  90. }

复制代码

输出的结果如下:

  1. [email protected] :      7149859 [email protected] :    24303408

复制代码

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

复制代码

时间: 2024-11-05 12:34:48

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

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

这两天在写一个java多线程的爬虫,以广度优先爬取网页,设置两个缓存: 一个保存已经访问过的URL:vistedUrls 一个保存没有访问过的URL:unVistedUrls 需要爬取的数据量不大,对URL压缩后,可以把这两个数据结构都放入内存,vistedUrls很显然用HashSet<String>实现,因为已经访问的URL只会添加,不会删除和修改,使用HashSet可以高效判断一个URL是否已经访问. 纠结unVistedUrls该用什么数据结构,如果用队列的话,并发情况下,队列中可能会

Java集合 之 队列Queue集合

什么是Queue集合? 答:Queue用于模拟队列这种数据结构.队列通常是指“先进先出(FIFO)”的容器.队列的头部保存在队列中存放时间最长的元素,尾部保存存放时间最短的元素.新元素插入到队列的尾部,取出元素会返回队列头部的元素.通常,队列不允许随机访问队列中的元素. Queue接口中定义了如下的几个方法: void add(Object e): 将指定元素插入到队列的尾部. object element(): 获取队列头部的元素,但是不删除该元素. boolean offer(Object

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

转载:大关的博客 Java多线程 阻塞队列和并发集合 本章主要探讨在多线程程序中与集合相关的内容.在多线程程序中,如果使用普通集合往往会造成数据错误,甚至造成程序崩溃.Java为多线程专门提供了特有的线程安全的集合类,通过下面的学习,您需要掌握这些集合的特点是什么,底层实现如何.在何时使用等问题. 3.1 BlockingQueue接口 java阻塞队列应用于生产者消费者模式.消息传递.并行任务执行和相关并发设计的大多数常见使用上下文. BlockingQueue在Queue接口基础上提供了额外

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

java中的具体容器类都不是从头构建的,他们都继承了一些抽象容器类.这些抽象容器类,提供了容器接口的部分实现,方便具体容器类在抽象类的基础上做具体实现.容器类和接口的关系架构图如下: 虚线框表示接口,有Collection, List, Set, Queue, Deque和Map. 有六个抽象容器类: AbstractCollection: 实现了Collection接口,被抽象类AbstractList, AbstractSet, AbstractQueue继承,ArrayDeque也继承自A

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

1 集合 1.1 为什么会出现集合框架 [1] 之前的数组作为容器时,不能自动拓容 [2] 数值在进行添加和删除操作时,需要开发者自己实现添加和删除. 1.2 Collection接口 1.2.1 Collection基础API Java集合框架提供了一套性能优良.使用方便的接口和类,它们位于java.util包中. Collection表示集合的根接口,可以看成一个容器,存储了很多对象,这些对象称为Collection元素.Collection要求元素必须是引用数据类型. Collection

Java 集合框架(五):阻塞队列

阻塞队列 如果我们想要在线程安全的场景下使用队列,只有两个选择,一个是上面讲过的 ConcurrentLinkedQueue,还有就是我们要将的阻塞队列. 从名字我们就可以判断出阻塞队列适用的场景,那就是生产者消费者模式.阻塞对垒的添加和删除操作在队列满或者空的时候会被阻塞.这就保证了线程安全. 阻塞队列提供了四种处理方法: 方法 抛异常 返回特殊值 一直阻塞 超时退出 插入 add(E e) offer(e) put(e) offer(e,time,unit) 删除 remove() poll

java 集合遍历时删除元素

本文探讨集合在遍历时删除其中元素的一些注意事项,代码如下 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 import java.util.ArrayList; import java.util.Iterator; import java

Java集合遍历时删除

public static void main(String[] args){ List<Integer> list = new ArrayList<Integer>(); list.add(1); list.add(2); list.add(3); list.add(4); list.add(5); Iterator<Integer> interator = list.iterator(); while(interator.hasNext()){ Integer i

Java 集合(List、Set)遍历、判断、删除元素时的小陷阱

开发中,常有场景:遍历集合,依次判断是否符合条件,如符合条件则删除当前元素. 不知不觉中,有些陷阱,不知你有没有犯. 1. 一.漏网之鱼-for循环递增下标方式遍历集合,并删除元素 如果你用for循环递增下标方式遍历集合,在遍历过程中删除元素,你可能会遗漏了某些元素.说那么说可能也说不清楚,看以下示例: import java.util.ArrayList; import java.util.List; public class ListTest_Unwork { public static v