Queue接口初步学习,无BlockingQueue接口。ArrayList和Vect,栈推荐使用ArrayDeque。

Queue集合接口

Queue是单列集合下的一个接口,它和它的实现类中Java打包好了对队列的操作。

 

ArrayList 与 Vector

ArrayList和Vector都是List接口下的实现类,完全支持List接口的全部功能。

这两者的底层实现原理基本相同,都是基于一个动态的、允许在分配的Object[]类型的数组实现的;同时而这也有着相同的扩容机制和扩容方法。

如果创建ArrayList和Vector的对象时,没有给构造方法传递参数(调用空参构造),创建的就是一个长度默认为10 长度默认为 0 的Object数组。

当调用ArrayList的空参构造创建一个集合的时候:

 

 

 

创造了一个默认长度为0 的数组,当添加进元素之后,底层数组就会扩容成10;之后单独再添加元素,当存满数组就会扩容,就会扩容到原来的1.5倍;之后一次添加多个元素,如果超过了现有长度的1.5倍就会扩容至刚好容下添加的元素。

当调用含参构造时会创建一个指定长度的底层数组。疯狂Java讲义里面也介绍了两个控制容量的方法:

方法名称说明
void ensureCapacity(int minCapacity)将底层数组长度增加值大于等于minCapacity
void trimToSize()将底层数组的长度调整为当前元素个数,可减少空间占用

Vector 和 ArrayList 的主要区别在于 Vector 是线程安全的,而ArrayList是线程不安全的。当多个线程访问同一个ArrayList集合时,如果有任何线程修改了ArrayList集合,则需要手动编写程序保证ArrayList集合的同步性;当多个线程同时访问 Vector 时,Vector 会使用 synchronized 关键字来保证线程安全,这会影响性能。因此,在单线程环境中,应该使用 ArrayList 而不是 Vector。

在 Java 1.2 之后,Collections.synchronizedList() 方法提供了一种将 ArrayList 转换成线程安全的 List 的方法,因此,除非需要使用 Vector 特有的方法,否则应该使用 Collections.synchronizedList() 方法来实现线程安全的 List。

Queue接口的常用API

方法名称说明
public void add(Object e)将指定元素加入此队列的尾部
public Object element()获取队列头部的元素,但是不删除该元素
public boolean offer(Object e)将指定元素加入到此队列尾部
public Object peek()获取队列头部的元素,但是不删除该元素。如果队列为空,返回null
public Object poll()获取队列头部的元素,并删除该元素。如果队列为空,返回null
public Object remove()获取队列头部元素,并删除该元素。如果队列为空,直接报错停止运行

当使用有容量限制的队列时,public boolean offer(Object e)方法通常比public void add(Object e)更好用。

当队列满了时,使用add(Object e)方法会抛出一个异常,而offer(Object e)方法会返回false。

当队列为空时,使用remove()方法会使程序出现异常停止运行,通常不如使用 poll()方法;

当然Queue也继承了单列集合中的

isEmpty():判断队列是否为空;

size():获取队列中的元素个数;

等方法。

PriorityQueue实现类

优先级队列:通过完全二叉树实现的小顶堆实现,属于线程不安全的队列。

特点:可排序

PriorityQueue保存队列元素的顺序不是按添加的顺序,而是按队列元素的大小(队首-->队尾 从小到大)进行重新排序。调用peek()方法或者poll()方法返回的不是最先入队列的元素,而是最小的元素。

注意:该实现类的集合不允许放入null元素。

底层原理

PriorityQueue底层是通过完全二叉树的小顶堆实现的,数据的存储使用数组(Object[] queue数组)实现的,这个数组的初始默认长度是11;堆是以二叉树的顺序存储法来用数组记录树的样子,对树进行操作。

在使用add() / offer() 添加、poll()删除队首(堆顶元素)等方法修改集合内容的时候会调用底层shiftUp()方法,该方法会根据指定的自定义规则对堆中的元素进行维护;如果没有自定义比较规则,就会按照自然排序规则进行维护。

    private void siftUp(int k, E x) {
        if (comparator != null)
            siftUpUsingComparator(k, x, queue, comparator);
        else
            siftUpComparable(k, x, queue);
    }

peek()查看堆顶元素,但不删除。

注意:要排序数据的时候不能直接遍历集合。

//类外导包
import java.util.PriorityQueue;
...
public static void main(String[] args) {
    PriorityQueue<Integer> queue = new PriorityQueue<>();
    queue.offer(9);
    queue.offer(3);
    queue.offer(7);
    queue.offer(-1);
    queue.offer(8);
    queue.offer(20);
    //按照队列现先进先出的规则:9 3 7 -1
    //使用Lambda表达式遍历
    queue.forEach(i -> System.out.print(i + " "));//-1 3 7 9 8 20
    System.out.println();
    while(!queue.isEmpty()){
        System.out.print(queue.poll() + " ");
    }//-1 3 7 8 9 20 
}

注意:因为PriorityQueue集合底层是一个顺序存储二叉树的一个数组;所以要想要给元素排序不能直接遍历集合;而是要将元素逐个出队(poll()方法)。

构造方法

构造方法说明
PriorityQueue()使用默认的容量(11)创建一个队列,元素排序采用自然排序
PriorityQueue(int initialCapacity)使用指定的容量创建一个队列,元素排序采用自然规则
PriorityQueue(Comparator<? super E> comparator)使用默认的容量(11)创建一个队列,元素排序采用自定义的comparator

排序方式

PriorityQueue有两大种排序方式:

自然排序:采用自然排序的PriorityQueue集合的元素的类必须实现了Comparable接口,并且重写了接口中的compareTo()方法;

自定义排序:创建PriorityQueue是调用构造函数PriorityQueue(Comparator<? super E> comparator);传入一个Comparator对象,该对象负责堆集合中的元素进行排序;使用这种方法时不要求实现Comparable接口。

自然排序

核心是继承Comparable接口,重写compareTo()方法

Java的包装类Integer、String等都是重写了compareTo的。如果需求符合他们重写的比较规则,那么可以直接使用无参构造。

对于数值类型:Integer、Double,默认按照从小到大排序;

对于字符、字符串类型:按照逐个字符的ASCII码表的从小到大排序(字典排序);compareTo();

但如果是自定义的类就需要手动实现Comparable接口,重写compareTo()方法。

class Student implements Comparable<Student>{
    private int age;
    private String name;
    public Student() {
    }
    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String toString() {
        return "Student{age = " + age + ", name = " + name + "}";
    }
​
    @Override
    public int compareTo(Student o) {
        //只看年龄排序
        int result = this.age - o.age;//从小到大  --->  前 - 后    从大到小   --->   后 - 前
        return result;
    }
}

这是自定义的一个Student类,有年龄和姓名两个私有字段;

现在要求按照年龄从小到大排序。就是上面的代码。

排序的规则:

this:表示当前要添加的元素;

o:表示队列中已经存在的元素;

返回值:负值:认为要添加的元素小,存左边;

正值:认为要添加的元素大,存右边;

零:认为要添加的元素已经存在,舍弃;

在主函数中测试:

import java.util.PriorityQueue;
​
public class PriorityQueueDomo02 {
    public static void main(String[] args) {
        PriorityQueue<Student3> pq = new PriorityQueue<>();
        pq.offer(new Student3(18,"zhansan"));
        pq.offer(new Student3(22,"zhansan"));
        pq.offer(new Student3(22,"zhansam"));
        pq.offer(new Student3(10,"lisi"));
        while(!pq.isEmpty()){
            System.out.println(pq.poll());
        }
    }
}

 

 

自定义排序

核心通过比较器

1)构造比较的类或内部类

class CompareMethod implements Comparator<String>{
​
    @Override
    public int compare(String o1, String o2) {
        //需求:排序字符串
        /*
        * 1.长度大的排在前
        * 2.长度相同,逐个比对,ASCII码大的排在前
        * */
        int i = o2.length() - o1.length();
        i = i == 0 ? o2.compareTo(o1) : i;
        return i;
    }
}

构造了一个专门比较的类,类继承Comparator,重写compare()方法制定排序规则;

因为这种方法时构造了一个比较器的实现类,所以一定要使用带参数的构造方法;PriorityQueue(Comparator<? super E>

main方法测试:

import java.util.Collections;
import java.util.Comparator;
import java.util.PriorityQueue;
​
public class PriorityQueueDomo02 {
    public static void main(String[] args) {
        //需求:排序字符串
        /*
        * 1.长度大的排在前
        * 2.长度相同,逐个比对,ASCII码大的排在前
        * */
        PriorityQueue<String> pq = new PriorityQueue<>(new CompareMethod());
        Collections.addAll(pq, "abs","abc","b","ij","a");
        while(!pq.isEmpty()){
            System.out.println(pq.poll());
        }
    }
}

运行测试结果:

 


class Student implements Comparator<Student>{
    private int age;
    private String name;
    public Student() {
    }
    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String toString() {
        return "Student{age = " + age + ", name = " + name + "}";
    }
​
    @Override
    public int compare(Student o1, Student o2) {
        //只看年龄排序(从小到大)
        return o1.getAge() - o2.getAge();
    }
}

main方法测试:

import java.util.PriorityQueue;
​
public class PriorityQueueDomo02 {
    public static void main(String[] args) {
        PriorityQueue<Student> pq = new PriorityQueue<>(new Student());
        pq.offer(new Student(18,"zhansan"));
        pq.offer(new Student(22,"zhansan"));
        pq.offer(new Student(22,"zhansam"));
        pq.offer(new Student(10,"lisi"));
        while(!pq.isEmpty()){
            System.out.println(pq.poll());
        }
    }
}

运行测试结果:

 

2)Comparator是一个函数式接口(Functional interface),所以可以写匿名内部类、Lambda表达式和方法引用的方式

  • 匿名内部类

import java.util.Collections;
import java.util.Comparator;
import java.util.PriorityQueue;
​
public class PriorityQueueDomo02 {
    public static void main(String[] args) {
        PriorityQueue<String> pq = new PriorityQueue<>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                //需求:排序字符串
                /*
                 * 1.长度大的排在前
                 * 2.长度相同,逐个比对,ASCII码大的排在前
                 * */
                int i = o2.length() - o1.length();
                return i == 0 ? o2.compareTo(o1) : i;
            }
        });
        Collections.addAll(pq, "abs","abc","b","ij","a");
        while(!pq.isEmpty()){
            System.out.println(pq.poll());
        }
    }
}
  • 匿名内部类改写成Lambda表达式

import java.util.Collections;
import java.util.PriorityQueue;
​
public class PriorityQueueDomo02 {
    public static void main(String[] args) {
        PriorityQueue<String> pq = new PriorityQueue<>((o1, o2) -> (o2.length() - o1.length()) == 0 ? o2.compareTo(o1) : o2.length() - o1.length());
        Collections.addAll(pq, "abs","abc","b","ij","a");
        while(!pq.isEmpty()){
            System.out.println(pq.poll());
        }
    }
}
  • 方法引用

我这里写的在 main方法同类的 myCompare() 静态方法。不同的类型引用方法不太一样。

import java.util.Collections;
import java.util.PriorityQueue;
​
public class PriorityQueueDomo02 {
    public static void main(String[] args) {
        PriorityQueue<String> pq = new PriorityQueue<>(PriorityQueueDomo02::myCompare);
        Collections.addAll(pq, "abs","abc","b","ij","a");
        while(!pq.isEmpty()){
            System.out.println(pq.poll());
        }
    }
    public static int myCompare(String o1, String o2){
        int i = o2.length() - o1.length();
        return i == 0 ? o2.compareTo(o1) : i;
    }
}

Comparable接口 和 Comparator接口

Comparable接口的方法是 public int compareTo(Object o)

它的比较是类中this和o的比较;

排序的规则:

this:表示当前要添加的元素;

o:表示队列中已经存在的元素;

Comparable接口完成的是一个有比较方法的数据类型 <T>,在主函数调用时不需要参数。

Comparator接口的方法是 public int compare(Object o1, Object o2)

排序的规则:

o1:表示当前要添加的元素;

o2:表示队列中已经存在的元素;

Comparator接口实现的是一个比较器,在主函数调用的时候作为PriorityQueue构造函数的参数。


返回值都是:负值:认为要添加的元素小,存左边;

正值:认为要添加的元素大,存右边;

零:认为要添加的元素已经存在,舍弃;

小结

使用原则:默认使用自然排序方式,如果第一种不能满足需求,就使用自定义排序方法。

如果自然排序方法和自定义排序方法都存在,那么以自定义排序方法为准。

PriorityQueue的排序方法定义和TreeSet一样。

使用场景:主要用于排序输出(例如入队一些元素,出队时按从小到大或从大到小输出),而可以用来模拟大小顶堆。

Deque接口

Deque接口是Queue接口的子接口,他代表一个双端队列,里面定义了一些双端队列的方法,这些方法允许从两端操作队列的元素,即可以从队列两端(头部和尾部)添加、删除、查看元素。

Deque接口有两个实现类:ArrayDeque 和 LinkedList,其中ArrayDeque不允许添加null元素,添加null元素会直接抛出异常,停止程序运行。

Deque的一些常用API

Deque用 first 代表队首的元素 last 代表队尾的元素。

  • 会抛出异常的方法

    1. 添加元素

public void addFrist(Object e):将指定元素插入双端队列的开头

public void addLast(Object e):将指定元素插入双端队列的结尾

    1. 移除元素

    public Object removeFirst():获取并删除该双端队列的第一个元素

    public Object removeLast():获取并删除该双端队列的最后一个元素

    1. 检查元素

    public Object getFirst():获取但不删除该双端队列的第一个元素

    public Object getLast():获取但不删除该双端队列的最后一个元素

  • 会返回特殊值的方法

    1. 添加元素

public boolean offerFirst(Object e):将指定元素插入该双端队列的开头

public boolean offerLast(Object e):将指定元素插入该双端队列的结尾

    1. 移除元素

    public Object pollFirst():获取并删除该双端队列的第一个元素,如果队列为空,返回null

    public Object pollLast():获取并删除该双端队列的最后一个元素,如果队列为空,返回null

    1. 检查元素

    public Object peekFirst():获取但不删除该双端队列的第一个元素,如果队列为空,返回null

    public Object peekLast():获取但不删除该双端队列的最后一个元素,如果队列为空,返回null

另外Deque接口中还有两个栈方法

public Object pop():将栈顶元素出栈,相当于removeFirst/pollFrist;

 

public void push():将元素压入栈顶,相当于addFirst/offerFirst;

 

还有public Object peek()方法,这个是队列方法和栈方法共用的一个方法。既可以查看栈顶元素,也可以查看队首元素。

 

注意这pop方法遇到栈空的特殊情况会直接抛出异常,所以使用的时候适当结合isEmpty方法判断栈是否为空。

Deque的实现类

ArrayDeque

底层实现是数组,创建Deque是可以指定一个numElements参数,该参数用于指定Object[] 数组的长度;如果不指定numElements参数,Deque底层数组的长度为16。

LinkedList

底层实现是链表。

对比两个实现类;ArratDeque和ArrayList一样是通过数组储存集合中的元素的,因此他们的随机访问集合元素的能力较强,但增加、删除元素的效率低。

因为LinkedList底层是靠链表储存集合元素的,所以他增加、删除元素的效率高,但随机访问集合元素的效率低;

两者看情况使用。

Deque--队列

验证双端队列作为队列的用法

import java.util.ArrayDeque;
​
public class ArrayDequeDomo02 {
    public static void main(String[] args) {
        ArrayDeque<Integer> ad = new ArrayDeque<>();
        ad.offer(1);
        ad.offer(2);
        ad.offer(3);
        ad.offer(4);
        System.out.println("查看队首元素" + ad.peek());//1
        while(!ad.isEmpty()){
            System.out.println(ad.poll());//1 2 3 4
        }
    }
}

Deque是Queue的子接口,自然有普通队列的方法;

offer方法调用了offerLast实现;peek方法调用了peekFirst实现;poll方法调用了pollFirst实现。

Deque--栈

验证双端队列当作栈的用法

import java.util.ArrayDeque;
​
public class ArrayDequeDomo01 {
    public static void main(String[] args) {
        //双端队列作为栈使用
        ArrayDeque<Integer> ad = new ArrayDeque<Integer>();
        ad.push(1);
        ad.push(2);
        ad.push(3);
        ad.push(4);
        System.out.println("查看栈顶元素 " + ad.peek());//4
        System.out.println("输出栈元素");
        while(!ad.isEmpty()){
            System.out.println(ad.pop());//4 3 2 1
        }
    }
}

这里演示的是ArrayDeque作为栈的使用。另一个实现栈的类Stack是集合Vector的子类,在前面就说过了Vector的特点:线程安全,效率低,性能差;Stack类也有这样的特点。所以一般推荐使用ArrayDeuqe(或者LinkedList)代替Stack使用。

拓展-阻塞队列接口BlockingQueue

主要通过put():将元素入队,如果队列已满,就等待队列不满的时候;take():将队首元素出队,如果队列为空,等待队列加入元素;完成对队列的阻塞操作。在多线程中使用。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值