`
milk_36
  • 浏览: 120761 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

转:关注Queue:Java 1.5 添加新的数据结构接口 (SynchronousQueue)

阅读更多

 

转载:http://blog.csdn.net/Victor_Jan/archive/2004/09/27/117695.aspx

Java 1.5版本最终提供了对编程中最基础数据结构之一-Queue的内在支持。本文章将探究新添加到java.util包中的Queue接口,演示如何去使用这个新特性去使你的数据处理流式化。


在 计算机学科中,基础数据结构之一 — Queue。你会想起Queue是一种数据结构,在它里边的元素可以按照添加它们的相同顺序被移除。在以前的Java版本中,这中FIFO(先进先出)数 据结构很不幸被忽略了。随着Java1.5(也叫Tiger)的出现,对Queue支持第一次成为固有特性。

过去在没有Queue的情况下如何管理?

在Java 1.5以前,通常的实现方式是使用java.util.List 集合来模仿Queue。Queue的概念通过把对象添加(称为enqueuing的操作)到List的尾部(即Queue的后部)并通过从List的头部 (即Queue的前部)提取对象而从List中移除(称为dequeuing的操作)来模拟。下面代码显示了你以前可能做法。


import java.util.Enumeration;
import java.util.LinkedList;
import java.util.Vector;

public class QueueEmulate 
{
     private LinkedList queueMembers;
     public Object enqueue(Object element)
     {
          queueMembers.add (element);
          return element;
     }

     public Object dequeue() 
     {
          return queueMembers.removeFirst();
     }
}


现在我们有了Queue

Java 1.5在java.util包中 新添加了Queue接口,下面代码中使用到它(从 sample codeSimpleQueueUsageExample.java 截取)。注意,在Java 1.5中,java.util.LinkedList被用来实现java.util.Queue,如同java.util.List接口。也要注意我是如 何显式地声明我的只包含字符串的Queue---使用<String> 泛型(如果需要深入了解泛型,请参阅"J2SE 1.5: Java's Evolution Continues ")。这样使我省却了当从 Queue 中提取它们时不得不进行对象造型的痛苦。


Queue<String> myQueue = new LinkedList<String>();
myQueue.add("Add Me");
myQueue.add("Add Me Too");
String enqueued = myQueue.remove();


你可以看到LinkedList类的add和remove方法被分别用于执行enqueuing和dequeuing操作。实际上没有更好的可用方法;Queue接口提供了新的offer和poll方法,如下显示(截取自SimpleQueueUsageExamplePreferred ):


Queue<String> myQueue = new LinkedList<String>();
boolean offerSuccess;
// offer method tries to enqueue.  
// A boolean is returned telling you if the 
// attempt to add to the queue was successful or not
offerSuccess=myQueue.offer("Add Me");
offerSuccess=myQueue.offer("Add Me Too");
// peek at the head of the queue, but don't grab it
Object pull = myQueue.peek();
String enqueued = null;
// grab the head of the queue
if (pull!=null) enqueued = myQueue.poll();
System.out.println(enqueued);


如果你的Queue有固定长度限制(常常是这种情形),使用add方法向Queue中添加内容,将导致抛出一个非检查异常。当你编译SimpleQueueUsageExample的代码时,编译器将会就此问题发出警告。相反,当新的offer方法试图添加时,如果一切正常则会返回TRUE,否则,返回FALSE。 同样地, 如果你试图使用remove方法对一个空Queue操作时 ,也将导致一个非检查异常。

你 也可以使用poll方法去从Queue中提取元素。如果在Queue中没有元素存在,poll方法将会返回一个null(即不会抛出异常)。在某些情况 下,你可能不想提取头部的元素而只是想看一下。Queue接口提供了一个peek方法来做这样的事情。如果你正在处理一个空Queue,peek方法返回 null。象add-offer和remove-poll关系一样,还有peek-element关系。在Queue为空的情形下,element方法抛 出一个非检查异常。但如果在Queue中有元素,peek方法允许你查看一下第一个元素而无需真的把他从Queue中取出来。peek方法的用法在SimpleQueueUsageExamplePreferred类中有示例。

AbstractQueue类

如你所见,java.util.LinkedList类实现了java.util.Queue接口,同样,AbstractQueue也是这样。 AbstractQueue 类实现了java.util接口的一些方法(因此在它的名字中包含abstract)。而AbstractQueue将重点放在了实现offer,poll和peek方法上。另外使用一些已经提供的具体实现。

PriorityQueue类

在 PriorityQueue中,当你添加元素到Queue中时,实现了自动排序。根据你使用的PriorityQueue的不同构造器,Queue元素的 顺序要么基于他们的自然顺序要么通过PriorirtyQueue构造器传入的Comparator来确定。下面的代码示例了PirorityQueue 类的使用方法。在Queue的前边是字符串"Alabama"-由于元素在PriorityQueue中是按自然顺序排列的(此例中是按字母表顺序)。


PriorityQueue<String> priorityQueue = new PriorityQueue<String>();
priorityQueue.offer("Texas");
priorityQueue.offer("Alabama");
priorityQueue.offer("California");
priorityQueue.offer("Rhode Island");
int queueSize = priorityQueue.size();
for (int i =0; i< queueSize; i++)
{
     System.out.println(priorityQueue.poll());
}


执行结果如下:

Alabama
California
Rhode Island
Texas





Queue各项按照自然顺序-字母顺序-来排列

如上提到的,你可以创建你自己的Comparator类并提供给PirorityQueue。如此,你可以定义你自己的排序方式。在PriorityQueueComparatorUsageExample 类中可找到此方式,在其中使用了一个名为State的助手类。如你在下边看到的,在类定义中,State只简单地包含了一个名字和人口。

private String name;
private int population;

public State(String name, int population)
{
     super();
     this.name = name;
     this.population = population;
}	

public String getName()
{
     return this.name;
}

public int getPopulation()
{
     return this.population;
}
public String toString()
{
     return getName() + " - " + getPopulation();
}


PriorityQueueComparatorUsageExample中,Queue使用了java.util.Comparator的自定义实现来定义排列顺序(如下)。


PriorityQueue<State> priorityQueue = 
     new PriorityQueue(6, new Comparator<State>()
{
     public int compare(State a, State b)
     {
       System.out.println("Comparing Populations");
       int populationA = a.getPopulation();
       int populationB = b.getPopulation();
       if (populationB>populationA)
            return 1;
       else if (populationB<populationA)
            return -1;
       else 
            return 0; 
     }
}
);


执行PriorityQueueComparatorUsageExample 类后,添加到Queue中的State对象将按人口数量排放(从低到高)。

阻塞Queue

Queue通常限定于给定大小。迄今为止,通过Queue的实现你已经看到,使用offer或add方法enqueue Queue(并用remove或poll来dequeue Queue)都是假设如果Queue不能提供添加或移除操作,那么你不需要等待程序执行。java.util.concurrent.BlockingQueue接口实现阻塞。它添加了put和take方法。举一个例子可能更有用。

使 用原来的producer/consumer关系来假定你的producer写一个Queue(更特定是一个BlockingQueue)。你有一些 consumer正从Queue中读取,在一个有序的方式下,哪种方式是你希望看到的。基本上,每个consumer需要等待先于它并获准从Queue中 提取项目的前一个consumer。用程序构建此结构,先生成一个producer线程用于向一个Queue中写数据,然后生成一些consumer线程 从同一Queue中读取数据。注意,线程会阻塞另一线程直到当前线程做完从Queue中提取一项的操作。

下 面的代码展示了类Producer写BlockingQueue的过程。注意run方法中的对象(你有责任实现,因为你继承了Thread)在等待了随机 数量的时间(范围从100到500毫秒)后,被放进了BlockingQueue。放到Queue中的对象只是一些包含消息产生时的时间的字符串。

添加对象的实际工作是由如下语句实现的:
blockingQueue.put("Enqueued at: " + time)

put方法会抛出InterruptedException,因此,put操作需要被try...catch块包围,用来捕获被抛出的异常 (见Listing 1

从producer中提取消息的是Consumer对象,它也继承自Thread对象并因此要实现run方法(见Listing 2 )。

Consumer 类在设计上是类似于Producer类的。Consumer类使用take方法去从Queue中取出(即dequeue)消息,而不是将消息放到 BlockingQueue中。如前所述,这需要等待到有什么内容确实存在于Queue中时才发生。如果producer线程停止放置(即 enqueue)对象到Queue中,那么consumer将等待到Queue的项目有效为止。下面所示的TestBlockingQueue类,产生四 个consumer线程,它们从BlockingQueue中尝试提取对象。

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class TestBlockingQueue
{
  public static void main(String args[])
  {
    BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<String>();
    Producer producer = new Producer(blockingQueue, System.out);
    Consumer consumerA = new Consumer("ConsumerA", blockingQueue, System.out);
    Consumer consumerB = new Consumer("ConsumerB", blockingQueue, System.out);
    Consumer consumerC = new Consumer("ConsumerC", blockingQueue, System.out);
    Consumer consumerD = new Consumer("ConsumerD", blockingQueue, System.out);	
    producer.start();
    consumerA.start();
    consumerB.start();
    consumerC.start();
    consumerD.start();  
  }
}


Figure 1. Consumer Threads: These threads dequeue messages from the BlockingQueue in the order that you spawned them.

下面一行创建BlockingQueue:


BlockingQueue<String> blockingQueue 
                    = new LinkedBlockingQueue<String>();


 

 

 

 

 

注意,它使用BlockingQueue的LinkedBlockingQueue实现。 这是因为BlockingQueue是一个抽象类,你不能直接实例化它。你也可以使用ArrayBlockingQueueQueue类型。 ArrayBlockingQueue使用一个数组作为它的存储设备,而LinkedBlockingQueue使用一个LinkedList。 ArrayBlockingQueue的容量是固定的。对于LinkedBlockingQueue,最大值可以指定;默认是无边界的。本示例代码采用无 边界方式。

在类的执行期间,从Queue中读取对象以顺序方式执行(见下面例子的执行)。实际上,一个consumer线程阻塞其他访问BlockingQueue的线程直到它可以从Queue中取出一个对象。

DelayQueue-我是/不是不完整的

在某些情况下,存放在Queue中的对象,在它们准备被取出之前,会需要被放在另一Queue中一段时间。这时你可使用java.util.concurrent.DelayQueue类,他实现类BlockingQueue接口。DelayQueue需要Queue对象被驻留在Queue上一段指定时间。

我想用来证实它的现实例子(这可能是你非常渴望的)是关于松饼(muffins)。噢,Muffin对象(象我们正在谈论的Java-没有coffee双关意图)。假定你有一个DelayQueue并在其中放了一些Muffin对象。Muffin对象(如下所示)必须实现java.util.concurrent.Delayed 接口,以便可被放在DelayQueue中。这个接口需要Muffin对象实现getDelay方法(如下所示)。getDelay方法,实际上声明给多 长时间让对象保存在DelayQueue中。当该方法返回的值变为0或小于0时,对象就准备完毕(或在本例子中,是烤制完毕)并允许被取出(见 Listing 3 )。

Muffin类也实现compareTo(java.util.concurrent.Delayed)方法。由于Delayed接口继承自 java.lang.Comparable 类,这通过约定限制你要实现Muffin对象的bakeCompletion时间。

由于你不是真想去吃没有完全烤熟的Muffin,因此,需要将Muffin放在DelayQueue中存放推荐的烤制时间。Listing 4 ,取自DelayQueueUsageExample类,展示了从DelayQueue中enqueue和dequeue Muffin对象。

如你所见,对Muffin对象的烤制时间是使用它的构造器设置的(构造器期望烤制时间是以秒计)。

如 前所讲,Muffin对象放到DelayQueue中是不允许被取出的,直到他的延时时间(又叫烤制时间)超期。元素被从Queue中取出基于最早的延时 时间。在本例中,如果你有一些已经烤过的Muffin对象,他们将按他们已经等待多久而被取出(换句话说,最早被烤制的Muffin会在新烤制的 Muffin之前被取出)。

SynchronousQueue

在Java 1.5中,另外一种阻塞Queue实现是SynchronousQueue。相当有趣的是,该Queue没有内在容量。这是故意的,因为Queue意在用 于传递目的。这意味着,在一个同步Queue结构中,put请求必须等待来自另一线程的给SynchronousQueue的take请求。同时,一个 take请求必须等待一个来自另一线程的给SynchronousQueue的put请求。用程序来示例此概念,可参见示例代码。类似于前边的 LinkedBlockingQueue例子,它包含一个consumer(SynchConsumer),见Listing 5

Listing 5 中的代码使用SynchronousQueue类的 poll(long timeout,TimeUnit unit)方法。此方法允许poll过程在厌倦等待另一消费线程写SynchronousQueue之前等待一个指定时间(本例中是20秒)。

Listing 6 中的producer(SynchProducer )使用相似的offer(E o,long timeout, TimeUnit unit)方法去放置对象到SynchronousQueue中。使用此方法允许在 厌倦等待另一线程去读取SynchronousQueue之前等待一段时间(本例中为10秒)

TestSynchQueue 展示了producer和consumer的动作:

import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class TestSynchQueue
{
  public static void main(String args[])
  {
    SynchronousQueue<String> synchQueue = new SynchronousQueue<String>();
    SynchProducer producer = new SynchProducer("ProducerA",synchQueue, System.out);
    SynchConsumer consumerA = new SynchConsumer("ConsumerA", synchQueue, System.out);
    consumerA.start();
    producer.start();
  }
}


当试图明白隐藏在SynchronousQueue后面的概念时,要牢记这些Queue通常被使用在什么地方。JavaDoc中关于同步Queue指出:

"它们[同步Queue]是适合于传递设计,在那里运行在一个线程中的对象必须与运行在另外一个线程中的对象同步以便于交给它一些信息,时间或任务。"

作者简介:

Kulvir Singh Bhogal 是为IBM工作的顾问, 规划并实现在客户网站上的Java-centric解决方案.
分享到:
评论

相关推荐

    java 同步器SynchronousQueue详解及实例

    Java 同步器 SynchronousQueue 详解及实例 Java 中的同步器 SynchronousQueue 是一种特殊的阻塞队列,它最多只能放一个元素,这个元素如果不在特定的时间消费掉就会被删除,队列的长度始终为 0。SynchronousQueue ...

    C++ Queue(带上限的)

    在C++编程语言中,`Queue`是一种常用的数据结构,它遵循“先进先出”(First In First Out, FIFO)的原则。通常,C++标准库提供了`&lt;queue&gt;`头文件来实现基本的队列操作,但这个标准队列并没有设置上限。在某些特定...

    C、C++、JAVA数据结构与算法电子书

    - **队列**:先进先出(FIFO)的数据结构,C++的std::queue,Java的java.util.Queue提供实现。 - **树**:如二叉树、AVL树、红黑树等,C++和Java都有标准库支持,C则需要自定义实现。 - **哈希表**:C++的std::...

    JAVA基本5种数据结构的实现。

    在Java编程语言中,数据结构是组织和管理数据的关键元素,它们提供了高效访问和操作数据的方式。本主题将深入探讨Java中的五种基础数据结构:链表、队列、栈、双端队列(deque)以及堆。我们将通过分析给定的源代码...

    Java软件结构与数据结构源码

    Java软件结构与数据结构源码是学习和理解Java编程中核心概念的重要资源。数据结构是计算机科学的基础,它涉及到如何组织和存储数据以便于高效地访问和修改。在Java中,掌握数据结构对于开发高性能、可扩展的软件至关...

    数据结构与算法(JAVA语言版)

    Java的`java.util.Queue`接口提供了enqueue(通常为add方法)、dequeue(通常为remove或poll方法)等操作。 5. **堆**:堆是一种特殊的树形数据结构,满足堆属性:父节点的值总是大于或等于(最大堆)或小于或等于...

    实用数据结构教程_Java语言描述

    Java提供了`java.util.Queue`接口及其实现,如`LinkedList`可以作为队列使用。 5. **集合**:包括ArrayList、LinkedList、HashSet、HashMap等,是Java中常用的容器类。ArrayList基于动态数组,适合随机访问;...

    Java程序设计与数据结构第九章习题答案

    在Java程序设计与数据结构的学习过程中,第九章通常会涵盖数据结构的重要概念和应用,包括数组、链表、栈、队列、树等基础数据结构,以及如何利用这些数据结构来解决问题。本资源提供了第九章的习题答案,旨在帮助...

    数据结构教程(java语言描述)-源码和课件-李春葆

    4. **队列**:队列是一种先进先出(FIFO)的数据结构,Java中的Queue接口和LinkedList类可实现队列。队列常用于任务调度、缓冲区等。 5. **树**:树是一种非线性数据结构,包括二叉树、平衡树(如AVL树、红黑树)等...

    Java队列实现,数据结构

    在这个Java队列实现的数据结构作业练习中,我们将会探讨如何使用Java来创建一个简单的队列,并分析`Queue.java`和`Node.java`这两个文件可能包含的内容。 首先,`Queue.java`很可能是实现队列接口或类的文件。在...

    Java数据结构和算法中文第二版

    - **数组(Array)**:Java中的数组是基本的数据结构,可以直接声明和使用。 - **链表(Linked List)**:`java.util.LinkedList`提供了链表的实现。 - **栈(Stack)**:`java.util.Stack`是一个继承自`Vector`的类,实现...

    数据结构JAVA实现

    这个压缩包中的代码示例可能包含了这些数据结构的实现,例如,链表可能包含`Node`类和相关的操作方法,有序二叉树可能有`BinarySearchTree`类,队列则可能是对`Queue`接口的简单实现。通过学习和理解这些代码,...

    实用数据结构教程_Java语言描述.zip

    Java中的`java.util.Queue`接口和其实现如`LinkedList`可实现队列操作。 5. 树:树是分层的数据结构,每个元素(节点)可能有零个或多个子节点。二叉树是最常见的树类型,包括二叉查找树(BST)、平衡二叉树(AVL、...

    Java 软件结构域数据结构第四版源代码

    《Java软件结构域数据结构第四版》是一本深入探讨如何在Java编程环境中设计和使用数据结构的权威书籍。这本书的核心目标是帮助读者理解和掌握数据结构的基本概念,以及如何有效地利用这些概念来解决实际的编程问题。...

    java程序设计与数据结构

    6. **集合框架**:Java集合框架是处理对象数组的工具,包括List、Set、Queue和Map等接口,以及ArrayList、LinkedList、HashSet、HashMap等实现类。掌握这些集合的使用能有效提高代码的效率和可维护性。 7. **数据...

    Java程序设计与数据结构第六章习题答案

    队列是先进先出(FIFO)的数据结构,适用于任务调度、消息传递等,Java的Queue接口和LinkedList类可以用来实现队列。 4. **树**:树是一种非线性的数据结构,每个节点可能有零个或多个子节点。二叉树是树的一种特殊...

    Java数据结构实现之Queue.zip

    本篇文章将深入探讨Java中队列(Queue)数据结构的实现。 队列是一种线性数据结构,遵循“先进先出”(FIFO,First In First Out)的原则。它的一端称为前端(Front)或头,另一端称为后端(Rear)。新元素在后端...

    免费:数据结构(c与c++与java三本书高清晰版).rar

    JAVA版的《数据结构》则可能强调Java平台特有的数据结构实现,如集合框架(Collection Framework),其中包括接口(如List、Set和Queue)和实现(如ArrayList、LinkedList、HashSet、TreeSet等)。Java的这些数据...

    数据结构(Java版)源代码

    Java的`java.util.Queue`接口及其实现类如`LinkedList`可以用来创建队列。 5. **哈希表**:哈希表(HashMap)通过哈希函数将键映射到值,提供了快速的插入、删除和查找操作。Java的`java.util.HashMap`类是实现哈希...

    数据结构常用接口下载

    在Java等面向对象的语言中,数据结构通常通过类或接口来实现。本资源提供的是“数据结构常用接口”,这意味着它包含了对常见数据结构操作的规范定义,使得开发人员可以方便地按照这些接口去编写自己的数据结构实现,...

Global site tag (gtag.js) - Google Analytics