铁道部发布了一个售票任务,要求销售1000张票,要求有3个窗口来进行销售,请编写多线程程序来模拟这个效果
窗口001正在销售第1000张票
窗口003正在销售第999张票
窗口002正在销售第998张票
。。。
窗口002正在销售第1张票
窗口002票已售完
窗口001票已售完
窗口003票已售完
使用线程类的方式解决该需求
问题1:三个线程各买了1000张票
出现原因:三个线程各自调用了三次run方法,ticket作为run方法的局部变量就有三个,一共卖了3000张票
解决方案:将ticket设置为静态的全局变量,让三个线程共享一份
package com.qf.test01_type01; public class Test01 { public static void main(String[] args) { MyThread t1 = new MyThread("001"); MyThread t2 = new MyThread("002"); MyThread t3 = new MyThread("003"); t1.start(); t2.start(); t3.start(); } } //线程类 package com.qf.test01_type01; public class MyThread extends Thread{ public MyThread(String name) { super(name); } @Override public void run() { int ticket = 1000; while(ticket > 0){ System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + ticket + "张票"); ticket--; } } } //以上线程类会出现三个线程各买了1000张票的问题。 //出现原因:三个线程各自调用了三次run方法,ticket作为run方法的局部变量就有三个,一共卖了3000张票 //解决方案:将ticket变量放在run方法的外面,且设置为静态变量(用static修饰,如果不用static修饰的话,即使放在了run方法的外面,它也还是会被每个线程对象都独享,还是会出现三个线程各买了1000张票的问题(每个对象独享一份成员变量,所有对象共享一份静态变量)),让三个线程对象共享一份静态变量。 //修改后的线程类 package com.qf.test01_type01; public class MyThread extends Thread{ private static int ticket = 1000; public MyThread(String name) { super(name); } @Override public void run() { while(ticket > 0){ System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + ticket + "张票"); ticket--; } } } //修改之后不会再出现三个线程各卖1000张票的问题,但是又会出现有些票卖重了的问题
问题2:有些票卖重了
出现原因:多个线程争抢资源,线程001抢到CPU资源后输出票的数量后,还没来得及做票的减减,就退出CPU资源了,然后被其他线程抢到CPU资源继续输出
解决方案:票的输出和票的减减必须同时完成,才能退出当前CPU资源 – 加锁
线程安全的问题:在何时的时候加锁,保证代码执行完毕后才能让其他线程抢到资源
注意 – 锁对象选择问题?
锁的分类(线程安全的方式):synchronized(关键字)和Lock(接口)
synchronized同步代码块:
synchronized(锁对象){//自动上锁
…要想互斥住的代码…
}//自动解锁
1.锁对象必须是引用数据类型
2.多个线程必须要使用同一把锁
//多个线程一把锁,while循环在锁外面 while(ticket > 0){ synchronized("abc"){//上锁 if(ticket > 0){ System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + ticket + "张票"); ticket--; } }//开锁 } //线程退出cpu必备的两个条件:1、时间片到了。2、锁打开了。两个条件必须同时具备线程才会退出cpu。 //线程一抢到cpu之后,cpu给它分配一个随机时间的时间片,每次循环都上锁和开锁,当时间片到了,锁刚好打开的时候,线程一就退出cpu和线程二再去争抢cpu。 //线程一抢到cpu之后,cpu给它分配一个随机时间的时间片,每次循环都上锁和开锁,当时间片到了,锁还没打开的时候,线程一就会等运行完代码,锁打开了之后,才会退出cpu和线程二再去争抢cpu。
//多个线程一把锁,while循环在锁里面 synchronized("abc"){//上锁 while(ticket > 0){ if(ticket > 0){ System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + ticket + "张票"); ticket--; } }//开锁 } //线程退出cpu必备的两个条件:1、时间片到了。2、锁打开了。两个条件必须同时具备线程才会退出cpu。 //线程一抢到cpu之后,cpu给它分配一个随机时间的时间片,整个循环只上锁和开锁一次,当时间片到了,锁还没打开,线程一就不会退出cpu,会等运行完代码,锁打开了之后,才会退出cpu。
/* 多个线程多把锁 一个线程对应一把锁,线程一抢到cpu之后,cpu给它分配一个随机时间的时间片,当时间片到了,另外的线程就会执行“看到自己的锁没上锁(最开始锁是默认不上锁的状态),就把线程一上了锁的锁给打开了”的操作,此时线程一就满足时间片到了、锁打开了,线程一就退出cpu。若线程一退出cpu的时候输出了票的数量但还没执行减减的操作,就会被其他线程抢到CPU资源继续输出,就会出现有些票卖了重票的现象 */
3.如果锁对象选择有问题会导致多个线程间不能互斥住(多个线程要想互斥住就必须使用同一把锁,理由同上)
4.字节码文件对象(是引用型数据类型且唯一)、String类对象(放常量池中的,是引用型数据类型且唯一)、static修饰的类对象(静态类的字节码文件只加载一次,且会把static修饰的类对象加载到静态区中,所以它是引用型数据类型且唯一)等可以作为锁对象。
package com.qf.test01_type01; public class MyThread extends Thread{ private static int ticket = 1000; private static Object obj = new Object(); public MyThread(String name) { super(name); } @Override public void run() { while(ticket > 0){ synchronized(obj){//()中的obj引申出下面紧挨着的代码块外的知识点 if(ticket > 0){ System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + ticket + "张票"); ticket--; }}} synchronized(Math.class) (是引用型数据类型且唯一)可以 synchronized("abc")--String类对象(放常量池中的,是引用型数据类型且唯一)可以 synchronized(new String("abc"))---三个线程每次都会调用run方法,所以"abc"会new三次,会有三把锁,违背了”唯一“的原则 synchronized(new object())----三个线程每次都会调用run方法,所以object会new三次,会有三把锁,违背了”唯一“的原则 private Object obj = new Object(); synchronized(obj){ 不可以,每个线程对象独享一份obj,会有三把锁,违背了”唯一“的原则 private static Object obj = new Object(); synchronized(obj){ 可以,每个线程对象共享一份静态的obj,只会有一把锁且obj是引用型数据类型 private static int ticket = 1000; synchronized(ticket){ 不可以, ticket是int类型的,不是引用型数据类型
//利用同步代码块锁的方法上锁修改后的线程类 package com.qf.test01_type01; public class MyThread extends Thread{ private static int ticket = 1000; private static Object obj = new Object(); public MyThread(String name) { super(name); } @Override public void run() { while(ticket > 0){ synchronized(obj){ System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + ticket + "张票"); ticket--; } } } } //修改之后不会再出现有些票卖重了的问题,但是又会出现负票的问题
问题3:出现负票
出现原因:ticket到1后,多个线程都进入到了循环中
解决方案:上锁后再判断一次
//修改以后的代码 package com.qf.test01_type01; public class MyThread extends Thread{ private static int ticket = 1000; private static Object obj = new Object(); public MyThread(String name) { super(name); } @Override public void run() { while(ticket > 0){ synchronized(obj){ if(ticket > 0){ System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + ticket + "张票"); ticket--; } if(ticket <= 0){ System.out.println("窗口" + Thread.currentThread().getName() + "票已售完"); } } } } }
最终版利用Thread类创建子线程且利用同步代码块加锁的方法解决售票模型
package com.qf.test01_type01; public class Test01 { public static void main(String[] args) { MyThread t1 = new MyThread("001"); MyThread t2 = new MyThread("002"); MyThread t3 = new MyThread("003"); t1.start(); t2.start(); t3.start(); } } //线程类 package com.qf.test01_type01; public class MyThread extends Thread{ private static int ticket = 1000; private static Object obj = new Object(); public MyThread(String name) { super(name); } @Override public void run() { while(ticket > 0){ synchronized(obj){ if(ticket > 0){ System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + ticket + "张票"); ticket--; } if(ticket <= 0){ System.out.println("窗口" + Thread.currentThread().getName() + "票已售完"); } } } } }
利用Thread类创建子线程且利用同步方法加锁的形式解决售票模型
public synchronized void method(){//自动上锁
…要想互斥住的代码…
}//自动解锁
此时的锁对象:MyThread.class
package com.qf.test01_type02; public class Test01 { public static void main(String[] args) { MyThread t1 = new MyThread("001"); MyThread t2 = new MyThread("002"); MyThread t3 = new MyThread("003"); t1.start(); t2.start(); t3.start(); } } //线程类 package com.qf.test01_type02; public class MyThread extends Thread{ private static int ticket = 1000; public MyThread(String name) { super(name); } @Override public void run() { while(ticket > 0){ method(); } } //锁对象:MyThread.class public static synchronized void method(){ if(ticket > 0){ System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + ticket + "张票"); ticket--; } if(ticket <= 0){ System.out.println("窗口" + Thread.currentThread().getName() + "票已售完"); } } }
利用Thread类创建子线程且利用lock锁的方法解决售票模型
Lock lock = new ReentrantLock();
lock.lock();//手动上锁
…要想互斥住的代码…
lock.unlock();//手动解锁
package com.qf.test01_type03; public class Test01 { public static void main(String[] args) { MyThread t1 = new MyThread("001"); MyThread t2 = new MyThread("002"); MyThread t3 = new MyThread("003"); t1.start(); t2.start(); t3.start(); } } //线程类 package com.qf.test01_type03; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class MyThread extends Thread{ private static int ticket = 1000; //创建锁对象 private static Lock lock = new ReentrantLock(); public MyThread(String name) { super(name); } @Override public void run() { while(ticket > 0){ lock.lock();//手动上锁 if(ticket > 0){ System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + ticket + "张票"); ticket--; } if(ticket <= 0){ System.out.println("窗口" + Thread.currentThread().getName() + "票已售完"); } lock.unlock();//手动解锁 } } }
初步思路:
1.任务就是对象。所以为了创建任务,必须首先定义一个任务类Task,任务类必须实现Runnable接口。
2、new 三个任务类的对象(即创建任务)
3、因为任务必须在线程中执行,所以创建三个任务的线程
package com.qf.test02_type01; public class Test01 { public static void main(String[] args) { Thread t1 = new Thread(new Task(), "001"); Thread t2 = new Thread(new Task(), "002"); Thread t3 = new Thread(new Task(), "003"); t1.start(); t2.start(); t3.start(); } } //任务类 package com.qf.test02_type01; public class Task implements Runnable{ @Override public void run() { int ticket = 1000; while(ticket > 0){ System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + ticket + "张票"); ticket--; } } }
以上代码会出现问题1:三个线程各卖1000张票
出现原因:创建了三个Task(任务)对象,每个对象中都有一个ticket变量
解决方案:三个线程共享一个Task(任务)对象
针对问题一进行的修改:
1.任务就是对象。所以为了创建任务,必须首先定义一个任务类Task,任务类必须实现Runnable接口。
2、new 一个任务类的对象(即创建一个任务)
3、因为任务必须在线程中执行,所以创建一个任务的三个子线程
package com.qf.test02_type01; public class Test01 { public static void main(String[] args) { Task task = new Task(); Thread t1 = new Thread(task, "001"); Thread t2 = new Thread(task, "002"); Thread t3 = new Thread(task, "003"); t1.start(); t2.start(); t3.start(); } } //任务类 package com.qf.test02_type01; public class Task implements Runnable{ //ticket必须定义在run方法的外面,因为如果放在run方法的里面的话,创建三个子线程对象时会调用三次run方法,还是会出现三个线程各卖1000张票的问题。但是不用加static修饰,因为只创建了一个任务类的对象。不用static修饰的话,放在run方法的外面,它也只会被new的这一个任务类对象独享。 private int ticket = 1000; @Override public void run() { while(ticket > 0){ System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + ticket + "张票"); ticket--; } } }
以上代码又会出现问题2:有些票卖了重票
出现原因:多个线程争抢资源,线程001抢到CPU资源后输出票的数量后,还没来得及做票的减减,就退出CPU资源了,然后被其他线程抢到CPU资源继续输出
解决方案:票的输出和票的减减必须同时完成,才能退出当前CPU资源 – 加锁
一个任务类一把锁,同一个任务类new出来的任务对象里面的多个子线程必须共用一把锁。此题就创建了一个任务类,new了一个任务对象,所以就只能有一把锁让任务对象里面的三个子线程共用。
针对问题二进行的修改:
加锁方式一:利用同步代码块的形式加锁
利用Runnable接口创建子线程且利用同步代码块加锁的方法解决售票模型时,锁对象使用的最佳方案是用this
synchronized(锁对象){//自动上锁
…要想互斥住的代码…
}//自动解锁
package com.qf.test02_type01; public class Test01 { public static void main(String[] args) { Task task = new Task(); Thread t1 = new Thread(task, "001"); Thread t2 = new Thread(task, "002"); Thread t3 = new Thread(task, "003"); t1.start(); t2.start(); t3.start(); } } //任务类 package com.qf.test02_type01; public class Task implements Runnable{ private int ticket = 1000; @Override public void run() { while(ticket > 0){ // synchronized(this){ System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + ticket + "张票"); ticket--; } } } }
以上代码又会出现问题3:出现负票
出现原因:ticket到1后,多个线程都进入到了循环中
解决方案:上锁后再判断一次
针对问题三进行的修改:
package com.qf.test02_type01; public class Test01 { public static void main(String[] args) { Task task = new Task(); Thread t1 = new Thread(task, "001"); Thread t2 = new Thread(task, "002"); Thread t3 = new Thread(task, "003"); t1.start(); t2.start(); t3.start(); } } //任务类 package com.qf.test02_type01; public class Task implements Runnable{ private int ticket = 1000; @Override public void run() { while(ticket > 0){ synchronized(this){ if(ticket > 0){ System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + ticket + "张票"); ticket--; } if(ticket <= 0){ System.out.println("窗口" + Thread.currentThread().getName() + "票已售完"); } } } } }
利用Runnable接口创建子线程且利用同步代码块加锁的方法解决售票模型最终版
package com.qf.test02_type01; public class Test01 { public static void main(String[] args) { Task task = new Task(); Thread t1 = new Thread(task, "001"); Thread t2 = new Thread(task, "002"); Thread t3 = new Thread(task, "003"); t1.start(); t2.start(); t3.start(); } } //任务类 package com.qf.test02_type01; public class Task implements Runnable{ private int ticket = 1000; @Override public void run() { while(ticket > 0){ synchronized(this){ if(ticket > 0){ System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + ticket + "张票"); ticket--; } if(ticket <= 0){ System.out.println("窗口" + Thread.currentThread().getName() + "票已售完"); } } } } }
利用Runnable接口创建子线程且利用同步方法加锁的方法解决售票模型
public synchronized void method(){//自动上锁
…要想互斥住的代码…
}//自动解锁
此时的锁对象:MyThread.class
package com.qf.test02_type02; public class Test01 { public static void main(String[] args) { Task task = new Task(); Thread t1 = new Thread(task, "001"); Thread t2 = new Thread(task, "002"); Thread t3 = new Thread(task, "003"); t1.start(); t2.start(); t3.start(); } } //任务类 package com.qf.test02_type02; public class Task implements Runnable{ private int ticket = 1000; @Override public void run() { while(ticket > 0){ method(); } } public synchronized void method(){ if(ticket > 0){ System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + ticket + "张票"); ticket--; } if(ticket <= 0){ System.out.println("窗口" + Thread.currentThread().getName() + "票已售完"); } } }
利用Runnable接口创建子线程且利用lock锁的方法解决售票模型
Lock lock = new ReentrantLock();
lock.lock();//手动上锁
…要想互斥住的代码…
lock.unlock();//手动解锁
package com.qf.test02_type03; public class Test01 { public static void main(String[] args) { Task task = new Task(); Thread t1 = new Thread(task, "001"); Thread t2 = new Thread(task, "002"); Thread t3 = new Thread(task, "003"); t1.start(); t2.start(); t3.start(); } } //任务类 package com.qf.test02_type03; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Task implements Runnable{ private int ticket = 1000; //必须放在run外面,如果放在run里面的话,三个子线程会调用三次run方法,会new出来三个锁对象,但是不用加static修饰,当一个任务类的时候没有区别,但是当有两个任务类时,就需要一个任务类一把锁,此时就不能用static修饰了。 private Lock lock = new ReentrantLock(); @Override public void run() { while(ticket > 0){ lock.lock(); if(ticket > 0){ System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + ticket + "张票"); ticket--; } if(ticket <= 0){ System.out.println("窗口" + Thread.currentThread().getName() + "票已售完"); } lock.unlock(); } } }
我们最常用的解决售票模型的方法是:
利用Runnable接口创建子线程且利用同步代码块加锁的方法解决售票模型
利用Runnable接口创建子线程且利用同步方法加锁的方法解决售票模型
注意:工作中要避免死锁,不要使用锁嵌套
package com.qf.die_lock; public class Test01 { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { synchronized (KuaiZi.a) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (KuaiZi.b) { System.out.println("哲学家1吃饭了"); } } } }, "哲学家1").start(); new Thread(new Runnable() { @Override public void run() { synchronized (KuaiZi.b) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (KuaiZi.a) { System.out.println("哲学家2吃饭了"); } } } }, "哲学家2").start(); } } class KuaiZi{ public static Object a = new Object(); public static Object b = new Object(); }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SODR0gRJ-1644159321426)(D:\2113java_workspace\Day21\Day21上午\线程池理解图.png)]
一个线程完成一项任务所需时间为:
- 创建线程时间 - Time1
- 线程中执行任务的时间 - Time2
- 销毁线程时间 - Time3
- 线程池技术正是关注如何缩短或调整创建线程和销毁线程的时间,从而提高程序的性能。项目中可以把Time1,Time3分别安排在项目的启动和结束的时间段或者一些空闲的时间段
- 线程池不仅调整Time1,Time3产生的时间段,而且它还显著减少了创建线程的数目,提高线程的复用率
- 系统启动一个新线程的成本是比较高的,因为涉及与操作系统的交互,在这种情形下,使用线程池可以很好地提高性能,尤其是当程序中需要创建大量生存期很短暂的线程时,优先考虑使用线程池
ExecutorService:线程池的接口
Executors:创建各种线程池的工具类
创建单个线程的线程池
ExecutorService pool = Executors.newSingleThreadExecutor();
创建指定线程的线程池
ExecutorService pool = Executors.newFixedThreadPool(3);
创建可缓存线程的线程池,自动回收60s闲置线程
ExecutorService pool = Executors.newCachedThreadPool();
package com.qf.pool01; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Test { public static void main(String[] args) { //创建单个线程的线程池 //ExecutorService pool = Executors.newSingleThreadExecutor(); //创建指定线程的线程池 //ExecutorService pool = Executors.newFixedThreadPool(3); //创建可缓存线程的线程池,自动回收60s闲置线程 //来一个任务就来一个线程,但是之所以没有100个线程是因为运行到后面的时候,前面的线程已经完成了任务 ExecutorService pool = Executors.newCachedThreadPool(); for (int i = 1; i <= 100; i++) { pool.execute(new Task(i));//提交任务,i为任务编号(方便理解),无返回值 //pool.submit(new Task(i));//提交任务,i为任务编号(方便理解),返回Future<?>对象,Future<?>表示线程返回结果 } pool.shutdown();//关闭线程池 } } class Task implements Runnable{ private int i; public Task(int i) { this.i = i; } @Override public void run() { System.out.println(Thread.currentThread().getName() + "-->" + i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
ExecutorService pool = Executors.newSingleThreadExecutor();
ExecutorService pool = Executors.newFixedThreadPool(3);
ExecutorService pool = Executors.newCachedThreadPool();
三种线程池底层都是ThreadPoolExecutor类的对象
-- 分析ThreadPoolExecutor类的构造方法源码-------------------------------- public ThreadPoolExecutor( int corePoolSize, ------------- 核心线程数量 int maximumPoolSize, ------------- 最大线程数量 long keepAliveTime, ------------- 闲置时间,作用于核心线程数与最大线程数之间的线程 TimeUnit unit, ------------- keepAliveTime的时间单位(可以是毫秒、秒....) BlockingQueue<Runnable> workQueue, -- 任务队列(任务太多线程处理不过来的时候,会放入任务队列) ThreadFactory threadFactory, -------- 线程工厂 RejectedExecutionHandler handler ---- 达到了线程界限和队列容量时的处理方案(拒绝策略) ) {} 执行步骤: 1.创建线程池后 2.任务提交后,查看是否有核心线程: 3.1 没有 -> 就创建核心线程 -> 执行任务 -> 执行完毕后又回到线程池中 3.2 有 -> 查看是否有闲置核心线程: 4.1 有 -> 执行任务 -> 执行完毕后又回到线程池 4.2 没有 -> 查看当前核心线程数是否核心线程数量: 5.1 否 -> 就创建核心线程 -> 执行任务 -> 执行完毕后又回到线程池中 5.2 是 -> 查看任务列表是否装载满: 6.1 没有 -> 就放入列表中,等待出现闲置线程 6.2 装满 -> 查看是否有普通线程(核心线程数到最大线程数量之间的线程) 7.1 没有 -> 就创建普通线程 -> 执行任务 -> 执行完毕后又回到线程池中 7.2 有 -> 查看是否有闲置普通线程 7.1.1 有 -> 执行任务 -> 执行完毕后又回到线程池中 7.1.2 没有 -> 查看现在所有线程数量是否为最大线程数: 8.1 是 -> 执行处理方案(默认处理抛出异常) 8.2 否 ->就创建普通线程-> 执行任务 -> 执行完毕后又回到线程池中 注: 1.为了更好的理解,在这里区分核心线程和普通线程,实际上区分的没这么清楚,都是线程 2.默认的处理方案就是抛出RejectedExecutionException 总结:核心线程满载 -> 任务队列 -> 普通线程 -- 分析单个线程的线程池的源码 -------------------------------- ExecutorService pool = Executors.newSingleThreadExecutor(); new ThreadPoolExecutor( 1, -- 核心线程数量 1, -- 最大线程数量 0L, -- 闲置时间 TimeUnit.MILLISECONDS, -- 时间单位(毫秒) new LinkedBlockingQueue<Runnable>() -- 无界任务队列,可以无限添加任务 ) -- 分析指定线程的线程池的源码 -------------------------------- ExecutorService pool = Executors.newFixedThreadPool(3); new ThreadPoolExecutor( nThreads, -- 核心线程数量 nThreads, -- 最大线程数量 0L, -- 闲置时间 TimeUnit.MILLISECONDS, -- 时间单位(毫秒) new LinkedBlockingQueue<Runnable>()-- 无界任务队列,可以无限添加任务 ) -- 创建可缓存线程的线程池 ----------------------------------- ExecutorService pool = Executors.newCachedThreadPool(); new ThreadPoolExecutor( 0, -- 核心线程数量 Integer.MAX_VALUE,-- 最大线程数量 60L, -- 闲置时间 TimeUnit.SECONDS, -- 时间单位(秒) new SynchronousQueue<Runnable>() -- 直接提交队列(同步队列):没有容量队列 工作中不用java提供的这三个线程池,因为会有风险 前两个是因为默认的无界任务队列存在风险,使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题。 第三个是因为来一个任务就创建一个普通线程,导致线程数量不可控。
队列名称 | 详解 |
---|---|
LinkedBlockingQueue无界任务队列 | 使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是你corePoolSize设置的数量,也就是说在这种情况下maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入,则直接进入队列等待,当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题 |
SynchronousQueue 同步任务队列 直接提交任务队列 | 使用直接提交任务队列,队列没有容量,每执行一个插入操作就会阻塞,需要再执行一个删除操作才会被唤醒,反之每一个删除操作也都要等待对应的插入操作。 任务队列为SynchronousQueue,创建的线程数大于maximumPoolSize时,直接执行了拒绝策略抛出异常。 使用SynchronousQueue队列,提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的线程,如果达到maximumPoolSize设置的最大值,则根据你设置的handler执行拒绝策略。因此这种方式你提交的任务不会被缓存起来,而是会被马上执行,在这种情况下,你需要对你程序的并发量有个准确的评估,才能设置合适的maximumPoolSize数量,否则很容易就会执行拒绝策略; |
ArrayBlockingQueue有界任务队列 | 使用有界任务队列,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。在这种情况下,线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在corePoolSize以下,反之当任务队列已满时,则会以maximumPoolSize为最大线程数上限。 |
PriorityBlockingQueue优先任务队列 | 使用优先任务队列,它其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行。 |
对优先队列的使用说明:
public class Test { public static void main(String[] args) { ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 200, 1000, TimeUnit.MILLISECONDS, new PriorityBlockingQueue<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); for (int i = 1; i <= 10; i++) { pool.execute(new Task(i)); } pool.shutdown(); } } class Task implements Runnable,Comparable<Task>{ private int priority; public Task(int priority) { this.priority = priority; } @Override public void run() { try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + " priority:" + this.priority); } catch (InterruptedException e) { e.printStackTrace(); } } //当前对象和其他对象做比较,当前优先级大就返回-1,优先级小就返回1,值越小优先级越高 @Override public int compareTo(Task o) { return (this.priority>o.priority)?-1:1; } } 总结:除了第一个任务直接创建线程执行外,其他的任务都被放入了优先任务队列,按优先级进行了重新排列执行,且线程池的线程数一直为corePoolSize,也就是只有一个。
ThreadPoolExecutor自带的拒绝策略有四种,都实现了RejectedExecutionHandler接口
比如:new ThreadPoolExecutor.AbortPolicy()
拒绝策略 | 解释 |
---|---|
AbortPolicy | 当有任务添加到线程池被拒绝时,会抛出RejectedExecutionException异常,线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。 |
DiscardPolicy | 当有任务添加到线程池被拒绝时,直接丢弃,其他啥都没有 |
CallerRunsPolicy | 当有任务添加到线程池被拒绝时,线程池会将被拒绝的任务添加到线程池正在运行的线程中去运行。 一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大 |
DiscardOledestPolicy | 当有任务添加到线程池被拒绝时,线程池会丢弃阻塞队列中末尾的任务(最老的任务–第一个添加的任务),然后将被拒绝的任务添加到末尾。 如果项目中有允许丢失任务的需求,可以使用 |
public class Test { public static void main(String[] args) { ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1), Executors.defaultThreadFactory(), new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println(r.toString()+"执行了拒绝策略"); } }); for (int i = 1; i <= 10; i++) { pool.execute(new Task()); } pool.shutdown(); } } class Task implements Runnable{ @Override public void run() { try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } }
在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面使线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。而线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式,这一方面是由于jdk中Executor框架虽然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活,而且有资源耗尽的风险(OOM - Out Of Memory )。
一般我们创建线程池时,为防止资源被耗尽,任务队列都会选择创建有界任务队列,但这种模式下如果出现任务队列已满且线程池创建的线程数达到你设置的最大线程数时,这时就需要你指定ThreadPoolExecutor的RejectedExecutionHandler参数即合理的拒绝策略,来处理线程池"超载"的情况
前提学习线程池中如何创建线程:
线程池中线程就是通过ThreadPoolExecutor中的ThreadFactory,线程工厂创建的。那么通过自定义ThreadFactory,可以按需要对线程池中创建的线程进行一些特殊的设置,如命名、优先级等
public class Test { public static void main(String[] args) { //创建单个线程的线程池 //ExecutorService pool = Executors.newSingleThreadExecutor(); //创建指定线程的线程池 //ExecutorService pool = Executors.newFixedThreadPool(3); //创建可缓存线程的线程池,自动回收60s闲置线程 //ExecutorService pool = Executors.newCachedThreadPool(); ThreadPoolExecutor pool = new ThreadPoolExecutor( 10,20, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1), new ThreadFactory() { int threadNum = 1; @Override public Thread newThread(Runnable r) { Thread t = new Thread(r,"线程"+threadNum++); return t; } }, new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println(r.toString()+"执行了拒绝策略"); } }); for (int i = 1; i <= 10; i++) { pool.execute(new Task()); //pool.submit(new Task(i));返回Future<?>对象,Future<?>表示线程返回结果 } pool.shutdown(); } } //class Task implements Callable<Integer>{//带有返回值的任务类 class Task implements Runnable{ @Override public void run() { try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } }
ThreadPoolExecutor扩展主要是围绕beforeExecute()、afterExecute()和terminated()三个方法的重写, 通过这三个方法我们可以监控每个任务的开始和结束时间,或者其他一些功能。
方法 解释 beforeExecute 线程池中任务运行前执行 afterExecute 线程池中任务运行完毕后执行 terminated 线程池退出后执行 下面我们可以通过代码实现一下
ThreadPoolExecutor pool = new ThreadPoolExecutor( 10, 10, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(10), new ThreadFactory() { int threadNum = 1; @Override public Thread newThread(Runnable r) { Thread t = new Thread(r,"线程"+threadNum++); return t; } }, new ThreadPoolExecutor.AbortPolicy() ){ @Override protected void beforeExecute(Thread t,Runnable r) { System.out.println("准备执行:"+ Thread.currentThread().getName()); } @Override protected void afterExecute(Runnable r,Throwable t) { System.out.println("执行完毕:"+ Thread.currentThread().getName()); } @Override protected void terminated() { System.out.println("线程池退出"); } }; for (int i = 1; i <= 10; i++) { pool.execute(new Task()); } pool.shutdown(); } } class Task implements Runnable{ @Override public void run() { try { Thread.sleep(1000); System.out.println("执行中:" + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } }
实际工作中使用 sysbench多线程性能测试工具
计算任务,一个包含了2万个整数的数组,分拆了多个线程来进行并行计算,最后汇总出计算的结果。
package com.qf.pool02; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Test01 { public static void main(String[] args) throws InterruptedException, ExecutionException { //1.创建数组 int[] is = new int[20000]; //2.初始化数据 for (int i = 0; i < is.length; i++) { is[i] = (i+1); } //3.创建线程池 ExecutorService pool = Executors.newFixedThreadPool(4); //4.提交任务 Future<Integer> f1 = pool.submit(new Task(is, 0, 5000)); Future<Integer> f2 = pool.submit(new Task(is, 5000, 10000)); Future<Integer> f3 = pool.submit(new Task(is, 10000, 15000)); Future<Integer> f4 = pool.submit(new Task(is, 15000, 20000)); //5.获取Future对象中的数据,并合并 int result = f1.get() + f2.get() + f3.get() + f4.get(); System.out.println(result); pool.shutdown(); } } //带有返回值的任务类 class Task implements Callable<Integer>{ private int[] is; private int startIndex; private int endIndex; public Task(int[] is, int startIndex, int endIndex) { this.is = is; this.startIndex = startIndex; this.endIndex = endIndex; } @Override public Integer call() throws Exception { int sum = 0; for (int i = startIndex; i < endIndex; i++) { sum += is[i]; } return sum; } }
含义:该类的对象可以表示一个文件或者目录路径
注意:该类的对象只能获取文件中的信息,并不能操作文件里的内容
需求1:通过程序,获取已知文件的以下信息:
package com.qf.file01; import java.io.File; import java.text.SimpleDateFormat; public class Test01 { public static void main(String[] args) { //创建File对象 File file = new File("C:\\Users\\hehanyu\\Desktop\\hhy.txt"); System.out.println("获取绝对路径:" + file.getAbsolutePath()); System.out.println("获取文件名:" + file.getName()); System.out.println("获取是否可读:" + file.canRead()); System.out.println("获取是否可写:" + file.canWrite()); System.out.println("获取是否隐藏:" + file.isHidden()); System.out.println("获取文件大小(字节):" + file.length()); SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); String format = sdf.format(file.lastModified()); System.out.println("获取文件最后修改时间:" + format); } }
绝对路径 和 相对路径
绝对路径:是从盘符开始的路径,完整描述文件位置的路径
相对路径:是从当前路径开始的路径
package com.qf.file01; import java.io.File; public class Test02 { public static void main(String[] args) { //创建File对象 File file = new File("hhy.txt"); //获取相对路径:相对于当前项目中的路径 System.out.println(file.getPath());//hhy.txt //获取绝对路径:盘符+具体的文件路径 System.out.println(file.getAbsolutePath());//C:\2113workspace\Day21\hhy.txt } }
需求2:通过程序,判断指定路径的文件是否存在,如果不存在,则创建该文件
1)目录已存在的情况(文件夹存在,但是不知道文件夹是否有文件)
package com.qf.file02; import java.io.File; import java.io.IOException; public class Test01 { public static void main(String[] args) throws IOException { //创建file对象 File file = new File("file01\\hhy.txt"); //判断文件是否存在 if(!file.exists()){ //创建文件 file.createNewFile(); } } }
需求2:通过程序,判断指定路径的文件是否存在,如果不存在,则创建该文件
2)有一个层级的目录不存在的情况(不知道文件夹是否存在,也不知道文件夹里面的文件是否存在)
package com.qf.file02; import java.io.File; import java.io.IOException; public class Test02 { public static void main(String[] args) throws IOException { //创建file对象 File file = new File("file01\\hhy.txt"); //获取父路径的file对象 File parentFile = file.getParentFile(); //判断是否有文件夹 if(!parentFile.exists()){ //创建一层文件夹 parentFile.mkdir(); } //判断文件是否存在 if(!file.exists()){ //创建文件 file.createNewFile(); } } }
需求2:通过程序,判断指定路径的文件是否存在,如果不存在,则创建该文件
3)有多个层级的目录不存在的情况
package com.qf.file02; import java.io.File; import java.io.IOException; public class Test03 { public static void main(String[] args) throws IOException { //创建file对象 File file = new File("file01\\file02\\file03\\hhy.txt"); //获取父路径的file对象 File parentFile = file.getParentFile(); //判断是否有文件夹 if(!parentFile.exists()){ //创建多层文件夹 parentFile.mkdirs(); } //判断文件是否存在 if(!file.exists()){ //创建文件 file.createNewFile(); } } }
需求3:输出指定目录下的所有文件信息
package com.qf.file03; import java.io.File; public class Test01 { public static void main(String[] args) { //创建file对象 File file = new File("C:\\视频资料"); //遍历文件夹中所有的文件名,并放入数组中 String[] list = file.list(); for (String name : list) { System.out.println(name); } //遍历文件夹中所有的文件对象,并放入数组中 File[] listFiles = file.listFiles(); for (File f : listFiles) { System.out.println(f.getName() + " -- " + f.canRead() + " -- " + f.canWrite()); } } }
需求3:输出指定目录下的所有文件信息
1)要求只输出文件后缀名为txt的文件
package com.qf.file03; import java.io.File; public class Test02 { public static void main(String[] args) { //创建file对象 File file = new File("C:\\视频资料"); //遍历文件夹中所有的文件对象,并放入数组中 File[] listFiles = file.listFiles(); for (File f : listFiles) { String name = f.getName(); //f.isFile() - 判断file对象是否是文件 if(f.isFile() && name.endsWith(".txt")){ System.out.println(name); } } } }
需求3:输出指定目录下的所有文件信息
2)根据API的过滤器来完成该功能,要求只输出文件后缀名为txt的文件(过滤掉不想要的,留下想要的)
package com.qf.file03; import java.io.File; import java.io.FilenameFilter; public class Test03 { public static void main(String[] args) { //创建file对象 File file = new File("C:\\视频资料"); //过滤器 File[] listFiles = file.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { //dir - C:\\视频资料 //name - xxx.txt File f = new File(dir, name);//C:\\视频资料\\xxx.txt if(f.isFile() && name.endsWith(".txt")){ return true; } return false; } }); for (File f : listFiles) { System.out.println(f.getName()); } } }
需求3:输出指定目录下的所有文件信息
3)列出当前目录及子目录中符合该条件的文件信息(递归)
package com.qf.file03; import java.io.File; import java.io.FilenameFilter; public class Test04 { public static void main(String[] args) { //创建file对象 File file = new File("C:\\视频资料"); method(file, ".txt"); } //File file--文件对象 String str--文件后缀名 public static void method(File file,String str){ //遍历文件夹中所有的文件对象,并放入数组中 File[] listFiles = file.listFiles(); for (File f : listFiles) { if(f.isDirectory()){//是否文件夹 method(f, str); }else if(f.isFile()){//是否文件 String name = f.getName(); if(name.endsWith(str)){ System.out.println(name + " -- " + f.getAbsolutePath()); } } } } }
通过程序把数据写入到文件里面叫out输出流
利用程序把文件里面的数据读取到程序当中叫in输入流
流的分类:节点流(基础流)和处理流,处理流中往往包含了节点流,让流的功能更加强大
学习路线:按照流的发展历程
注意:流与流之间的继承关系
字节流:
应用场景:操作二进制文件(音频、视频)
abstract class InputStream – 字节输入流的基类(抽象类)
abstract class OutputStream - 字节输出流的基类(抽象类)
class FileInputStream extends InputStream – 字节输入流
class FileOutputStream extends OutputStream- 字节输出流
class FilterInputStream extends InputStream – 过滤器字节输入流(不用管)
class FilterOutputStream extends OutputStream- 过滤器字节输出流(不用管)
class BufferedInputStream extends FilterInputStream – 带缓冲区的字节输入流
class BufferedOutputStream extends FilterOutputStream- 带缓冲区的字节输出流
注意:
1.缓冲区大小为8192字节
2.因为有了缓冲区,导致程序和硬盘的交互次数减少,所以效率得到提升
字符流:
应用场景:操作文本文件
注意:字符流底层其实就是字节流+编译器(通过编码格式去获取字节)
abstract class Reader 字符输入流的基类(抽象类)
abstract class Writer 字符输出流的基类(抽象类)
class InputStreamReader extends Reader – 字符输入转换流(可以将字节输入流转换为字符输入流)
class OutputStreamWriter extends Writer - 字符输出转换流(可以将字节输出流转换为字符输出流)
注意:转换流是将字节流转换为字符流,Java提供的一些类的方法给我们返回的是字节流,但是我们利用这个流去操作文本文件,就需要转换一下
class FileReader extends InputStreamReader - 字符输入流
class FileWriter extends OutputStreamWriter- 字符输出流
class BufferedReader extends Reader - 带缓冲区的字符输入流
class BufferedWriter extends Writer - 带缓冲区的字符输出流
注意:
1.缓冲区大小为8192字符
2.因为有了缓冲区,导致程序和硬盘的交互次数减少,所以效率得到提升
对象流:
ObjectInputStream – 对象输入流
ObjectOutputStream - 对象输出流
特点:可以将对象存入文件,并且可以从文件中读取出对象来
注意:
1.我们的目的是将对象存入文件,并且从文件中再取出对象时还能正常使用(对象写入到文件后,我们打开文件是看不懂的,能拿出来使用即可)
2.某个类的对象要想写入到内存中,该类必须使用Serializable(序列化)接口
3.Serializable(序列化)接口中没有任何内容,这种接口我们又叫做标记型接口
4.transient修饰的属性不会随着对象写入到文件而写入
序列化/钝化:将对象从内存写入到文件
反序列化/活化:将文件中的数据读取到内存
内存流:
ByteArrayInputStream – 内存输入流
ByteArrayOutputStream - 内存输出流
特点:程序 与 内存之间写入、读取数据
注意:内存流是程序与内存之间的通道,关闭不掉
应用场景:当一些不重要并且需要频繁使用的数据就可以存到内存流中
打印流:
PrintStream – 字节打印流
PrintWriter – 字符打印流
注意:打印流的方向都是程序到文件,只有一个方向
PrintStream vs PrintWriter
PrintStream:可以将字节流转换为打印流
PrintWriter:可以将字节流、字符流转换为打印流
扩展知识点:重定向
System.in:系统标准的输入流(方向:控制台->程序)
System.setIn(new FileInputStream(“hhy.txt”)); 重定向:重新定义系统标准输入流的方向(方向:文件->程序)
System.out:系统标准的输出流(方向:程序->控制台)
System.setOut(new PrintStream(new FileOutputStream(“hhy.txt”, true))); 重定向:重新定义系统标准输出流的方向(方向:程序->文件)
System.err:系统标准的错误输出流(方向:程序->控制台)
System.setErr(new PrintStream(new FileOutputStream(“hhy.txt”, true))); 重定向:重新定义系统标准错误输出流的方向(方向:程序->文件)
随机访问流
含义:该流认为文件是一个大型的byte数组,该流有个指针,默认在0的位置,指针指向哪,就可以从哪开始读取和写入
注意:
1.该流的方向是双向的(程序<->文件)
2.mode: r-读 rw-读写
自学内容:BIO -> NIO -> AIO
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iOlH3Pyi-1644159321427)(D:\2113java_workspace\Day22\Day22上午\字节流继承图.png)]
1.文件存在的情况下
2.文件不存在的情况下
经验:所有的输出流,当文件不存在的情况下,自动创建该文件
3.在末尾追加内容
经验:所有输出流中的基础流都有在末尾追加内容的构造方法
package com.qf.io01; import java.io.FileOutputStream; import java.io.IOException; public class Test02 { public static void main(String[] args) throws IOException { //1.创建流对象 FileOutputStream fos = new FileOutputStream("hhy.txt",true);//在末尾追加内容 //2.写入数据 fos.write(97);//写入ASCII//a fos.write("abcdefg".getBytes());//写入byte数组,getBytes()将字符串转化为Byte类型数组 fos.write("abcdefg".getBytes(),2,3);//写入byte数组,偏移量,写入长度//cde //3.关闭资源 fos.close(); } }
上面用到的是抛异常的方式去使用IO流
下面用到的是处理异常的方式去使用IO流
package com.qf.io01; import java.io.FileOutputStream; import java.io.IOException; public class Test03 { public static void main(String[] args) { FileOutputStream fos = null;//在外面声明fos,在try-catch中使用 try { //1.创建流对象 //FileOutputStream fos = new FileOutputStream("hhy.txt",true);会报错,因为此时fos在try里面定义的,只能在try里面使用,没办法在catch里面使用。 fos = new FileOutputStream("hhy.txt",true);//在末尾追加内容 //2.写入数据 fos.write(97);//写入ASCII fos.write("abcdefg".getBytes());//写入byte数组 fos.write("abcdefg".getBytes(),2,3);//写入byte数组,偏移量,写入长度 } catch (IOException e) { System.out.println("处理IO异常"); } finally { /* fos.close();必须写在finally里面,假如写到try里面的话,try中的1.2两步出现异常的话,catch就会直接捕获并处理异常,fos.close()这一步就不会执行,流就无法关闭。 另外,当try中的"fos = new FileOutputStream("hhy.txt",true)"这一步就出现异常时,catch直接捕获并处理了异常,此时"fos = new FileOutputStream("hhy.txt",true)"这一步没有执行,对象没有创建出来,FileOutputStream fos = null,fos.close()--这两步结合起来看就相当于null.close(),就会报空指针异常。 */ //3.关闭资源 if(fos != null){ try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
package com.qf.io01; import java.io.FileInputStream; import java.io.IOException; public class Test04 { public static void main(String[] args) throws IOException { //1.创建流对象 FileInputStream fis = new FileInputStream("hhy.txt"); //2.读取文件 //read():只读取一个字节(字符的时候读取出来的是字符对应的ASCII),读取到文件末尾返回-1 int read = fis.read(); System.out.println((char)read);//(char)read-将读取出来的字符对应的ASCII转化为字符 read = fis.read(); System.out.println((char)read); read = fis.read(); System.out.println((char)read); read = fis.read(); System.out.println(read); //3.关闭资源 fis.close(); } }
package com.qf.io01; import java.io.FileInputStream; import java.io.IOException; public class Test05 { public static void main(String[] args) throws IOException { //1.创建流对象 FileInputStream fis = new FileInputStream("hhy.txt"); //2.读取文件 - 不知道文件里多少个字节的时候,就一个一个字节的循环读取 int read; while((read = fis.read()) != -1){ System.out.println((char)read); } //3.关闭资源 fis.close(); } }
package com.qf.io01; import java.io.FileInputStream; import java.io.IOException; public class Test06 { public static void main(String[] args) throws IOException { //1.创建流对象 FileInputStream fis = new FileInputStream("hhy.txt"); //2.读取文件 - 不知道文件里多少个字节的时候,可以1024个1024个字节的循环读取 //fis.read(bs):每次读取自定义个字节的数据存入bs数组中,len-并返回数组中最后一个数据在数组中的有效下标+1(即个数--abc--len=3),文件读取完之后再读取的话会在控制台返回-1,len也变成-1. byte[] bs = new byte[1024]; int len; while((len = fis.read(bs)) != -1){ System.out.println(new String(bs, 0, len));//字符的时候读取出来的是字符对应的ASCII,将读取出来的字符对应的ASCII转化为字符,以字符串的形式输出) } //3.关闭资源 fis.close(); } }
经验:所有的输入流,文件不存在的情况都会报错 - FileNotFoundException
上面用到的是抛异常的方式去使用IO流
下面用到的是处理异常的方式去使用IO流
package com.qf.io01; import java.io.FileInputStream; import java.io.IOException; public class Test07 { public static void main(String[] args) { FileInputStream fis = null; try { //1.创建流对象 fis = new FileInputStream("hhy.txt"); //2.读取文件 - 1024个1024个字节的循环读取 byte[] bs = new byte[1024]; int len; while((len = fis.read(bs)) != -1){ System.out.println(new String(bs, 0, len)); } } catch (IOException e) { System.out.println("处理IO异常"); } finally { //3.关闭资源 if(fis != null){ try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
思路:读取源文件,写入目标文件
package com.qf.io01; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class Copy01 { public static void main(String[] args) throws IOException { //1.创建流对象 FileInputStream fis = new FileInputStream("IO流.txt"); FileOutputStream fos = new FileOutputStream("copy.txt"); //2.拷贝--一个字节一个字节的读取与拷贝 int read; while((read = fis.read()) != -1){ fos.write(read); } //3.关闭资源 fis.close(); fos.close(); } }
package com.qf.io01; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class Copy02 { public static void main(String[] args) throws IOException { //1.创建流对象 FileInputStream fis = new FileInputStream("麻生希.mp4"); FileOutputStream fos = new FileOutputStream("copy.mp4"); //2.拷贝--1024个1024个字节的读取与拷贝 byte[] bs = new byte[1024]; int len; while((len = fis.read(bs)) != -1){ fos.write(bs, 0, len); } //3.关闭资源 fis.close(); fos.close(); } }
上面用到的是抛异常的方式去使用IO流
下面用到的是处理异常的方式去使用IO流
package com.qf.io01; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class Copy03 { public static void main(String[] args) { FileInputStream fis = null; FileOutputStream fos = null; try { //1.创建流对象 fis = new FileInputStream("麻生希.mp4"); fos = new FileOutputStream("copy.mp4"); //2.拷贝 byte[] bs = new byte[1024]; int len; while((len = fis.read(bs)) != -1){ fos.write(bs, 0, len); } } catch (IOException e) { e.printStackTrace(); } finally { //3.关闭资源 if(fos != null){ try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } if(fis != null){ try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
package com.qf.io01; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class Copy04 { public static void main(String[] args) { //1.创建流对象-在try里面创建流对象,在try-catch里面用完之后会自动关闭掉 try(FileInputStream fis = new FileInputStream("麻生希.mp4"); FileOutputStream fos = new FileOutputStream("copy.mp4")) { //2.拷贝 byte[] bs = new byte[1024]; int len; while((len = fis.read(bs)) != -1){ fos.write(bs, 0, len); } } catch (IOException e) { e.printStackTrace(); } } }
1.缓冲区是大小为8192字节的数组,写入的数据先存到缓冲区,最后再从缓冲区刷到文件里面
2.没有缓冲区的时候写入一次数据就交互一次,因为有了缓冲区,写入的数据先存到缓冲区(缓冲区是8192字节的),最后再从缓冲区刷到文件里面,所以程序和硬盘的交互次数减少,效率会得到提升
package com.qf.io01; import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; public class Test01 { public static void main(String[] args) throws IOException { //1.创建流对象 //默认缓存数组:8192个字节 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("hhy.txt")); //自定义缓存数组:2048个字节 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("hhy.txt"),2048); //在文件末尾追加 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("hhy.txt",true)); //2.写入数据 bos.write("aaaa".getBytes()); bos.write("bbbb".getBytes()); bos.write("cccc".getBytes()); bos.write("dddd".getBytes()); bos.write("eeee".getBytes()); bos.flush();//将缓存数组中的数据刷到文件中 //3.关闭资源 bos.close(); } }
package com.qf.io01; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; public class Test02 { public static void main(String[] args) throws IOException { //1.创建流对象 //默认缓存数组:8192个字节 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("hhy.txt")); //自定义缓存数组:2048个字节 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("hhy.txt"),2048); //2.读取数据,1024指的是每次读取文件中的多少数据,存到缓存区,再到程序 byte[] bs = new byte[1024]; int len; while((len = bis.read(bs)) != -1){ System.out.println(new String(bs, 0, len)); } //3.关闭资源 bis.close(); } }
package com.qf.io01; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class Copy { public static void main(String[] args){ BufferedInputStream bis = null; BufferedOutputStream bos = null; try { bis = new BufferedInputStream(new FileInputStream("麻生希.mp4")); bos = new BufferedOutputStream(new FileOutputStream("copy.mp4")); byte[] bs = new byte[1024]; int len; while((len = bis.read(bs)) != -1){ bos.write(bs, 0, len); } } catch (IOException e) { e.printStackTrace(); } finally { if(bis != null){ try { bis.close(); } catch (IOException e) { e.printStackTrace(); } } if(bos != null){ try { bos.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KvfYQARv-1644159321428)(D:\2113java_workspace\Day22\Day22上午\字符流继承图.png)]
package com.qf.io02; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; public class Test01 { public static void main(String[] args) throws IOException { //1.创建流对象 //获取当前系统默认的编码格式 OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("hhy.txt")); //设置程序以哪种编码格式将数据写入文件 -- 推荐使用 //eclipse以GBK编码格式编译文件中的数据,编译完成后打开文件 //程序以GBK编码格式将数据写入文件,eclipse以GBK编码格式编译文件中的数据,编译完成后打开文件,不然会乱码 OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("hhy.txt"), "GBK"); OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("hhy.txt"), Charset.forName("GBK")); //在末尾追加内容 OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("hhy.txt",true), "GBK"); //2.写入数据 osw.write(97);//写入ASCII osw.write(new char[]{'a','b','c','d','e','f'});//写入字符数组 osw.write(new char[]{'a','b','c','d','e','f'}, 2, 3);//写入字符数组,偏移量,写入数据的长度 osw.write("钟燕小可爱,皇冠给你带");//写入字符串 osw.write("钟燕小可爱,皇冠给你带",2,3);//写入字符串,偏移量,写入数据的长度 //3.关闭资源 osw.close(); } }
package com.qf.io02; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; public class Test02 { public static void main(String[] args) throws IOException { //1.创建流对象 InputStreamReader isr = new InputStreamReader(new FileInputStream("hhy.txt")); //2.读取数据 - 一个字符一个字符的读取 int read; while((read = isr.read()) != -1){ System.out.println((char)read); } //3.关闭资源 isr.close(); } }
package com.qf.io02; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; public class Test03 { public static void main(String[] args) throws IOException { //1.创建流对象 //获取当前系统默认的编码格式 InputStreamReader isr = new InputStreamReader(new FileInputStream("hhy.txt")); //设置程序以哪种编码格式读取文件中的数据 -- 推荐使用 //eclipse以GBK编码格式编译程序读取到的文件中的数据,编译完成后显示在eclipse控制台 //程序以GBK编码格式读取文件中的数据,eeclipse以GBK编码格式编译程序读取到的文件中的数据,编译完成后显示在eclipse控制台,不然会乱码 InputStreamReader isr = new InputStreamReader(new FileInputStream("hhy.txt"),"GBK"); //2.读取数据 - 1024个字符1024个字符的读取 char[] cs = new char[1024]; int len; while((len = isr.read(cs)) != -1){ System.out.println(new String(cs, 0, len)); } //3.关闭资源 isr.close(); } }
package com.qf.io02; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; public class Copy { public static void main(String[] args) throws IOException{ InputStreamReader isr = new InputStreamReader(new FileInputStream("小说.txt"), "GBK"); OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("copy.txt"), "GBK"); char[] cs = new char[1024]; int len; while((len = isr.read(cs)) != -1){ osw.write(cs, 0, len); } isr.close(); osw.close(); } }
package com.qf.io03; import java.io.FileWriter; import java.io.IOException; public class Test01 { public static void main(String[] args) throws IOException { //1.创建流对象 FileWriter fw = new FileWriter("hhy.txt"); //在文件末尾追加 FileWriter fw = new FileWriter("hhy.txt", true); //2.写入数据 fw.write(97);//写入ASCII fw.write(new char[]{'a','b','c','d','e','f'});//写入字符数组 fw.write(new char[]{'a','b','c','d','e','f'}, 2, 3);//写入字符数组,偏移量,写入数据的长度 fw.write("钟燕小可爱,皇冠给你带");//写入字符串 fw.write("钟燕小可爱,皇冠给你带",2,3);//写入字符串,偏移量,写入数据的长度 //3.关闭资源 fw.close(); } }
package com.qf.io03; import java.io.FileReader; import java.io.IOException; public class Test02 { public static void main(String[] args) throws IOException { //1.创建流对象 FileReader fr = new FileReader("hhy.txt"); //2.读取数据 - 一个字符一个字符的读取 int read; while((read = fr.read()) != -1){ System.out.println((char)read); } //3.关闭资源 fr.close(); } }
package com.qf.io03; import java.io.FileReader; import java.io.IOException; public class Test03 { public static void main(String[] args) throws IOException { //1.创建流对象 FileReader fr = new FileReader("hhy.txt"); //2.读取数据 - 1024个字符1024个字符的读取 char[] cs = new char[1024]; int len; while((len = fr.read(cs)) != -1){ System.out.println(new String(cs, 0, len)); } //3.关闭资源 fr.close(); } }
package com.qf.io03; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; public class Copy { public static void main(String[] args) throws IOException{ FileReader fr = new FileReader("小说.txt"); FileWriter fw = new FileWriter("copy.txt"); char[] cs = new char[1024]; int len; while((len = fr.read(cs)) != -1){ fw.write(cs, 0, len); } fr.close(); fw.close(); } }
package com.qf.io04; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; public class Test01 { public static void main(String[] args) throws IOException { //1.创建流对象 //字节流 --> 转换流 --> 带缓冲区的字符流 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("hhy.txt"),"GBK")); //字符流 --> 带缓冲区的字符流 BufferedWriter bw = new BufferedWriter(new FileWriter("hhy.txt")); //2.写入数据 bw.write(97);//写入ASCII bw.write(new char[]{'a','b','c','d','e','f'});//写入字符数组 bw.write(new char[]{'a','b','c','d','e','f'}, 2, 3);//写入字符数组,偏移量,写入数据的长度 bw.write("钟燕小可爱,皇冠给你带");//写入字符串 bw.write("钟燕小可爱,皇冠给你带",2,3);//写入字符串,偏移量,写入数据的长度 //3.关闭资源 bw.close(); } }
package com.qf.io04; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class Test02 { public static void main(String[] args) throws IOException { //1.创建流对象 //字节流 --> 转换流 --> 带缓冲区的字符流 BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("hhy.txt"),"GBK")); //字符流 --> 带缓冲区的字符流 BufferedReader br = new BufferedReader(new FileReader("hhy.txt")); //2.读取数据 - 1024个字符1024个字符的读取 char[] cs = new char[1024]; int len; while((len = br.read(cs)) != -1){ System.out.println(new String(cs, 0, len)); } //3.关闭资源 br.close(); } }
每次读取1024个字符,再写入1024个字符
package com.qf.io04; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; public class Copy01 { public static void main(String[] args) throws IOException{ BufferedReader br = new BufferedReader(new FileReader("小说.txt")); BufferedWriter bw = new BufferedWriter(new FileWriter("copy.txt")); char[] cs = new char[1024]; int len; while((len = br.read(cs)) != -1){ bw.write(cs, 0, len); } br.close(); bw.close(); } }
每次读取一行,再写入一行
package com.qf.io04; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; public class Copy02 { public static void main(String[] args) throws IOException{ BufferedReader br = new BufferedReader(new FileReader("小说.txt")); BufferedWriter bw = new BufferedWriter(new FileWriter("copy.txt")); String readLine; while((readLine = br.readLine()) != null){ bw.write(readLine); bw.newLine();//换行 } br.close(); bw.close(); } }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0TzRbaoq-1644159321429)(D:\2113java_workspace\Day22\Day22上午\字节流继承图.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F5524Tpv-1644159321430)(D:\2113java_workspace\Day22\Day22上午\字符流继承图.png)]
.mp3 .mp4 .avi .exe等 音频视频图片二进制文件 用字节流
文本文件 用字符流
字节流可以写字节数组
字符流可以写字符数组(里面的元素是一个个的字符)(也可以写字符串,但字符串底层还是字符数组)
把数据写入到文件里面 用out输出流
把文件里面的数据读取到程序当中 用in输入流
字节流–输出流–考虑效率–用带缓冲区的字节输出流–BufferedOutputStream
字节流–输出流–不考虑效率–用字节输出流–FileOutputStream
字符流–输出流–考虑效率–用带缓冲区的字符输出流–BufferedWriter
字符流–输出流–不考虑效率–用字符输出流–FileWriter
字节流–输入流–考虑效率–用带缓冲区的字节输入流–BufferedInputStream
字节流–输入流–不考虑效率–用字节输入流–FileInputStream
字符流–输入流–考虑效率–用带缓冲区的字符输入流–BufferedReader
字符流–输入流–不考虑效率–用字符输入流–FileReader
转换流:OutputStreamWriter----InputStreamReader
转换流是将字节流转换为字符流,Java提供的一些类的方法给我们返回的是字节流,但是我们利用这个流去操作文本文件,就需要用转换流转换一下
ObjectInputStream – 对象输入流
ObjectOutputStream - 对象输出流
特点:可以将对象存入文件,并且可以从文件中读取出对象来
注意:
1.我们的目的是将对象存入文件,并且从文件中再取出对象时还能正常使用(对象写入到文件后,我们打开文件是看不懂的,能拿出来使用即可)
2.某个类的对象要想写入到文件中,该类必须实现Serializable(序列化)接口
3.Serializable(序列化)接口中没有任何内容,这种接口我们又叫做标记型接口
4.transient、static修饰的属性不会随着对象写入到文件而写入,transient修饰的属性,利用对象输入流读取对象的时候,会显示为null
序列化/钝化:将对象从内存写入到文件
反序列化/活化:将文件中的对象读取到内存
package com.qf.io05; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.util.Date; public class Test01 { public static void main(String[] args) throws IOException { //1.创建流对象 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hhy.txt")); //2.写入数据 oos.writeInt(100); oos.writeDouble(123.123); oos.writeBoolean(true); oos.writeChar('a'); oos.writeObject(new Date()); //3.关闭资源 oos.close(); } }
package com.qf.io05; import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; public class Test02 { public static void main(String[] args) throws IOException, ClassNotFoundException { //1.创建流对象 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hhy.txt")); //2.读取数据(顺序问题:读取数据的顺序必须和写入数据的顺序一致) System.out.println(ois.readInt()); System.out.println(ois.readDouble()); System.out.println(ois.readBoolean()); System.out.println(ois.readChar()); System.out.println(ois.readObject()); //3.关闭资源 ois.close(); } }
User类:
package com.qf.io05; import java.io.Serializable; public class User implements Serializable{ /*把某个类的对象写入到文件中之后,再更改类里面的属性和方法,读取文件中的对象时会报”序列化版本号不一致“的错。 因为,类在最开始被创建出来之后会有一个最开始的序列号1,创建类的对象,类的对象也会有序列号1,把序列号1的对象写入到文件中之后,此时如果再更改类里面的属性和方法的话,对象的序列号1会发生变化变为序列号2,对象输入流读取文件中的对象时,发现对象的序列号和写入的时候不一致,便会报错。 在创建类的时候的最开始加上private static final long serialVersionUID = 1L;即可 此时系统就把序列号固定为了一个不可变化的常量,此时,如果再更改类里面的属性和方法的话,类和对象的序列号都不会再发生变化。 */ //序列化版本号(不加,该类会报警告) private static final long serialVersionUID = 2593200594764899280L; private String username; //transient修饰的属性不会随着对象写入到文件而写入 //利用对象输入流读取对象的时候,会显示为null private transient String password; private String name; private String phone; private String role; private double hp; private double mp; public User() { } public User(String username, String password, String role, double hp, double mp) { this.username = username; this.password = password; this.role = role; this.hp = hp; this.mp = mp; } public User(String username, String password, String name, String role, double hp, double mp) { this.username = username; this.password = password; this.name = name; this.role = role; this.hp = hp; this.mp = mp; } public User(String username, String password, String name, String phone, String role, double hp, double mp) { this.username = username; this.password = password; this.name = name; this.phone = phone; this.role = role; this.hp = hp; this.mp = mp; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getRole() { return role; } public void setRole(String role) { this.role = role; } public double getHp() { return hp; } public void setHp(double hp) { this.hp = hp; } public double getMp() { return mp; } public void setMp(double mp) { this.mp = mp; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } @Override public String toString() { return "User [username=" + username + ", password=" + password + ", name=" + name + ", phone=" + phone + ", role=" + role + ", hp=" + hp + ", mp=" + mp + "]"; } }
package com.qf.io05; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; public class Test03 { public static void main(String[] args) throws IOException { //1.创建流对象 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hhy.txt")); //2.写入数据 oos.writeObject(new User("1445584980", "123123", "彭于晏", "亚索", 1000, 500)); oos.writeObject(new User("1234567890", "111222", "燕","阿狸", 200, 1000)); oos.writeObject(new User("0987654321", "123321", "伟","盖伦", 8000, 0)); oos.writeObject(null); //3.关闭资源 oos.close(); } }
把某个类的对象写入到文件中之后,再更改类里面的属性和方法,读取文件中的对象时会报”序列化版本号不一致“的错。
因为,类在最开始被创建出来之后会有一个最开始的序列号1,创建类的对象,类的对象也会有序列号1,把序列号1的对象写入到文件中之后,此时如果再更改类里面的属性和方法的话,对象的序列号1会发生变化变为序列号2,对象输入流读取文件中的对象时,发现对象的序列号和写入的时候不一致,便会报错。
在创建类的时候的最开始加上private static final long serialVersionUID = 1L;即可
此时系统就把序列号固定为了一个不可变化的常量,此时,如果再更改类里面的属性和方法的话,类和对象的序列号都不会再发生变化。
package com.qf.io05; import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; public class Test04 { public static void main(String[] args) throws IOException, ClassNotFoundException { //1.创建流对象 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hhy.txt")); //2.读取数据(顺序问题:读取数据的顺序必须和写入数据的顺序一致) //知道文件中的自定义对象是User类的对象: User user; while((user = (User) ois.readObject()) != null){ System.out.println(user); }//读取到末尾的时候,默认返回的是报错,不是null /*当不知道文件中的自定义对象是什么类的对象时: Object obj; while((obj = ois.readObject()) != null){ System.out.println(obj); } */ //3.关闭资源 ois.close(); } }
ByteArrayInputStream – 内存输入流
ByteArrayOutputStream - 内存输出流
特点:程序 与 内存之间写入、读取数据
注意:内存流是程序与内存之间的通道(其他流是程序与文件即硬盘的通道),关闭不掉
应用场景:当一些不重要并且需要频繁使用的数据就可以存到内存流中
package com.qf.io06; import java.io.ByteArrayOutputStream; import java.io.IOException; public class Test01 { public static void main(String[] args) throws IOException { //1.创建流对象 ByteArrayOutputStream baos = new ByteArrayOutputStream(); //2.写入数据(将数据存储到对象中即cpu的堆内存中) baos.write("123abc".getBytes()); //获取对象中(即堆内存中)的数据 byte[] byteArray = baos.toByteArray(); System.out.println(new String(byteArray));//123abc String string = baos.toString(); System.out.println(string);//123abc } }
package com.qf.io06; import java.io.ByteArrayInputStream; import java.io.IOException; public class Test02 { public static void main(String[] args) throws IOException { //1.创建流对象,并将数据存入到对象中 ByteArrayInputStream bais = new ByteArrayInputStream("123abc".getBytes()); //2.读取数据(读取对象中数据) byte[] bs = new byte[1024]; int len; while((len = bais.read(bs)) != -1){ System.out.println(new String(bs, 0, len)); } } }
PrintStream – 字节打印流
PrintWriter – 字符打印流
注意:打印流的方向都是程序到文件,只有一个方向
PrintStream vs PrintWriter
PrintStream:可以将字节流转换为打印流
PrintWriter:可以将字节流、字符流转换为打印流
扩展知识点:重定向
System.in:系统标准的输入流(方向:控制台->程序)
System.setIn(new FileInputStream(“hhy.txt”)); 重定向:重新定义系统标准输入流的方向(方向:文件->程序)
System.out:系统标准的输出流(方向:程序->控制台)
System.setOut(new PrintStream(new FileOutputStream(“hhy.txt”, true))); 重定向:重新定义系统标准输出流的方向(方向:程序->文件)
System.err:系统标准的错误输出流(方向:程序->控制台)
System.setErr(new PrintStream(new FileOutputStream(“hhy.txt”, true))); 重定向:重新定义系统标准错误输出流的方向(方向:程序->文件)
package com.qf.io07; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.PrintStream; public class Test01 { public static void main(String[] args) throws FileNotFoundException { //1.创建流对象 //PrintStream ps = new PrintStream("hhy.txt"); //在文件末尾追加内容(可以将字节流转换为打印流) PrintStream ps = new PrintStream(new FileOutputStream("hhy.txt", true)); //2.写入数据 ps.println("abc123"); //3.关闭资源 ps.close(); } }
package com.qf.io07; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; public class Test02 { public static void main(String[] args) throws IOException { //1.创建流对象 //PrintWriter pw = new PrintWriter("hhy.txt"); //在文件末尾追加内容(可以将字节流转换为打印流) //PrintWriter pw = new PrintWriter(new FileOutputStream("hhy.txt", true)); //在文件末尾追加内容(可以将字符流转换为打印流) PrintWriter pw = new PrintWriter(new FileWriter("hhy.txt", true)); //2.写入数据 pw.println("abc123"); //3.关闭资源 pw.close(); } }
重定向:重新定义系统标准输入流的方向(方向:文件->程序)
System.in:系统标准的输入流(方向:控制台->程序)
package com.qf.io07; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.util.Scanner; public class Test03 { public static void main(String[] args) throws FileNotFoundException { //重定向:重新定义系统标准输入流的方向(方向:文件->程序) System.setIn(new FileInputStream("hhy.txt")); InputStream in = System.in; Scanner scan = new Scanner(in); String str = scan.next(); System.out.println(str); scan.close(); } }
重定向:重新定义系统标准输入流的方向(方向:文件->程序)
System.out:系统标准的输出流(方向:程序->控制台)
package com.qf.io07; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.PrintStream; public class Test04 { public static void main(String[] args) throws FileNotFoundException { //重定向:重新定义系统标准输出流的方向(方向:程序->文件) System.setOut(new PrintStream(new FileOutputStream("hhy.txt", true))); PrintStream out = System.out; out.println("abc111222"); } }
System.err:系统标准的错误输出流(方向:程序->控制台)
package com.qf.io07; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.PrintStream; public class Test05 { public static void main(String[] args) throws FileNotFoundException { //重定向:重新定义系统标准错误输出流的方向(方向:程序->文件) System.setErr(new PrintStream(new FileOutputStream("hhy.txt", true))); PrintStream out = System.err; out.println("abc111222"); } }
含义:该流认为文件是一个大型的byte数组,该流有个指针,默认在0的位置,指针指向哪,就可以从哪开始读取和写入
注意:
1.该流的方向是双向的(程序<->文件)
2.mode: r-读 rw-读写
package com.qf.io08; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; public class Test01 { public static void main(String[] args) throws IOException { //1.创建流对象 RandomAccessFile w = new RandomAccessFile("hhy.txt", "rw"); //2.写入数据 w.write("123abc我爱你".getBytes()); //3.关闭资源 w.close(); } } //假设最开始文件中就有内容,内容为"111111111111111" //这时打印出的结果就为"123abc我爱你111111" //把前九个1给替换掉了
package com.qf.io08; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; public class Test01 { public static void main(String[] args) throws IOException { //1.创建流对象 //在文件末尾写入数据 File file = new File("hhy.txt"); RandomAccessFile w = new RandomAccessFile(file, "rw"); w.seek(file.length()); //因为要在文件末尾写入数据,所以需要将指针设置到文件末尾,所以就要知道文件的字节长度,又因为length()是成员方法要用对象调用,所以创建一个文件的对象,用这个文件的对象去调用length(),就能知道文件的字节长度。 //2.写入数据 w.write("123abc我爱你".getBytes()); //3.关闭资源 w.close(); } } //假设最开始文件中就有内容,内容为"111111111111111" //这时打印出的结果就为"111111123abc我爱你" //把后九个1给替换掉了
package com.qf.io08; import java.io.IOException; import java.io.RandomAccessFile; public class Test02 { public static void main(String[] args) throws IOException { //1.创建流对象 RandomAccessFile r = new RandomAccessFile("hhy.txt", "r"); //2.读取数据 byte[] bs = new byte[1024]; int len; while((len = r.read(bs)) != -1){ System.out.println(new String(bs, 0, len)); } //3.关闭资源 r.close(); } }
package com.qf.io08; import java.io.IOException; import java.io.RandomAccessFile; public class Test03 { public static void main(String[] args) throws IOException { //1.创建流对象 RandomAccessFile r = new RandomAccessFile("hhy.txt", "r"); //设置指针 r.seek(3); //2.读取数据 byte[] bs = new byte[1024]; int len; while((len = r.read(bs)) != -1){ System.out.println(new String(bs, 0, len)); } //3.关闭资源 r.close(); } }
//不是断点续传 package com.qf.io08; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; public class Copy { public static void main(String[] args) throws IOException { RandomAccessFile r = new RandomAccessFile("麻生希.mp4", "r"); RandomAccessFile w = new RandomAccessFile("copy.mp4", "rw"); //2.拷贝 byte[] bs = new byte[10]; int len; while((len = r.read(bs)) != -1){ w.write(bs, 0, len); } //3.关闭资源 r.close(); w.close(); } }
断点续传
package com.qf.io08; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; public class Copy { public static void main(String[] args) throws IOException { RandomAccessFile r = new RandomAccessFile("麻生希.mp4", "r"); File file = new File("copy.mp4"); RandomAccessFile w = new RandomAccessFile(file, "rw"); //设置指针、断了再连上之后从file.length()处开始读,从file.length()处开始写 r.seek(file.length());//最开始的时候file.length()是0 w.seek(file.length()); //2.拷贝 byte[] bs = new byte[1024]; int len; while((len = r.read(bs)) != -1){ w.write(bs, 0, len); } //3.关闭资源 r.close(); w.close(); } }
out–写--创建对应的输出流对象–让输出流对象找到这个文件并存储这个文件的地址–若没有这个文件就创建
in----读–创建对应的输入流对象–让输入流对象找到这个文件并存储这个文件的地址–若没有这个文件就报错
字节输出流:FileOutputStream fos = new FileOutputStream("hhy.txt",true);
字节输入流:FileInputStream fis = new FileInputStream("hhy.txt");
带缓冲区的字节输出流:BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("hhy.txt",true));
带缓冲区的字节输入流:BufferedInputStream bis = new BufferedInputStream(new FileInputStream("hhy.txt"));
字符输出转换流:OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("hhy.txt",true), "GBK");
字符输入转换流:InputStreamReader isr = new InputStreamReader(new FileInputStream("hhy.txt"),"GBK");
字符输出流:FileWriter fw = new FileWriter("hhy.txt", true);
字符输入流:FileReader fr = new FileReader("hhy.txt");
带缓冲区的字符输出流:
//字节流 --> 转换流 --> 带缓冲区的字符流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("hhy.txt"),"GBK"));
//字符流 --> 带缓冲区的字符流
BufferedWriter bw = new BufferedWriter(new FileWriter("hhy.txt"));
带缓冲区的字符输入流:
//字节流 --> 转换流 --> 带缓冲区的字符流
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("hhy.txt"),"GBK"));
//字符流 --> 带缓冲区的字符流
BufferedReader br = new BufferedReader(new FileReader("hhy.txt"));
对象输出流:ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hhy.txt"));
对象输入流:ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hhy.txt"));
内存输出流:ByteArrayOutputStream baos = new ByteArrayOutputStream();
内存输入流:ByteArrayInputStream bais = new ByteArrayInputStream("123abc".getBytes());
字节打印流:
PrintStream ps = new PrintStream("hhy.txt");
PrintStream ps = new PrintStream(new FileOutputStream("hhy.txt", true));
字符打印流:
PrintWriter pw = new PrintWriter("hhy.txt");
PrintWriter pw = new PrintWriter(new FileOutputStream("hhy.txt", true)); PrintWriter pw = new PrintWriter(new FileWriter("hhy.txt", true));
随机访问输出流:
File file = new File("hhy.txt");
RandomAccessFile w = new RandomAccessFile(file, "rw");
随机访问输入流:
RandomAccessFile r = new RandomAccessFile("hhy.txt", "r");
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PuRpzuua-1644159321431)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wps98DF.tmp.jpg)]
实现多台计算机之间实现数据的共享和传递,网络应用程序主要组成为:
网络编程+IO流+多线程
两台计算机之间的通信是根据什么规则来走的(OSI & TCP/IP)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xAZwwIHV-1644159321432)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wps98E0.tmp.jpg)]
此处简单了解该模型就行《TCP/IP详解》《TCP协议簇》
网络通讯的模型:请求-响应,客户端-服务端
三要素:IP地址,端口,协议(数据传输的规则)
IP地址:网络中计算机的唯一标识(IP地址是一个32位的二进制数据,为了方便,将一个字节的二进制转换为一个十进制的数据)
IP地址的组成:网络号段+主机段
A类IP地址:第一段号码为网络号码,剩下的三段号码为本地计算机的号码
----可以配置256256256台主机
B类IP地址:前二段号码为网络号码,剩下的二段号码为本地计算机的号码
C类IP地址:前三段号码为网络号码,剩下的一段号码为本地计算机的号码
补充:
A类地址的表示范围为:0.0.0.0126.255.255.255,默认网络屏蔽为:255.0.0.0;A类地址分配给规模特别大的网络使用。A类网络用第一组数字表示网络本身的地址,后面三组数字作为连接于网络上的主机的地址。分配给具有大量主机(直接个人用户)而局域网络个数较少的大型网络。例如IBM公司的网络。B类地址的表示范围为:128.0.0.0191.255.255.255,默认网络屏蔽为:255.255.0.0;C类地址的表示范围为:192.0.0.0~223.255.255.255,默认网络屏蔽为:255.255.255.0;C类地址分配给小型网络,如一般的局域网和校园网,它可连接的主机数量是最少的,采用把所属的用户分为若干的网段进行管理。
特殊地址:
127.0.0.1回环地址,可用于测试本机的网络是否有问题. ping 127.0.0.1
DOS命令 ipconfig:查看本机IP地址
xxx.xxx.xxx.255 广播地址
访问百度(IP和域名–DNS服务器)
一个IP可以对应多个域名,但是一个域名在一个时刻只能对应一个IP
正在运行的程序的标识
每个网络程序都会至少有一个逻辑端口
用于标识进程的逻辑地址,不同进程的标识不同
有效端口:065535,其中01024系统使用或保留端口。
注意:端口与协议有关:TCP和UDP的端口互不相干,同一个协议的端口不能重复,不同协议的可以重复
通信规则,就是数据的传输规则
TCP、UDP都是传输层的协议
TCP
建立连接,形成传输数据的通道;在连接中进行大数据量传输;通过三次握手完成连接,是可靠协议;必须建立连接,效率会稍低,例如:打电话
UDP
将数据源和目的封装到数据包中,不需要建立连接;每个数据报的大小在限制在64k;因无连接,是不可靠协议;不需要建立连接,速度快:例如发短信
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0daWsHOx-1644159321432)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wps98F4.tmp.jpg)]
看看如何采用API来进行网络编程?IP 端口 协议
用来表示主机的信息
package com.qf.inetaddress; import java.net.InetAddress; import java.net.UnknownHostException; public class Test01 { public static void main(String[] args) throws UnknownHostException { //获取本机信息 //InetAddress localHost = InetAddress.getLocalHost(); //System.out.println(localHost); //通过域名获取到服务器的地址 //InetAddress byName = InetAddress.getByName("www.baidu.com"); //System.out.println(byName); //通过域名获取到所有服务器的地址 InetAddress[] allByName = InetAddress.getAllByName("www.baidu.com"); for (InetAddress inetAddress : allByName) { System.out.println(inetAddress); } //总结:一个域名对应多个IP地址 } }
API:Socket,ServerSocket
客户端(发送一个请求) 服务端(接收到这个请求,给予响应)
Scoket也叫套接字,其表示的是IP地址和端口号的组合。网络编程主要就是指Socket编程,网络间的通信其实就是Socket间的通信,数据就通过IO流在两个Scoket间进行传递。
1.1.1. 编写服务端程序
1.1.2. 编写客户端程序
1.1.3. 客户端向服务端发送请求信息,服务端成功接收
1.1.4. 服务端向客户端发送响应信息,客户端成功接收
服务端:
package com.qf.tcp01; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; //服务端 - 银泰休闲会所 public class Server { public static void main(String[] args) throws IOException { //大堂经理--8080自己的端口号 ServerSocket server = new ServerSocket(8080); System.out.println("等待客人的到来..."); //安排18号技师 //accept():线程阻塞的方法,等到客户端连接成功后,才会在服务端生产一个Socket对象,也就是等到客户端连接成功后,才会接着往下运行。 Socket socket = server.accept(); System.out.println("客人已就位..."); //2.接收来自客户端的消息 /* getInputStream()--Socket类提供的字节输入流 InputStreamReader()--字符输入转换流--将字节输入流转换为字符输入流 BufferedReader()--带缓冲区的字符输入流(带缓冲区的字符输入输出流可以一行一行的读或者写) */ BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "GBK")); String readLine = br.readLine(); System.out.println(readLine); //3.向客户端发送消息(只有是发送消息就用打印流,因为只有打印流才具有换行的功能) /* getOutputStream()--Socket类提供的字节输出流 PrintStream -- 字节打印流 PrintWriter -- 字符打印流 注意:打印流的方向都是程序到文件,只有一个方向 PrintStream vs PrintWriter PrintStream:可以将字节流转换为打印流 PrintWriter:可以将字节流、字符流转换为打印流 */ PrintStream ps = new PrintStream(socket.getOutputStream()); ps.println("18号技师:李总,你问的年龄还是什么???"); //关闭流等同于关闭Socket,所以流要统一放到最后再关 ps.close(); br.close(); socket.close(); server.close(); } }
客户端:
package com.qf.tcp01; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.Socket; import java.net.UnknownHostException; //客户端 public class Client { public static void main(String[] args) throws UnknownHostException, IOException { //李涛,8080--服务端端口号 Socket socket = new Socket("127.0.0.1", 8080); //1.客户端向服务端发送消息(只有是发送消息就用打印流,因为只有打印流才具有换行的功能) /* getOutputStream()--Socket类提供的字节输出流 PrintStream -- 字节打印流 PrintWriter -- 字符打印流 注意:打印流的方向都是程序到文件,只有一个方向 PrintStream vs PrintWriter PrintStream:可以将字节流转换为打印流 PrintWriter:可以将字节流、字符流转换为打印流 */ PrintStream ps = new PrintStream(socket.getOutputStream()); //必须用带换行的传输工具--println ps.println("李涛:小妹妹,你多大了???"); //4.接收来自服务端的消息 BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "GBK")); String readLine = br.readLine(); System.out.println(readLine); //关闭流等同于关闭Socket,所以流要统一放到最后再关 ps.close(); br.close(); socket.close(); } }
一对一循环聊天,客户端先聊,且不能连续聊
服务端
package com.qf.tcp02; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; public class Server { @SuppressWarnings("all") public static void main(String[] args) throws IOException { ServerSocket server = new ServerSocket(8080); //accept():线程阻塞的方法,等到客户端连接成功后,才会在服务端生产一个Socket对象,也就是等到客户端连接成功后,才会接着往下运行。 Socket socket = server.accept(); Scanner scan = new Scanner(System.in); /* getInputStream()--Socket类提供的字节输入流 InputStreamReader()--字符输入转换流--将字节输入流转换为字符输入流 BufferedReader()--带缓冲区的字符输入流(带缓冲区的字符输入输出流可以一行一行的读或者写) getOutputStream()--Socket类提供的字节输出流 PrintStream -- 字节打印流 PrintWriter -- 字符打印流 注意:打印流的方向都是程序到文件,只有一个方向 PrintStream vs PrintWriter PrintStream:可以将字节流转换为打印流 PrintWriter:可以将字节流、字符流转换为打印流 */ BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "GBK")); PrintStream ps = new PrintStream(socket.getOutputStream()); while(true){ String readLine = br.readLine(); System.out.println(readLine); ps.println("18号技师:" + scan.next()); } } }
客户端
package com.qf.tcp02; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.Socket; import java.net.UnknownHostException; import java.util.Scanner; public class Client { @SuppressWarnings("all") public static void main(String[] args) throws UnknownHostException, IOException { Socket socket = new Socket("127.0.0.1", 8080); Scanner scan = new Scanner(System.in); /* getOutputStream()--Socket类提供的字节输出流 PrintStream -- 字节打印流 PrintWriter -- 字符打印流 注意:打印流的方向都是程序到文件,只有一个方向 PrintStream vs PrintWriter PrintStream:可以将字节流转换为打印流 PrintWriter:可以将字节流、字符流转换为打印流 getInputStream()--Socket类提供的字节输入流 InputStreamReader()--字符输入转换流--将字节输入流转换为字符输入流 BufferedReader()--带缓冲区的字符输入流(带缓冲区的字符输入输出流可以一行一行的读或者写) */ PrintStream ps = new PrintStream(socket.getOutputStream()); BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "GBK")); while(true){ ps.println("李涛:" + scan.next()); String readLine = br.readLine(); System.out.println(readLine); } } }
一对一循环聊天(多线程实现)
创建一个专门接收消息的线程
package com.qf.tcp03; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; public class ReceiveThread extends Thread{ private Socket socket; public ReceiveThread(Socket socket) { this.socket = socket; } @Override public void run() { try { BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "GBK")); while(true){ String readLine = br.readLine(); System.out.println(readLine); } } catch (IOException e) { e.printStackTrace(); } } }
服务端
package com.qf.tcp03; import java.io.IOException; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; public class Server { @SuppressWarnings("all") public static void main(String[] args) throws IOException { ServerSocket server = new ServerSocket(8080); Socket socket = server.accept(); Scanner scan = new Scanner(System.in); new ReceiveThread(socket).start(); PrintStream ps = new PrintStream(socket.getOutputStream()); while(true){ ps.println("18号技师:" + scan.next()); } } }
客户端
package com.qf.tcp03; import java.io.IOException; import java.io.PrintStream; import java.net.Socket; import java.net.UnknownHostException; import java.util.Scanner; public class Client { @SuppressWarnings("all") public static void main(String[] args) throws UnknownHostException, IOException { Socket socket = new Socket("127.0.0.1", 8080); Scanner scan = new Scanner(System.in); new ReceiveThread(socket).start(); PrintStream ps = new PrintStream(socket.getOutputStream()); while(true){ ps.println("李涛:" + scan.next()); } } }
传输文件
服务端
package com.qf.tcp04; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) throws IOException { ServerSocket server = new ServerSocket(8080); Socket socket = server.accept(); //接收来自客户端的数据,并写入到本地文件中 BufferedInputStream bis = new BufferedInputStream(socket.getInputStream()); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.mp4")); byte[] bs = new byte[1024]; int len; while((len = bis.read(bs)) != -1){ bos.write(bs, 0, len); } bis.close(); bos.close(); socket.close(); server.close(); } }
客户端
package com.qf.tcp04; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.FileInputStream; import java.io.IOException; import java.net.Socket; import java.net.UnknownHostException; public class Client { public static void main(String[] args) throws UnknownHostException, IOException { Socket socket = new Socket("127.0.0.1", 8080); //读取源文件,并写入到服务端 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("麻生希.mp4")); BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream()); byte[] bs = new byte[1024]; int len; while((len = bis.read(bs)) != -1){ bos.write(bs, 0, len); } bis.close(); bos.close(); socket.close(); } }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TtqJMAku-1644159321433)(file:///C:\Users\ADMINI1\AppData\Local\Temp\ksohtml\wps98F5.tmp.jpg)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PnYf6tOC-1644159321434)(file:///C:\Users\ADMINI1\AppData\Local\Temp\ksohtml\wps98F6.tmp.jpg)]
UDP(User Datagram Protocol)用户数据报包协议,UDP和TCP位于同一层-传输层,但它对于数据包的顺序错误或重发没有TCP可靠;UDP是一种面向无连接的通信协议。UDP向应用程序提供一种发送封装的原始IP数据报的方法,并且发送时无需建立连接,不保证可靠数据的传输
UDP — 发短信
TCP — 打电话
TCP UDP
是否连接 面向连接 无面向连接
传输可靠性 可靠 不可靠
应用场合 传输大量数据 少量数据
速度 慢 快
java.annotation包Annotation是从JDK1.5开始引入的新技术,注解即可以对程序员解释又可以对程序解释
注释:对程序员解释代码信息
注解:对程序和程序员解释代码信息
注解是以“@注释名”在代码中存在的,还可以添加一些参数例如:@SuppressWarnings(value=“unchecked”)
可以附加在package、class(type)、method、field等上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制实现对这些数据的访问
****@Overrlde****:定义在java.lang.Override中,此注解只适用于修饰方法,表示一个方法声明打算重写超类中的另一个方法声明
****@Deprecated****:定义在java.lang.Deprecated中.此注解可以用于修饰方法,属性,类。表示不鼓励使用这样的元素.通常是因为它很危险或者存在更好的选择
****@SuppressWarnings****:镇压警告,定义在java.lang.SuppressWarnings中用来抑制编译时的警告信息,与前两个注释有所不同.你需要添加参数才能正确使用。这些参数都是已经定义好了的.我们选择性的使用就好了
@SuppressWarnings(“all”)抑制所有类型的警告信息
@SuppressWarnings(“unchecked”)抑制单类型的警告信息
@SuppressWarnings(value={“unchecked”,“deprecation”})抑制多类型的警告信息
元注解的作用:负责注解其他注解,Java定义了4个标准的meta-annotation类型,他们被用来提供对其他annotation类型作说明。这些类型和它们所支持的类在java.lang.annotation包中可以找到(@Target,@Retention,@Documented,@Inherited )
@Target:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
@Retention:表示需要要在什么级别保存该注解信息,用于描述注解的生命周期 (SOURCE < CLASS < RUNTIME)
SOURCE – 只有.java中显示
CLASS – .java中显示,编译成.class文件后也会在.class文件中显示
RUNTIME --.java中显示,编译成.class文件后也会在.class文件中显示,运行程序时也会显示
@Document:说明该注解将被包含在javadoc中
@lnherited:说明子类可以继承父类中的该注解
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口
分析:@interface用来声明一个注解,格式:public @interface 注解名 {定义内容}
定义内容中的每一个方法实际上是声明了一个自定义注解的配置参数.
定义内容中方法的名称就是参数的名称.
定义内容中参数类型只能是基本类型、Class、String、enum
定义内容中可以通过default来声明参数的默认值
如果只有一个参数成员,一般参数名为value()
注解元素必須要有值,我们定义注解元素时,经常使用空字符串.0作为默认值[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8yu2aVtK-1644159321434)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wpsCCAE.tmp.png)]
package com.qf.annotation07; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE,ElementType.METHOD})//注解的使用范围 @Retention(RetentionPolicy.RUNTIME)//设置注解的生命周期 public @interface MyAnnotation { //参数(必须加上小括号) String[] Value(); String author(); int age(); char sex(); String[] hobby(); String Value() default "abc";//添加默认值 } package com.qf.annotation07; //"Value="可以省略,其他不能省略 @MyAnnotation({xxx,yyy},author="何老师",age=18,sex='男',hobby={"篮球","LOL"}) public class Test01 { public static void main(String[] args) { } }
所有类加载到方法区的时候都会在堆内存中创建这个类的字节码文件对象
每个类的字节码文件对象都只有唯一的一个
所有字节码文件对象都是Class类的对象
所有方法的方法对象都是method类的对象
构造方法的方法对象是Constructor类的对象
所有属性的属性对象都是Field类的对象
所有方法名里面的参数的参数对象都是Parameter类的对象
在堆内存中new对象的时候,会通过类的字节码文件对象找到方法区中这个类的字节码文件,并调用里面的构造方法,调用构造方法会在栈内存中开辟空间
类的字节码文件只能通过这个类的字节码文件对象去访问,类的字节码文件对象相当于类的字节码文件的访问入口
反射机制:获取类的字节码文件对象,通过类的字节码文件对象获取该类的所有属性和方法
//先创建一个Person类 package com.qf.reflex01; public class Person { private String name; private char sex; private int age; public Person() { } public Person(String name, char sex, int age) { this.name = name; this.sex = sex; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public char getSex() { return sex; } public void setSex(char sex) { this.sex = sex; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person [name=" + name + ", sex=" + sex + ", age=" + age + "]"; } }
//创建一个Student类继承Person类 package com.qf.reflex01; public class Student extends Person{ private String classId; private String id; public Student() { } public Student(String name, char sex, int age, String classId, String id) { super(name, sex, age); this.classId = classId; this.id = id; } public String getClassId() { return classId; } public void setClassId(String classId) { this.classId = classId; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String study(String name,int age,String type){ return age + "岁的" + name + "学生学习" + type + "科目"; } @Override public String toString() { return "Student [classId=" + classId + ", id=" + id + ", toString()=" + super.toString() + "]"; } }
//创建一个User类 package com.qf.reflex01; public class User { private String username; private String password; private String name; public User() { } public User(String username, String password, String name) { this.username = username; this.password = password; this.name = name; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "User [username=" + username + ", password=" + password + ", name=" + name + "]"; } }
package com.qf.reflex01; import java.io.IOException; import java.util.Properties; public class Test01 { public static void main(String[] args) throws ClassNotFoundException, IOException { //方式1 //直接使用类名获取类的字节码文件对象 //? extends A:表示可返回A类或者是A的子类 //Class<? extends Student>--泛型,字节码文件对象是Class类的对象,字节码文件对象是Student类或者Student类的子类的字节码文件对象 Class<? extends Student> c1 = Student.class; System.out.println(c1); //方式2 //创建一个这个类的对象,通过这个类的对象去获取这个类的字节码文件对象 Student stu = new Student(); Class<? extends Student> c2 = stu.getClass(); System.out.println(c2); //c2.getSuperclass--通过子类的字节码文件对象获取到父类的字节码文件对象 //方式3 -- 推荐 //通过类的路径获取类的字节码文件对象 //创建配置文件类的对象 Properties p = new Properties(); //将配置文件加到对象中 p.load(Test01.class.getClassLoader().getResourceAsStream("ClassPath.properties")); //获取配置文件中的数据 String classPath = p.getProperty("classPath"); Class<?> c3 = Class.forName(classPath); System.out.println(c3); System.out.println(c1 == c2);//true,每个类的字节码文件对象都只有唯一的一个 System.out.println(c2 == c3);//true,每个类的字节码文件对象都只有唯一的一个 } }
package com.qf.reflex01; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Properties; public class Test02 { public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { //获取类的字节码文件对象--方法三 Properties p = new Properties(); p.load(Test01.class.getClassLoader().getResourceAsStream("ClassPath.properties")); String classPath = p.getProperty("classPath"); Class<?> c = Class.forName(classPath); //利用获取到的该类的字节码文件对象获取该类及父类所有的公有属性的属性对象并将其放入到一个数组中 Field[] fields = c.getFields(); for (Field field : fields) { System.out.println(field); } //利用获取到的该类的字节码文件对象获取该类所有属性的属性对象并将其放入到一个数组中 Field[] declaredFields = c.getDeclaredFields(); for (Field field : declaredFields) { System.out.println(field); } //利用获取到的该类的字节码文件对象获取该类及父类所有属性的属性对象并将其放入到一个数组中 //clazz.getSuperclass()--通过子类的字节码文件对象获取到父类的字节码文件对象 //把Student类的字节码文件对象赋给clazz,判断Student类的字节码文件对象是否为空,不为空的话,通过Student类的字节码文件对象获取Student类的所有属性的属性对象并输出,之后再获取Student类的父类的字节码文件对象,判断Student类的父类的字节码文件对象是否为空,不为空的话,通过Student类的父类的字节码文件对象获取Student类的父类的所有属性的属性对象并输出,再获取Student类的父类的父类的字节码文件对象,判断Student类的父类的父类的字节码文件对象是否为空,不为空的话,通过Student类的父类的父类的字节码文件对象获取Student类的父类的父类的所有属性的属性对象并输出......循环下去 for(Class<?> clazz = c;clazz != null;clazz = clazz.getSuperclass()){ Field[] declaredFields = clazz.getDeclaredFields(); for (Field field : declaredFields) { System.out.println(field); } } //通过Student类的字节码文件对象获取Student类的其中一个属性的属性对象 Field classIdField = c.getDeclaredField("classId");//获取类里面classId的属性对象 System.out.println(classIdField.getName());//获取属性对象的属性名 System.out.println("判断属性是否是private:" + Modifier.isPrivate(classIdField.getModifiers())); System.out.println("判断属性是否是public:" + Modifier.isPublic(classIdField.getModifiers())); System.out.println("判断属性是否是static:" + Modifier.isStatic(classIdField.getModifiers())); System.out.println("判断属性是否是final:" + Modifier.isFinal(classIdField.getModifiers())); //通过获取到的类里面的属性的属性对象设置stu对象中对应的属性的数据 Student stu = new Student(); classIdField.setAccessible(true);//设置修改权限 classIdField.set(stu, "2113");//设置stu对象中的classId的属性为2113 System.out.println(stu); //封装方法 设置对象属性 //目的:感受反射给我们带来的好处 setFiled(stu, "id", "001"); setFiled(stu, "name", "钟燕"); setFiled(stu, "sex", '女'); setFiled(stu, "age", 18); System.out.println(stu); User user = new User(); setFiled(user, "username", "zhongyan"); setFiled(user, "password", "123123"); setFiled(user, "name", "钟燕"); setFiled(user, "xxxx", "xxx"); } public static<T> void setFiled(T t,String fieldName,Object val) throws SecurityException, IllegalArgumentException, IllegalAccessException { //获取类的字节码文件对象 Class<? extends Object> c = t.getClass(); //利用获取到的该类的字节码文件对象获取属性的属性对象 Field field = null; try { field = c.getDeclaredField(fieldName); } catch (NoSuchFieldException e) {//处理异常 for(Class<?> superClass = c.getSuperclass();superClass != null;superClass = superClass.getSuperclass()){ try { field = superClass.getDeclaredField(fieldName); } catch (NoSuchFieldException e1) { } } } if(field != null){ //设置修改权限 field.setAccessible(true); //设置参数 field.set(t, val); } } }
package com.qf.reflex01; import java.lang.reflect.Constructor; import java.util.Properties; public class Test03 { public static void main(String[] args) throws Exception { //获取类的字节码文件对象 Properties p = new Properties(); p.load(Test01.class.getClassLoader().getResourceAsStream("ClassPath.properties")); String classPath = p.getProperty("classPath"); Class<?> c = Class.forName(classPath); //newInstance是通过反射创建对象的,在创建一个类的对象的时候,你可以对该类一无所知 //new 后面接类名参数,是最常见的创建对象的方式,这是必须要知道一个明确的类才能使用 // Student stu = (Student) c.newInstance(); // System.out.println(stu); //利用获取到的该类的字节码文件对象获取该类及父类公有构造方法的方法对象并将其放入到一个数组中 Constructor<?>[] constructors = c.getConstructors(); for (Constructor<?> constructor : constructors) { System.out.println(constructor); } //利用获取到的该类的字节码文件对象获取该类所有的构造方法的方法对象并将其放入到一个数组中 Constructor<?>[] declaredConstructors = c.getDeclaredConstructors(); for (Constructor<?> constructor : declaredConstructors) { System.out.println(constructor); } //利用获取到的该类的字节码文件对象获取该类的无参构造方法的方法对象 Constructor<?> constructor = c.getDeclaredConstructor(); constructor.setAccessible(true);//设置修改权限 //newInstance是通过反射创建对象的,在创建一个类的对象的时候,你可以对该类一无所知 //new 后面接类名参数,是最常见的创建对象的方式,这是必须要知道一个明确的类才能使用 Student stu = (Student) constructor.newInstance(); System.out.println(stu); //利用获取到的该类的字节码文件对象获取该类的有参构造方法的方法对象 Constructor<?> constructor = c.getDeclaredConstructor(String.class,char.class,int.class,String.class,String.class);//对应类里面的顺序去写 constructor.setAccessible(true);//设置修改权限 //通过反射创建这个类的对象,调用获取到的有参构造方法的方法对象设置类里面属性的数据 //注意和获取到类里面的属性的属性对象设置类里面的属性的数据时的区别,那个已经获取到类里面的属性的属性对象了,可以直接new这个类的对象,然后通过获取到的类里面的属性的属性对象直接设置类里面的属性的数据即可。这个获取到的是类里面的有参构造方法的方法对象,需要通过反射创建这个类的对象,然后调用获取到的类里面的有参构造方法的方法对象设置类里面的属性的数据 Student stu = (Student) constructor.newInstance("钟燕",'女',18,"2113","001"); System.out.println(stu); } }
package com.qf.reflex01; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Properties; public class Test04 { public static void main(String[] args) throws Exception { //获取类的字节码文件对象 Properties p = new Properties(); p.load(Test01.class.getClassLoader().getResourceAsStream("ClassPath.properties")); String classPath = p.getProperty("classPath"); Class<?> c = Class.forName(classPath); //利用获取到的该类的字节码文件对象获取该类及父类公有的方法对象并将其放入到一个数组中 Method[] methods = c.getMethods(); for (Method method : methods) { System.out.println(method); } //利用获取到的该类的字节码文件对象获取该类所有的方法对象并将其放入到一个数组中 Method[] declaredMethods = c.getDeclaredMethods(); for (Method method : declaredMethods) { System.out.println(method); } //利用获取到的该类的字节码文件对象通过特定的方法名获取到这个方法的方法对象 Method method = c.getDeclaredMethod("study", String.class,int.class,String.class);//类里面的这个方法里面的参数的参数类型.class--获取到参数类型的字节码文件对象,通过参数类型的字节码文件对象获取到参数的类型 //获取该类特定的方法对象中的参数信息 // int parameterCount = method.getParameterCount(); // System.out.println("获取参数个数:" + parameterCount); // // Parameter[] parameters = method.getParameters();//获取参数的参数对象并将其放入到一个数组中 // for (Parameter parameter : parameters) { // System.out.println("参数类型:" + parameter.getType()); // System.out.println("参数名字:" + parameter.getName());//在编译时参数名不会编译到.class文件,会用arg0、arg1、arg2......代替 // } //利用获取到的该类的字节码文件对象通过特定的方法名获取到这个方法的方法对象,并调用该类的方法 //注意和获取到类里面的属性的属性对象设置类里面的属性的数据、获取到类里面的有参构造方法的方法对象设置类里面的属性的数据的区别 Student stu = new Student(); Object invoke = method.invoke(stu, "钟燕",18,"Java"); System.out.println(invoke); System.out.println("判断该方法是否是private:" + Modifier.isPrivate(method.getModifiers())); System.out.println("判断该方法是否是public:" + Modifier.isPublic(method.getModifiers())); System.out.println("判断该方法是否是static:" + Modifier.isStatic(method.getModifiers())); System.out.println("判断该方法是否是final:" + Modifier.isFinal(method.getModifiers())); } }
利用反射创建或操作数组的时候用Array工具类
package com.qf.reflex01; import java.lang.reflect.Array; public class Test05 { public static void main(String[] args) throws Exception { //利用反射创建数组 int[] is = (int[]) Array.newInstance(int.class, 5); System.out.println("获取数组长度:" + Array.getLength(is)); for(int i = 0 ;i<Array.getLength(is);i++){ Array.set(is, i, i+1);//反射设置数组的数据(目标数组,下标,值) } for(int i = 0 ;i<Array.getLength(is);i++){ Object object = Array.get(is, i);//反射获取数组的数据 System.out.println(object); } } }
package com.qf.reflex01; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.HashMap; public class Test06 { public static void main(String[] args) throws Exception { //获取Test06类的字节码文件对象 Class<?> c = Test06.class; //利用获取到的类的字节码文件对象通过类里面的method01这个方法名获取到该类中method01这个方法的方法对象 Method method01 = c.getDeclaredMethod("method01", ArrayList.class,HashMap.class); //通过method01这个方法的方法对象获取到这个方法里面的参数的参数对象,并放入到一个数组中 Parameter[] parameters = method01.getParameters(); //通过method01这个方法的方法对象获取到的这个方法里面的参数的参数对象获取参数的参数类型对象 for (Parameter parameter : parameters) { Type type = parameter.getParameterizedType(); //通过参数类型对象获取到对应参数类型上的泛型对象,并放入到一个数组中 //通过.getParameterizedType()获取到的参数类型对象不能通过.getActualTypeArguments()获取到对应参数类型上的泛型对象,要把通过.getParameterizedType()获取到的参数类型对象强转为ParameterizedType类型,然后才能通过parameterizedType.getActualTypeArguments()获取到对应参数类型上的泛型对象 ParameterizedType parameterizedType = (ParameterizedType) type; Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); for (Type aTA : actualTypeArguments) { System.out.println(aTA); } }//class java.lang.String //class java.lang.String //class java.lang.Integer System.out.println("------------------"); //获取返回值上的泛型对象 //利用获取到的类的字节码文件对象通过类里面的method02这个方法名获取到该类中method02这个方法的方法对象 Method method02 = c.getDeclaredMethod("method02"); //通过method02这个方法的方法对象获取到这个方法里面的返回值类型的返回值类型对象,并放入到一个数组中 Type returnType = method02.getGenericReturnType(); //通过返回值类型对象获取到对应返回值类型上的泛型对象,并放入到一个数组中 //通过.getGenericReturnType()获取到的返回值类型对象不能通过.getActualTypeArguments()获取到对应返回值类型上的泛型对象,要把通过.getGenericReturnType()获取到的返回值类型对象强转为ParameterizedType类型,然后才能通过parameterizedType.getActualTypeArguments()获取到对应返回值类型上的泛型对象 ParameterizedType parameterizedType = (ParameterizedType) returnType; Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); for (Type aTA : actualTypeArguments) { System.out.println(aTA); } }//class java.lang.String //class java.lang.Integer public void method01(ArrayList<String> list,HashMap<String, Integer> map){} public HashMap<String, Integer> method02(){ return null; } }
任何类型的数组能都实现扩容
package com.qf.reflex02; import java.lang.reflect.Array; import java.util.Arrays; public class Test01 { public static void main(String[] args) { int[] is = {1,2,3,4,5}; int[] copyOf1 = copyOf(is, 10); System.out.println(Arrays.toString(copyOf1)); String[] strs = {"aaa","bbb","ccc"}; String[] copyOf2 = copyOf(strs, 6); System.out.println(Arrays.toString(copyOf2)); } public static<T> T copyOf(T t, int newLength){ //获取数组的字节码文件对象 Class<? extends Object> c = t.getClass(); //利用获取到的该数组的字节码文件对象获取数组中元素的类型的字节码文件对象 Class<?> elementType = c.getComponentType(); //利用反射创建新的数组,利用反射创建或操作数组的时候用Array工具类 @SuppressWarnings("unchecked") T newArr = (T) Array.newInstance(elementType, newLength); //数据的迁移 for (int i = 0; i < Array.getLength(t); i++) { //获取源数组的数据 Object element = Array.get(t, i); //赋值给新的数组 Array.set(newArr, i, element); } return newArr; } }
不改动代码,只需要通过更改配置文件就能实现想达到的目的
package com.qf.reflex03; import java.util.ArrayList; import java.util.Scanner; public class Test01 { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException { Scanner scan = new Scanner(System.in); System.out.print("请选择获取数据的方式:"); printMenu(); int num = scan.nextInt(); Data data = getData(num); //利用通过反射创建出来的对象调用对应路径名的类里面的getSource()方法 data.getSource(); scan.close(); } //获取Data对象 public static Data getData(int num) throws ClassNotFoundException, InstantiationException, IllegalAccessException{ //获取到对应的类的路径 String classPath = Config.path.get(num-1); //通过类的路径获取类的字节码文件对象 Class<?> c = Class.forName(classPath); //newInstance是通过反射创建对象的,在创建一个类的对象的时候,你可以对该类一无所知 //new 后面接类名参数,是最常见的创建对象的方式,这是必须要知道一个明确的类才能使用 Data data = (Data) c.newInstance(); return data; } public static void printMenu(){ ArrayList<String> names = Config.names; for (String str : names) { System.out.print(str); } System.out.println(); } }
package com.qf.reflex03; //获取数据的类 public abstract class Data { public abstract void getSource(); }
package com.qf.reflex03; //获取本地数据的类 public class LocalData extends Data{ @Override public void getSource(){ System.out.println("本地数据"); } }
package com.qf.reflex03; //获取网络数据的类 public class NetworkData extends Data{ @Override public void getSource(){ System.out.println("网络数据"); } }
package com.qf.reflex03; //获取其他数据的类 public class OtherData extends Data{ @Override public void getSource(){ System.out.println("其他数据"); } }
package com.qf.reflex03; import java.util.ArrayList; //配置文件类 public class Config { public static final ArrayList<String> names; public static final ArrayList<String> path; static{ names = new ArrayList<>(); names.add("1-本地数据 "); names.add("2-网络数据 "); names.add("3-其他数据 "); path = new ArrayList<>(); path.add("com.qf.reflex03.LocalData"); path.add("com.qf.reflex03.NetworkData"); path.add("com.qf.reflex03.OtherData"); } }
package com.qf.reflex04; @TableName("db_student") public class Student { @TableFieldInfo(fieldName="s_id",type="int",length=3) private int id; @TableFieldInfo(fieldName="s_name",type="varchar",length=32) private String name; @TableFieldInfo(fieldName="s_age",type="int",length=3) private int age; @TableFieldInfo(fieldName="s_sex",type="varchar",length=32) private String sex; public Student() { } public Student(int id, String name, int age, String sex) { this.id = id; this.name = name; this.age = age; this.sex = sex; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } @Override public String toString() { return "Student [id=" + id + ", name=" + name + ", age=" + age + ", sex=" + sex + "]"; } }
package com.qf.reflex04; import java.lang.reflect.Field; public class Test01 { public static void main(String[] args) { Student stu = new Student(1, "钟燕", 18, "女"); //获取Student类的字节码文件对象 Class<? extends Student> c = stu.getClass(); //利用获取到的该类的字节码文件对象通过特定的注解名获取到这个注解里面的参数的参数对象(注解里面只有Value这一个参数时这样操作) TableName tableNameAnnotaction = c.getDeclaredAnnotation(TableName.class); System.out.println(tableNameAnnotaction.value()); //利用获取到的该类的字节码文件对象获取该类所有属性的属性对象并将其放入到一个数组中 Field[] fields = c.getDeclaredFields(); //利用获取到的该类的属性的属性对象通过特定的注解名获取到这个注解里面的参数的参数对象 //(注解里面有多个参数时这样操作,注意和上面的区别) for (Field field : fields) { TableFieldInfo tableFieldInfoAnnotaction = field.getDeclaredAnnotation(TableFieldInfo.class); System.out.print(tableFieldInfoAnnotaction.fieldName() + " -- "); System.out.print(tableFieldInfoAnnotaction.type() + " -- "); System.out.print(tableFieldInfoAnnotaction.length()); System.out.println(); } //INSERT INTO db_student(s_name,s_age,s_sex) VALUES('钟燕',18,'女'); } }
package com.qf.reflex04; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface TableName { String value(); }
package com.qf.reflex04; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface TableFieldInfo { String fieldName(); String type(); int length(); }
- 速度更快 - 优化底层源码,比如HashMap、ConcurrentHashMap
- 代码更少 - 添加新的语法Lambda表达式
- 强大的Stream API
- 便于并行
- 最大化减少空指针异常 - Optional
Lambda是一个匿名函数(方法), 允许把函数作为一个方法的参数 。利用Lambda表达式可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。一般都是优化匿名内部类
无参数、无返回值的抽象方法
public class Test1 { @Test public void test01() { // I1 i1 = new I1() { // @Override // public void method() { // System.out.println("传统使用匿名内部类的方式"); // } // }; // i1.method(); /* I1 i1 会找到这个接口,代表这个接口的对象 ()--代表重写的接口里面的无形参的方法(接口里面的方法无形参) System.out.println("采用Lambda表达式的方式");-代表重写的接口里面的方法要实现的功能 Lambda表达式只能用在接口中只有一个方法的时候 */ I1 i1 = ()-> System.out.println("采用Lambda表达式的方式"); i1.method(); } } interface I1{ public void method();//无参数、无返回值的抽象方法 }
一个参数、无返回值的抽象方法
public class Test1 { /* I1 i1 会找到这个接口,代表这个接口的对象 (x)--代表重写的接口里面的带有一个形参的方法(接口里面的方法有一个形参),重写方法的形参只有一个时,可以不加小括号 System.out.println("采用Lambda表达式的方式 " + x);-代表重写的接口里面的方法要实现的功能 Lambda表达式只能用在接口中只有一个方法的时候 */ /* public void test01() { int num=100; num=200;//会报错,Lambda表达式中访问外层的局部变量,外层的局部变量自动变成隐式常量,默认添加final,不能再重新赋值。 I1 i1 = (x)-> System.out.println("采用Lambda表达式的方式 " + x + num); i1.method(1000); } */ //Lambda表达式当中不允许声明一个与局部变量同名的参数或者局部变量 //int x;声明一个与局部变量同名的局部变量--下面就会报错 @Test public void test01() { I1 i1 = (x)-> System.out.println("采用Lambda表达式的方式 " + x); i1.method(1000); } } interface I1{ public void method(int num1);//一个参数、无返回值的抽象方法 }
多个参数、无返回值的抽象方法
public class Test1 { /* I1 i1 会找到这个接口,代表这个接口的对象 (x,y,z)--代表重写的接口里面的带有三个形参的方法(接口里面的方法有三个形参) System.out.println("采用Lambda表达式的方式 " + x + y + z);-代表重写的接口里面的方法要实现的功能 (x,y,z)或者(int x,int y,int z)重写方法的形参要么同时加类型要么同时不加类型 Lambda表达式只能用在接口中只有一个方法的时候 */ @Test public void test01() { I1 i1 = (x,y,z)-> System.out.println("采用Lambda表达式的方式 " + x + y + z); i1.method(1000,2000,3000); } } interface I1{ //多个参数、无返回值的抽象方法 public void method(int num1,int num2,int num3); }
多个参数、有返回值的抽象方法
public class Test1 { /* I1 i1 会找到这个接口,代表这个接口的对象 (x,y,z)--代表重写的接口里面的带有三个形参的方法(接口里面的方法有三个形参) (x,y,z)或者(int x,int y,int z)重写方法的形参要么同时加类型要么同时不加类型 x+y+z;-代表重写的接口里面的方法要实现的功能 Lambda表达式只能用在接口中只有一个方法的时候 */ @Test public void test01() { I1 i1 = (x,y,z)-> x+y+z; int result = i1.method(1000,2000,3000); System.out.println(result); } } interface I1{ //多个参数、有返回值的抽象方法 public int method(int num1,int num2,int num3); }
- 重写方法的形参只有一个时,可以不加小括号
- Lambda表达式当中不允许声明一个与局部变量同名的参数或者局部变量
- Lambda表达式中访问外层的局部变量,外层的局部变量自动变成隐式常量,默认添加final
- 重写方法的形参同时加类型或同时不加类型
public class Test1 { @Test public void test01() { int x; int num = 10; I1 i1 = x -> System.out.println(x + (num++)); i1.method(1000); I2 i2 = (int x,int y) -> { int result = x+y; return result; }; int result = i2.method(10, 20); System.out.println(result); } } interface I1{ public void method(int num1); } interface I2{ public int method(int num1,int num2); }
- 调用Collections.sort()方法,通过定制排序比较两个Student对象(先按年龄比较,年龄相同按照薪资比较),使用Lambda表达式作为参数传递
public class Test1 { @Test public void test01() { List<Student> stuList = Arrays.asList( new Student("张三", 28, 4800,Course.JAVA), new Student("李四", 36, 7200,Course.JAVA), new Student("王五", 19, 9600,Course.HTML), new Student("赵六", 42, 6100,Course.HTML), new Student("孙七", 23, 9600,Course.PYTHON), new Student("吴八", 31, 3000,Course.PYTHON)); Collections.sort(stuList, (a,b)-> { if(a.getAge() == b.getAge()){ return Double.compare(a.getSalary(),b.getSalary()); } return a.getAge()-b.getAge(); }); for (Student stu : stuList) { System.out.println(stu); } } } enum Course{//课程枚举 JAVA,HTML,PYTHON; } class Student{//学生类 private String name; private int age; private double salary; private Course course; ... }
- 创建I1接口,创建抽象方法:public String getValue(String str),在测试类中编写方法使用接口作为参数,将一个字符串转为大写,并作为方法的返回值
public class Test1 { @Test public void test01() { String strHandler = strHandler("abc", x-> x.toUpperCase()); System.out.println(strHandler); } public static String strHandler(String str,I1 i1){ return i1.getValue(str); } } interface I1{ public String getValue(String str); }
- 创建I1<T,R>接口,泛型T为参数,R为返回值,创建抽象方法:public R add(T t1,T t2),在测试类中编写方法使用接口作为参数,计算两个long类型的和
public class Test1 { @Test public void test01() { Long addLong = addLong(100L, 200L, (x,y)-> x+y); System.out.println(addLong); } public static Long addLong(Long l1,Long l2,I1<Long,Long> i1){ return i1.add(l1, l2); } } interface I1<T,R>{ public R add(T t1,T t2); }
函数式接口是指仅仅只包含一个抽象方法的接口,jdk1.8提供了一个@FunctionalInterface注解来定义函数式接口,如果我们定义的接口不符合函数式的规范便会报错。配合Lambda表达式一起使用
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer 消费型接口 | T | void | void accept(T t); |
Supplier 供给型接口 | void | T | T get(); |
Function<T, R> 函数型接口 | T | R | R apply(T t); |
Predicate 断言型接口 | T | boolean | booelan test(T t); |
BiConsumer<T, U> | T,U | void | 对类型为T,U参数应用操作。包含方法为void accept(T t,U u); |
BiFunction<T, U, R> | T,U | R | 对类型为T,U参数应用操作,并返回R类型的结果。包含方法为R apply(T t,U u); |
UnaryOperator extends Function<T, T> | T | T | 对类型为T的对象进行一元运算,并返回T类型的结果。包含方法为T apply(T t); |
BinaryOperator extends BiFunction<T,T,T> | T,T | T | 对类型为T的对象进行二元运算,并返回T类型的结果。包含方法为T apply(T t1,T t2); |
ToIntFunction ToLongFunction ToDoubleFunction | T | int long double | 分别计算int、long、double值的函数 |
IntFunction LongFunction DoubleFunction | int long double | R | 参数为int、long、double类型的函数 |
方法、构造方法和数组引用就是Lamdba的另一种表现形式
若Lamdba表达式中的内容由方法已经实现了,可以使用方法引用这个技能
当你需要使用方法引用时,目标引用放在分隔符::前,方法的名称放在后面
对象::实例方法
Lambda表达式中调用方法的参数类型和返回值必须和函数式接口中的抽象方法一致
public class Test1 { @Test public void test01() { // I1 i1 = (x)->System.out.println(x); // i1.method("abcd"); //println里的参数列表和返回值类型必须和method方法一致才行 PrintStream ps = System.out; I1 i1 = ps::println;//对象::实例方法 i1.method("abcd"); } } interface I1{ public void method(String str); }
类名::静态方法
Lambda表达式中调用方法的参数类型和返回值必须和函数式接口中的抽象方法一致
public class Test1 { @Test public void test01() { // Comparator<Integer> com = (x,y)-> Integer.compare(x, y); // int compare = com.compare(10, 20); // System.out.println(compare); //类名::静态方法 Comparator<Integer> com = Integer::compare; int compare = com.compare(10, 20); System.out.println(compare); } }
类名::实例方法
Lambda表达式参数列表中第一个参数必须是实例方法的调用者
Lambda表达式参数列表中第二个参数必须是实例方法的参数
public class Test1 { @Test public void test01() { // I1<String> i1 = (x,y) -> x.equals(y); // boolean method = i1.method("abc", "abc"); // System.out.println(method); //类名::实例方法 //注意:Lambda表达式参数列表中第一个参数是equals方法的调用者, // Lambda表达式参数列表中第二个参数是equals方法的参数 I1<String> i1 = String::equals; boolean method = i1.method("abc", "abc"); System.out.println(method); } } interface I1<T>{ public boolean method(T t1,T t2); }
类名::new
需要调用的构造方法的参数列表必须和函数式接口中抽象方法的参数列表一致
public class Test1 { @Test public void test01() { //需求:创建学生对象 I1<Student> i1 = Student::new; // System.out.println(i1.method()); // System.out.println(i1.method("桥本有菜",24)); System.out.println(i1.method("桥本有菜",24,8888,Course.JAVA)); } } interface I1<T>{ // public T method(); public T method(String name,int age,double salary,Course course); } enum Course{//课程枚举 JAVA,HTML,PYTHON; } class Student{//学生类 private String name; private int age; private double salary; private Course course; ... }
语法格式:type[]::new
public class Test1 { @Test public void test01() { //创建数组 I1<String[]> i1 = String[]::new; System.out.println(Arrays.toString(i1.method(10))); } } interface I1<T>{ public T method(int capacity); }
Stream(流)是数据渠道,用于操作数据源(集合、数组等),生成元素序列。换言之,集合是存储数据的容器,流使用操作这些数据的
Stream可以对集合进行非常复杂的查找、过滤、映射数据等操作,类似于SQL执行数据库查询。Stream提供了一种高效且易于使用的处理数据的方式
注意:
- Stream不会存储数据
- Stream不会改变源数据,通过一系列操作数据源会返回一个持有结果的新Stream
- Stream操作是延迟执行的,意味着流会等到需要结果的时候才执行
- 创建Stream:通过数据源(集合、数组等)获取一个Stream
- 中间操作:中间操作链,对源数据的数据进行处理
- 终止操作:执行中间操作,并产生结果
public class Test1 { @Test public void test01() { //方式一:通过Collection接口提供的stream()-串行流或parallelStream()-并行流 获取流对象 List<String> list = new ArrayList<>(); Stream<String> stream1 = list.stream(); //方式二:通过Arrays的静态方法stream()获取流对象 String[] strs = new String[10]; Stream<String> stream2 = Arrays.stream(strs); //方式三:通过Stream的静态方法of()获取流对象 Stream<String> stream3 = Stream.of("aaa","bbb","ccc"); //方式四:创建无限流 //iterate()迭代 Stream<Integer> stream4 = Stream.iterate(1, (x)->x+=100); stream4.limit(3).forEach(System.out::println); //方式五:创建无限流 //generate()生成 Stream<Double> stream5 = Stream.generate(()->Math.random()); stream5.limit(3).forEach(System.out::println); } }
注意:多个中间操作可以连接成一个流水线,除非流水线触发终止操作,否则中间操作不会执行任何的处理,而在终止操作时一次性全部处理,称为惰性求值/延迟加载
方法 | 描述 |
---|---|
filter(Predicate p) | 从流中排除元素 |
limit(long maxSize) | 设置限制数据条数 |
skip(long n) | 跳过元素,返回跳过n个元素的流,若流中不满足n个元素则返回空流。与limit()互补 |
distinct() | 筛选,流通过元素对象的hashCode()和equals()方法去除重复元素 |
如果没有终止操作,中间操作就不会被调用,终止操作时一次性全部处理,这种称为惰性求值/延迟加载
public class Test1 { List<Student> stuList = Arrays.asList( new Student("张三", 28, 4800,Course.JAVA), new Student("李四", 36, 7200,Course.JAVA), new Student("王五", 19, 9600,Course.HTML), new Student("赵六", 42, 6100,Course.HTML), new Student("孙七", 23, 9600,Course.PYTHON), new Student("吴八", 31, 3000,Course.PYTHON), new Student("李四", 36, 7200,Course.JAVA)); @Test public void test01() { //需求1:过滤掉小于5000的学生对象 Stream<Student> stream = stuList.stream().filter((x)-> { System.out.println("中间操作"); return x.getSalary()>5000; }); //迭代输出流里的数据就等同于终止操作 //迭代功能在forEach()中完成,称为内部迭代(集合使用iterator()称为外部迭代) stream.forEach(System.out::println); } @Test public void test02() { //需求2:过滤掉小于5000的学生对象,并显示3条 //注意:因为限制了数据条数,所以满足数据条数后,后续的操作就不再运行了,效率就提高了 Stream<Student> stream = stuList.stream().filter((x)-> { System.out.println("短路"); return x.getSalary()>5000; }).limit(3); //迭代输出流里的数据就等同于终止操作 //迭代功能在forEach()中完成,称为内部迭代(集合使用iterator()称为外部迭代) stream.forEach(System.out::println); } @Test public void test03() { //需求3:过滤掉小于5000的学生对象,并跳过第1个学生对象 Stream<Student> stream = stuList.stream(). filter((x)-> x.getSalary()>5000). skip(1); //迭代输出流里的数据就等同于终止操作 //迭代功能在forEach()中完成,称为内部迭代(集合使用iterator()称为外部迭代) stream.forEach(System.out::println); } @Test public void test04() { //需求4:过滤掉小于5000的学生对象,并筛选掉重复元素 //Stream底层通过元素对象(Student对象)的hashCode()和equals()方法去除重复元素 Stream<Student> stream = stuList.stream(). filter((x)-> x.getSalary()>5000). distinct(); //迭代输出流里的数据就等同于终止操作 //迭代功能在forEach()中完成,称为内部迭代(集合使用iterator()称为外部迭代) stream.forEach(System.out::println); } } enum Course{//课程枚举 JAVA,HTML,PYTHON; } class Student{//学生类 private String name; private int age; private double salary; private Course course; ... }
方法 | 描述 |
---|---|
map(Function<?, ? > mapper) | 将流中所有元素映射成一个新的元素或者提取信息 |
flatMap(Function<?, ? extends Stream<? >> mapper) | 将流中的流整合(整合成平面/平铺流) |
public class Test1 { List<String> nameList = Arrays.asList("张三","李四","王五","赵六","孙七","吴八"); List<Student> stuList = Arrays.asList( new Student("张三", 28, 4800,Course.JAVA), new Student("李四", 36, 7200,Course.JAVA), new Student("王五", 19, 9600,Course.HTML), new Student("赵六", 42, 6100,Course.HTML), new Student("孙七", 23, 9600,Course.PYTHON), new Student("吴八", 31, 3000,Course.PYTHON), new Student("李四", 36, 7200,Course.JAVA)); @Test public void test01() { //map() - 将流中所有元素映射成一个新的元素 或者 提取信息 //方式1:映射成一个新的元素 //需求:将流中所有字符串拼接成新的字符串 nameList.stream().map((str)-> str.charAt(0)).forEach(System.out::println); //方式2:映射成提取信息 //需求:把原来流中的学生对象替换成学生姓名 stuList.stream().map((stu)-> stu.getName()).forEach(System.out::println); } @Test public void test02() { //带着需求学flatMap() //flatMap() - 将流中的流整合(整合成平面/平铺流) //需求:将nameList里的字符串转换为字符输出 //解决方案1:使用map()完成需求,可以看到流里包含另外的流,非常麻烦 Stream<Stream<Character>> stream = nameList.stream(). map(Test1::getCharacterStream);//{{'张','三'},{'李','四'},...} stream.forEach((sm) -> { sm.forEach(System.out::println); }); //解决方案2:使用flatMap()完成需求,将流中的流一并整合 nameList.stream().flatMap((str)-> getCharacterStream(str)). forEach(System.out::println);//{'张','三'},{'李','四'},... } //将字符串拆分出字符转换为流的方法 public static Stream<Character> getCharacterStream(String str){//"张三" ArrayList<Character> list = new ArrayList<>();//'张','三' for (Character c : str.toCharArray()) { list.add(c); } return list.stream(); } } enum Course{//课程枚举 JAVA,HTML,PYTHON; } class Student{//学生类 private String name; private int age; private double salary; private Course course; ... }
方法 | 解释 |
---|---|
sorted() | 使用元素原有排序规则 - Comparable |
sorted(Comparator<? super T> comparator) | 使用自定义排序规则 - Comparator |
public class Test1 { List<Student> stuList = Arrays.asList( new Student("张三", 28, 4800,Course.JAVA), new Student("李四", 36, 7200,Course.JAVA), new Student("王五", 19, 9600,Course.HTML), new Student("赵六", 42, 6100,Course.HTML), new Student("孙七", 23, 9600,Course.PYTHON), new Student("吴八", 31, 3000,Course.PYTHON), new Student("吴八", 31, 3000,Course.PYTHON)); @Test public void test01() { //使用元素原有排序规则(Comparable<T>) //需求:按照年龄排序 stuList.stream().sorted().forEach(System.out::println); } @Test public void test02() { //使用自定义排序规则(Comparator<T>) //需求:按照工资排序 stuList.stream().sorted((e1,e2)->{ if(e1.getSalary() == e2.getSalary()){ return 1; } return (int)(e1.getSalary() - e2.getSalary()); }).forEach(System.out::println); } } enum Course{//课程枚举 JAVA,HTML,PYTHON; } class Student implements Comparable<Student>{//学生类 private String name; private int age; private double salary; ... }
方法 | 描述 |
---|---|
allMatch(Predicate<? super T> predicate) | 检查是否匹配所有元素 |
anyMatch(Predicate<? super T> predicate) | 检查是否匹配至少一个元素 |
noneMatch(Predicate<? super T> predicate) | 检查是否没有匹配所有元素 |
findFirst() | 返回第一个元素 |
findAny() | 返回任意一个元素(但效果不好) |
count() | 返回流中元素的个数 |
max(Comparator<? super T> comparator) | 返回流中最大值 |
min(Comparator<? super T> comparator) | 返回流中最小值 |
public class Test1 { List<Student> stuList = Arrays.asList( new Student("张三", 28, 4800,Course.JAVA), new Student("李四", 36, 7200,Course.JAVA), new Student("王五", 19, 9600,Course.HTML), new Student("赵六", 42, 6100,Course.HTML), new Student("孙七", 23, 9600,Course.PYTHON), new Student("吴八", 31, 3000,Course.PYTHON), new Student("吴八", 31, 3000,Course.PYTHON)); @Test public void test01() { //需求1:检查流中所有元素是否匹配 工资>5000 boolean allMatch = stuList.stream().allMatch((stu) -> stu.getSalary()>5000); System.out.println(allMatch);//false //需求2:检查流中所有元素至少匹配一个 工资>5000 boolean anyMatch = stuList.stream().anyMatch((stu) -> stu.getSalary()>5000); System.out.println(anyMatch);//true //需求3:检查流中所有元素是否没有匹配 工资>5000 boolean noneMatch = stuList.stream().noneMatch((stu) -> stu.getSalary()>5000); System.out.println(noneMatch); //需求3:返回工资最高的学生信息 Optional<Student> findFirst = stuList.stream(). sorted((stu1,stu2)->Double.compare( stu1.getSalary(),stu2.getSalary())). findFirst(); Student stu = findFirst.get(); //这种写法防止NullPointerException出现 //Student stu = findFirst.orElse(new Student()); System.out.println(stu); //需求4:返回随机学生信息(但效果不好) Optional<Student> findAny = stuList.stream().findAny(); Student stu = findAny.get(); System.out.println(stu); //需求5:获取学生个数 long count = stuList.stream().count(); System.out.println(count); //需求6:获取最高工资的学生信息 Optional<Student> max = stuList.stream(). max((stu1,stu2)->Double.compare(stu1.getSalary(),stu2.getSalary())); System.out.println(max.get()); //需求7:获取最低工资的学生信息 Optional<Student> min = stuList.stream(). min((stu1,stu2)->Double.compare(stu1.getSalary(),stu2.getSalary())); System.out.println(min.get()); } } enum Course{//课程枚举 JAVA,HTML,PYTHON; } class Student implements Comparable<Student>{//学生类 private String name; private int age; private double salary; private Course course; ... }
归约:将流中的元素反复结合起来,得到一个值
map+reduce的连接通常称为map_reduce模式,因Google用它进行网络搜索而出名
方法 | 描述 |
---|---|
reduce( T identity , BinaryOperator accumulator) | 参数:(初始值,结合逻辑) |
reduce(BinaryOperator accumulator) | 参数:(结合逻辑) |
public class Test1 { List<Integer> numList = Arrays.asList(1,2,3,4,5,6,7,8,9,10); List<Student> stuList = Arrays.asList( new Student("张三", 28, 4800,Course.JAVA), new Student("李四", 36, 7200,Course.JAVA), new Student("王五", 19, 9600,Course.HTML), new Student("赵六", 42, 6100,Course.HTML), new Student("孙七", 23, 9600,Course.PYTHON), new Student("吴八", 31, 3000,Course.PYTHON), new Student("李四", 36, 7200,Course.JAVA)); @Test public void test01() { //需求:获取numList集合中元素的总和 Integer reduce = numList.stream(). reduce(0, (x,y)->x+y); System.out.println(reduce); } @Test public void test02() { //需求:获取stuList集合中所有学生工资总和 Optional<Double> reduce = stuList.stream(). map(Student::getSalary).reduce(Double::sum); Double sumSalary = reduce.get(); System.out.println(sumSalary); } } enum Course{//课程枚举 JAVA,HTML,PYTHON; } class Student implements Comparable<Student>{//学生类 private String name; private int age; private double salary; private Course course;
收集:将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
方法 | 描述 |
---|---|
collect(Collector<? super T, A, R> collector) | 把元素放入Collector集合中 |
public class Test1 { List<Student> stuList = Arrays.asList( new Student("张三", 28, 4800,Course.JAVA), new Student("李四", 36, 7200,Course.JAVA), new Student("王五", 19, 9600,Course.HTML), new Student("赵六", 42, 6100,Course.HTML), new Student("孙七", 23, 9600,Course.PYTHON), new Student("吴八", 31, 3000,Course.PYTHON), new Student("李四", 36, 7200,Course.JAVA)); @Test public void test01() { //把数据收集到集合中 //需求1:把当前学生姓名提取出来,并把数据放入List集合中 List<String> list = stuList.stream(). map(Student::getName). collect(Collectors.toList()); list.forEach(System.out::println); //需求2:把当前学生姓名提取出来,并把数据放入Set集合中 Set<String> set = stuList.stream(). map(Student::getName).collect(Collectors.toSet()); set.forEach(System.out::println); //需求3:把当前学生姓名提取出来,并把数据放入指定集合中 HashSet<String> hashSet = stuList.stream(). map(Student::getName).collect(Collectors.toCollection(HashSet::new)); hashSet.forEach(System.out::println); } @Test public void test02() { //收集流中的各种数据 //需求1:收集/获取学生个数 Long count = stuList.stream(). map(Student::getName).collect(Collectors.counting()); System.out.println(count); //需求2:收集/获取学生平均工资 Double avg = stuList.stream(). collect(Collectors.averagingDouble(Student::getSalary)); System.out.println(avg); //需求3:收集/获取学生总工资 Double sum = stuList.stream(). collect(Collectors.summingDouble(Student::getSalary)); System.out.println(sum); //需求4:收集/获取学生工资最大值 Optional<Double> max = stuList.stream().map(Student::getSalary). collect(Collectors.maxBy(Double::compareTo)); System.out.println(max.get()); //需求5:收集/获取学生工资最小值 Optional<Double> min = stuList.stream().map(Student::getSalary). collect(Collectors.minBy(Double::compareTo)); System.out.println(min.get()); //需求6:收集/获取工资最多的学生信息 Optional<Student> maxStu = stuList.stream(). collect(Collectors.maxBy( (stu1,stu2)-> (int)(stu1.getSalary()-stu2.getSalary()))); System.out.println(maxStu.get()); //需求7:收集/获取工资最少的学生信息 Optional<Student> minStu = stuList.stream(). collect(Collectors.minBy( (stu1,stu2)-> (int)(stu1.getSalary()-stu2.getSalary()))); System.out.println(minStu.get()); } @Test public void test03() {//分组 //需求:按照学科分组 Map<Course, List<Student>> map = stuList.stream().collect( Collectors.groupingBy(Student::getCourse)); System.out.println(map); } @Test public void test04() {//多级分组 //需求:按照学科分组,在按照年龄分组 Map<Course, Map<String, List<Student>>> map = stuList.stream(). collect(Collectors.groupingBy( Student::getCourse,Collectors.groupingBy((stu)->{ if(((Student)stu).getAge() < 28){ return "青年"; }else if(((Student)stu).getAge() < 40){ return "中年"; }else{ return "老年"; } }))); System.out.println(map); } @Test public void test05() {//分区 //需求:按照工资5000为标准分区 Map<Boolean, List<Student>> map = stuList.stream().collect( Collectors.partitioningBy((stu) -> stu.getSalary()>5000)); System.out.println(map); } @Test public void test06() {//获取元素中字段的各种信息 //需求:获取学生工资信息,再获取总值、平均值、最大值、最小值 DoubleSummaryStatistics collect = stuList.stream().collect( Collectors.summarizingDouble(Student::getSalary)); System.out.println(collect.getSum()); System.out.println(collect.getAverage()); System.out.println(collect.getMax()); System.out.println(collect.getMin()); } @Test public void test07() {//拼接信息 //需求:拼接学生姓名 String str1 = stuList.stream().map(Student::getName).collect( Collectors.joining()); System.out.println(str1); String str2 = stuList.stream().map(Student::getName).collect( Collectors.joining(",")); System.out.println(str2); String str3 = stuList.stream().map(Student::getName).collect( Collectors.joining(",","--","--")); System.out.println(str3); } } enum Course{//课程枚举 JAVA,HTML,PYTHON; } class Student implements Comparable<Student>{//学生类 private String name; private int age; private double salary; private Course course; ... }
并行流就是把一个内容拆分成多个数据块,并用不同的线程分别处理每个数据块的流。Java8中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API可以声明性地通过 parallel() - 并行流 与sequential()-顺序流 之间进行切换。
注意
- 默认为顺序流/串行流
- 并行流一般在大数据搜索里使用到
- JDK1.8之前也有并行流,叫做Fork/Join并行计算框架
public class Test1 { @Test public void test01() { //需求:使用并行流计算1-10000000L之和 OptionalLong reduce = LongStream. range(1, 10000000L).//生成1-10000000的数流 parallel(). //转换为并行流 reduce(Long::sum); //求和 System.out.println(reduce); } }
Optional类(java. util. Optional)是一个容器类,代表一个存在或不存在的值,原来用null表示一个值不存在,现在Optional可以更好的表达这个概念。并且可以避免空指针异常
此类的设计就是更好的避免空指针异常
方法 | 描述 |
---|---|
Optional.of(T t) | 创建一个Optional实例 |
Optional.empty() | 创建一 个空的 Optional实例 |
Optional.ofNullable(T t) | 若t不为null,创建Optional实例,否则创建空实例 |
get() | 获取Optional实例里的对象 |
isPresent() | 判断是否包含值 |
orElse(T t) | 如果调用对象包含值, 返回该值,否则返回t |
orElseGet(Supplier s) | 如果调用对象包含值,返回该值,否则返回s获取的值 |
map(Function f) | 如果有值对其处理,并返回处理后的Optional,否则返回optional. empty() |
flatMap(Function mapper) | 与map 类似,要求返回值必须是Optional |
public class Test1 { @Test public void test01() { //创建一个Optional实例,把对象封装到Optional容器里 // Optional<Student> op = Optional.of( // new Student("aaa", 26, 6666, Course.HTML)); //创建一个空的Optional容器 // Optional<Student> op = Optional.empty(); //创建一个Optional实例,若对象为null->创建空实例,若对象为不为null->创建Optional实例 Optional<Student> op = Optional.ofNullable( new Student("bbb", 26, 7777, Course.PYTHON)); //判断容器里是否包含对象 if(op.isPresent()){//包含 System.out.println("获取容器里的对象:" + op.get().getName()); }else{//不包含 System.out.println("容器里的对象为null"); } //如果容器里有对象就返回,否则就返回新对象 Student stu = op.orElse(new Student("ccc", 26, 8888, Course.JAVA)); System.out.println(stu.getName()); //不同情况下可以返回不同对象,orElseGet()比orElse()可变性更强 boolean bool = true; stu = op.orElseGet(()->{ if(bool){ return new Student("吴彦祖", 26, 8888, Course.JAVA); }else{ return new Student("麻生希", 26, 8888, Course.JAVA); } }); //获取原容器中的某个值并返回新容器中 //map(Function<? super T, ? extends U> mapper) Optional<String> map = op.map(Student::getName); System.out.println(map.get()); //与map 类似,要求返回值必须是Optional //flatMap(Function<? super T, Optional<U>> mapper) Optional<String> flatMap = op.flatMap((e)->Optional.of(e.getName())); System.out.println(flatMap.get()); } } enum Course{//课程枚举 JAVA,HTML,PYTHON; } class Student implements Comparable<Student>{//学生类 private String name; private int age; private double salary; private Course course; ... }
从JDK1.8开始,接口中可以有默认方法,既default修饰的方法,此方法可以让接口的实现类所调用,而接口中的静态方法直接用接口名调用即可
public class Test1 { @Test public void test01() { MyClass myClass = new MyClass(); myClass.defaultMethod(); I1.staticMethod(); } } interface I1{ default void defaultMethod(){ System.out.println("接口中的默认方法"); } public static void staticMethod(){ System.out.println("接口中的静态方法"); } } class MyClass implements I1{}
接口默认方法的”类优先”原则:
如果一个接口中定义了一个默认方法,而接口实现类的父类定义了一个同名的方法时,选择父类中的方法
接口冲突:如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突
public class Test1 { @Test public void test01() { MyClass myClass = new MyClass(); myClass.method(); } } interface I1{ default void method(){ System.out.println("I1接口中的默认方法"); } } class Father { public void method(){ System.out.println("Father类中的默认方法"); } } class MyClass extends Father implements I1 {}
public class Test1 { @Test public void test01() { MyClass myClass = new MyClass(); myClass.method(); } } interface I1{ default void method(){ System.out.println("I1接口中的默认方法"); } } interface I2{ default void method(){ System.out.println("I2接口中的默认方法"); } } class MyClass implements I1,I2 { @Override public void method() { //I1.super.method(); I2.super.method(); } }
JDK1.8提供的新日期类都是不可变的,既不管怎么样的改变,都会产生一个新的实例,他们都是线程安全的
日期组件遵循与IOS-8601世界时间标准
包路径 | 类名 | 描述 |
---|---|---|
java.time | 针对日期和时间操作的包 | |
LocalDate | 用于表示日期的类 | |
LocalTime | 用于表示时间的类 | |
LocalDateTime | 用于表示日期时间的类 | |
Instant | 时间戳类(1970.1.1 0:0:0 到现在的毫秒数) | |
Period | 两个日期间隔类 | |
Duration | 两个时间间隔类 | |
java.time.chrono | 针对日期时间特殊格式操作的包 | |
JapaneseChronology | 日本帝国历法系统类 | |
ThaiBuddhistChronology | 泰国佛教日历系统类 | |
java.time.format | 针对时间日期时间格式化操作的包 | |
DateTimeFormatter | 格式化日期时间类 | |
java.time.temporal | 针对时间矫正操作的包 | |
java.time.zone | 针对时区操作的包 |
public class Test1 { @Test public void test01() { //LocalDate LocalTime LocalDateTime //这三个日期类的使用大致一样 //获取当前日期时间对象 LocalDateTime ldt1 = LocalDateTime.now(); System.out.println(ldt1); //获取指定日期时间对象 LocalDateTime ldt2 = LocalDateTime.of(2020, 1, 23, 8, 30, 10, 10); System.out.println(ldt2); //获取ldt1推后的时间日期对象 LocalDateTime ldt3 = ldt1.plusYears(2); System.out.println(ldt3); //获取ldt1提前的时间日期对象 LocalDateTime ldt4 = ldt3.minusMonths(2); System.out.println(ldt4.getYear()); System.out.println(ldt4.getMonthValue()); System.out.println(ldt4.getDayOfMonth()); System.out.println(ldt4.getHour()); System.out.println(ldt4.getMinute()); System.out.println(ldt4.getSecond()); } @Test public void test02() { //使用时间戳(从1970年1月1日0:0:0到现在的毫秒值) //默认创建UTC(世界标准时间)时区的时间戳对象 Instant now1 = Instant.now();//2021-10-29T15:34:30 System.out.println(now1); //获取偏移8小时的偏移日期时间对象 OffsetDateTime odt = now1.atOffset(ZoneOffset.ofHours(8)); System.out.println(odt); //获取时间戳的毫秒值形式 System.out.println(now1.toEpochMilli()); //获取一个1970年1月1日0:0:0 往后退1秒的时间戳对象 Instant now2 = Instant.ofEpochSecond(1); System.out.println(now2); } @Test public void test03() throws InterruptedException { //Duration:时间间隔类 Instant now1 = Instant.now(); Thread.sleep(1000); Instant now2 = Instant.now(); //获取时间间隔类对象 Duration duration1 = Duration.between(now1, now2); System.out.println(duration1.toMillis()); System.out.println("-----------------------------"); LocalTime lt1 = LocalTime.now(); Thread.sleep(1000); LocalTime lt2 = LocalTime.now(); //获取时间间隔类对象 Duration duration2 = Duration.between(lt1, lt2); System.out.println(duration2.toMillis()); } @Test public void test04() throws InterruptedException { //Period:日期间隔类 LocalDate ld1 = LocalDate.now(); Thread.sleep(1000); LocalDate ld2 = LocalDate.of(2021, 12, 31); Period period = Period.between(ld1, ld2); System.out.println(period.getYears()); System.out.println(period.getMonths()); System.out.println(period.getDays()); } }
public class Test1 { @Test public void test01() { //格式化日期时间类 LocalDateTime ldt1 = LocalDateTime.now(); //获取本地标准的日期时间格式化对象 DateTimeFormatter dtf1 = DateTimeFormatter.ISO_LOCAL_DATE_TIME; String strDateTime1 = ldt1.format(dtf1);//格式化时间日期 System.out.println(strDateTime1); //自定义日期时间格式化对象 DateTimeFormatter dtf2 = DateTimeFormatter. ofPattern("yyyy年MM月dd日 HH:mm:ss"); String strDateTime2 = ldt1.format(dtf2);//格式化时间日期 System.out.println(strDateTime2); //将指定格式的字符串解析成LocalDateTime对象 LocalDateTime parse = LocalDateTime.parse("2020年03月12日 11:04:14", dtf2); System.out.println(parse); } }
public class Test1 { @Test public void test01() { //时间矫正器 LocalDateTime ldt1 = LocalDateTime.now(); //设置指定月份 LocalDateTime ldt2 = ldt1.withMonth(10); System.out.println(ldt2); //设置下一个周末 LocalDateTime ldt3 = ldt1.with(TemporalAdjusters.next(DayOfWeek.SUNDAY)); System.out.println(ldt3); //自定义时间矫正器:设置下一个工作 LocalDateTime ldt4 = ldt1.with((temporal)->{ LocalDateTime ldt = (LocalDateTime) temporal; DayOfWeek week = ldt.getDayOfWeek(); if(week.equals(DayOfWeek.FRIDAY)){//周五 return ldt.plusDays(3); }else if(week.equals(DayOfWeek.SATURDAY)){//周六 return ldt.plusDays(2); }else{ return ldt.plusDays(1); } }); System.out.println(ldt4); } }
public class Test1 { @Test public void test01() { //时区类 //获取所有时区字符串 Set<String> set = ZoneId.getAvailableZoneIds(); for (String str : set) { System.out.println(str); } //获取指定时区的日期时间对象 LocalDateTime ldt1 = LocalDateTime.now(ZoneId.of("Asia/Tokyo")); System.out.println(ldt1); //获取指定时区的日期时间对象 + 偏移量 LocalDateTime ldt2 = LocalDateTime.now(ZoneId.of("Asia/Tokyo")); ZonedDateTime zonedDateTime = ldt2.atZone(ZoneId.of("Asia/Tokyo")); System.out.println(zonedDateTime); } }
jdk1.8开始可以重复注解
ps:一个类可有多个同样的注解
import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.ElementType.TYPE_PARAMETER; import java.lang.annotation.Annotation; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; import org.junit.Test; //重复注解 @Author(name="何老师") @Author(name="苍老师") public class Test1<@Author(name="xxx") T> { @Test public void test01() throws Exception{ //需求1:获取Test1的多个作者注解 Class<?> ct = Test1.class; Author[] as = ct.getAnnotationsByType(Author.class); for (Author a : as) { System.out.println(a.name()); } //需求2:获取method方法中参数里的注解 Method m = ct.getMethod("method", String.class); Annotation[][] parameterAnnotations = m.getParameterAnnotations(); //获取参数中注解列表(有可能一个参数多个注解) for (Annotation[] annotations : parameterAnnotations) { for (Annotation an : annotations) {//获取具体注解 System.out.println(an); } } } public static void method(@Author(name="波老师") String str){} } //作者注解的容器 @Target(TYPE) @Retention(RetentionPolicy.RUNTIME) @interface Authors{ Author[] value(); } //作者注解 @Repeatable(Authors.class) //TYPE_PARAMETER-类型注解:作用泛型 @Target({TYPE, PARAMETER,TYPE_PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @interface Author{ String name(); } | #### 日期时间类、时间戳、间隔类 ```java public class Test1 { @Test public void test01() { //LocalDate LocalTime LocalDateTime //这三个日期类的使用大致一样 //获取当前日期时间对象 LocalDateTime ldt1 = LocalDateTime.now(); System.out.println(ldt1); //获取指定日期时间对象 LocalDateTime ldt2 = LocalDateTime.of(2020, 1, 23, 8, 30, 10, 10); System.out.println(ldt2); //获取ldt1推后的时间日期对象 LocalDateTime ldt3 = ldt1.plusYears(2); System.out.println(ldt3); //获取ldt1提前的时间日期对象 LocalDateTime ldt4 = ldt3.minusMonths(2); System.out.println(ldt4.getYear()); System.out.println(ldt4.getMonthValue()); System.out.println(ldt4.getDayOfMonth()); System.out.println(ldt4.getHour()); System.out.println(ldt4.getMinute()); System.out.println(ldt4.getSecond()); } @Test public void test02() { //使用时间戳(从1970年1月1日0:0:0到现在的毫秒值) //默认创建UTC(世界标准时间)时区的时间戳对象 Instant now1 = Instant.now();//2021-10-29T15:34:30 System.out.println(now1); //获取偏移8小时的偏移日期时间对象 OffsetDateTime odt = now1.atOffset(ZoneOffset.ofHours(8)); System.out.println(odt); //获取时间戳的毫秒值形式 System.out.println(now1.toEpochMilli()); //获取一个1970年1月1日0:0:0 往后退1秒的时间戳对象 Instant now2 = Instant.ofEpochSecond(1); System.out.println(now2); } @Test public void test03() throws InterruptedException { //Duration:时间间隔类 Instant now1 = Instant.now(); Thread.sleep(1000); Instant now2 = Instant.now(); //获取时间间隔类对象 Duration duration1 = Duration.between(now1, now2); System.out.println(duration1.toMillis()); System.out.println("-----------------------------"); LocalTime lt1 = LocalTime.now(); Thread.sleep(1000); LocalTime lt2 = LocalTime.now(); //获取时间间隔类对象 Duration duration2 = Duration.between(lt1, lt2); System.out.println(duration2.toMillis()); } @Test public void test04() throws InterruptedException { //Period:日期间隔类 LocalDate ld1 = LocalDate.now(); Thread.sleep(1000); LocalDate ld2 = LocalDate.of(2021, 12, 31); Period period = Period.between(ld1, ld2); System.out.println(period.getYears()); System.out.println(period.getMonths()); System.out.println(period.getDays()); } }
public class Test1 { @Test public void test01() { //格式化日期时间类 LocalDateTime ldt1 = LocalDateTime.now(); //获取本地标准的日期时间格式化对象 DateTimeFormatter dtf1 = DateTimeFormatter.ISO_LOCAL_DATE_TIME; String strDateTime1 = ldt1.format(dtf1);//格式化时间日期 System.out.println(strDateTime1); //自定义日期时间格式化对象 DateTimeFormatter dtf2 = DateTimeFormatter. ofPattern("yyyy年MM月dd日 HH:mm:ss"); String strDateTime2 = ldt1.format(dtf2);//格式化时间日期 System.out.println(strDateTime2); //将指定格式的字符串解析成LocalDateTime对象 LocalDateTime parse = LocalDateTime.parse("2020年03月12日 11:04:14", dtf2); System.out.println(parse); } }
public class Test1 { @Test public void test01() { //时间矫正器 LocalDateTime ldt1 = LocalDateTime.now(); //设置指定月份 LocalDateTime ldt2 = ldt1.withMonth(10); System.out.println(ldt2); //设置下一个周末 LocalDateTime ldt3 = ldt1.with(TemporalAdjusters.next(DayOfWeek.SUNDAY)); System.out.println(ldt3); //自定义时间矫正器:设置下一个工作 LocalDateTime ldt4 = ldt1.with((temporal)->{ LocalDateTime ldt = (LocalDateTime) temporal; DayOfWeek week = ldt.getDayOfWeek(); if(week.equals(DayOfWeek.FRIDAY)){//周五 return ldt.plusDays(3); }else if(week.equals(DayOfWeek.SATURDAY)){//周六 return ldt.plusDays(2); }else{ return ldt.plusDays(1); } }); System.out.println(ldt4); } }
public class Test1 { @Test public void test01() { //时区类 //获取所有时区字符串 Set<String> set = ZoneId.getAvailableZoneIds(); for (String str : set) { System.out.println(str); } //获取指定时区的日期时间对象 LocalDateTime ldt1 = LocalDateTime.now(ZoneId.of("Asia/Tokyo")); System.out.println(ldt1); //获取指定时区的日期时间对象 + 偏移量 LocalDateTime ldt2 = LocalDateTime.now(ZoneId.of("Asia/Tokyo")); ZonedDateTime zonedDateTime = ldt2.atZone(ZoneId.of("Asia/Tokyo")); System.out.println(zonedDateTime); } }
jdk1.8开始可以重复注解
ps:一个类可有多个同样的注解
import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.ElementType.TYPE_PARAMETER; import java.lang.annotation.Annotation; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; import org.junit.Test; //重复注解 @Author(name="何老师") @Author(name="苍老师") public class Test1<@Author(name="xxx") T> { @Test public void test01() throws Exception{ //需求1:获取Test1的多个作者注解 Class<?> ct = Test1.class; Author[] as = ct.getAnnotationsByType(Author.class); for (Author a : as) { System.out.println(a.name()); } //需求2:获取method方法中参数里的注解 Method m = ct.getMethod("method", String.class); Annotation[][] parameterAnnotations = m.getParameterAnnotations(); //获取参数中注解列表(有可能一个参数多个注解) for (Annotation[] annotations : parameterAnnotations) { for (Annotation an : annotations) {//获取具体注解 System.out.println(an); } } } public static void method(@Author(name="波老师") String str){} } //作者注解的容器 @Target(TYPE) @Retention(RetentionPolicy.RUNTIME) @interface Authors{ Author[] value(); } //作者注解 @Repeatable(Authors.class) //TYPE_PARAMETER-类型注解:作用泛型 @Target({TYPE, PARAMETER,TYPE_PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @interface Author{ String name(); }