day5 (多)线程(定义,方法,安全, 安全问题条件,同步代码块,同步函数,死锁),线程通讯,后台线程,Object[], 集合,List接口, ArrayList, LinkedList, Vector, Stack, Queue, LinkedBlockingQueue, PriorityQueue
摘要:
(多)线程(定义,方法,安全, 安全问题条件,同步代码块,同步函数,死锁),线程通讯,后台线程,Object[], 集合,List接口, ArrayList, LinkedList, Vector, Stack, Queue, LinkedBlockingQueue, PriorityQueue
进程:正在执行的程序就是一个进程,进程负责了内存空间的划分
Windows 号称是多任务的操作系统。那么Windows是同时运行多个任务程序吗?
从宏观的角度:Windows的确是在同时运行多个应用程序
从微观的角度:CPU是做了快速的切换执行的动作,由于速度太快,所以感觉不到在切换而已
单核的CPU在一个时间片里只能执行一个应用程序。各个应用程序其实是在做CPU的争夺战,CPU做了快速的切换动作
线程:在一个进程中,负责了代码的执行,就是进程中的最小执行单位
多线程:在一个进程中,有多个线程在执行不同的任务
类似于一个进程(杀毒软件中,同时可以执行清理垃圾和杀毒的任务,就是分别两个线程来同时执行)
疑问:线程负责了代码的执行。之前没有学过线程,代码靠什么来执行呢?
任何一个Java程序在JVM虚拟机运行的时候都会创建主main线程来执行主main方法中的代码
一个Java的应用程序至少有几个线程?
至少有两个线程。一个是主线程负责main方法的执行,一个是负责垃圾回收器的线程,负责回收垃圾。两个可以同时执行互不干扰
与其说是进程在做CPU的资源争夺战,还不如说是线程在做CPU的资源争夺战,因为一个进程中的代码都由线程执行,多个线程都要抢夺CPU
多线程的好处:
1 解决了我们一个进程能同时执行多个任务的问题
2 提高了资源的利用率
多线程的弊端:
1 增加了CPU的负担
2 降低了一个进程中线程的执行概率
3 会引发线程安全问题
4 出现了死锁现象
迅雷下载是多线程下载的(可以同时下载多部电影)
如何创建多线程:
方式1:
1 自定义一个类,继承Thread类
2 重写run方法
@Override
public void run(){}
3 创建Thread的子类对象,并且调用start方法开启线程。
一个线程一旦开启,那么线程就会执行run方法中的代码,run方法千万不能直接调用。直接调用run方法就相当于调用了一个普通的方法而已,并没有开启新的线程
使用Eclipse可以在空白的地方点右键选择source然后选择override/implement 然后选择重写某个类的某方法就可以生成相应的代码块
使用IntelliJ可以在需要插入方法的地方点击 alt + insert 选择override/implement方法, 从列表中选择生产哪个方法
为什么要重写run方法?
把自定义线程的任务代码写在run方法中
每个线程都有自己的任务代码。JVM 创建的主线程任务代码就是main方法中的所有代码,自定义线程的任务代码就写在run方法中,自定义线程负责了run方法中的代码
public class Demo2 extends Thread { public static void main(String[] args) { Demo2 d = new Demo2(); d.start(); for (int i = 0; i < 1000; i++) { System.out.println("Hello"); } } @Override public void run(){ for (int i = 0; i < 1000; i++) { System.out.println("自定义" + i); } } }
例如QQ中,可以视频也可以打字聊天,也是属于多线程
需求:模拟QQ聊天(视频和聊天同时进行)
方法2:
1 自定义一个类,实现Runnable接口 class Human implements Runnable
2 实现run方法,把自定义线程任务写入run方法 public void run(){}
3 创建Runnable实现类的对象,并传入new Thread()中,再调用start方法
Human h = new Human();
Thread t1 = new Thread(h, “新线程1”); t1.start()
Thread t2 = new Thread(h, “新线程2”);t2.start()
实际创建多线程的时候,如果都是基于一个Runnable接口实现类对象,那么就创建一个对象即可,将这个对象传入多个new Thread来执行。
public class Demo2{ public static void main(String[] args) { Human h = new Human("alex"); new Thread(h).start(); } } class Human implements Runnable { String name; public Human (String name) { this.name = name; } public void run(){ System.out.println(Thread.currentThread()); } }
Thread类有一个构造方法是Thread(Runnable r, String name)可以给线程赋予名字或Thread(Runnable r)
问题1:Runnable实现类的对象是线程对象吗?
Runnable实现类的对象并不是一个线程对象,只不过是实现了,Runnable接口的对象,没有特殊的地方。只有Thread或者是Thread的子类才是线程对象。线程对象必须具备一个start方法来开启线程
问题2:为什么要把Runnable的实现类的对象作为实参传给Thread对象呢?作用是什么?
把Runnable实现类的对象的run方法,作为了线程的任务代码去执行
public void run () { System.out.println(Thread.currentThread().getName() + " hello"); System.out.println("this: " + this.name);//这个this是Runnable的实现类对象 System.out.println("current thread: " + Thread.currentThread().getName());//代表了接收实现类对象的线程 }
线程创建方式2的练习:三个窗口售票50张:
public class Demo2{ public static void main(String[] args) { TicketWindow t = new TicketWindow("window1"); //只创建一个接口实现类的对象 new Thread(t, "window1").start();//创建三个线程,每个线程都传入这个实现类对象 new Thread(t, "window2").start(); new Thread(t, "window3").start(); } } class TicketWindow implements Runnable { int ticket = 500;//因为我只创建了一次接口的实现类,所以不需要static来共享,只有一个对象 String name; public TicketWindow (String name) { this.name = name; } public void run () { while (true) { synchronized ("ticket") { if (ticket > 0) { System.out.println(Thread.currentThread().getName() + " sold 1 ticket " + (ticket - 1) + " left"); ticket --; } else { System.out.println(Thread.currentThread().getName() + " Sold out"); break; } } } } }
推荐使用方式2;原因:因为Java是单继承,多实现的。如果要创建多线程,直接继承了Thread,就不能再继承别的类了。但是实现了Runnable接口,还可以实现别的接口,比较灵活
线程的生命周期:
CPU的等待资格:每个线程都有可以被CPU执行的资格
CPU的执行权:
创建线程的时候,什么权利和资格都没有

调用sleep方法,sleep方法时间到了以后,就从阻塞状态转为了可运行状态
推荐:Thinking in Java,Java核心技术
线程的常用方法:
Thread(String name) 初始化线程的名字VideoThread v = new VideoThread(“aaa”)
如果不写,默认第一个创建的名字为Thread-0
public VideoThread(String name){//调用父类有一个参数的构造函数 super(name); }
下方都是用线程的对象调用d.getName()...
d.getName() 返回线程的名字
d.setName(String name) 设置线程对象名
Thread.sleep() 线程睡眠指定的毫秒数。是一个静态方法。哪个线程执行了sleep方法,那么就是哪个线程睡眠
这个sleep方法不需要对象调用,可以类来调用(或者父类Thread.sleep()),不论哪个对象调用. 如果这个sleep是在main方法中,就是main方法睡眠
d.getPriority() 返回当前线程对象的优先级 默认线程的优先级是5
d.setPriority(int newPriority) 设置线程的优先级 虽然设置了线程的优先级,但是具体的实现取决于底层的操作系统的实现(最大的优先级是10 ,最小的1 , 默认是5)。
Thread.currentThread() 返回当前的线程对象,这是一个静态的方法。注意哪个线程执行了currentThread()那么就返回哪个线程的对象
Thread mainThread = Thread.currentThread();
System.out.println(mainThread.getName());
为什么Thread.sleep()只能捕获而不能抛出呢?
class VideoThread extends Thread { public VideoThread(String name){ super(name); } @Override public void run () { for (int i = 0; i < 1000; i ++) { System.out.println("QQ Video " + i); try { Thread.sleep(1000);//为什么在这里只能try catch来捕获,而不能抛出异常呢 } catch(InterruptedException e) { } } } }
因为方法重写的原则就是两少一多,少犯错(子类方法异常<=父类的),少BB(子类返回类型<=父类的),多干活(子类权限修饰符>=父类的)
方法重写时,子类抛出的异常类型<=父类抛出的异常类型,抛出的错误类型要小于父类,一辈要比一辈强
Thread类的run方法没有抛出异常类型,所以子类不能抛出异常类型
那么在run方法内的this和Thread.currentThread()指向的对象相同吗
public void run () { System.out.println(this); System.out.println(Thread.currentThread());
答案是相同的,因为在主方法中VideoThread v = new VideoThread(“aaa”) 在v.start()之后就会调用run方法, 而this指的是方法的调用者对象,也就是v这个当前线程,那么也就是Thread.currentThread(); 所以线程的run方法内使用Thread.currentThread()和this是一样的
方法.getPriority()
查看线程的优先级,默认都是5
方法d.setPriority (int i)数字越大,优先级越高.优先级的范围是1 ~10
线程安全问题
需求:模拟三个窗口同时售50张票
为什么50张票被卖出了150次?每张票被卖了3次
因为ticketNum是非静态的,非静态的成员变量是在每个对象中都会维护一份数据的,三个线程就有三份
解决方案:把ticketNum共享出来给三个线程使用,使用static修饰
解决了重复售出同一张票3次的问题,但是却有些票没有被售出,个别票重复了。
class TicketWindow extends Thread { static int ticketNum = 50; public TicketWindow (String name) { super(name); } @Override public void run() { while (true) { if(ticketNum > 0) { System.out.println(Thread.currentThread().getName() + " 售出了第" + (50 - ticketNum) + "号票"); ticketNum --; } else { System.out.println(Thread.currentThread().getName() + "售罄"); break; } } } }
第一次当线程1执行了判断语句以后,执行了打印售出第一张票,还没有执行下一句时,线程2抢过了CPU执行权,也执行了打印的这一句
出现线程安全问题的根本原因:(多线程有多个语句操作同一个资源)
1 必须要存在两个或两个以上的线程对象,而且线程之间共享着一个资源
2 有多个语句操作了共享资源
线程安全问题的解决方案:SUN提供了线程同步机制,让我们解决这类问题
Java线程的同步机制的方式:
方法1: 同步代码块
同步代码块格式:在run里
synchronized (锁对象) { //需要被同步的代码 }
同步代码块注意事项:(同步的意义就是排队,异步就是并发)
1 任意的对象都可以作为锁对象
2 在同步代码块中,调用Thread.sleep()并不会释放锁对象
3 只有真正存在线程安全的情况下,才使用同步代码块,否则会降低代码执行效率 (用之前的出现线程安全的情况条件来判断是否会出线程安全问题)
4 多线程操作的锁对象必须是唯一共享的,否则无效。这就要求从别处定义一个static对象,再把引用类型变量传入,而不要在这里new 对象。因为每次判断new 对象,都会是新的对象,为开状态,锁不住,所以必须要共享的锁对象。
static Object o = new Object()
synchronized (o) {}
使用一对双引号,空字符串也是可以做锁的,因为这个字符串也是在字符串常量池中共享的。synchronized(“”){}或者写个synchronized("锁"){} 都是可以的
同步代码块的原理:
任意对象都可以作为锁对象。凡是对象内部都维护了一个状态,Java同步机制就是使用了对象中的状态作为锁的标识(任何对象都有状态) 一个线程运行到判断语句,先看看状态是否为开,如果为开就进入执行,同时将状态变成关;如果状态为关,就阻塞等待
加了synchronized块,每次执行都要进行判断,会降低代码执行效率
需求:一个银行账户有5000块,两夫妻一个拿存折,一个拿卡,开始取钱比赛,每次只能取1000块。不能出现线程安全问题。(两个线程(夫妻),共用一个账户,如果代码多行就可能出现线程安全问题,所以适合用锁)
class People extends Thread { static int sum = 5000; static Object lock = new Object(); public People (String name) { super(name); } @Override public void run () { while (true) { synchronized (lock) { if (sum >= 1000) { System.out.println(Thread.currentThread().getName() + "取走了" + 1000); sum -= 1000; } else { System.out.println("取完了"); break; } } } } }
不要把所有的run内部的代码都放到锁里,要把真正会出现线程安全的部分放入,不然锁了没有意义。真正会出现线程安全的部分就是,多个线程的多句代码操作同一个对象
方法2: 同步函数,使用synchronized修饰一个函数
@Override public synchronized void run () {} public static synchronized void get () {}
同步函数要注意的事项:
1 如果是一个非静态的同步函数的锁对象是this对象,如果是静态的同步函数的锁对象是当前函数所属类的字节码文件(也就是class对象,比如People.class文件)
Java中有个Class类,把类文件的信息,全部都保存到该对象中
2 同步函数的锁对象是固定的,不能由你来指定的.
推荐使用同步代码块,原因:
1 同步代码块的锁可以由我们随意指定,方便控制,而同步函数锁固定的,不能由你指定
2 同步代码块可以很方便控制需要被同步代码的范围,同步函数必须是整个函数的所有代码都被同步了
Java 中的同步机制,解决了线程安全问题,也同时引发了死锁现象。
死锁现象:(线程安全问题的根本原因:多个线程有多个语句操作同一个资源)
死锁现象的的根本原因:
1 两个或两个以上的线程
2 存在两个或者两个以上的共享资源
两个人抢夺遥控器和电池的例子
class DeadLock extends Thread { public DeadLock(String name) { super(name); } @Override public void run(){ if ("张三".equals(Thread.currentThread().getName())) { synchronized ("遥控器") { //这个 遥控器和狗娃那里的遥控器是同一个锁对象,因为字符串相同内容都在字符串常量池中维护一份 System.out.println("张三拿到了遥控器,准备去取电池"); synchronized ("电池") { System.out.println("张三拿到了遥控器和电池,正在吹空调"); } } } else if ("狗娃".equals(Thread.currentThread().getName())) { synchronized ("电池") { System.out.println("狗娃拿到了电池,准备去抢遥控器"); synchronized ("遥控器") { System.out.println("狗娃拿到了电池和遥控器,正在吹空调"); } } } } }
出现了张三拿到了遥控器,而狗娃拿到了电池,两方锁都关闭了,互相等待对方。当然不是每次都会出现的,如果一方执行的块,很可能一方全部执行结束后放开代码,另一方接着执行。
死锁现象解决方案:没有方案,只能尽量避免发生而已。尽量避免双方共享资源,互相等待。
线程的通讯:一个线程,完成了自己的任务时,要通知另外一个线程去完成另一个任务
问题1:价格错乱。通过加同步代码块来解决,在生产和消费的过程中,不可以交互
线程通讯的两个方法:
wait()方法: 等待,如果线程执行了wait,该线程会进入等待。等待状态下的线程,必须要被其他线程调用notify方法才能唤醒
notify()方法:唤醒,唤醒一个在这个锁对象上等待的线程。(wait和notify要锁对象来调用)
notifyAll()方法:唤醒这个锁对象的线程池上的所有等待的线程
wait与notify方法的要注意事项:
1 wait方法和notify方法是属于Object类的(因为锁对象是任意的对象,如果是属于Thread类,那么就不是任意的了)
2 wait方法和notify方法必须要在同步代码块或同步函数中进行(同步函数和同步代码块才有锁对象的概念)
3 wait方法和notify方法必须要由锁对象来调用,否则会报错 (因为要以锁对象为标识符,建立一个线程池)
4 wait方法会抛出InterruptedException,必须抛出或者捕获处理,而notify方法就不会,所以p.notify 可以直接用,p.wait 需要在try块中用
wait方法:
一个线程如果执行了wait方法,那就会进入一个以锁对象为标识符的线程池中等待
notify方法:
一个线程如果执行了notify方法,那就会唤醒以锁对象为标识符的线程池中等待的线程其中一个
生产者与消费者模型:
class Product { String name; double price; boolean flag = false;//产品是否生产完毕的标志, 默认情况是没有生产完成的 } class Producer implements Runnable { String name; Product p; public Producer(String name, Product p) { this.name = name; this.p = p; } public void run () { int i = 0; while (true) { synchronized (p) { if (!p.flag) { if (i % 2 == 0) { p.name = "apple"; p.price = 6.5; } else { p.name = "banana"; p.price = 2; } System.out.println("生产者生产了" + p.name + "价格是" + p.price); i ++; p.flag = true; p.notify();//唤醒消费者去消费 } else { //已经生产完毕的,等待消费者先去消费 try { // p.notify() p.wait(); } catch (Exception e) { e.printStackTrace(); } } } } } } class Consumer implements Runnable { String name; Product p; public Consumer(String name, Product p) { this.name = name; this.p = p; } public void run () { while (true) { synchronized (p) { if (p.flag) {//产品已经生产完毕 System.out.println("消费者消费了" + p.name + " 价格是 " + p.price); p.flag = false; p.notify();//唤醒生产者去生产 } else {//产品还没有生产,等待生产者先生产 try { p.wait(); } catch (Exception e) { e.printStackTrace(); } } } } } }
作业:有个水池,容量固定为500L,一边进水口,一边出水口,进水出水不能同时进行
水池一旦满了,就不能继续注水,一旦放空了,不可以继续放水,进水速度5L/s, 放水速度2L/s
public class Demo2{ public static void main(String[] args) { Pipe pipe = new Pipe(new Pool()); new Thread(pipe, "inlet").start(); new Thread(pipe, "outlet").start(); } } class Pool { int volume = 500; boolean full = true; } class Pipe implements Runnable { Pool p; public Pipe (Pool p) { this.p = p; } public void run () { if ("inlet".equals(Thread.currentThread().getName())) { while (true) { synchronized (p) { if (p.volume == 500) { p.full = true; } else if (p.volume == 0) { p.full = false; } if (!p.full) { p.volume += 5; System.out.println(Thread.currentThread().getName() + " is working, volume of water: " + p.volume); try { Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); } } else if (p.full){ System.out.println("the pool is full, outlet is called"); p.notify(); try { p.wait(); } catch (Exception e) { e.printStackTrace(); } } } } } else if ("outlet".equals(Thread.currentThread().getName())) { while (true) { synchronized (p) { if (p.volume == 500) { p.full = true; } else if (p.volume == 0) { p.full = false; } if (p.full) { p.volume -= 2; System.out.println(Thread.currentThread().getName() + " is working, volume of water: " + p.volume); try { Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); } } else if (!p.full){ System.out.println("the pool is empty, inlet is called"); p.notify(); try { p.wait(); } catch (Exception e) { e.printStackTrace(); } } } } } } }
程序中p.notify()之后立刻用p.wait()的理由:
刚开始,水池是满的,那么水池p对象为标识的线程池中没有任何对象,所以p.notify没有唤醒任何线程,紧接着,由于满的,所以inlet运行了p.wait()该线程进入了等待。等到outlet线程把水放空后,p.notify()真正发挥了作用,唤醒了等待的inlet线程,再执行p.wait()让自己进入等待状态
线程的停止:
stop方法已经停止使用,interrupt方法就是粗暴唤醒,把线程等待状态(wait)强制清除,被清除wait的线程还会收到一个InterruptException. Notify比较温和.
interrupt可以指定唤醒,而notify不可以
1 停止一个线程,一般可以通过一个变量去控制(比如让变量为false,停止循环)
2 如果需要停止一个等待状态的线程,我们需要变量配合notify或者interrupt方法来使用
守护(后台)线程:在一个进程中,如果只剩下守护线程,那么守护线程也会死亡. (道理就像守护天使,它因你而存在,你在它才在,你要线程死亡了,它也会消失,但是它要是先死亡了,不会影响你)
Thread t = new Thread()
t.isDaemon()判断是否为守护线程
t.setDaemon(true);设置是true为守护线程,false不是守护线程,一个线程默认都不是守护线程
类似QQ的后台下载程序,如果QQ退出了,他也退出;但是QQ在线时,你却不能操作他。或者像流量的流失,一些广告如果你不点击,后台就会帮你自动点击,你的流量就会消失
public class Demo2{ public static void main(String[] args) { Test t = new Test("haha"); Thread t1 = new Thread(t, "Thread1"); t1.setDaemon(true);//设置为守护线程 t1.start(); for(int i = 0; i < 100; i ++) { System.out.println(Thread.currentThread().getName() + " " + i); if (i == 99) { System.out.println(t1.isDaemon());//是守护线程,主线程结束,守护线程也会结束 } } } } class Test implements Runnable { String name; public Test (String name) { this.name = name; } public void run() { for (int i = 1; i <= 100; i ++) { System.out.println("更新包下载:" + i + "%"); if (i == 100) { System.out.println("更新包下载完毕,准备安装"); } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }
join 方法:
在一个线程中,有别的线程启动,并且执行了.join()方法,那么这个外部线程就要等待这个内部线程完成后,再向下执行。其实这个join方法也是外部线程执行的
一个线程如果执行join语句,那么就有新的线程加入,执行该语句的线程必须让步给新加入的线程先完成任务,然后才能继续执行
public class Demo2{ public static void main(String[] args) { Mom m = new Mom("mom"); m.start(); } } class Son extends Thread { public Son () {} @Override public void run () { for (int i = 0; i < 10; i ++) { System.out.println(Thread.currentThread().getName() + i); } } } class Mom extends Thread { public Mom (String name) { super(name); } @Override public void run () { for (int i = 0; i < 100; i ++) { if (i == 50) { Son s = new Son(); s.start(); try { s.join();//外部线程要让步给内部线程,等s执行完了再向下打印执行 } catch(InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + i); } } }
数组的回顾:
存储同一种数据类型的容器
特点:
1 只能存储同一种数据类型
2 一旦初始化,长度固定
3 数组中的元素与元素之间的内存地址是连续的
注意:Object类型的数组可以存储任意类型的数据
Object[] arr = new Object[10]; arr[0] = 123; arr[1] = "Hello"; arr[2] = 'c'; arr[3] = true; arr[4] = new Test();
因为Object是所有类的超级父类,相当于父类引用类型变量指向了子类的对象,多态的一种体现
集合:存储对象数据的集合容器
集合比数组的优势:
1 集合可以存储任意类型的对象数据,数组只能存储同一种数据类型的数据(除Object数组)
2 集合的长度是可以发生变化的,数组的长度是固定的
使用接口对容器做出一定的规范,使其具备一定的具体的功能
集合位于java.util包
这个Collection是集合类的根接口
---------| Collection 单例集合的根接口
----------------| List子接口,如果实现了List接口的集合类,具备特点是有序,可重复的(这个有序指的是插入集合的顺序)
----------------| Set子接口,如果实现了Set接口的集合类,具备特点是无序,不可重复的
Collection中的方法:
增加:简单认为这个E是个类
add(E e) 添加成功,返回true,失败,返回false
addAll(Collection c) 把一个集合添加到另一个集合中去。
删除:
clear() 清空集合的元素
remove(Object o) 指定删除集合中的元素,删除成功返回true,失败返回false。arr.remove(“apple”)
removeAll(Collection<?> c)
arr.remove(c);删除arr集合中的,与c集合的交集元素。相当于arr - (arr∩c)
Collection arr = new ArrayList(); arr.add("abc"); arr.add(123); arr.add(3.14); Collection c = new ArrayList(); c.add("abc"); c.add(123); c.add(1); arr.removeAll(c); System.out.println(arr);//[3.14]
retainAll(Collection<?> c)
保留arr集合中的,与c集合交集的元素,删除非交集元素。相当于arr∩c,并返回arr的那部分
arr.retainAll(c); System.out.println(arr);//[abc, 123]
查看:
hashCode() 查看集合的哈希值,貌似每次集合有操作,操作前后哈希值都不一样,地址都会变
size() 查看元素的个数
判断:
contains(Object o) 判断集合中是否存在指定的元素
containsAll(Collection<?> c) 如果arr包含了c的所有元素,就返回true
equals(Object o) 判断集合内容是否相同 System.out.println(arr.equals(c));//true
isEmpty()判断集合是否为空集合
arr.add(new Person(101, "alex"));//添加自定义元素 arr.add(new Person(102, "bob")); System.out.println(arr);//[abc, 123, 3.14, Person@1540e19d, Person@677327b6]
打印出的是元素的名字和地址,那么重写Person类的toString方法就可以
class Person { int id; String name; public Person(int id, String name) { this.id = id; this.name = name; } public String toString() { return this.id + " " + this.name; } } System.out.println(arr.contains(new Person(102, "bob")));//false, 一旦new一个对象就是不同的对象
那么如何让输出的结果为true呢,也就是说id和name相等就是相等的元素;
在ArrayList的原码中,contains方法内部是依赖于equals方法进行比较的。而equals方法默认又是比较两个的内存地址,所以new之后不能相等
所以可以重写equals方法
import java.util.*; public class Demo2 { public static void main(String[] args) { Collection arr = new ArrayList(); arr.add("abc"); arr.add(123); arr.add(3.14); arr.add(new Person(101, "alex")); arr.add(new Person(102, "bob")); System.out.println(arr.contains(new Person(101, "alex")));//false } } class Person { int id; String name; public Person(int id, String name) { this.id = id; this.name = name; } @Override public boolean equals (Object obj) { if (obj instanceof Person) { Person p = (Person) obj; return this.id == p.id; } else { return this.hashCode() == obj.hashCode(); } } }
java中的规范是一般重写了equals方法,那就要重写hashCode方法
迭代:
iterator() 用于抓取集合中的元素(就好像抓毛绒玩具的游戏机)。返回一个迭代器接口
迭代器的三个方法:
hasNext() 是否还有元素可以继续遍历,如果有,返回true,如果没有返回false
next() 取出元素
Collection c = new ArrayList(); c.add("alex"); c.add("bob"); c.add("lily"); c.add("george"); Iterator it = c.iterator(); System.out.println(it.next());//alex System.out.println(it.next());//bob System.out.println(it.hasNext());//true
如果到了最后,再用一次next就会产生异常
remove()移除迭代器最后一次返回的最后一个元素
System.out.println(it.next());//alex System.out.println(c);//[alex, bob, lily, george] it.remove();//从c中删除了迭代器最后一次返回的元素,alex System.out.println(it.next());//bob System.out.println(c);//[bob, lily, george]
一边遍历,一边从集合中删除上一个遍历到的元素
Iterator it = c.iterator(); while (it.hasNext()) { System.out.println(it.next()); it.remove();//边输出元素,边把集合的元素一个一个清空 } System.out.println(c);//[]
遍历集合的元素:
方式1:使用toArray方法,返回一个Object[]数组,把集合的元素存储到一个Object[]数组中返回,然后遍历这个数组就可以
方式2:使用迭代器iterator()遍历。c.iterator()会返回一个Iterator接口 Iterator it = c.iterator()
使用了多态,父接口的引用类型变量指向了接口实现类的对象
Iterator it = c.iterator(); while (it.hasNext()) { System.out.println(it.next()); }
遍历时,如果集合里存了自定义类型的数据,那么需要强转才可以调用自定义类型数据自己的属性和方法,比如Person p = (Person) it.next();
toArray() 返回包含此集合的,所有元素的数组
Object[] arr1 = arr.toArray();//[abc, 123, 3.14] System.out.println(Arrays.toString(arr1));
创建一个Object[]数组,把集合中的元素都放入一个数组,再返回这个数组.
为什么要返回Object[]类数组?
可以返回任意类型元素的数组
Collection是接口,不能直接创建对象,可以用多态,来创建他的实现类的对象,比如ArrayList
Collection arr = new ArrayList(); arr.add("abc"); arr.add(123); arr.add(3.14); System.out.println(arr);//[abc, 123, 3.14]
System.out.println(arr.size()); // 3
可以直接用System.out.println()来输出整个集合的
Collection c = new ArrayList(); c.add("apple"); c.add(true); c.add("banana"); c.add(new int[] {1, 2, 3}); System.out.println(c); arr.addAll(c); System.out.println(arr);
需求:将Collection转成数组toArray中, 找出编号为101的人的信息
Collection arr = new ArrayList(); arr.add("abc"); arr.add(123); arr.add(3.14); arr.add(new Person(101, "alex")); arr.add(new Person(102, "bob")); Object[] obj = arr.toArray(); for (int i = 0; i < obj.length; i ++) { if (obj[i] instanceof Person) { Person p = (Person)obj[i]; if (p.id == 101 ) { System.out.println(p); } } }
从Object[]数组中取出的元素,只能声明Object类型接受,如果需要其他的类型接受,那就需要强制类型转换
Collection类接口,以及其继承接口List接口的实现类都可以直接用System.out.println()打印输出了,不像数组类型需要Arrays.toString(arr)来转换再输出
作业:使用集合实现注册登录的功能
注册:1 提示用户输入注册的id与密码,如果输入的id号已经存在在集合中,提示用户重新输入。注册完毕后,把集合中的所有元素打印出来
登录:提示用户输入id与密码,如果这个id与密码已经存在在集合中,登陆成功,否则失败。
import java.util.*; class Demo2 { public static void main (String[] args) { ClientSystem cs = ClientSystem.getInstance(); cs.run(); } } class Customer { String userId; String pwd; public Customer(String userId, String pwd) { this.userId = userId; this.pwd = pwd; } @Override public String toString () { return "{userId: " + this.userId + ", password: " + this.pwd + "}"; } } class ClientSystem { private static ClientSystem csy = new ClientSystem(); static Collection cs = new ArrayList(); Scanner scanner = new Scanner(System.in); private ClientSystem () {} public static ClientSystem getInstance() { return csy; } private void register () { Object[] obj = ClientSystem.cs.toArray(); while (true) { System.out.println("请输入用户名(按q退出):"); String userId = scanner.next(); if ("q".equals(userId)) { this.show(); this.login(); break; } if (obj.length != 0) { for (int i = 0; i < obj.length; i++) { Customer c = (Customer) obj[i]; if (c.userId.equals(userId)) { System.out.println("此用户名已存在,请重新输入"); break; } } } System.out.println("请输入密码(按q退出):"); String pwd = scanner.next(); if ("q".equals(pwd)) { this.show(); this.login(); break; } ClientSystem.cs.add(new Customer(userId, pwd)); System.out.println("注册成功"); } } private void login () { int times = 0; outer: while (true) { if (times == 3) { System.out.println("输入3次错误信息,退出系统"); break; } System.out.println("用户名:"); String userId = scanner.next(); System.out.println("密码:"); String pwd = scanner.next(); times ++; Object[] obj = ClientSystem.cs.toArray(); for (int i = 0; i < obj.length; i++) { Customer c = (Customer) obj[i]; if (userId.equals(c.userId) && pwd.equals(c.pwd)) { System.out.println("登录成功"); break outer; // java这个命名功能比较好,可以直接退出外部循环 } } System.out.println("用户名或密码错误,请再输入一次:"); } } private void show() { Object[] obj = ClientSystem.cs.toArray(); System.out.println("现有用户清单"); for (int i = 0; i < obj.length; i ++) { Customer c = (Customer) obj[i]; System.out.println(c); } } public void run() { this.register(); } }
应用了单例设计的登录注册系统,系统内包含了登录,注册,运行,展示用户信息的功能,使用了Object[] arr = new ArrayList()
迭代器的原理:
Iterator it = c.iterator();
获取到迭代器的时候,迭代器内部有一个指针,指向了集合中的第1个元素
hasNext()方法,当前指针是否有指向元素,如果有,返回true,如果没有返回false
next()方法,获取当前指针指向的元素并返回当前元素,然后指针向下移动一个元素
Ctrl + shift + /添加或取消多行注释
List接口:接口。有序可重复
List的实现类:AbstractList, AbstractSequentialList, ArrayList, AttributeList, CopyOnWriteArrayList, LinkedList, RoleList,RoleUnresolvedList, Stack, Vector
通过多态来定义List的实现类
List lst = new ArrayList()
这里的有序,不是指自然顺序,而是指添加进去的顺序,与元素出来的顺序是一致的。
List 接口继承了Collection接口,也就继承Collection接口的方法,那么List的特有方法
添加:
add(int index, E element) 把元素添加到集合中的指定索引值位置上
list.add()将元素添加到集合的末尾处,添加之后,被添加的元素要出现在该索引位置上
addAll(Collection<? extends E> c) 将list2的元素,添加到list()集合指定索引位置上
List lst = new ArrayList(); lst.add("alex");lst.add("bob"); lst.add("hah");lst.add("vvv"); lst.add(1, "hello"); System.out.println(lst); List lst1 = new ArrayList(); lst1.add("hello"); lst1.add("world"); lst.addAll(2, lst1); System.out.println(lst);
获取:
get(int index) 根据索引值来获取集合中的元素 lst.get(int 2);遍历集合的元素又多了一种
indexOf(Object o)找出指定元素在集合中的索引值(找到返回1,找不到返回-1)
lastIndexOf(Object o)找出指定元素最后一次出现在集合中的索引值
subList(int fromIndex, int toIndex) list.subList()指定开始与结尾的索引值。
迭代:
listIterator() 返回List接口特有的迭代器。返回一个ListIterator形象
ListIterator特有的方法:
添加:
hasPrevious(); 判断是否存在上一个元素
previous()当前指针先向上移动一个单位,然后再取出当前指针指向的元素
next() 先取出当前指针指向的元素,然后指针向下移动一个单位
List lst = new ArrayList(); lst.add("alex");lst.add("bob"); lst.add("hah");lst.add("vvv"); lst.add(1, "hello"); System.out.println(lst); ListIterator it = lst.listIterator(); it.next();//alex 先取元素,后移动指针到第二个位置 System.out.println(it.hasPrevious());//true,现在指针前边还有一个位置 System.out.println(it.previous());//alex,先移动指针,再去元素
List lst = new ArrayList(); lst.add("alex");lst.add("bob"); lst.add("hah");lst.add("vvv"); ListIterator it = lst.listIterator(); while (it.hasNext()) { it.next(); } while (it.hasPrevious()) { System.out.println(it.previous()); }
当it.next()运行时,到最后元素,指针其实是移动到了下一个元素位置,虽然不存在。然后it.previous(),运行时,先移动指针到倒数第一的位置上,以此类推,产生倒序的数组
和nextIndex();
add(E, e) 把当前元素插入到当前指针指向的位置上
List lst = new ArrayList(); lst.add("alex");lst.add("bob"); lst.add("hah");lst.add("vvv"); ListIterator it = lst.listIterator(); it.next(); it.next(); it.add("Hello"); System.out.println(lst);//[alex, bob, Hello, hah, vvv]
set(E, e) 用指定元素替换迭代器最后一次返回的元素
List lst = new ArrayList(); lst.add("alex");lst.add("bob"); lst.add("hah");lst.add("vvv"); ListIterator it = lst.listIterator(); it.next();//alex it.set("vivian"); System.out.println(lst);//[vivian, bob, hah, vvv]
listIterator(int index)
修改:
set(int index, E element) 使用指定的元素替换指定索引值位置的元素
集合类的所有类都是在java.util包的
List接口内特有的方法具备的特点; 操作方法都存在索引值
只有List接口,下面的集合类,才具备索引值。其他接口 下面的集合类都没有索引值
使用三种方式遍历集合:
List lst = new ArrayList(); lst.add("alex");lst.add("bob"); lst.add("hah");lst.add("vvv"); ListIterator it = lst.listIterator(); 方法1:使用get方法遍历 //方法1: for (int i = 0; i < lst.size(); i ++) { System.out.println(lst.get(i)); } 方法2:使用迭代器正序的遍历 //方法2: while(it.hasNext()) { System.out.println(it.next()); } 方法3:使用迭代器逆序遍历(必须要基于方法2,因为刚开始指针要在集合的下方) //方法3: while (it.hasPrevious()) { System.out.println(it.previous()); }
也可以使用增强for循环的:for (int i: list) {System.out.println(i);}
迭代器在遍历元素时的注意事项:
在迭代器迭代元素的过程中,不允许使用集合对象改变集合中的元素(list.add()或list.remove()),如果需要添加或者删除只能使用迭代器的方法进行操作(it.add() it.remove())。
在迭代器遍历的过程中添加元素,那么迭代器内部会自动跳过这个添加的元素,而按照原List来遍历。所以不会出现,死循环,无法结束的问题
List lst = new ArrayList(); lst.add("abc");lst.add('a');lst.add(true); ListIterator it = lst.listIterator(); while (it.hasNext()) { it.add("hello"); System.out.println(it.next()); } System.out.println(lst);//[abc, hello, a, hello, true, hello]
但是如果使用了集合对象改变了原集合List中的元素个数, 那么迭代器就会报错,ConcurrentModificationException异常。原因是:迭代器内的.add()当前指针位置添加 和 .remove() 移除上一个迭代输出的元素,都是在指针附近操作,可以跳过这个元素。而集合Collection和List的add会添加到整个集合的末尾,那么这时迭代器既不能跳到末尾的元素的后方位置,也不能继续运行,会导致死循环,所以会报错。
只要集合增删元素之后,没有迭代器的操作语句就可以允许执行。但是集合增删元素之后,有迭代器的操作语句,那么就会报错。
迭代元素的过程的含义:迭代器一旦创建到使用结束的时间。
集合的体系:
-------| Collection 单列集合的根接口
------------| List 实现了List接口的集合类,有序可重复
-----------------| ArrayList 底层维护了一个Object[]数组实现的,特点是查询速度快,增删慢
-----------------| LinkedList 底层由链表数据结构实现,特点是查询速度慢,增删快
-----------------| Vector(了解) 底层实现方式与Arraylist相同,只不过他是线程安全的
------------| Set 实现了Set接口的集合类,无序不可重复
最常用的ArrayList,LinkedList,Vector
ArrayList特有的方法:(之前Collection和List的方法ArrayList都具备)
ensureCapacity(int minCapacity) 也可以保证初始容量,但是习惯用构造方法指定
trimToSize() 将ArrayList的列表长度调整为当前列表的长度。比如10个长度的列表内部只有3个,那么就缩小为3个。但是一般也不用,因为一旦需要增删,效率就会很低
主要学习,ArrayList的实现原理与特点:
ArrayList的构造方法:
ArrayList() 构造一个容量为10的空列表。
ArrayList(int initialCapacity) 构造一个指定初始容量的空列表
笔试题目:使用ArrayList无参的构造函数,创建一个对象时,默认的容量是多长?如果长度不够使用时,又自动增长多少?
ArrayList底层是维护了一个Object[] 数组实现的,使用无参的构造函数时,默认Object[]数组默认容量为10,当长度不够用时,自动增长0.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1)
原长度加上原长度右移1位(除以2^1) 也就是原来的1.5倍,增长了50%。移位运算符速度更快
为什么ArrayList的查询快,增删慢呢?它的底层的实现原理是怎样的呢?
数组中的元素和元素间的内存地址是连续的,查询比较快。如果增加时,检测是否超过长度,如果超过了,那么就要创建一个1.5倍的数组,并将原来的数组拷贝进入这个数组. 删除元素的时候,默认使用arraycopy方法,删除一个元素,就要把后方的元素全部拷贝,从空出来的位置再放进去。所以增删非常慢
什么时候使用ArrayList:如果目前的数据是查询比较多,增删比较少,那么就是用ArrayList存储数据。比如高校的图书馆查询多
需求:定义一个函数,清除集合中的重复元素,如果书号是一样的就称为重复元素. 要求遍历集合元素的时候,必须使用迭代器
import java.util.*; public class Demo3 { public static void main(String[] args) { ArrayList list = new ArrayList(); list.add(new Book(110, "Thinking in Java")); list.add(new Book(111, "java编程思想")); list.add(new Book(112, "深入Javaweb")); list.add(new Book(110, "Thinking in Java")); ArrayList idx = new ArrayList();//定义一个列表存储找到的重复的书 ListIterator it = list.listIterator();//原图书列表的迭代器 int times = 1;//遍历初始索引为1 while (it.hasNext()) { //原图书列表还有元素可以遍历时进行遍历 Book b = (Book) it.next();//强转类型为Book类型后,才可以取出Book类对象的元素 ListIterator it1 = list.listIterator(times ++);//再定义一个迭代器,从下一个元素开始遍历 while (it1.hasNext()) { Book b1 = (Book) it1.next();//强转第二个迭代器的每次迭代取出的元素 if (b1.id == b.id) {//如果发现有和外迭代循环中的书有相同的书号 idx.add(b);//将重复的书元素添加到idx列表中 break;//退出循环 } } } ListIterator idt = idx.listIterator();//建立一个idx列表的迭代器,来迭代剔除list 的重复元素 while (idt.hasNext()) { list.remove(idt.next()); idt.remove();//边遍历,边把idt中的元素删除 } ListIterator after = list.listIterator();//操作完成后,遍历显示list中图书的详细信息,当然也可以改写Book的toString方法 while (after.hasNext()) { Book b = (Book) after.next(); System.out.println(b.id + b.name); } System.out.println(list);//调用改写后的Book的toString方法来遍历输出 } } class Book { int id; String name; public Book (int id, String name) { this.id = id; this.name = name; } @Override public String toString () { return this.id + this.name; } }
教师思路:
新创建一个ArrayList,然后遍历原列表,从列表中拿出的每个元素,和新创建的ArrayList中的元素比较,如果存在就不要,如果不存在就放入
import java.util.*; public class Demo3 { public static void main(String[] args) { ArrayList list = new ArrayList(); list.add(new Book(110, "Thinking in Java")); list.add(new Book(111, "java编程思想")); list.add(new Book(112, "深入Javaweb")); list.add(new Book(110, "Thinking in Java")); ArrayList newList = clearRepeat(list); System.out.println(newList); } public static ArrayList clearRepeat(ArrayList list) { ArrayList lst = new ArrayList(); ListIterator it = list.listIterator(); while (it.hasNext()) { Book b = (Book) it.next(); if (!lst.contains (b)) { lst.add(b); } } return lst; } } class Book { int id; String name; public Book (int id, String name) { this.id = id; this.name = name; } @Override public String toString () { return this.id + this.name; } @Override public boolean equals(Object obj) {//重写equals方法,因为contains方法底层是靠equals方法的 Book b = (Book)obj; return this.id == b.id; } }
LinkedList是List接口的链接列表实现的。
LinkedList底层是使用了链表的数据结构实现的。
LinkedList的实现原理,使用链表数据结构实现的,具备的特点:
查询速度慢,增删快,和ArrayList正好相反。
每个元素分两部分,一部分是内容,另一部分是下一个元素的内存地址
为什么查询慢?增删快?
元素间的内存地址不是连续的,不像Object[]数组实现。增删的时候,就只需要更改内存地址就可以了。删除元素之后,一旦没有变量指向,那么就成为垃圾对象,等待被回收

1 LinkedList特有的方法:
addFirst(E e) 把元素添加到链表的首位置
addLast(E e) 把元素添加到链表的末尾
getFirst() 获取集合中的首元素
getLast() 获取集合中的末尾元素
removeFirst() 删除集合中的首元素, 并返回
removeLast() 删除集合中的末元素, 并返回
扩展:贪吃蛇的项目中,蛇头和食物要重合,末尾要增长
2 数据结构:
1 栈:先进后出,只有开头开口 主要是为了让用户实现堆栈数据结构的存储方式
push() 将元素插入到集合的开头处(和JavaScript不同,js中是插入到末尾)
pop() 将集合开头的元素弹出,并返回
2 队列:先进先出,两端开口,末尾进,开口出 主要是为了让用户可以使用LinkedList模拟队列数据结构的存储方式
offer() 将元素插入到集合的末尾处
poll() 将集合首位置的元素弹出,并返回
3 返回逆序的迭代器对象
descendingIterator() 产生一个逆序的迭代器
Iterator it = lst.descendingIterator(); while (it.hasNext()) { System.out.println(it.next()); }
element() 找到并返回链表头部第一个元素
重点掌握Collection和List中的方法
为什么会有重复的方法出现呢?栈和队列的方法
都是为了模拟实现堆栈和队列的数据结构存储方式
机试题目:使用LinkedList实现堆栈的数据结构存储方式与队列的数据结构存储方式
堆栈数据结构:
存储特点:先进后出,后进先出。一端开口,类似桶
队列数据结构:
存储特点:先进先出,后进后出。两端开口,末尾进,头部出,类似排队
模拟堆栈数据结构存储方式:
class StackList { LinkedList list; public StackList() { list = new LinkedList(); } public void add (Object obj) {//进栈 list.push(obj); } public Object pop () {//弹栈,删除元素,并返回 return list.pop(); } public int size () { return list.size(); } }
StackList st = new StackList(); st.add("a"); st.add("b");st.add("c"); for (int i = 0; i < st.size(); i ++) { System.out.println(st.pop()); }
只弹出来两个元素;原因是,每次弹出,链表的长度.size()都会变化,所以导致没有弹出所有元素。所以在刚开始定义变量来获取初始长度即可int size = st.size();
int length = st.size(); for (int i = 0; i < length; i ++) { System.out.println(st.pop()); }
模拟队列的数据结构存储方式:
class StackList { LinkedList list; public StackList() { list = new LinkedList(); } public void add (Object obj) {//进队 list.offer(obj); } public Object poll () {//出队 return list.poll(); } public int size () { return list.size(); } }
需求:使用LinkedList存储一副扑克牌,实现洗牌功能。
52张牌,一张扑克牌的属性:花色,点数
import java.util.*; public class Demo3 { public static void main(String[] args) { LinkedList pokers = Pokers.createPoker(); Pokers.shuffle(pokers); System.out.println(Pokers.showPokers(pokers)); } } class Poker { String type; String point; public Poker (String type, String point) { this.type = type; this.point = point; } @Override public String toString () { return "{" + this.type + ":"+ this.point + "}"; } } class Pokers { //生成扑克牌的方法 public static LinkedList createPoker () { LinkedList list = new LinkedList();//这个集合用于存储扑克对象 String[] types = {"黑桃", "红桃", "梅花", "方片"}; String[] points = {"A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"}; for (int i = 0; i < types.length; i ++) { for (int j = 0; j < points.length; j ++) { list.add(new Poker(types[i], points[j])); } } return list; } public static String showPokers (LinkedList pokers) {//显示poker牌,每10张换一行 String str = ""; int times = 0; ListIterator it = pokers.listIterator(); while (it.hasNext()) { Poker poker = (Poker) it.next(); str += poker.toString(); if (times % 10 == 9) { str += "\n\r"; } times ++; } return str; } public static void shuffle (LinkedList pokers) { Random random = new Random();//先创建随机数对象 for (int i = 0; i < 100; i ++) { // 随机产生两个索引,相当于取出两张扑克牌,并交换两张牌顺序 int idx1 = random.nextInt(pokers.size());//大于等于0,小于等于51的范围 int idx2 = random.nextInt(pokers.size()); Poker p1 = (Poker) pokers.get(idx1); Poker p2 = (Poker) pokers.get(idx2); pokers.set(idx1, p2); pokers.set(idx2, p1); } } }
需求:有一个LinkedList里保存了人的信息,编写一个函数,根据人的年龄排序,并存储
import java.util.*; public class Demo3 { public static void main(String[] args) { LinkedList list = new LinkedList(); list.add(new Person("alex", 7)); list.add(new Person("bob", 17)); list.add(new Person("george", 5)); list.add(new Person("peter", 9)); reorderList(list); System.out.println(list); } public static void reorderList (LinkedList crowd) {//内部使用冒泡排序 for (int i = 0; i < crowd.size() - 1; i ++) { for (int j = 0; j < crowd.size() - i - 1; j ++) { Person p1 = (Person) crowd.get(j); Person p2 = (Person) crowd.get(j + 1); if (p1.age > p2.age) { crowd.set(j, p2); crowd.set(j + 1, p1); } } } } } class Person { String name; int age; public Person (String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "{" + this.name + ":" + this.age + "}"; } }
List的实现接口之Vector
Vector类可实现可增长的对象数组Object[],与数组一样,它包含可以使用整数索引进行访问的组件
Vector 底层也是维护了一个Object[]数组实现的,实现与ArrayList是一样的。但是Vector是线程安全的。(意味着,只能有一个线程操作,操作是同步的,操作效率低,已经要被ArrayList取代)
笔试题目:说出ArrayList与Vector的区别?
相同点,ArrayList与Vector底层都是使用了Object[]数组实现的
不同点:1 ArrayList是线程异步的,操作效率高;Vector是线程同步的,操作效率低
2 ArrayList是JDK1.2时出现的;Vector是JDK1.0时出现的
Vector v = new Vector(); v.addElement("alex");//早起Vector添加元素 v.addElement("george"); v.addElement("eric"); Enumeration e = v.elements();//获取迭代器 while (e.hasMoreElements()) { System.out.println(e.nextElement()); }
早期的一些系统还可能会有Vector,现在已经基本废弃了
Stack 栈 继承自Vector,也是出现在JDK1.0中,SUN公司实现好的LIFO(后进先出)的栈
构造函数:
Stack<String> stack = new Stack<String>(); 创建一个空的栈
方法:
empty() 清空一个栈,返回boolean,成功就是true,失败是false
peek() 本意是看一眼,就是查看栈顶的元素,但并不做任何操作
pop() 弹出栈顶的元素
push(E item) 元素入栈
search(Object o) 返回从1开始的,元素在栈中的位置
Queue 队列接口, 先进先出FIFO, 它的实现类有LinkedList和PriorityQueue
使用方法:可以用多态来创建一个队列
Queue<Integer> queue = new LinkedList<Integer>();
方法:
add(E e) 和 offer(E e) 添加元素,成功返回true,失败的话,前者会抛出异常,后者返回false,所以推荐offer
element() 和 peek() 查看并返回队头的元素,但是并不将元素出队,元素还在那里。如果队列为空,peek()返回null
remove() 和 poll() 弹出并返回队头的元素。如果队列为空,poll()方法返回null
Queue 接口并未定义阻塞队列的方法,而这在并发编程中是很常见的。BlockingQueue 接口定义了那些等待元素出现或等待队列中有可用空间的方法,这些方法扩展了此接口。
BlockingQueue 接口和LinkedBlockingQueue类
构造方法:
LinkedBlockingQueue() 建立一个空的阻塞队列
LinkedBlockingQueue(int capacity) 创建一个指定容量的阻塞队列
LinkedBlockingQueue(Collection<? extends E> c) 创建一个容量是 Integer.MAX_VALUE 的 LinkedBlockingQueue,最初包含给定 collection 的元素,元素按该 collection 迭代器的遍历顺序添加。
方法:实现了Queue接口,并且具有部分LinkedList的方法
特有的
offer(E e, long timeout, TimeUnit unit) 将指定元素插入到此队列的尾部,如有必要,则等待指定的时间以使空间变得可用
put(E e) 将指定元素插入到此队列的尾部,如有必要,则等待空间变得可用
poll(long timeout, TimeUnit unit) 获取并移除队头,在指定时间之前等待可用元素
take() 获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)
有点类似python中的队列,具有阻塞性质
PriorityQueue 优先级队列 所有实现的接口Serializable, Iterable<E>, Collection<E>, Queue<E>
优先级队列接受的参数,默认都是按照最小堆来排列的,可以传入比较器改变为最大堆
构造函数:
PriorityQueue()
PriorityQueue(int initialCapacity)
PriorityQueue(Comparator<? super E> comparator) 接受一个比较器类型
PriorityQueue(int initialCapacity, Comparator<? super E> comparator)
PriorityQueue(PriorityQueue<? extends E> c)
//按默认的比较器构建,以最小堆排列内部数据 PriorityQueue<Integer> pq = new PriorityQueue<Integer>();
//传入一个比较器,使其按最大堆排列 PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o2 - o1; } });
方法:
clear() 清空队列 remove(Object o)删除某元素
comparator() 返回队列使用的比较器
iterator() 返回迭代器
peek() 查看队列顶部元素,但是不删除。contains(Object o)判断队列是否含有某元素
poll()从队列顶部弹出元素
size() 返回队列长度
toArray() 返回 Object[]类型数组
toArray(T[] a) 返回一个T[] 类型数组,数组类型可指定
Integer[] arr1 = queue.toArray(new Integer[arr.length]);
。。。。。。。。。。。。。。。。。
浙公网安备 33010602011771号