Collection in Java (3) hereda la interfaz de cola de Collection

Collection in Java (3) hereda la interfaz de cola de Collection

1. Introducción a la cola

La interfaz de cola hereda de la interfaz de colección y es una estructura de datos de cola definida en Java. Los elementos están ordenados (ordenados por orden de inserción) y el principio de primero en entrar, primero en salir (FIFO). No se admite el acceso aleatorio a los datos, se insertan nuevos elementos al final de la cola y la operación de acceder a los elementos (sondeo) devuelve los elementos en la cabecera de la cola. En general, las colas no permiten el acceso aleatorio a elementos en la cola.

Cola: es una estructura de datos en la computadora. Los datos almacenados en ella tienen las características de "Primero en entrar , primero en salir (FIFO, Primero en entrar , primero en salir)" . El nuevo elemento se inserta al final de la cola y se accede al elemento. Se devolverá el elemento al principio de la cola. En general, las colas no permiten el acceso aleatorio a elementos en la cola.

En Java, la cola se divide en dos formas, una es una cola única, la otra es una cola circular;

  Por lo general, las matrices se usan para implementar colas, suponiendo que la longitud de la matriz es 5, es decir, la longitud de la cola es 5;

  (1) Cola única:

1. Cree una matriz vacía de longitud 5 y defina dos propiedades delante y detrás, que representan el puntero de la cabeza y el puntero de la cola, respectivamente.

2. Insertar datos en la matriz

 

3. Retire el elemento de cabeza: elemento 1, elemento 2

 

4. Inserte datos en la matriz nuevamente, y en este momento, puntos reales a un subíndice inexistente

 

En este momento, la matriz aparecerá fenómeno de "desbordamiento falso", el puntero de cola apunta a un subíndice inexistente, si desea resolver esta situación, generalmente hay dos métodos:

1. Expansión ilimitada de la capacidad de la matriz;

2. Use una cola circular.

(2) cola circular

Cuando el puntero de cola apunta a un subíndice no existente, es decir, excede el tamaño de la matriz, en este momento determinamos si hay espacio libre en la cabecera de la matriz, de ser así, apunte el puntero de cola al espacio libre en la cabeza, como se muestra a continuación:

 

La cola circular es conectar la cabeza y la cola de la cola única para formar un círculo, de modo que no habrá desbordamiento de subíndice (implementación del distribuidor). 

En segundo lugar, el diagrama de clase de cola

1. La interfaz de cola hereda de la interfaz de colección;

2. La interfaz de cola tiene la subinterfaz Deque y la clase abstracta AbstractQueue respectivamente;

3. Las subinterfaces Deque tienen la clase LinkedList y la clase ArrayDeque respectivamente;

4. La clase abstracta AbstractQueue tiene la clase de implementación PriorityQueue

3. Interfaz Deque (cola doble)

La interfaz Deque es una subinterfaz de la interfaz Queue. Crea una estructura de cola de doble extremo con mayor flexibilidad. Puede iterar hacia adelante o hacia atrás, y puede insertar o eliminar colecciones lineales de elementos en la cabecera y cola de la cola. Sus dos clases principales de implementación son ArrayDeque y LinkedList .

La interfaz Deque admite colas de doble extremo con capacidad fija y colas de doble extremo con capacidad variable En general, la capacidad de las colas de doble extremo no es fija.

(1) Características

1. Las operaciones de inserción, eliminación y obtención admiten dos formas: falla rápida y devuelve nulo o verdadero / falso;

2. Tiene características FIFO (primero en entrar, primero en salir) y LIFO (último en entrar, primero en salir);

3. No se recomienda insertar nulo, porque nulo como el valor de retorno indica que la cola está vacía;

4. Iguales indefinidos basados ​​en elementos igual y hashCode;

5. No admite acceso de índice a elementos;

6. Deque no es solo una cola de doble extremo, sino que también se puede usar como una pila, porque esta clase define pop (outbound), push (push) y otros métodos.

(2) Relación entre la interfaz Deque, la interfaz de cola y la pila

Como se puede ver en la descripción anterior, Deque se puede usar no solo como una cola de doble extremo, sino también como una pila.

1. Cuando Deque se usa como una cola de doble extremo, la relación entre la interfaz Deque y la interfaz Queue

Cuando Deque se usa como una Cola (FIFO), el elemento agregado se agrega a la cola de la cola, y el elemento eliminado es el elemento principal. Los métodos heredados de la interfaz de la cola corresponden a los métodos de Deque como se muestra en la figura:

2. Cuando Deque se usa como una pila, la relación entre la interfaz Deque y la Pila

Cuando Deque se usa como una pila (LIFO). En este momento, los elementos de apilamiento y desapilamiento se realizan en la cabecera de la cola de doble extremo. El método correspondiente a Stack in Deque se muestra en la figura:


Nota: Debido a que Stack es relativamente antiguo y la implementación de la función es muy hostil, básicamente no es adecuado para el desarrollo de programas ahora, por lo que puede elegir la interfaz Deque en lugar de Stack para la operación de pila. 

Cuatro, clase de implementación ArrayDeque

Hay dos clases de implementación bajo la interfaz Deque, a saber, ArrayDeque y LinkedList. LinkedList no se describirá en esta sección, esta sección habla principalmente de ArrayDeque.

(1) ArrayDeque: no se permite una cola lineal de doble extremo basada en una matriz circular, tamaño variable y nulo.

1. La capa inferior se realiza a través de una matriz. El tamaño de la matriz es 16 por defecto. Puede especificar la longitud o no. La capacidad de la matriz se expande dinámicamente de acuerdo con el número de elementos agregados. Para asegurarse de que puede insertar y eliminar elementos al mismo tiempo, la matriz debe ser una matriz circular, es decir, cada punto de la matriz puede considerarse como un punto de inicio o un punto final.

2. ArrayDeque no es seguro para subprocesos: en un entorno multiproceso, se requiere sincronización manual, además, ArrayDeque no permite la inserción de elementos nulos.

3. Dado que ArrayDeque implementa Deque en función de los punteros de cabeza y cola, no puede acceder directamente al primer y último elemento. Si desea atravesar los elementos, debe usar el iterador Iterator, puede usar la iteración positiva y negativa para atravesar los elementos.

4. ArrayDeque es generalmente superior a las colas de listas vinculadas / colas de doble extremo. Se genera una cantidad limitada de basura (se desecharán las matrices antiguas para la expansión). Se recomienda usar deque, ArrayDeque tiene prioridad.

(B), diagrama de operación ArrayDeque

Suponga que la longitud de la matriz es 6, es decir , la longitud de la cola ArrayDeque es 6;


 

Se puede ver en la figura anterior: el frente siempre apunta a la posición del primer elemento válido en la matriz, y el trasero siempre apunta a la primera posición espacial donde se puede insertar el elemento, por lo que el frente no debe ser igual a 0 y no siempre La parte trasera es grande y la trasera no siempre es más pequeña que la delantera.

(3) En ArrayDeque, la realización de una matriz circular

ArrayDeque mantiene dos propiedades, que apuntan al puntero de cabeza y al puntero de cola:

 transient int head; // Puntero de punto a cabeza 

 transient int tail; // 指向尾指针 

假定数组的长度为10,也就是ArrayDeque队列的长度为10;

1、ArrayDeque刚创建时;

2、当向尾部插入时,直接在tail下标的位置插入元素,所以tail下标 - 1;


3、当从头部插入时,head下标 - 1,然后插入元素,tail下标为当前数组末尾元素的下标 + 1;

通过上面的步骤可以知道,将ArrayDeque看出成是一个首尾相接的圆形数组更好理解循环数组的含义。 

通过addFrist(E e)代码,看看ArrayQueue是如何实现的:

1 public void addFirst(E e) {
2     if (e == null)
3         throw new NullPointerException();
4 
5     elements[head = (head - 1) & (elements.length - 1)] = e;
6     if (head == tail)
7         doubleCapacity();
8 }        
    • 当加入元素时,先看是否为空(ArrayDeque不可以存取null元素,因为系统根据某个位置是否为null来判断元素的存在)。然后head-1,插入元素。
    • head = (head - 1) & (elements.length - 1)很好的解决了下标越界的问题。这段代码相当于取模,同时解决了head为负值的情况。因为elements.length必需是2的指数倍(代码中有具体操作),elements - 1就是二进制低位全1,跟head - 1相与之后就起到了取模的作用。如果head - 1为负数,其实只可能是-1,当为-1时,和elements.length - 1进行与操作,这时结果为elements.length - 1。其他情况则不变,等于它本身。
    • 当插入元素后,在进行判断是否还有余量。因为tail总是指向下一个可插入的空位,也就意味着elements数组至少有一个空位,所以插入元素的时候不用考虑空间问题。

(四)、扩容函数doubleCapacity()

扩容函数doubleCapacity()的逻辑是:申请一个更大容量的数组,将原数组原样复制到新数组中。


从上图可以看出,复制分为两次进行:先复制head右边的元素,然后再复制head左边的元素。

 1 private void doubleCapacity() {
 2     assert head == tail;
 3     int p = head;
 4     int n = elements.length;
 5     int r = n - p; // head右边元素的个数
 6     int newCapacity = n << 1;//原空间的2倍
 7     if (newCapacity < 0)
 8         throw new IllegalStateException("Sorry, deque too big");
 9     Object[] a = new Object[newCapacity];
10     System.arraycopy(elements, p, a, 0, r);//复制右半部分,对应上图中绿色部分
11     System.arraycopy(elements, 0, a, r, p);//复制左半部分,对应上图中灰色部分
12     elements = (E[])a;
13     head = 0;
14     tail = n;
15 }

(五)、小结

通过上面描述,我们便理解了ArrayDeque循环数组添加以及扩容的过程,另外需要注意的是:ArrayDeque不是线程安全的。 当作为栈使用时,性能比Stack好;当作为队列使用时,性能比LinkedList好。

五、PriorityQueue实现类

(一)、PriorityQueue:底层基于数组实现的堆结构的优先队列。

 PriorityQueue是AbstractQueue的子类,AbstractQueue又实现了Queue接口,所以PriorityQueue具有Queue接口的优先队列。

 优先队列与普通队列不同,普通队列遵循“FIFO”的特性,获取元素时根据元素的插入顺序获取,优先队列获取元素时根据元素的优先级,获取优先级最高的数据。

(二)、PriorityQueue的排序方式

PriorityQueue保存队列元素时不是按照插入队列顺序进行排序,而是按照插入元素的大小进行排序的。因此当调用peek()、pop()方法时,不是取出最先插入的元素,而是取出队列当中最小元素。

1、排序的方式

PriorityQueue队列当中的元素是可以默认自然排序(数值型元素默认是最小的在队列头部,字符串则按字典序排序),或者通过Comparator(比较器)在队列实例化指定排序方式。

注意:当PriorityQueue中没有指定Comparator时,加入PriorityQueue的元素必须实现了Comparable接口(即元素是可比较的),否则会导致 ClassCastException。

(三)、PriorityQueue的本质

PriorityQueue本质是一个动态数组,默认实现由三种构造方法:

  1.  public PriorityQueue() 调用无参构造方法时,使用默认的初始容量(DEFAULT_INITIAL_CAPACITY=11)来创建PriorityQueue,并根据其自然顺序排序其中的元素(排序方式使用的是元素中实现的Comparable接口);
  2.  public PriorityQueue(int initialCapacity) 调用指定容量构造方法时,使用initialCapacity定义初始容量创建PriorityQueue,并根据其自然顺序排序其中的元素(排序方式使用的是元素中实现的Comparable接口);
  3.  PriorityQueue(int initialCapacity,Comparator<? super E> comparator) 当使用指定的初始容量创建一个 PriorityQueue,并根据指定的比较器comparator来排序其元素。

从上面的三个构造函数可以得出:PriorityQueue内部维护了一个动态数组,

除此之外,还要注意:

    • PriorityQueue不是线程安全的。如果多个线程中的任意线程从结构上修改了列表, 则这些线程不应同时访问 PriorityQueue 实例,这时请使用线程安全的PriorityBlockingQueue 类。
    • 不允许插入 null 元素。
    • PriorityQueue实现插入方法(offer、poll、remove() 和 add 方法) 的时间复杂度是O(log(n)) ;实现 remove(Object) 和 contains(Object) 方法的时间复杂度是O(n) ;实现检索方法(peek、element 和 size)的时间复杂度是O(1)。所以在遍历时,若不需要删除元素,则以peek的方式遍历每个元素。
    • 方法iterator()中提供的迭代器并不保证以有序的方式遍历优PriorityQueue中的元素。

(四)、自然排序和Comparator比较器

1、Java中的两种比较器:Conparator和Comparable

Comparable和Comparator接口都是为了对类进行比较,众所周知,诸如int,double等基本数据类型,Java可以对他们进行比较,而对于对象即类的比较,需要人工定义比较用到的字段比较逻辑。可以把Comparable理解为内部比较器,而Comparator是外部比较器

(1)、Comparable接口:内部比较器,实现了Comparable接口的类需要实现compareTo(T o)方法,传入一个外部参数进行比对;

当一个对象调用该compareTo(T o)方法与另一个对象比较时,例如o1.compareTo(o2)

  • 如果该方法返回0,则表明两个对象相等;
  • 如果该方法返回一个整数,则表明o1大于o2;
  • 如果该方法返回一个负整数,则表明o1小于o2。

(2)、Conparator接口:外部比较器,实现了Comparator接口的方法需要实现compare(T o1,T o2)方法,对外部传入的两个类进行比较,从而让外部方法在比较时调用;

该compare(T o1,T o2)方法用于比较o1,o2的大小:

  • 如果该方法返回正整数,则表明o1大于o2;
  • 如果该方法返回0,则表明o1等于o2;
  • 如果该方法返回负整数,则表明o1小于o2。

2、自然排序

自然排序是调用元素所属类的compareTo(T o)方法来比较元素之间的大小关系,然后将集合元素按升序排列,即把通过compareTo(T o)方法比较后比较大的的往后排。这种方式就是自然排序。 

Supongo que te gusta

Origin www.cnblogs.com/lingq/p/12729471.html
Recomendado
Clasificación