1.特点:队列有“先进先出”的特点,使用的场景多数用于资源池这种,例如:线程池中对等待线程的处理,数据库连接池中对线程的管理
2. 队列的生成有两种方式:(1)基于数组生成,称为顺序队列。(2)基于链表生成,称为链式队列。
3.基于数组生成队列的程序:
(1)在队列中进行入队操作时,tail指针会不断地向后移动,直到数组已经被装满。进行出队操作时,head指针会不断地从0开始向后移动,这样会出现一个情况:(当tail指针移动到最后时,即使数组的0---head区间有位置,也不会承装数据,造成数组资源的浪费),所以我们需要采用 “数据搬移” 的操作。
数据搬移有两种方式:(1)每次有数据入队,便会搬移一次,相当于每次都删除下标为0的数据。这样入队操作的时间复杂对便会从O(1)变成O(n)。(2)只有当tail指针到达最后时,并且head指针不为0,才进行一次整体的数据搬移操作,这样入队操作的平均时间复杂度还是O(1)。
/*
* 基于数组实现的顺序队列*/
public class ArrayQueue {
/*定义队列中承装数据的数组*/
private Object[] elementData;
/*定义队列的容量*/
private int capacity;
/*定义队列的头指针和尾指针*/
private int head;
private int tail;
public ArrayQueue(int capacity) {
this.elementData = new Object[capacity];
this.capacity = capacity;
this.head = 0;
this.tail = 0;
}
/*
* 入队操作*/
public boolean enQueue(Object element) {
/*判断当前队列是否已满*/
if(capacity == tail) return false;
elementData[tail] = element;
tail++;
return true;
}
/*
* 采用数据搬移的方法,使数组中的空闲空间,得以充分的利用*/
public boolean enQueueMove(Object element) {
/*如果尾指针tail=capacity && head=0时,表明数组已经装满*/
if(tail == capacity ) {
if(head == 0) return false;
/*如果尾指针tail=capacity,并且head!=0时,进行数据搬移操作*/
for(int i=head;i<tail;i++) {
elementData[i-head] = elementData[i];
}
//搬移完数据之后,重新设置head,tail的指针
tail = tail - head;
head = 0;
}
/*如果队列后面还有剩余的位置,那么数据正常加入队尾*/
elementData[tail] = element;
tail++;
return true;
}
/*
* 出队操作*/
public Object deQueue() {
/*判断当前队列是否为空*/
if(tail == head) return null;
Object element = elementData[head];
head++;
return element;
}
public static void main(String[] args) {
ArrayQueue queue = new ArrayQueue(5);
for(int i=0;i<=5;i++) {
System.out.println(queue.enQueue(i)+" , "+i);
}
for(int i=0;i<=2;i++) {
System.out.println(queue.deQueue()+" ,出队");
}
System.out.println(queue.enQueueMove("chen")+" , chen");
System.out.println(queue.enQueueMove("chen")+" , chen");
System.out.println(queue.enQueueMove("chen")+" , chen");
System.out.println(queue.enQueueMove("chen")+" , chen");
}
}
/*基于链表生成的队列*/
public class LinkedListQueue<E> {
/*定义两个指针,head指向队列头部,tail指向队列尾部*/
private Node head = null;
private Node tail = null;
/*入队操作*/
public void push(E value) {
Node<E> node = new Node<>(value,null);
/*tail == null 表示队列中还没有加入元素*/
if(tail == null) {
head = node;
tail = node;
}else {
tail.setNext(node);
tail = tail.getNext();
}
}
/*出队操作*/
public E pop() {
if(head == null) return null;
E value = (E)head.getValue();
head = head.getNext();
return value;
}
public void printAll() {
//if(head == null) return;
while (head != null) {
System.out.println(head.getValue());
head = head.getNext();
}
return;
}
private class Node<E>{
private E value;
private Node<E> next;
public Node(E value,Node<E> next) {
this.value = value;
this.next = next;
}
public E getValue() {
return value;
}
public void setValue(E value) {
this.value = value;
}
public Node<E> getNext() {
return next;
}
public void setNext(Node<E> next) {
this.next = next;
}
}
public static void main(String[] args) {
LinkedListQueue queue = new LinkedListQueue();
queue.push("1");
queue.push("2");
queue.pop();
queue.push("3");
queue.push("4");
queue.pop();
queue.push("5");
queue.pop();
queue.printAll();
}
}
基于数组生成的循环队列,它的优点在于,在队列中删除数据后,空闲的空间可以得到充分的利用,并且不需要进行“搬移数据操作”
/*基于数组生成一个循环队列*/
public class CircularQueue<E> {
/*循环队列中的内部数组*/
private Object[] elementData;
/*循环队列的容量*/
private int capacity;
/*循环队列的头指针和尾指针*/
private int head = 0;
private int tail = 0;
public CircularQueue(int capacity) {
this.elementData = new Object[capacity];
this.capacity = capacity;
}
/*入队操作:
* 当tail指针指向数组中最后一位时,(tail+1)%capacity == head,最后一位加不上数据,
* 所以使用循环队列会浪费一个数组空间*/
public boolean enQueue(E value) {
/*如果队列满了,返回false*/
if((tail+1)%capacity == head)
return false;
elementData[tail] = value;
/*因为当前是一个环形队列,所以判断下一位时需要使用(tail+1) % capacity*/
tail = (tail+1) % capacity;
return true;
}
/*出队操作*/
public E deQueue() {
if(head == tail) return null;
E value = (E)elementData[head];
head = (head + 1) % capacity;
return value;
}
public void printAll() {
while ((head+1)%capacity < tail) {
System.out.println(elementData[head]);
head = (head+1)%capacity;
}
/*因为在while循环中,由于判断的原因,tai指针的前一个元素遍历不到,所以需要另外打印*/
if((head+1) % capacity == tail) {
System.out.println(elementData[head]);
}
}
public static void main(String[] args) {
/*由于在循环队列中,最后一个位置不会承装数据,所以如果定义数组的容量为9,
那么实际上只能承装8个数据*/
CircularQueue<String> queue = new CircularQueue<>(9);
queue.enQueue("1");
queue.enQueue("2");
queue.enQueue("3");
queue.enQueue("4");
queue.deQueue();
queue.enQueue("5");
queue.enQueue("6");
queue.printAll();
}
}
阻塞队列:
(1)定义:阻塞队列就是在正常队列的情况下,添加阻塞操作。当队列为空时,从队列获取数据的操作会被阻塞,直到队列中 有数据。当队列满时,向队列添加数据时会被阻塞,直到队列中存在空闲位置。
基于阻塞队列可以实现一个 生产者---消费者模型,并且还可以通过协调生产者和消费者的个数,来提高数据的处理效率。
线程池的底层,也是通过一个队列实现的,当线程池中的线程全部都在工作的时候,当有新的任务请求过来时,就会将任务加入到线程池的队列中,等待线程去执行。
实现队列有两种方式,数据和链表,基于链表实现的队列,是一个没有界限的队列,所以当前任务等待的时间会比较长,对于时间响应敏感的系统来说,不太适合,这时可以选用基于数组实现的队列,可以固定队列的长度,队列装满之后的任务会被拒绝掉,所以系统的响应时间会比较快。
合理的设置队列的容量也是有讲究的,容量太大,会降低系统的响应时间,容量太小,系统的资源得不到充分的利用。实际上对于大部分资源有限的场景,在没有空闲资源时,都可以使用队列这种数据结构来实现请求排队。