Java基础学习

文章目录

Java基础学习

笔记后半部分(续)

第十七章 多线程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qaYubEzy-1573823584716)(C:\Users\Think-Pad\Desktop\java\并发与并行.jpg)]

17.1 继承thread
package cn.code;

import com.sun.corba.se.impl.activation.NameServiceStartThread;

/*
    创建线程的方式一:
    1. 创建:继承Thread + 重写 run
    2. 启动:创建子类对象 + start
 */
public class Demo015 extends Thread {
    //  线程入口点
    @Override
    public void run(){
        for (int i = 0; i < 200; i++) {
            System.out.println("一边听歌");
        }
    }

    public static void main(String[] args) {
        //  创建子类对象
        Demo015 dem = new Demo015();
        //  启动
        dem.start();
        dem.run();  //  普通方法调用
        for (int i = 0; i < 200; i++) {
            System.out.println("一遍coding");
        }

    }
}
17.2 图片下载
package cn.code.Demo015;

public class TDownloader extends Thread {
    private String name;  //  存储路径
    private String url;  //  存储名字

    public TDownloader(String name, String url) {
        this.name = name;
        this.url = url;
    }

    @Override
    public void run() {
        WedDownloader wd = new WedDownloader();
        wd.download(url,name);
    }

    public static void main(String[] args) {
        TDownloader td1 = new TDownloader("","");
        TDownloader td2 = new TDownloader("","");
        TDownloader td3 = new TDownloader("","");

        //  启动三个线程
        td1.start();
        td2.start();
        td3.start();
    }
   }

package cn.code.Demo015;

import java.io.FileNotFoundException;

/*

 */
public class WedDownloader {
    public void download(String url,String name){

    }
}

17.3 实现Runnable
package cn.code.Demo015;
/*
    创建线程的方式二:
    1. 创建:实现Runnable + 重写 run
    2. 启动:创建实现类对象 + Thread对象 + start
 */
public class StartRun implements Runnable {
    //  线程入口点
    @Override
    public void run(){
        for (int i = 0; i < 200; i++) {
            System.out.println("一边听歌");
        }
    }

    public static void main(String[] args) {
        //  创建实现类对象
        StartRun sr = new StartRun();
        //  创建代理类对象
        Thread t = new Thread(sr);
        //  启动
        t.start();  //  不能保证料立即运行 CPU 调用
        for (int i = 0; i < 200; i++) {
            System.out.println("一遍coding");
        }

    }
}
17.4 练习

抢票:

package cn.code.Demo015;
/*
    共享资源
 */
public class Web12306 implements Runnable{
    //  票数
    private int ticketNums = 999;

    @Override
    public void run() {
        while(true){
            if (ticketNums < 1){
                break;
            }try{
                Thread.sleep(200);
            }catch (Exception e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "-->" + ticketNums--);
        }
    }
    public static void main(String[] args){
        //  一份资源
        Web12306 web =new Web12306();
        System.out.println(Thread.currentThread().getName());
        //  多个代理
        new Thread(web,"码畜").start();
        new Thread(web,"码农").start();
        new Thread(web,"码蟥").start();
    }
}

龟兔赛跑:

package cn.code.Demo015;
/*
    模拟龟兔赛跑
 */
public class Racer implements Runnable {
    private static String winner;

    @Override
    public void run() {
        for (int steps = 0; steps <= 100; steps++) {
            //  模拟休息
            if (Thread.currentThread().getName().equals("rabbit") && steps % 10 ==0){
                try{
                    Thread.sleep(100);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + "-->" + steps);
            //  比赛是否结束
            boolean flag = gameOver(steps);
            if (flag){
                break;
            }
        }
    }
    private boolean gameOver(int steps){
        if (winner != null){  //  存在胜利者
            return true;
        }else{
            if (steps == 100){
                winner = Thread.currentThread().getName();
                System.out.println("winner = " + winner);
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Racer racer = new Racer();
        new Thread(racer,"tortoise").start();
        new Thread(racer,"rabbit").start();
    }
}
17.5 了解callable

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QrwY42Es-1573823584793)(C:\Users\Think-Pad\Desktop\java\callable.jpg)]


17.6 静态代理设计模式
package cn.code.Demo015;
/*
    静态代理:
    公共接口:
    1. 真实角色
    2. 代理角色
 */
public class StaticProxy {
    public static void main(String[] args) {
        new WeddingCompany(new You()).happyMarry();

        //  new Throw(线程对象).start();
    }
}
interface Marry{
    void happyMarry();
}
//  真实角色
class You implements Marry{
    @Override
    public void happyMarry() {
        System.out.println("you and 嫦娥终于奔月了...");
        }
    }
//  代理角色
class WeddingCompany implements Marry{
    //  真实角色
    private Marry target;

    public WeddingCompany(Marry target) {
        this.target = target;
    }

    @Override
    public void happyMarry() {
        ready();
        this.target.happyMarry();
        after();

    }

    private void ready() {
        System.out.println("布置猪窝...");
    }

    private void after() {
        System.out.println("闹玉兔...");
    }
}
17.7 推导Lamda——简化线程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p8WbiA10-1573823584794)(C:\Users\Think-Pad\Desktop\java\函数式编程思想.jpg)]


package cn.code.Demo016;
/*
    Lambda表达式  简化线程(用一次)的使用
    Lambda推导必须存在类型
     */
public class LambdaThread {
    //  静态内部类
    static class Test implements Runnable {
        public void run() {
            for (int i = 0; i < 200; i++) {
                System.out.println("一边听歌");
            }
        }
    }

    public static void main(String[] args) {
//        new Thread(new Test()).start();
        //  匿名内部类
        class Test2 implements Runnable {
            public void run() {
                for (int i = 0; i < 200; i++) {
                    System.out.println("一边听歌");
                }
            }
        }
        new Thread(new Test2()).start();

        //  匿名内部类  必须借助接口或者父类
        new Thread(new Runnable(){
            public void run() {
                for (int i = 0; i < 200; i++) {
                    System.out.println("一边听歌");
                }
            }
        }).start();

        //  jdk8 简化  lambda表达式
        new Thread(() -> {
                for (int i = 0; i < 200; i++) {
                    System.out.println("一边听歌");
                }
            }
        ).start();
    }
}

package cn.code.Demo016;
/*
    Lambda推导 + 参数
 */
public class LambdaTest01 {
    public static void main(String[] args) {
        ILove love = (int a) -> {
            System.out.println("i like lambda -->" + a);
        };

        love.lambda(100);

        //  简化
        love = a -> {
            System.out.println("i like lambda -->" + a);
        };
        love.lambda(10);

        love = a -> System.out.println("i like lambda -->" + a);
        love.lambda(0);
    }
    interface ILove{
        void lambda(int a);
    }
    //  外部类
    class Love implements ILove{

        @Override
        public void lambda(int a) {
            System.out.println("i like lambda -->" + a);
        }
    }
}

package cn.code.Demo016;
/*
    Lambda推导 + 参数
 */
public class LambdaTest02 {
    public static void main(String[] args) {
        IInterst inters = (int a,int b) -> {
            System.out.println("i like lambda -->" + (a + b));
            return a + b;
        };
        inters.lambda(12,6);

        inters = (a,b) -> {
            System.out.println("i like lambda -->" + (a + b));
            return a + b;
        };
        inters.lambda(54,8);

        inters = (a,b) -> a + b;
    }
    interface IInterst{
        int lambda(int a,int b);
    }
    //  外部类
    class interst implements IInterst{

        @Override
        public int lambda(int a, int b) {
            System.out.println("i like lambda -->" + (a + b));
            return a + b;
        }
    }
}
17.8 线程状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g8MQmUFu-1573823584808)(C:\Users\Think-Pad\Desktop\java\线程状态.jpg)]


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5CEbdwpB-1573823584810)(C:\Users\Think-Pad\Desktop\java\线程状态01.jpg)]


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S0jAbPBi-1573823584811)(C:\Users\Think-Pad\Desktop\java\线程方法.jpg)]

17.9 终止
package cn.code.Demo016;
/*
    终止线程:
    1. 线程正常执行完毕 --> 次数
    2. 外部干涉 --> 加入标识
    不要使用 stop destroy
 */
public class TerminateThread implements Runnable{
    //  1. 加入标识  标记线程体是否可以运行
    private boolean flag = true;
    private String name;

    public TerminateThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        int i = 0;
        //  2. 关联标识,true --> 运行 false --> 停止
        while (flag){
            System.out.println(name + "-->" + i++);
        }
    }
    // 3.对外提供方法改变标识
    public void terminate(){
        this.flag = false;
    }

    public static void main(String[] args) {
        TerminateThread tt = new TerminateThread("C罗");
        new Thread(tt).start();
        for (int i = 0; i < 99; i++) {
            if (i == 88){
                tt.terminate();  //  线程的终止
                System.out.println("tt game over");
            }
            System.out.println("main -->" +i);
        }
    }
}
17.10 暂停sleep
package cn.code.Demo016;
import java.text.SimpleDateFormat;
import java.util.Date;
/*
    sleep模拟网络延时,放大了发生问题的可能性
    sleep模拟倒计时
 */
public class BlockedSleep {
    public static void main(String[] args) throws InterruptedException {
        //  倒计时
        Date endTime = new Date(System.currentTimeMillis() + 1000*10);
        long end = endTime.getTime();
        while (true){
            System.out.println(new SimpleDateFormat("HH:mm:ss").format(endTime));
            Thread.sleep(1000);
            endTime = new Date(endTime.getTime() - 1000);
            if (end - 10000 > endTime.getTime()){
                break;
            }
        }
    }
    public static void test() throws InterruptedException {
        //  倒数10个数,1秒一个
        int num = 10;
        while(true){
            Thread.sleep(1000);
            System.out.println(num--);
        }
    }
}
17.11 礼让 yield
package cn.code.Demo016;
/*
    yield 礼让线程,暂停线程,直接进入就绪状态不是阻塞状态
 */
public class YieldDemo02 {
    public static void main(String[] args) {
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                System.out.println("lambda..." + i);
            }
        }).start();

        for (int i = 0; i < 100; i++) {
            if (i % 20 == 0){
                Thread.yield();  //  main礼让
            }
            System.out.println("main..." + i);
        }
    }
}
17.12 插队 join
package cn.code.Demo016;

public class BlockedJoin01 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                System.out.println("lambda..." + i);
            }
        });
        t.start();

        for (int i = 0; i < 100; i++) {
            if (i == 20){
                t.join();  //  插队  main函数被阻塞了
            }
            System.out.println("main..." + i);
        }
    }
}
17.13 优先级
package cn.code.Demo016;
/*
    线程的优先级 1~10
    1. NORM_PRIORITY    5
    2. MIN_PRIORITY     1
    3. MAX_PRIORITY     10
    概率,不代表绝对的先后顺序
 */
public class PriorityTest {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getPriority());

        MyPriority mp = new MyPriority();
        Thread t1 = new Thread(mp,"adidas");
        Thread t2 = new Thread(mp,"NIKE");
        Thread t3 = new Thread(mp,"回力");
        Thread t4 = new Thread(mp,"李宁");

        //  设置优先级在启动前
        t1.setPriority(Thread.MAX_PRIORITY);  //  可以设置数字
        t2.setPriority(Thread.MIN_PRIORITY);
        t3.setPriority(Thread.MAX_PRIORITY);
        t4.setPriority(Thread.MIN_PRIORITY);

        t1.start();
        t2.start();
        t3.start();
        t4.start();

    }
    static class MyPriority implements Runnable{

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
            Thread.yield();
        }
    }
}
17.14 守护线程
package cn.code.Demo016;
/*
    守护线程:是为用户线程服务的;jvm停止不用等待守护线程执行完毕
    默认:用户线程jvm等待用户线程执行完毕才会停止
 */
public class DaemonTest {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();
        Thread t = new Thread(god);
        t.setDaemon(true);  //  将用户线程调整为守护   虚拟机不用等待它执行完
        t.start();
        new Thread(you).start();
    }

    static class You implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 365 * 100; i++) {
                System.out.println("happy life...");
            }
            System.out.println("ooooooooo...");
        }
    }

    static class God implements Runnable{
        @Override
        public void run() {
            for (;true;){
                System.out.println("bless you");
            }
        }
    }
}
17.15 基本信息
package cn.code.Demo016;
/*
    其他方法
    isAlive:线程是否还活着
    Thread.currentThread():当前线程
    setName、getName:代理名称
 */
public class InfoTest {
    public static void main(String[] args) throws InterruptedException {
        System.out.println(Thread.currentThread().isAlive());
        //  设置名称:真实角色 + 代理角色
        MyInfo info = new MyInfo("战斗机");
        Thread t = new Thread(info);
        t.setName("公鸡");
        t.start();
        t.sleep(1000);
        System.out.println(t.isAlive());
    }
    static class MyInfo implements Runnable{
        private String name;

        public MyInfo(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "-->" + name);
        }
    }
}
17.16 线程安全性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-23WyfwCr-1573823584814)(C:\Users\Think-Pad\Desktop\java\多线程.jpg)]


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WcNKywa0-1573823584815)(C:\Users\Think-Pad\Desktop\java\多线程安全性.jpg)]


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gy3KgSdX-1573823584837)(C:\Users\Think-Pad\Desktop\java\线程安全问题.jpg)]

17.17 解决线程安全问题——同步代码块
package cn.code.Demo017;
/*
    模拟卖票案例
    创建3个线程,同时开启,对共享的票进行出售
 */
public class Demo01Ticket {
    public static void main(String[] args) {
        //  创建Runnable接口的实现类对象
        RunnableImple run= new RunnableImple();
        //  创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //  调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }
}

package cn.code.Demo017;
/*
    卖票案例出现了线程安全问题
    卖出了不存在的票和重复的票

    解决线程安全问题的一种方案:使用同步代码块
    格式:
        synchronized(锁对象){
            可能会出现线程安全问题的代码(访问了共享数据的代码)
        }

    注意:
        1. 通过代码块中的锁对象,可以使用任意的对象
        2. 但是必须保证多个线程使用的锁对象是同一个
        3. 锁对象作用:
            把同步代码块锁住,只让一个线程在同步代码块中执行
 */
public class RunnableImple implements Runnable {
    //  定义一个多线程共享的票源
    private int ticket = 100;

    //  创建一个锁对象
    Object obj = new Object();

    //  设置线程任务:卖票
    @Override
    public void run() {
        //  使用死循环,让卖票操作重复执行
        while(true){
            //  同步代码块
            synchronized(obj){
                //  先判断票是否存在
                if(ticket > 0){
                    //  提高安全问题出现的概率,让程序睡眠
                    try{
                        Thread.sleep(10);

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    //  票存在,卖票  titck--
                    System.out.println(Thread.currentThread().getName() + "--> 正在卖第" + ticket + "张票");
                    ticket--;
                        }
                 }
            }
        }
    }
同步技术的原理:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NTgVFhr0-1573823584839)(C:\Users\Think-Pad\Desktop\java\同步技术的原理.jpg)]

解决线程的安全问题——同步方法:
package cn.code.Demo017;
/*
    模拟卖票案例
    创建3个线程,同时开启,对共享的票进行出售
 */
public class Demo01Ticket {
    public static void main(String[] args) {
        //  创建Runnable接口的实现类对象
        RunnableImple01 run= new RunnableImple01();
        //  创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //  调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }
}

package cn.code.Demo017;
/*
    解决线程安全问题的第二种方案:使用同步方法
    使用步骤:
        1. 把访问了共享数据的代码抽取出来,放到一个方法中
        2. 在方法上添加synchronized修饰符

    格式:定义方法的格式
    修饰符 synchronized 返回值类型  方法名(参数列表){
        可能会出现线程安全问题的代码(访问了共享数据的代码)
    }
 */
public class RunnableImple01 implements Runnable {
    //  定义一个多线程共享的票源
    private int ticket = 100;

    //  设置线程任务:卖票
    @Override
    public void run(){
        while (true){
            payTicket();
        }
    }

    /*
        定义一个同步方法
        同步方法也会把方法内部的代码锁住
        只让一个线程执行
        同步方法的锁对象是谁?
        就是实现类对象  new RunnableImple01()
        也就是 this
     */
    public synchronized void payTicket(){
        //  先判断票是否存在
        if(ticket > 0){
            //  提高安全问题出现的概率,让程序睡眠
            try{
                Thread.sleep(10);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //  票存在,卖票  titck--
            System.out.println(Thread.currentThread().getName() + "--> 正在卖第" + ticket + "张票");
            ticket--;
        }
    }
}
静态的同步方法:

锁对象是谁?

不能是this

this是创建对象之后产生的,静态方法优先于对象

静态方法的锁对象是本类的class属性 --> class文件对象(反射)

17.18 解决线程安全问题——Lock锁
package cn.code.Demo017;
/*
    模拟卖票案例
    创建3个线程,同时开启,对共享的票进行出售
 */
public class Demo01Ticket {
    public static void main(String[] args) {
        //  创建Runnable接口的实现类对象
        RunnableImpl02 run= new RunnableImpl02();
        //  创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //  调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }
}

package cn.code.Demo017;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/*
    解决线程安全问题的三种方案:使用Lock锁
    java.util.concurrent.locks.lock接口
    lock 实现提供了比使用 synchronized  方法和语句获得的更广泛的锁定操作。
    Lock接口中的方法:
        void lock() 获取锁
        void unlock() 释放锁
    java.util.concurrent.locks.lock.ReentrantLock implements lock接口


    使用步骤:
        1. 在成员位置创建一个ReentrantLock 对象
        2. 在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
        3. 在可能会出现安全问题的代码后调用Lock接口中的方法unLock释放锁
 */
public class RunnableImpl02 implements Runnable {
    //  定义一个多线程共享的票源
    private int ticket = 100;

    //  1. 在成员位置创建一个ReentrantLock 对象
    Lock l = new ReentrantLock();

    @Override
    public void run() {
        //  使用死循环,让卖票操作重复执行
        while (true) {
            //  2. 在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
            l.lock();

            //  先判断票是否存在
            if (ticket > 0) {
                //  提高安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                    //  票存在,卖票  titck--
                    System.out.println(Thread.currentThread().getName() + "--> 正在卖第" + ticket + "张票");
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //  3. 在可能会出现安全问题的代码后调用Lock接口中的方法unLock释放锁
                    l.unlock();  //  无论程序是否异常,都会把锁释放(能提高程序的效率)
                }
            }

        }

   /* @Override
    public void run() {
        //  使用死循环,让卖票操作重复执行
        while (true) {
            //  2. 在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
            l.lock();

            //  先判断票是否存在
            if (ticket > 0) {
                //  提高安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //  票存在,卖票  titck--
                System.out.println(Thread.currentThread().getName() + "--> 正在卖第" + ticket + "张票");
                ticket--;
            }
            //  3. 在可能会出现安全问题的代码后调用Lock接口中的方法unLock释放锁
            l.unlock();
        }*/
    }
}
17.19 等待唤醒机制

Object类中wait带参数方法和notifyAll方法:

进入到TimeWaiting(计时器)有两种方式

  1. 使用sleep(Long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态

  2. 使用wait(Long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Block状态


package cn.code.Demo018;
/*
    测试类:
    包含main方法,程序执行的入口,启动程序
    创建包子类对象;
    创建包子铺线程,开启,生产包子;
    创建吃货线程,开启,吃包子;
 */
public class Demo {
    public static void main(String[] args) {
        //  创建包子类对象
        BaoZi bz = new BaoZi();
        //  创建包子铺线程,开启,生产包子
        new Thread(new BaoZiPu(bz)).start();
        //  创建吃货线程,开启,吃包子
        new Thread(new ChiHuo(bz)).start();
    }
}

package cn.code.Demo018;
/*
    资源类:包子类
    设置包子的属性
        皮
        馅
        包子状态:有 true ,没有 false
 */
public class BaoZi {
    //  皮
    String pi;
    //  馅
    String xian;
    //  包子的状态: 有 true ,没有 false
    boolean flag = false;
}

package cn.code.Demo018;
/*
    生产者(包子铺)类:是一个线程类,可以继承Thread  实现接口
    设置线程任务(run):生产包子
    对包子的状态尽心判断
    true:有包子
        包子铺调用wait方法进入等待状态
    false:没有包子
         包子铺生产包子
         增加一些趣味性:交替生产两种包子
            有两种状态(i % 2 == 0)
         包子铺生产好了包子
         修改包子的状态为true 有
         唤醒吃货线程,让吃货线程吃包子

    注意:
        包子铺线程和包子线程关系 --> 通信(互斥)
        必须同时同步技术保证两个线程只能有一个在执行
        锁对象必须保证唯一,可以使用包子对象作为锁对象
        包子铺类和吃货的类就需要把包子对象作为参数传递过来
            1. 需要在成员位置创建一个包子变量
            2. 使用带参数构造方法,为这个包子变量赋值
 */
public class BaoZiPu implements Runnable {
    //  1. 需要在成员位置创建一个包子变量
    private BaoZi bz;

    //  2. 使用带构造方法,为这个包子变量赋值
    public BaoZiPu(BaoZi bz) {
        this.bz = bz;
    }

    //  设置线程任务(run):生产包子
    @Override
    public void run() {
        int count = 0;
        //  让包子铺一直生产包子
        while(true){
            //  必须同时同步技术保证两个线程只能有一个在执行
            synchronized (bz){
                //  对包子的状态进行判断
                if (bz.flag == true){
                    //  包子铺调用wait方法进入等待状态
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                //  唤醒之后执行,包子铺生产包子
                //  增加一些趣味性:交替生产两种包子
                if (count % 2 == 0){
                    //  生产  薄皮三鲜馅包子
                    bz.pi = "薄皮";
                    bz.xian = "三鲜馅";
                }else {
                    //  生产  冰皮  牛肉大葱馅
                    bz.pi = "冰皮";
                    bz.xian = "牛肉大葱馅";
                }
                count++;
                System.out.println("包子铺正在生产:" + bz.pi + bz.xian + "包子");
                //  生产包子需要3秒钟
                try{
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //  包子铺生产好了包子
                //  修改包子的状态为true有
                bz.flag = true;
                //  唤醒吃货线程,让吃货线程吃包子
                bz.notify();
                System.out.println("包子铺已经生产好了:" + bz.pi + bz.xian + "包子,吃货可以开始吃了");
            }
        }

    }
}

package cn.code.Demo018;
/*
    消费者(吃货):是一个线程类,可以继承Thread
    设置线程任务(run):吃包子
    对包子的状态进行判断
    false:没有包子
        吃货调用wait方法进入等待状态
    true: 有包子
        吃货吃包子
        吃货吃完包子
        修改包子的状态为false没有
        吃货唤醒包子铺线程,生产包子
 */
public class ChiHuo implements Runnable {
    //  1. 需要在成员位置创建一个包子变量
    private BaoZi bz;

    //  2. 使用带参数构造方法,为这个包子变量赋值
    public ChiHuo(BaoZi bz) {
        this.bz = bz;
    }

    //  设置线程任务(run):吃包子
    @Override
    public void run() {
        //  使用死循环,让吃货一直吃包子
        while (true) {
            //  必须同时同步技术保证两个线程只能有一个在执行
            synchronized (bz) {
                //  对包子的状态进行判断
                if (bz.flag == false) {
                    //  包子铺调用wait方法进入等待状态
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                //  被唤醒之后执行代码,吃包子
                System.out.println("吃货正在吃:" + bz.pi + bz.xian + "的包子");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //  吃货吃完包子
                //  修改包子的状态为false没有
                bz.flag = false;
                //  吃货唤醒包子铺线程,生产包子
                bz.notify();
                System.out.println("吃货已经把:" + bz.pi + bz.xian + "的包子吃完了,包子铺开始生产包子");
                System.out.println("=======================================");
            }

        }
    }
}
17.20 线程的状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eC7XUWAE-1573823584840)(C:\Users\Think-Pad\Desktop\java\线程的状态.jpg)]

17.21 线程池的原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Ggpf1Zm-1573823584842)(C:\Users\Think-Pad\Desktop\java\线程池原理.jpg)]


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EStSqjBW-1573823584868)(C:\Users\Think-Pad\Desktop\java\线程池原理01.jpg)]


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BTsXE2GI-1573823584878)(C:\Users\Think-Pad\Desktop\java\线程池的好处.jpg)]


17.22 线程池的代码实现
package cn.code.Demo019;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/*
    线程池:JDK1.5 之后提供的
    java.util.concurrent.Executors: 线程池的工厂类,用来生成线程池
    Executors类中的静态方法:
        static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重复用固定线程数的线程池
        参数:
            int nThreads:创建线程池中包含的线程数量
        返回值:
            ExecutorService接口,返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程)
    java.util.concurrent.ExecutorService: 线程池接口
        用来从线程池中获取线程,调用start方法,执行线程任务
            submit(Runnable task) 提交一个 Runnable  任务用于执行
        关闭/销毁线程池的方法
            void shutdown()
    线程池的使用步骤:
        1. 使用线程池的工厂类Executors里边提供的静态方法  newFixedThreadPool  生产一个指定数量的线程池
        2. 创建一个类,实现 Runnable 接口,重写run方法,设置线程任务
        3. 调用ExecutorService中的方法submit,传递线程任务(实现类), 开启线程,执行run方法
        4.  调用ExecutorService中的方法shutdown销毁线程池(不建议执行)
 */
public class Demo01ThreadPool {
    public static void main(String[] args) {
        //  1. 使用线程池的工厂类Executors里边提供的静态方法  newFixedThreadPool  生产一个指定数量的线程池
        ExecutorService es = Executors.newFixedThreadPool(2);
        //  3. 调用ExecutorService中的方法submit,传递线程任务(实现类), 开启线程,执行run方法
        es.submit(new RunnableImpl());  //  pool-1-thread-1创建了一个新的线程执行
        //  线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程可以继续使用
        es.submit(new RunnableImpl());  //  pool-1-thread-1创建了一个新的线程执行
        es.submit(new RunnableImpl());  //  pool-1-thread-2创建了一个新的线程执行

        //  4.  调用ExecutorService中的方法shutdown销毁线程池(不建议执行)
        es.shutdown();

        es.submit(new RunnableImpl());  //  抛异常,线程池都没有了,就不能获取线程了
    }
}

package cn.code.Demo019;
/*
  2. 创建一个类,实现 Runnable 接口,重写run方法,设置线程任务
 */
public class RunnableImpl implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "创建了一个新的线程执行");
    }
}

第十八章 File类

18.1 File类的静态成员变量
package cn.code.Demo019;

import java.io.File;

/*
    java.io.File类
    文件和目录路径名的抽象表示形式。
    java把电脑中的文件和文件夹(目录)封装为了一个File类,我们可以使用File类对文件和文件夹进行操作
    我们可以使用File可的方法
        创建一个文件/文件夹
        删除文件/文件夹
        获取文件/文件夹
        判断文件/文件夹是否存在
        对文件夹进行遍历
        获取文件的大小
    File类是一个与系统无关的类,任何操作系统都可以使用这个类中的方法

    重点:记住这三个单词
        file:文件
        directory:文件夹/目录
        path:路径
 */
public class Demo01File {
    public static void main(String[] args) {
        /*
            static String pathSeparator 与系统有关的路径分隔符,为了方便,它被表示为一个字符串
            static char pathSeparatorChar  与系统有关的路径分隔符

            static String  separator  与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串
            static char separatorchar  与系统有关的默认名称分隔符

            操作路径:路径不能写死了
            c:\develop\a\a.txt  windows
            c:/develop/a/a.txt  linux
            "c:"+ File.separator +"develop"+ File.separator +"a"+ File.separator +"a.txt "
         */
        String pathSeparator = File.pathSeparator;
        System.out.println(pathSeparator);  //  路径分隔符 Windows:分号 ;   linux:冒号 :


        char separatorChar = File.separatorChar;
        System.out.println(separatorChar);  //  文件名称分隔符  Windows:反斜杠 \   linux:正斜杠  /
    }
}
18.2 File类的构造方法
package cn.code.Demo019;

import java.io.File;

/*
    路径:
        绝对路径:是一个完整的路径
            以盘符(c:,d:)开始的路径
                E:\\IdeaProjects\\basic-code
        相对路径:是一个简化的路径
            相对指的是相对于当前项目的根目录(E:\\IdeaProjects\\basic-code)
            如果使用当前项目的根目录,路径可以简化书写
            E:\\IdeaProjects\\basic-code\\123.txt  --> 简化为: 123.txt(可以省略项目的根目录)
        注意:
            1. 路径是不区分大小写
            2. 路径中的文件名分隔符Windows使用反斜杠,反斜杠是转义字符,两个反斜杠代表一个普通的反斜杠
 */
public class Demo02File {
    public static void main(String[] args) {
        /*
        File类的构造方法
     */
//        show02("c","123.txt");
//        show02("d","123.txt");
        show03();  //   c:\hello.java
    }
    /*
        File(String parent,String child) 根据  parent  路径名字符串和 child  路径名字符串创建一个新的  File实例
        参数:把路径分成了两个部分
            String parent: 父路径
            String child: 子路径
        好处:
            父路径和子路径,可以单独书写,使用起来非常灵活;父路径和子路径都可以变化
            父路径是File类型,可以使用File的方法对路径进行一些操作,再使用路径创建对象
     */
    private static void show03() {
        File parent = new File("c:\\");
        File file = new File(parent,"hello.java");
        System.out.println(file);
    }

    /*
        File(String parent,String child) 根据  parent  路径名字符串和 child  路径名字符串创建一个新的  File实例
        参数:把路径分成了两个部分
            String parent: 父路径
            String child: 子路径
        好处:
            父路径和子路径,可以单独书写,使用起来非常灵活;父路径和子路径都可以变化
     */
    private static void show02(String parent,String child) {
        File file = new File(parent,child);
        System.out.println(file);  
    }

    /*
        File(String pathname) 通过将给定路径字符串转换为抽象路径来创建一个新  File  实例
        参数:
            String pathanme: 字符串的路径名称
            路径可以是以文件结尾,也可以是以文件夹结尾
            路径可以相对路径,也可以是绝对路径
            路径可以是存在,也可以是不存在
            创建File对象,只是把字符串路径封装为File对象,不考虑路径的真假情况
     */
    private static void show01() {
        File f1 = new File("E:\\IdeaProjects\\basic-code\\123.txt");
        System.out.println(f1);  //  重写了Object类的toString方法  E:\IdeaProjects\basic-code\123.txt

        File f2 = new File("E:\\IdeaProjects\\basic-code");
        System.out.println(f2);  //  E:\IdeaProjects\basic-code

        File f3 = new File("123.txt");
        System.out.println(f3);  //  123.txt
    }
}
18.3 File类获取功能的方法
package cn.code.Demo019;

import java.io.File;

/*
    File类获取功能的方法
        public string getAbsolutePath(): 返回此File的绝对路径名字符串
        public string getPath(): 将此File转换为路径名字符串
        public string getName(): 返回从File表示的文件或目录的名称
        public long length(): 返回由此File表示的长度
 */
public class Demo03File {
    public static void main(String[] args) {
        show04();
    }
/*
     public long length(): 返回由此File表示的长度
     获取的是构造方法指定的文件大小,以字节为单位
     注意:
        文件夹没有大小的概念,不能获取问价的大小
        如果构造方法中给出的路径不存在,那么length方法返回0
 */
    private static void show04() {
        File f1 = new File("C:\\Users\\Think-Pad\\Desktop\\java\\if语句.jpg");
        long l1 = f1.length();
        System.out.println(l1);  //  14254

        File f2 = new File("C:\\Users\\Think-Pad\\Desktop\\java\\if.jpg");
        long l2 = f2.length();
        System.out.println(l2);  //  0

        File f3 = new File("C:\\AMD");
        long l3 = f3.length();
        System.out.println(l3);  //  0
    }

    /*
        public string getName(): 返回从File表示的文件或目录的名称
        获取的就是构造方法传递路径的结尾部分(文件/文件夹)
     */
    private static void show03() {
        File f1 = new File("E:\\IdeaProjects\\basic-code\\123.txt");
        String name1 = f1.getName();
        System.out.println(name1);  //  123.txt

        File f2 = new File("E:\\IdeaProjects\\basic-code");
        String name2 = f2.getName();
        System.out.println(name2);  //  basic-code
    }

    /*
        public string getPath(): 将此File转换为路径名字符串
        获取的构造方法中传递的路径

        toString方法调用的就是getPath方法
        源码:
            public String toString(){
                return getPath();
            }
     */
    private static void show02() {
        File f1 = new File("E:\\IdeaProjects\\basic-code\\123.txt");
        File f2 = new File("123.txt");
        String path1 = f1.getPath();
        String path2 = f2.getPath();
        System.out.println(path1);  //  E:\IdeaProjects\basic-code\123.txt
        System.out.println(path2);  //  123.txt

        System.out.println(f1);  //  E:\IdeaProjects\basic-code\123.txt
        System.out.println(f1.toString());  //  E:\IdeaProjects\basic-code\123.txt
    }

    /*
        public String getAbsolutePath(): 返回此File的绝对路径名字符串
        获取的构造方法中传递的路径
        无论路径是绝对的还是相对的,getAbsolutePath方法返回的都是绝对路径
     */
    private static void show01() {
        File f1 = new File("E:\\IdeaProjects\\basic-code\\123.txt");
        String absolutePath1 = f1.getAbsolutePath();
        System.out.println(absolutePath1);  //  E:\IdeaProjects\basic-code\123.txt

        File f2 = new File("123.txt");
        String absolutePath2 = f2.getAbsolutePath();
        System.out.println(absolutePath2);  //  E:\IdeaProjects\basic-code\123.txt
    }
}
18.4 File类判断功能的方法
package cn.code.Demo019;

import java.io.File;

/*
    File判断功能的方法
        public boolean exists(): 此File表示的文件或目录是否存在
        public boolean isDirectory(): 此File表示的是否为目录
        public boolean isFile(): 此File表示的是否为文件
 */
public class Demo04File {
    public static void main(String[] args) {
        show02();
    }
/*
    public boolean isDirectory(): 此File表示的是否为目录
        用于判断构造方法中给定的路径是否以文件夹结尾
            是:true
            否:false
    public boolean isFile(): 此File表示的是否为文件
        用于判断构造方法中给定的路径是否以文件结尾
            是:true
            否:false
    注意:
        电脑的硬盘中只有文件/文件夹,两个方法是互斥的
        这两个方法使用前提,路径必须是存在的,否则都返回false
 */
    private static void show02() {
        File f1 = new File("C:\\Users\\Think-Pad\\Desktop\\java\\if语句.jpg");

        //  不存在,就没有必要获取
        if (f1.exists()){
            System.out.println(f1.isDirectory());
            System.out.println(f1.isFile());
        }

        File f2 = new File("C:\\Users\\Think-Pad\\Desktop\\java");

        //  不存在,就没有必要获取
        if (f2.exists()){
            System.out.println(f2.isDirectory());
            System.out.println(f2.isFile());
        }
    }

    /*
        public boolean exists(): 此File表示的文件或目录是否存在
        用于判断构造方法中的路径是否存在
            存在:true
            不存在:false
     */
    private static void show01() {
        File f1 = new File("C:\\Users\\Think-Pad\\Desktop\\java\\if语句.jpg");
        System.out.println(f1.exists());  //  true

        File f2 = new File("C:\\Users\\Think-Pad\\Desktop\\java\\if.jpg");
        System.out.println(f2.exists());  //  false
    }
}
18.5 File类创建删除功能的方法
package cn.code.Demo019;

import java.io.File;
import java.io.IOException;

/*
    File类创建删除功能的方法
        public boolean createNewFile(): 当且仅当具有该名称的文件尚不存在在时,创建一个新的空文件
        public boolean delete(): 删除由此File表示的文件后或目录
        public boolean mkdir(): 创建由此File表示的目录
        public boolean mkdirs(): 创建由此File表示的目录,包括任何必须但不存在的父目录
 */
public class Demo05File {
    public static void main(String[] args) throws IOException {
        show03();
    }
/*
    public boolean delete(): 删除由此File表示的文件后或目录
    此方法,可以删除构造方法路径中给出的文件/文件夹
    返回值:布尔值
        true:文件/文件夹删除成功,返回true
        false:文件夹中有内容,不会删除返回false;构造方法中路径不存在false
    注意:
        delete方法是直接在硬盘删除文件/文件夹,不走回收站,删除更谨慎
 */
    private static void show03() {
        File f1 = new File("2.txt");
        boolean b1 = f1.delete();
        System.out.println("b1 = " + b1);
    }

    /*
        public boolean mkdir(): 创建单级空文件夹
        public boolean mkdirs(): 既可以创建单级空文件夹,也可以创建多级文件夹
        创建文件夹的路径和名称在构造方法中给出(构造方法的参数)
        返回值:
            true:文件夹不存在,创建文件夹,返回true
            false:文件夹存在,不会创建,返回false;构造方法中给出的路径不存在返回false
        注意:
            1. 此方法只能创建文件夹,不能创建文件
     */
    private static void show02() {
        File f1 = new File("aaa");
        boolean b1 = f1.mkdir();
        System.out.println("b1 = " + b1);

        File f2 = new File("E:\\IdeaProjects\\mmb\\bbb\\ccc");
        boolean b2 = f1.mkdirs();
        System.out.println("b2 = " + b2);
    }

    /*
        public boolean createNewFile(): 当且仅当具有该名称的文件尚不存在在时,创建一个新的空文件
        创建文件的路径和名称在构造方法中给出(构造方法的参数)
        返回值:布尔值
            true:文件不能存在,创建文件,返回true
            false:文件存在,不会创建,返回false
        注意:
            1. 此方法只能创建文件,不能创建文件夹
            2. 创建文件的路径必须存在,否则会抛出异常

        public boolean createNewFile()  throws IOException
        createNewFile声明抛出了IOException,我们调用这个方法,就必须的处理这个异常,要么throws,要么tryctch
     */
    private static void show01() throws IOException {
        File f1 = new File("E:\\IdeaProjects\\1.txt");
        boolean b1 = f1.createNewFile();
        System.out.println(b1);

        /*File f2 = new File("IdeaProjects\\2.txt");
        System.out.println(f2.createNewFile());*/  //  IOException
        File f3 = new File("basic-code\\新建文件夹1");
        System.out.println(f3.createNewFile());  //  不要被名称迷惑,要看类型
    }
}
18.6 File类遍历(文件夹)目录功能
package cn.code.Demo019;

import java.io.File;

/*
    File类遍历(文件夹)目录功能
        public String[] list(): 返回一个String数组,表示File目录中的所有子文件或目录
        public File[] listFiles(): 返回一个File数组,表示该File目录中的所有子文件或目录
    注意:
        list方法和listFiles方法遍历的是构造方法中给出的目录
        如果构造方法中给出的目录路径不存在,会抛出空指针异常
        如果构造方法中给出的目录路径不是一个目录,会抛出空指针异常
 */
public class Demo06File {
    public static void main(String[] args) {
        show02();
    }
/*
    public File[] listFiles(): 返回一个File数组,表示该File目录中的所有子文件或目录
    遍历构造方法中给出的目录,会获取目录中所有文件/文件夹的名称,把文件/文件夹封装为File对象,多个File对象存储到File数组中
 */
    private static void show02() {
        File file = new File("E:\\IdeaProjects");
        File[] files = file.listFiles();
        for (File file1: files){
            System.out.println("file1 = " + file1);  //   file1 = E:\IdeaProjects\1.txt   file1 = E:\IdeaProjects\basic-code
        }
    }

    /*
        public String[] list(): 返回一个String数组,表示File目录中的所有子文件或目录(可以查看被隐藏的文件和文件夹)
        遍历构造方法中给出的目录,会获取目录中所有文件/文件夹的名称,把获取到的多个名称存储到一个String类型的数组中
     */
    private static void show01() {
        File file = new File("E:\\IdeaProjects");
        String[] arr = file.list();
        for (String fileName: arr){
            System.out.println("fileName = " + fileName);  //  fileName = 1.txt    fileName = basic-code
        }
    }
}
18.7 递归概念&分类&注意事项
package cn.code.Demo020;
/*
    递归:方法自己调用自己
        递归的分类:
            递归分为两种,直接递归和间接递归
            直接递归称为方法自身调用自己
            间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法
        注意事项:
            递归一定要有条件限定,保证递归能够停下来,否则会发生栈内存溢出
            在递归中虽然有限定条件,但是递归次数不能太多,否则也会发生栈内存溢出
            构造方法,禁止递归
    递归的使用前提:
        当调用方法的时候,方法的主题不变,每次调用方法的参数不同,可以使用递归
 */
public class Demo01Recurison {
    public static void main(String[] args) {
        a();
    }
    /*
        递归一定要有条件限定,保证递归能够停下来,否则会发生栈内存溢出
        Exception in thread "main" java.lang.StackOverflowError
     */
    private static void a() {
        System.out.println("a方法!");
//        a();
        b(1);
    }
    /*
        构造方法,禁止递归
            编译报错:构造方法是创建对象使用的,一直递归会导致内存中有无数多个对象
     */
    public Demo01Recurison(){
        Demo01Recurison();
    }
/*
    在递归中虽然有限定条件,但是递归次数不能太多,否则也会发生栈内存溢出
    11159
    Exception in thread "main" java.lang.StackOverflowError
 */
    private static void b(int i) {
        System.out.println(i);
        if (i == 20000){
            return;  //  结束方法
        }
        b(++i);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DUic2ofd-1573823584880)(C:\Users\Think-Pad\Desktop\java\递归.jpg)]

18.7.1 使用递归计算1~n之间的和
package cn.code.Demo020;
/*
    练习:
        使用递归计算1~n之间的和
 */
public class Demo02Recurison {
    public static void main(String[] args) {
        int s = sum(36);
        System.out.println(s);
    }
    /*
        定义一个方法,使用递归计算1~n之间的和
        1+2+3+...+n
        n+(n-1)+(n-2)+...+1
        已知:
            最大值:n
            最小值:1
        使用递归必须明确:
            1. 递归的结束条件
                获取到1的时候
            2. 递归的目的
                获取下一个被加的数字(n-1)
     */
    public static int sum(int n){
        //  获取到1的时候结束
        if (n == 1){
            return 1;
        }
        //  获取下一个被加的数字
        return (n + sum(n-1));
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YLBUpMo7-1573823584896)(C:\Users\Think-Pad\Desktop\java\递归求和.jpg)]

18.7.2 使用递归计算阶乘
package cn.code.Demo020;
/*
    练习:
        使用递归计算阶乘
        n的阶乘:n! = n * (n-1) *... * 3 * 2 * 1
 */
public class Demo03Recurison {
    public static void main(String[] args) {
        int jiecheng = jc(5);
        System.out.println(jiecheng);  //  120
    }
    /*
        定义方法使用递归计算阶乘
        5的阶乘:5! = 5 * (5-1) *... * 3 * 2 * 1 = 5*4*3*2*1
        递归结束的条件
            获取到1的时候结束
        递归的目的
            获取下一个被乘的数字(n-1)
        方法的参数发生变化
            5,4,3,2,1
     */
    public static int jc(int n){
        //  获取到1的时候结束
        if (n == 1){
            return 1;
        }
        //  获取下一个被乘的数字(n-1)
        return (n * jc(n-1));
    }
}
18.7.3 递归打印多级目录
package cn.code.Demo020;

import java.io.File;

/*
    练习:递归打印多级目录
 */
public class Demo04Recurison {
    public static void main(String[] args) {
        getAllFile(new File("E:\\IdeaProjects"));
    }
    /*
        定义一个方法,参数传递File类型的目录
        方法中对目录进行遍历
     */
    public static void getAllFile(File dir){
        System.out.println(dir);
        File[] files = dir.listFiles();
        for (File f : files){
            //  对遍历得到的File对象f进行判断,判断是否是文件夹
            if (f.isDirectory()){
                //  f是一个文件夹,则继续遍历这个文件夹
                //  我们发现getAllFile方法就是传递文件夹,遍历文件夹的方法
                //  所以直接调用getAllFile方法即可:递归(自己调用自己)
                getAllFile(f);
            }else {
                System.out.println(f);
            }
        }
    }
}
18.7.4 综合案例:文件搜索
package cn.code.Demo020;

import java.io.File;

public class Demo04Recurison {
    public static void main(String[] args) {
        getAllFile(new File("E:\\IdeaProjects"));
    }
    /*
        定义一个方法,参数传递File类型的目录
        方法中对目录进行遍历
     */
    public static void getAllFile(File dir){
        System.out.println(dir);
        File[] files = dir.listFiles();
        for (File f : files){
            //  对遍历得到的File对象f进行判断,判断是否是文件夹
            if (f.isDirectory()){
                //  f是一个文件夹,则继续遍历这个文件夹
                //  我们发现getAllFile方法就是传递文件夹,遍历文件夹的方法
                //  所以直接调用getAllFile方法即可:递归(自己调用自己)
                getAllFile(f);
            }else {
                //  f是一个文件,直接打印即可
                /*
                    c:\\abc\\abc.java
                    只要.java结尾的文件
                    1. 把File对象f,转化为字符串对象
                 */
//                String name = f.getName();  //  abc.java
//                String path = f.getPath();  //  c:\\abc\\abc.java
//                String s = f.toString();  //  c:\\abc\\abc.java

                //  把字符串转换为小写
//                s.toLowerCase();

                //  2. 调用String类中的方法endswith判断字符串是否是以.java结尾
//                boolean b = s.endsWith(".java");

                //  3. 如果是以.java结尾的文件,则输出
//                if (b){
//                    System.out.println(f);
//                }
                if (f.getName().toLowerCase().endsWith(".java")){
                    System.out.println(f);
                }
            }
        }
    }
}
18.8 FileFilter过滤器的原理和使用
package cn.code.Demo020;

import java.io.File;

/*
    只要.java文件
    我们可以使用过滤器来实现
    在File类中有两个和listFiles重载的方法,方法的参数传递的就是过滤器
    File[] listFiles(FileFilter filter)
    java.io.FileFilter接口:用于抽象路径名(File对象)的过滤器
        作用:用来过滤文件(File对象)
        抽象方法:用来过滤文件的方法
            boolean accept(File pathname) 测试指定抽象路径是否应该包含在某个路径名列表中
            参数:
                File pathname:使用ListFiles方法遍历目录,得到的每一个文件对象
    File[] listFiles(FilenameFilter filter)
        作用:用来过滤文件名称
        抽象方法:用来过滤文件的方法
        boolean accept(File dir,String name) 测试指定文件是否应该包含在某一文件列表中
        参数:
            File dir:构造方法中传递的被遍历的目录
            String name:使用ListFiles方法遍历目录,获取到每一个文件/文件夹的名称
    注意:
        两个过滤器接口是没有实现类的,需要我们自己写实现类,重写过滤的方法accept,在方法中自己定义过滤的规则
 */
public class Demo01Fileter {
    public static void main(String[] args) {
        getAllFile(new File("E:\\IdeaProjects"));
    }
    public static void getAllFile(File dir) {
        File[] files = dir.listFiles(new FileFilterImpl());  //  传递过滤对象
        for (File f : files){
            if (f.isDirectory()){
                getAllFile(f);
            }else {
                System.out.println(f);
            }
        }
    }

}

package cn.code.Demo020;

import java.io.File;
import java.io.FileFilter;

/*
    创建过滤器FileFilter的实现类,重写过滤方法accept,定义过滤规则
 */
public class FileFilterImpl implements FileFilter {
    @Override
    public boolean accept(File pathname) {
        /*
            过滤规则:
            在accept方法中,判断File对象是否是以.java结尾
            是就返回true
            不是就返回false
         */
        //  如果pathname是一个文件夹,返回true,继续遍历这个文件夹
        if (pathname.isDirectory()){
            return true;
        }
        return pathname.getName().toLowerCase().endsWith(".java");
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-luFZ6Yxx-1573823584898)(C:\Users\Think-Pad\Desktop\java\过滤器.jpg)]

18.8.1 FilenameFilter过滤器的使用和lambda简化程序
package cn.code.Demo020;

import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;

public class Demo02Fileter {
    public static void main(String[] args) {
        getAllFile(new File("E:\\IdeaProjects"));
    }
    public static void getAllFile(File dir) {
        //  传递过滤对象  使用匿名内部类
        /*File[] files = dir.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                //  过滤规则,pathname是文件夹或者是.java结尾的文件返回true
                return pathname.isDirectory() || pathname.getName().toLowerCase().endsWith(".java");
            }
        } );*/

        //  使用lambda表达式简化匿名内部类(接口中只有一个抽象方法)
//        File[] files = dir.listFiles((File pathname) ->  pathname.isDirectory() || pathname.getName().toLowerCase().endsWith(".java"));

       /* File[] files = dir.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                //  过滤规则,pathname是文件夹或者是.java结尾的文件返回true
                return new File(dir,name).isDirectory() || name.toLowerCase().endsWith(".java");
            }
        });*/
        //  使用lambda表达式简化匿名内部类(接口中只有一个抽象方法)
        File[] files = dir.listFiles((d,name) ->  new File(dir,name).isDirectory() || name.toLowerCase().endsWith(".java"));
        for (File f : files){
            if (f.isDirectory()){
                getAllFile(f);
            }else {
                System.out.println(f);
            }
        }
    }

}

第十九章 IO流

19.1 IO的概念

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dJEZ4nA3-1573823584905)(C:\Users\Think-Pad\Desktop\java\IO概念.jpg)]


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ogQfq6Do-1573823584907)(C:\Users\Think-Pad\Desktop\java\FileOutputStream.jpg)]

19.2 字节输出流
19.2.0 字节输出流写多个字节的方法
package cn.code.Demo021;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;

/*
    一次写多个字节的方法:
        public void write(byte[] b): 将b.length字节从指定的字节数组写入此输出流
        public void write(byte[] b,int off ,int len): 从指定的字节数组写入len字节,从偏移量off开始输出到此输出流
 */
public class Demo02OutputStream {
    public static void main(String[] args) throws IOException {
        //  1. 创建一个FileOutputStream对象,构造方法中传递写入数据的目的地
        FileOutputStream fos = new FileOutputStream("E:\\IdeaProjects\\1.txt");
        //  2. 调用FileOutputStream对象中的方法write,把数据写入到文件中
        //  在文件中显示100,写个字节
        fos.write(49);
        fos.write(48);
        fos.write(48);
        /*
            public void write(byte[] b): 将b.length字节从指定的字节数组写入此输出流
            一次写多个字节:
                如果写的第一个字节正数(0~127),那么显示的时候会查询ASCII表
                如果写的第一个字节是负数,那第一个字节会和第二个字节,两个字节组成一个中文显示,查询系统默认码表(GBK)
         */
        byte[] bytes = {65,66,67,68,69};  //  ABCDE
//        byte[] bytes = {-65,-66,-67,68,69};  //  烤紻E
        fos.write(bytes);

        /*
           public void write(byte[] b,int off ,int len): 把字节数组的一部分写入到文件中
               int off : 数组的开始索引
               int len : 写几个字节
         */
        fos.write(bytes,1,2);  //  BC

        /*
            写入字符的方法:可以使用String类中的方法把字符串,转换为字节数组
                byte[] getBytes()  把字符串转换为字节数组
         */
        byte[] bytes2 = "你好".getBytes();
        System.out.println(Arrays.toString(bytes2));  //  [-28, -67, -96, -27, -91, -67]
        fos.write(bytes2);
        //  释放资源
        fos.close();
    }
}
19.2.1 字节输出流的续写和换行
package cn.code.Demo021;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/*
    追加写/续写:使用两个参数的构造方法
        FileOutStream(String name,boolean append)创建一个向具有指定 name 的文件中写入数据的输出文件流
        FileOutStream(File file,boolean append)创建一个向具有指定 file 对象表示的文件中写入数据的文件输出流
        参数:
            String name,File file: 写入数据的目的地
            boolean append:追加写开关
                true:创建对象不会覆盖源文件,继续在文件的末尾追加写数据
                false:创建一个新文件,覆盖源文件
    写换行:写换行符号
        windows: \r\n
        linux: /n
        mac: /r
 */
public class Demo03OutputStream {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("E:\\IdeaProjects\\2.txt",true);
        for (int i = 0; i < 10; i++) {
            fos.write("你好".getBytes());
            fos.write("\r\n".getBytes());
        }

        fos.close();
    }
}
19.2.2 字节输出流写入数据到文件
package cn.code.Demo021;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/*
    java.io.OutputStream: 字节输出流
        此抽象类是表示输出字节列的所有类的超类

    定义了一些子类共性的成员方法:
        public void close(): 关闭此输出流并释放与此流相关的任何系统资源
        public void flush(): 刷新此输出流并强制任何缓冲流的输出字节被写出
        public void write(byte[] b): 将b.length 字节从指定的字节数组写入此输出流
        public void write(byte[] b,int off,int len): 从指定的字节数组写入len字节,从偏移量 off开始输出到此输出流
        public abstract void write(int b): 将指定的字节输出流

    java.io.FileOutputStream extends OutputStream
    FileOutputStream: 文件字节输出流
    作用:把内存中的数据写入到硬盘的文件中

    构造方法:
        FileOutputStream(String name)创建一个向指定名称的文件中写入数据的输出文件流
        FileOutputStream(File file)创建一个向指定 File 对象表示的文件中写入数据的文件输出流
        参数:写入数据的目的
            String name:目的地是一个文件的路径
            File file:目的地是一个文件
        构造方法的作用:
            1. 创建一个FileOutputStream对象
            2. 会根据构造方法中传递的文件/文件路径,创建一个空的文件
            3. 会把FileOutputStream对象指向创建好的文件

    写入数据的原理(内存 -->  硬盘)
        java程序  --> JVM(java虚拟机) --> OS(操作系统) --> OS调用写数据的方法 --> 把数据写入到文件中

    字符输出流的使用步骤(重点):
        1. 创建一个FileOutputStream对象,构造方法中传递写入数据的目的地
        2. 调用FileOutputStream对象中的方法,把数据写入到文件中
        3. 释放资源(流使用会占用一定的内存,使用完毕要把内存清空,提供程序的效率)
 */
public class Demo01OutputStream {
    public static void main(String[] args) throws IOException {
        //  1. 创建一个FileOutputStream对象,构造方法中传递写入数据的目的地
        FileOutputStream fos = new FileOutputStream("E:\\IdeaProjects\\1.txt");
        //  2. 调用FileOutputStream对象中的方法write,把数据写入到文件中
        //  public abstract void write(int b): 将指定的字节输出流
        fos.write(85);
        //  3. 释放资源(流使用会占用一定的内存,使用完毕要把内存清空,提供程序的效率)
        fos.close();
    }
}
19.2.3 写出单个字符到文件
package cn.code.Demo021;

import java.io.FileWriter;
import java.io.IOException;

/*
    java.io.Writer: 字符输出流,是所有字符输出流的最高顶层的父类,是一个抽象类

    共性的成员方法:
        public write(int c)  写入单个字符
        void write(char[] cbuf)  写入字符数组
        abstract void write(char[] cbuf,int len)  写入字符串的某一部分,off字符串的开始索引,len写的字符个数
        void flush()  刷新该流的缓冲
        void close()  关闭此流 ,但要先刷新它

    java.io.FileWriter extends OutputStreamWriter extends Writer
    FileWriter: 文件字符输出流
    作用:把内存中的字符数据写入到文件中

    构造方法:
        FileWriter(File file) 根据给定的 File 对象构造一个 FileWriter 对象
        FileWriter(String fileName) 根据给定的文件名构造一个FileWriter 对象
        参数:写入数据的目的地
            String fileName: 文件的路径
            File file:是一个文件
        构造方法的作用:
            1. 会创建一个FileWriter对象
            2. 会根据构造方法中传递的文件/文件路径,创建文件
            3. 会把FileWriter对象指向创建好的文件

    字符输出流的使用步骤(重点):
        1. 创建FileWriter对象,构造方法中绑定要写入数据的目的地
        2. 使用FileWriter中的方法writer,把数据写入到内存的缓冲区中(字符转换为字节的过程)
        3. 使用FileWriter中的方法flush,把内存缓冲区中的数据,刷新到文件中
        4. 释放资源(会先把内存缓冲区中的数据刷新到文件中)
 */
public class Demo01Writer {
    public static void main(String[] args) throws IOException {
        //  1. 创建FileWriter对象,构造方法中绑定要写入数据的目的地
        FileWriter fw = new FileWriter("E:\\IdeaProjects\\1.txt");
        //  2. 使用FileWriter中的方法writer,把数据写入到内存的缓冲区中(字符转换为字节的过程)
        //  public write(int c)  写入单个字符
        fw.write('号');
        //  3. 使用FileWriter中的方法flush,把内存缓冲区中的数据,刷新到文件中
        fw.flush();
        //  4. 释放资源(会先把内存缓冲区中的数据刷新到文件中)
        fw.close();
    }
}
19.2.4 flush方法与close方法的区别
import java.io.FileWriter;
import java.io.IOException;
public class Demo01Writer {
    public static void main(String[] args) throws IOException {
        //  1. 创建FileWriter对象,构造方法中绑定要写入数据的目的地
        FileWriter fw = new FileWriter("E:\\IdeaProjects\\1.txt");
        //  2. 使用FileWriter中的方法writer,把数据写入到内存的缓冲区中(字符转换为字节的过程)
        //  public write(int c)  写入单个字符
        fw.write('号');
        //  3. 使用FileWriter中的方法flush,把内存缓冲区中的数据,刷新到文件中
        fw.flush();
        //  刷新之后流可以继续使用
        fw.write(98);
        //  4. 释放资源(会先把内存缓冲区中的数据刷新到文件中)
        fw.close();
        //  close方法之后,流已经关闭了,已经从内存中消失了,流就不能再使用了
        fw.write(99);  //  java.io.IOException

    }
}

19.2.5 字符输出流写数据的其他方法
package cn.code.Demo021;

import java.io.FileWriter;
import java.io.IOException;

/*
    字符输出流写数据的其他方法
        void write(char[] cbuf)  写入字符数组
        abstract void write(char[] cbuf,int off,int len)  写入字符串的某一部分,off字符串的开始索引,len写的字符个数
        void write(String str)  写入字符串
        void write(String str,int off , int len)  写入字符串的某一部分,off字符串的开始索引,len写的字符个数
 */
public class Demo03Writer {
    public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("E:\\IdeaProjects\\1.txt");
        char[] cs = {'a','b','c','d'};
        //  void write(char[] cbuf)  写入字符数组
        fw.write(cs);  //  abcd

        //  void write(char[] cbuf,int off,int len)  写入字符串的某一部分,off字符串的开始索引,len写的字符个数
        fw.write(cs,0,2);

        //  void write(String str)  写入字符串
        fw.write("我想当空军!!!");

        //  void write(String str,int off , int len)  写入字符串的某一部分,off字符串的开始索引,len写的字符个数
        fw.write("飞行员",0,3);

        fw.close();
    }
}
19.2.6 字符输出流的续写和换行
package cn.code.Demo021;

import java.io.FileWriter;
import java.io.IOException;

/*
    续写和换行:
    续写,追加写:使用两个参数的构造方法
        FileWriter(String filenNmae,boolean append)
        FileWriter(File file,boolean append)
        参数:
            String filenNmae,File file :写入数据的目的地
            boolean append:续写开关     true:不会创建新的文件覆盖文件,可以续写; false:创建新的文件覆盖源文件
    换行:换行符号
        windows: \r\n
        linux: /n
        mac: /r
 */
public class Demo04Writer {
    public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("E:\\IdeaProjects\\1.txt",true);
        for (int i = 0; i < 10; i++) {
            fw.write("我要当空军!!!" + "\r\n");
        }
        fw.close();
    }
}
19.3 字节输入流
19.3.1 字节输入流读取字节数据
package cn.code.Demo021;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/*
    java.io.InputStream: 字节输入流
    此抽象类是表示字节输入流的所有类是超类

    定义了所有子类共性的方法:
        int read() 从输入流中读取数据的下一个字节
        int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中
        void close() 关闭此输入流并释放与该流关联的所有系统资源

    java.io.FileInputStream extends InputStream
    FileInputStream:文件字节输入流
    作用:把硬盘文件中的数据,读取到内存中使用

    构造方法:
        FileInputStream(String name)
        FileInputStream(File file)
        参数:读取文件的数据源
            String name: 文件的路径
            File file: 文件
        构造方法的作用:
        1. 会创建一个FileInputStream对象
        2. 会把FileInputStream对象指定构造方法中要读取的文件

    读取数据的原理(硬盘 --> 内存)
        java程序 --> JVM  --> OS  -->  OS读取数据的方法  --> 读取文件

    字节输入流的使用步骤(重点):
        1. 创建FileInputStream对象,构造方法中绑定要读取的数据源
        2. 使用FileInputStream对象中的方法read,读取文件
        3. 释放资源
 */
public class Demo01InputStream {
    public static void main(String[] args) throws IOException {
        //  1. 创建FileInputStream对象,构造方法中绑定要读取的数据源
        FileInputStream fis = new FileInputStream("E:\\IdeaProjects\\1.txt");
        //  2. 使用FileInputStream对象中的方法read,读取文件
        //  int read() 读取文件中的一个字节并返回,读取到文件的末尾返回 -1
        /*int len1 = fis.read();
        System.out.println(len1);

        int len2 = fis.read();
        System.out.println(len2);

        int len3 = fis.read();
        System.out.println(len3);

        int len4 = fis.read();
        System.out.println(len4);

        int len5 = fis.read();
        System.out.println(len5);  //  读取结束,就会打印 -1

        int len6 = fis.read();
        System.out.println(len6); */ //  读取结束后再读取,打印的还是 -1

        /*
            发现以上读取的文件是一个重复的过程,所以可以使用循环优化
            不知道文件中有多少字节,使用while循环
            while循环结束条件,读取到-1的时候结束

            布尔表达式(len = fis.read()) != -1
                1. fis.read():读取一个字节
                2. len = fis.read():把读取到的字节赋值给变量len
                3. (len = fis.read()) != -1:判断变量len是否不等于 -1
         */
        int len = 0;  //  记录读取到的字节
        while((len = fis.read()) != -1){
            System.out.println(len);
        }
        // 3. 释放资源
        fis.close();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EqbLdnC8-1573823584908)(C:\Users\Think-Pad\Desktop\java\读取文件数据原理.jpg)]

19.3.2 字节输入流一次读取多个字节

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JfQppyKv-1573823584910)(C:\Users\Think-Pad\Desktop\java\一次读取多个字节.jpg)]


package cn.code.Demo021;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;

/*
    字节输入流一次读取多个字节的方法:
        int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中
    明确两件事情:
        1. 方法的参数byte[]的作用?
            起到缓冲作用,存储每次读取到的多个字节
            数组的长度一把定义为1024(1kb) 或者1024 的整数倍
        2. 方法的返回值int 是什么
            每次读取的有效字节个数

    String类的构造方法
        String(byte[] bytes):把字节数组转换为字符串
        String(byte[] bytes,int off, int length):把字节数的一部分转换为字符串 offset:数组的开始索引 length:转换的字节个数
 */
public class Demo02InputStream {
    public static void main(String[] args) throws IOException {
        //  1. 创建FileInputStream对象,构造方法中绑定要读取的数据源
        FileInputStream fis = new FileInputStream("E:\\IdeaProjects\\1.txt");
        //  2. 使用FileInputStream对象中的方法read,读取文件
        //  int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中
        /*byte[] bytes = new byte[2];
        int len = fis.read(bytes);
        System.out.println(len);  //  2
//        System.out.println(Arrays.toString(bytes));  //  [49, 48]
        System.out.println(new String(bytes));  //  10

        int len1 = fis.read(bytes);
        System.out.println(len1);  //  2
        System.out.println(new String(bytes));  //  0A

        int len2 = fis.read(bytes);
        System.out.println(len2);  //  1
        System.out.println(new String(bytes));  //  BA

        int len3 = fis.read(bytes);
        System.out.println(len3);  //  -1
        System.out.println(new String(bytes));  //  BA*/

        /*
            发现以上读取时一个重复的过程,可以使用循环优化
            不知道文件中有多少字节,所以使用while循环
            while循环结束是条件,读取到-1结束
         */
        byte[] bytes = new byte[1024];  //  存储读取到的多个字节
        int len = 0;  //  记录每次读取的有效字节个数
        while ((len = fis.read(bytes)) != -1){
            //  String(byte[] bytes,int off, int length):把字节数的一部分转换为字符串 offset:数组的开始索引 length:转换的字节个数
            System.out.println(new String(bytes,0,len));  //  100AB
        }

        // 3. 释放资源
        fis.close();
    }
}
19.3.3 文件复制
package cn.code.Demo021;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/*
    文件复制练习:一读一写

    明确:
        数据源:E:\IdeaProjects\1.txt
        数据的目的地:E:\1.txt

    文件复制的步骤:
        1. 创建一个字节输入流,构造方法中绑定要读取的数据源
        2. 创建一个字节输出流,构造方法中绑定要写入的目的地
        3. 使用字节输入流中的方法read读取文件
        4. 使用字节输出流中的方法write,把读取到的字节写入到目的地的文件中
        5. 释放资源
 */
public class Demo01CopyFile {
    public static void main(String[] args) throws IOException {
        //  1. 创建一个字节输入流,构造方法中绑定要读取的数据源
        FileInputStream fis = new FileInputStream("E:\\IdeaProjects\\2.txt");
        //  2. 创建一个字节输出流,构造方法中绑定要写入的目的地
        FileOutputStream fos = new FileOutputStream("E:\\2.txt");
        //  一次读取一个字节写入一个字的方式
        //  3. 使用字节输入流中的方法read读取文件
        /*int len = 0;
        while ((len = fis.read()) != -1){
            //  4. 使用字节输出流中的方法write,把读取到的字节写入到目的地的文件中
            fos.write(len);
        }*/

        //  使用数组缓冲读取多个字节,写入多个字节
        byte[] bytes = new byte[1024];
        //  3. 使用字节输入流中的方法read读取文件
        int len = 0;  //  每次读取的有效字节个数
        while ((len = fis.read(bytes)) != -1){
            //  4. 使用字节输出流中的方法write,把读取到的字节写入到目的地的文件中
            fos.write(bytes,0,len);
        }
        //  5. 释放资源(先关写的,后关闭读的;如果写完了,肯定读取完毕了)
        fos.close();
        fis.close();
    }
}
19.3.4 使用字节流读取中文的问题
package cn.code.Demo021;

import java.io.FileInputStream;
import java.io.IOException;

/*
    使用字节轮流读取中文文件
    1个中文
        GBK:占用两个字节
        UTF-8:占用3个字节
 */
public class Reader {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("E:\\IdeaProjects\\1.txt");
        int len = 0;
        while ((len = fis.read()) != -1){
            System.out.println((char) len);    //   æ
        }
        fis.close();
    }
}
19.3.5 字符输入流读取字符数据
package cn.code.Demo021;

import java.io.FileReader;
import java.io.IOException;

/*
    java.io.Reader: 字符输入流,是字符输入流的最顶层的父类,定义了一些共性的成员方法,是一个抽象类

    共性的成员方法:
        int read() 读取单个字符并返回
        int read(char[] cbuf) 一次读取多个字符,将字符读入数组
        void close() 关闭该流并释放与之关联的所有资源

    java.io.Reader extends InputStreamReader exends Reader
    FileReader: 文件字符输入流
    作用:把硬盘文件中的数据以字符的方式读取到内存中

    构造方法:
        FileReader(String fileName)
        FileReader(File file)
        参数:读取文件的数据源
            String fileName:文件的路径
            File file:一个文件
        FileReader构造方法的作用:
            1. 创建一个FileReader对象
            2. 会把FileReader对象指向要读取的文件
    字符输入流的使用步骤:
        1. 创建FileReader对象,构造方法中绑定要读取的数据源
        2. 使用FileReader对象中的方法read读取文件
        3. 释放资源
 */
public class Demo02Reader {
    public static void main(String[] args) throws IOException {
        //  1. 创建FileReader对象,构造方法中绑定要读取的数据源
        FileReader fr = new FileReader("E:\\IdeaProjects\\1.txt");
        //  2. 使用FileReader对象中的方法read读取文件
        //  int read()   读取单个字符并返回
        /*int len = 0;
        while ((len = fr.read()) != -1){
            System.out.println((char)len);
        }*/

        //  int read(char[] cbuf) 一次读取多个字符,将字符读入数组
        char[] cs = new char[1024];  //  存储读取到的多个字符
        int len = 0;  //  记录的是每次读取的有效字符个数
        while ((len = fr.read(cs)) != -1){
            /*
                 String类的构造方法
        String(byte[] bytes):把字节数组转换为字符串
        String(byte[] bytes,int off, int length):把字节数的一部分转换为字符串 offset:数组的开始索引 length:转换的字节个数
             */
            System.out.println(new String(cs,0,len));  //  100AB我
        }
        //  3. 释放资源
        fr.close();
    }
}
19.4 使用try_catch_finally处理流中的异常
package cn.code.Demo021;

import java.io.FileWriter;
import java.io.IOException;

/*
    在JDK1.7 之前使用try catch finally 处理流中的异常
    格式:
        try{
            可能会产生异常的代码
        }catch(异常类变量 变量名){
            异常的处理逻辑
        }finally{
            一定会执行的代码
            资源释放
        }
        
    JDK1.7 的新特性
    在try的后边可以增加一个(),在括号中可以定义流对象
    那么这个流对象的作用域就在try中有效
    try中的代码执行完毕,会自动把流对象释放,不用写finally
    格式:
        try(定义流对象; 定义流对象...){
            可能会产生异常的代码
        }catch(异常类变量 变量名){
            异常的处理逻辑
        }
 */
public class Demo01TryCatch {
    public static void main(String[] args) {
        //  提高变量fw的作用域,让finally可以使用
        //  变量咋一定义的时候,可以没有值,但是使用的时候必须有值
        FileWriter fw = null;
        try{
            //  可能会产生异常的代码
            fw = new FileWriter("E:\\IdeaProjects",true);
            for (int i = 0; i < 10; i++) {
                fw.write("我要当空军,报效祖国!!!" + "\r\n");
            }
            }catch(IOException e){
                //  异常的处理逻辑
                System.out.println(e);
            }finally{
                //  一定会执行的代码
                if (fw != null){
                    try {
                        //  fw.close()方法声明抛出了IOException异常对象,所以我们就得处理这个异常对象,要么throws,要么 try  catch
                        fw.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    }
}
19.5 Properties集合
19.5.1 使用Properties集合存储数据,遍历出Properties集合中的数据
package cn.code.Demo022;


import java.util.Properties;
import java.util.Set;

/*
    java.util.Properties集合 extends Hashtable<k,v> implements Map<k,v>
    Properties 类表示了一个持久的属性集。 Properties 可保存在流中或从流中加载。
    Properties集合是一个唯一和IO流相结合的集合
        可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入硬盘中存储
        可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用
        
    属性列表中每个键及其对应值都是一个字符串
        Properties集合是一个双列集合,key和value默认都是字符串
 */
public class Demo01Properties {
    public static void main(String[] args) {
        show01();
    }
/*
    使用Properties集合存储数据,遍历取出Properties集合中的数据
    Properties集合是一个双列集合,key和value默认都是字符串
    Properties集合有一些操作字符串的特有方法
        Object setProperty(String key,String value)调用Hashtable 的方法 put
        String getProperty(String key) 通过key找到value值,此方法相当于Map集合中的get(key)方法
        Set<String> stringPropertyNames() 返回此属性列表中的链集,其中该键及其对应是字符串,此方法相当于Map集合中的keySet方法
 */
    private static void show01() {
        //  创建Properties集合对象
        Properties prop = new Properties();
        //  使用setProperty往集合中添加数据
        prop.setProperty("欧阳娜娜","168");
        prop.setProperty("迪丽热巴","168");
        prop.setProperty("古力娜扎","168");
        //  使用stringPropertyName把Properties集合中的键取出来,存储到一个Set集合中
        Set<String> strings = prop.stringPropertyNames();
        //  遍历Set集合,取出Properties集合的每一个键
        for (String key : strings){
            //  使用getProperty方法通过key获取value
            String value = prop.getProperty(key);
            System.out.println(key + "身高" +value);
        }
    }
}
19.5.2 Properties集合中的方法store
/*
        可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
        void store(OutputStream out, String comments)
        void store(Write write, String comments)
        参数:
            OutputStream out:字节输出流,不能写中文
            Write write:字符输出流,可以写中文
            String comments:注释,用来解释说明保存的文件是做什么用的
                不能使用中文,会产生乱码,默认是Unicode编码
                一般使用"" 空字符串

        使用步骤:
            1. 创建Properties集合对象,添加数据
            2. 创建字节输出流/字符输出流对象,构造方法中绑定要输出的目的地
            3. 使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
            4. 释放资源
 */
    private static void show02() throws IOException {
        //  1. 创建Properties集合对象
        Properties prop = new Properties();
        //  使用setProperty往集合中添加数据
        /*prop.setProperty("欧阳娜娜","168");
        prop.setProperty("迪丽热巴","168");
        prop.setProperty("古力娜扎","168");*/
        //  2. 创建字节输出流/字符输出流对象,构造方法中绑定要输出的目的地
//        FileWriter fw = new FileWriter("E:\\IdeaProjects\\2.txt",true);

        //  3. 使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
//        prop.store(fw,"zhixiang");

        //  4. 释放资源
//        fw.close();
        prop.setProperty("99","haha");
        prop.store(new FileOutputStream("E:\\\\IdeaProjects\\\\2.txt"),"ui");
    }
19.53 Oroperties集合中的方法load
/*
    可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用
    void load(InputStream inStream)
    void load(Reader reader)
    参数:
        InputStream inStream:字节输出流,不能读取含有中文的键值对
        Reader reader:字符输入流,能读取含有中文的键值对
    使用步骤:
        1. 创建Properties集合对象
        2. 使用Properties集合对象中的方法load读取保存键值对的文件
        3. 遍历Properties集合

    注意:
        1. 存储键值对的文件中,键与值默认的连接符号可以使用 =, 空格(其他符号)
        2. 存储键值对的文件中,可以使用#进行注释,被注释的键值对不会再被读取
        3. 存储键值对的文件中,键值对默认都是字符串,不用再加引号
 */
    private static void show03() throws IOException {
        //  1. 创建Properties集合对象
        Properties prop = new Properties();
        //  2. 使用Properties集合对象中的方法load读取保存键值对的文件
        prop.load(new FileReader("E:\\IdeaProjects\\1.txt"));
//        prop.load(new FileInputStream("E:\\IdeaProjects\\1.txt"));  //  出现乱码
        //  3. 遍历Properties集合
        Set<String> sets = prop.stringPropertyNames();
        for (String key : sets){
            String value = prop.getProperty(key);
            System.out.println(key + "=" + value);
        }
    }
19.6 缓冲流

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K8qMNG59-1573823584911)(C:\Users\Think-Pad\Desktop\java\缓冲流原理.jpg)]

19.6.1 BufferedOutputStream_字节缓冲输出流
package cn.code.Demo022;

import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/*
    java.io.BufferedOutputStream extends OutputStream
    BufferedOutputStream:字节缓冲输出流

    继承自父类的共性成员方法:
        public void close(): 关闭此输出流并释放与此流相关的任何系统资源
        public void flush(): 刷新此输出流并强制任何缓冲流的输出字节被写出
        public void write(byte[] b): 将b.length字节从指定的字节数组写入此输出流
        public void write(byte[] b,int off ,int len): 从指定的字节数组写入 len字节,从偏移量 off 开始输出到此输出流
        public abstract void write(int b): 将指定的字节输出流写入

    构造方法:
        BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流,以将数据写入指定的底层输出流
        BufferedOutputStream(OutputStream out,int size) 创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流
        参数:
           OutputStream out:字节输出流
            我们可以传递FileOutputStream,缓冲流会给 FileOutputStream 增加一个缓冲区,提高FileOutputStream的写入效率
           int size:指定缓冲流缓冲区的大小,不指定默认
    使用步骤(重点)
        1. 创建 FileOutputStream对象,构造方法中绑定要输出的目的地
        2. 创建 BufferedOutputStream对象,构造方法中传递FileOutputStream对象,提高FileOutputStream对象写入效率
        3. 使用 BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区中
        4. 使用 BufferedOutputStream对象中的方法flush,把内部缓冲区中的数据,刷新到文件中
        5. 释放资源(会先调用flush方法刷新数据,第4步可以省略)
 */
public class Demo01BufferedOutputStream {
    public static void main(String[] args) throws IOException {
        //  1. 创建 FileOutputStream对象,构造方法中绑定要输出的目的地
        FileOutputStream fos = new FileOutputStream("E:\\IdeaProjects\\2.txt");
        //  2. 创建 BufferedOutputStream对象,构造方法中传递FileOutputStream对象,提高FileOutputStream对象效率
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        //  3. 使用 BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区中
        bos.write("我把数据写入到内部缓冲区中".getBytes());
        //  4. 使用 BufferedOutputStream对象中的方法flush,把内部缓冲区中的数据,刷新到文件中
        bos.flush();
        //  5. 释放资源(会先调用flush方法刷新数据,第4步可以省略)
        bos.close();
    }
}
19.6.2 BufferedInputStream_字节缓冲输入流
package cn.code.Demo022;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

/*
    java.io.BufferedInputStream extends InputStream
    BufferedInputStream:字节缓冲输入流

    继承自父类的成员方法:
        int read() 从输入流中读取数据的下一个
        int read(byte[] b) 从输入流或者读取一定数量的字节,并将其存储在缓冲区数组 b 中
        void close() 关闭此输入流并释放与该流关联的所有系统资源

    构造方法:
        BufferedInputStream(InputStream in) 创建一个 BufferedInputStream 并保存其参数,即输入流 in ,以便将来使用
        BufferedInputStream(InputStream in,int size) 创建具有指定缓冲区大小的 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用
        参数:
            InputStream in:字节输入流
                我们可以传递FileInputStream,缓冲流会给 FileInputStream 增加一个缓冲区,提高FileInputStream的写入效率
            int size:指定缓冲流缓冲区的大小,不指定默认

    使用步骤(重点):
        1. 创建FileInputStream对象,构造方法中绑定要读取的数据源
        2. 创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的读取效率
        3. 使用BufferedInputStream对象中的方法read,读取文件
        4. 释放资源
 */
public class Demo02BufferedInputStream {
    public static void main(String[] args) throws IOException {
        //  1. 创建FileInputStream对象,构造方法中绑定要读取的数据源
        FileInputStream fis = new FileInputStream("E:\\IdeaProjects\\2.txt");
        //  2. 创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的读取效率
        BufferedInputStream bis = new BufferedInputStream(fis);
        //  3. 使用BufferedInputStream对象中的方法read,读取文件
//        int read() 从输入流中读取数据的下一个
        /*int len = 0;  //  记录每次读取到的字节
        while ((len = bis.read()) != -1){
            System.out.println(len);
        }*/

//        int read(byte[] b) 从输入流或者读取一定数量的字节,并将其存储在缓冲区数组 b 中
        byte[] bytes = new byte[1024];  //  存储每次读取的数据
        int len = 0;  //  记录每次读取到的有效字节个数
        while ((len = bis.read(bytes)) != -1){
            System.out.println(new String(bytes,0,len));  //  我把数据写入到内部缓冲区中
        }

        //  4. 释放资源
        bis.close();
    }
}
19.6.3 BufferedWrite_字符缓冲输出流
package cn.code.Demo022;

import java.io.*;

/*
    java.io.BufferedWriter extends Writer
    BufferedWriter:字符缓冲输出流

    继承自父类的共性成员方法:
        public write(int c)  写入单个字符
        void write(char[] cbuf)  写入字符数组
        abstract void write(char[] cbuf,int len)  写入字符串的某一部分,off字符串的开始索引,len写的字符个数
        void flush()  刷新该流的缓冲
        void close()  关闭此流 ,但要先刷新它

    构造方法:
        BufferedWriter(Writer out) 创建一个使用默认大小输出缓冲区的缓冲字符输出流
        BufferedWriter(Writer out , int sz) 创建一个使用给定大小输出缓冲区的新缓冲字符输出流
        参数:
            Writer out:字符输出流
            int sz:指定缓冲区的大小,不写默认大小
   特有的成员方法:
        void newline() 写入一个行分隔符,会根据不同的操作系统,获取不同的行分隔符
        换行:换行符号
        windows: \r\n
        linux: /n
        mac: /r
    使用步骤:
        1. 创建字符缓冲输出流对象,构造方法中传递字符输出流
        2. 调用字符缓冲输出流中的方法write,把数据写入到内存缓冲区中
        3. 调用字符缓冲输出流中的方法flush,把内存缓冲区中的数据,刷新到文件中
        4. 释放资源
 */
public class Demo03BufferedWriter {
    public static void main(String[] args) throws IOException {
        //  1. 创建字符缓冲输出流对象,构造方法中传递字符输出流
        BufferedWriter bw = new BufferedWriter(new FileWriter("E:\\IdeaProjects\\2.txt"));
        //  2. 调用字符缓冲输出流中的方法write,把数据写入到内存缓冲区中
        for (int i = 0; i < 10; i++) {
            bw.write("我想要当空军,报效祖国!!!");
//            bw.write("\r\n");
            bw.newLine();
        }
        //  3. 调用字符缓冲输出流中的方法flush,把内存缓冲区中的数据,刷新到文件
        bw.flush();
        //  4. 释放资源
        bw.close();
    }
}
19.6.4 BufferedReader_字符缓冲输入流
package cn.code.Demo022;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

/*
    java.io.BufferedReader extends Reader

    继承自父类的共性成员方法:
        int read() 从输入流中读取数据的下一个字节
        int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中
        void close() 关闭此输入流并释放与该流关联的所有系统资源

    构造方法:
        BufferedReader(Reader in) 创建一个使用默认大小输入缓冲区的缓冲字符输入流
        BufferedReader(Reader in,int sz) 创建一个使用指定大小输入缓冲区的缓冲字符输入流
        参数:
            Reader in:字符输入流
                我们传递FileReader,缓冲区会给FileReader增加一个缓冲区,提高FileReader的读取效率
    特有的成员方法:
        String readerline() 读取一个文本行。读取一行数据
            行的终止符号:通过下列字符之一即可认为某行已终止:换行('\n')、回车('\r')或回车后直接跟着换行(\r\n)
        返回值:
            包含该行内容的字符串,不包含任何行终止符,如果已到达末尾,则返回 null

    使用步骤:
        1. 创建字符缓冲输入流对象,构造方法中传递字符输入流
        2. 使用字符缓冲输入流对象中的方法read/readline读取文本
        3. 释放资源
 */
public class Demo04BufferedReader {
    public static void main(String[] args) throws IOException {
        //  1. 创建字符缓冲输入流对象,构造方法中传递字符输入流
        BufferedReader br = new BufferedReader(new FileReader("E:\\IdeaProjects\\2.txt"));
        //  2. 使用字符缓冲输入流对象中的方法read/readline读取文本
        /*String s1 = br.readLine();
        System.out.println(s1);

        String s2 = br.readLine();
        System.out.println(s2);

        String s3 = br.readLine();
        System.out.println(s3);*/

        /*
            发现以上读取的是一个重复的过程,所以可以使用循环优化
            不知道文件中有多少行数据,所以使用while循环
            while的结束条件,读到null结束
         */
        String line;
        while ((line = br.readLine()) != null){
            System.out.println(line);
        }

        //  3. 释放资源
        br.close();
    }
}
19.7 转换流

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-femuMn7X-1573823584989)(C:\Users\Think-Pad\Desktop\java\转换流的原理.jpg)]

19.7.1 OutputStreamWriter 介绍&代码实现
package cn.code.Demo022;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

/*
    java.io.OutputStreamWriter extends Writer
    OutputStreamWriter:是字符流通向字节流的桥梁,可指定的 charset 将要写入流中的字符编码成字节。(编码:把能看懂的变成看不懂的)

    继承自父类的共性成员方法:
        public write(int c)  写入单个字符
        void write(char[] cbuf)  写入字符数组
        abstract void write(char[] cbuf,int len)  写入字符串的某一部分,off字符串的开始索引,len写的字符个数
        void flush()  刷新该流的缓冲
        void close()  关闭此流 ,但要先刷新它
    构造方法:
          OutputStreamWriter(OutputStream out) 创建使用默认字符编码的 OutputStreamWriter
          OutputStreamWriter(OutputStream out,String charsetName) 创建使用指定字符集的OutputStreamWriter
          参数:
              OutputStream out:字节输出流,可以用来写转换之后的字节到文件中
              String charsetName:指定的编码表名称,不区分大小写,可以是 utf-8/UTF-8,gbk/GBK,...不指定默认使用UTF-8
    使用步骤:
        1. 创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称
        2. 使用OutputStreamWriter对象中的方法write,把字符转换为字符存储缓冲区中(编码)
        3. 使用OutputStreamWriter对象中的方法flush,把内存缓冲区中的字节刷新到文件中(使用字节流写字节的过程)
        4. 释放资源
 */
public class Demo01OutputStreamWriter {
    public static void main(String[] args) throws IOException {
        write_gbk();
    }
/*
    使用转换OutputStreamWriter写GBK格式的文件
 */
    private static void write_gbk() throws IOException {
        //  1. 创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\IdeaProjects\\2.txt"),"GBK");  //  不指定默认使用UTF-8
        //  2. 使用OutputStreamWriter对象中的方法write,把字符转换为字符存储缓冲区中(编码)
        osw.write("我的理想");
        //  3. 使用OutputStreamWriter对象中的方法flush,把内存缓冲区中的字节刷新到文件中(使用字节流写字节的过程)
        osw.flush();
        //  4. 释放资源
        osw.close();
    }

    /*
        使用转换OutputStreamWriter写UTF-8格式的文件
     */
    private static void write_utf_8() throws IOException {
        //  1. 创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称
//        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\IdeaProjects\\2.txt"),"utf-8");
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\IdeaProjects\\2.txt"));  //  不指定默认使用UTF-8
        //  2. 使用OutputStreamWriter对象中的方法write,把字符转换为字符存储缓冲区中(编码)
        osw.write("我的理想");
        //  3. 使用OutputStreamWriter对象中的方法flush,把内存缓冲区中的字节刷新到文件中(使用字节流写字节的过程)
        osw.flush();
        //  4. 释放资源
        osw.close();
    }
}
19.7.2 InputStreamWriter 介绍&代码实现
package cn.code.Demo022;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

/*
    java.io.InputStreamReader extends Reader
    InputStreamReader:是字节流通向字符流的桥梁,可指定的 charset 将要写入流中的字节编码成字符。(编码:把能看不懂的变成看懂的)

    继承自父类的共性成员方法:
        int read() 从输入流中读取数据的下一个
        int read(byte[] b) 从输入流或者读取一定数量的字节,并将其存储在缓冲区数组 b 中
        void close() 关闭此输入流并释放与该流关联的所有系统资源
    构造方法:
        InputStreamReader(InptuStream in) 创建一个使用默认字符集的 InputStreamReader
        InputStreamReader(InptuStream in ,String charsetName) 创建使用指定字符集的 InputStreamReader
        参数:
            InptuStream in:字节输入流,用来读取文件中保存的字节
            String charsetName:指定的编码表名称,不区分大小写,可以是utf-8/UTF-8,gbk/GBK,...不指定默认使用UTF-8
    使用步骤:
        1. 创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称
        2. 使用InputStreamReader对象中的read读取文件
        3. 释放资源
    注意事项:
        构造方法中指定的编码表名称要和文件的编码相同,否则会出现乱码
 */
public class Demo03InputStreamReader {
    public static void main(String[] args) throws IOException {
//        read_utf_8();
        read_GBK();
    }
/*
    使用InputStreamReader读取GBK格式的文件
 */
    private static void read_GBK() throws IOException {
        //  1. 创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称
        InputStreamReader isr = new InputStreamReader(new FileInputStream("1.txt"),"GBK");  //  不指定默认使用UTF-8
        //  2. 使用InputStreamReader对象中的read读取文件
        int len = 0;
        while ((len = isr.read()) != -1){
            System.out.println((char)len);
        }
        //  3. 释放资源
        isr.close();
    }

    /*
        使用InputStreamReader读取UTF-8格式的文件
     */
    private static void read_utf_8() throws IOException {
        //  1. 创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称
//        InputStreamReader isr = new InputStreamReader(new FileInputStream("1.txt"),"utf-8");
        InputStreamReader isr = new InputStreamReader(new FileInputStream("1.txt"));  //  不指定默认使用UTF-8
        //  2. 使用InputStreamReader对象中的read读取文件
        int len = 0;
        while ((len = isr.read()) != -1){
            System.out.println((char)len);
        }
        //  3. 释放资源
        isr.close();
    }
}
19.7.3 转换文件编码
package cn.code.Demo022;

import java.io.*;

/*
    练习:转换文件编码
        将GBK编码的文件,转换为UTF-8编码的文件
    分析:
        1. 创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称GBK
        2. 创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称UTF-8
        3. 使用InputStreamReader对象中的方法read读取文件
        4. 使用OutputStreamWriter对象中的方法write,把读取的数据写入到文件中
        5. 释放资源
 */
public class Demo04Test {
    public static void main(String[] args) throws IOException {
        //  1. 创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称GBK
        InputStreamReader isr = new InputStreamReader(new FileInputStream("1.txt"),"GBK");
        //  2. 创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称UTF-8
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("2.txt"),"UTF-8");
        //  3. 使用InputStreamReader对象中的方法read读取文件
        int len = 0;
        while ((len = isr.read()) != -1){
            //  4. 使用OutputStreamWriter对象中的方法write,把读取的数据写入到文件中
            osw.write(len);
        }

        //  5. 释放资源
        osw.close();
        isr.close();
    }
}
19.8 序列化与反序列化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VlfjhaTp-1573823584990)(C:\Users\Think-Pad\Desktop\java\序列化与反序列化.jpg)]

19.8.1 对象的序列化流 ObjectOutputSream
package cn.code.Demo022;


import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

/*
    java.io.ObjectOutputStream extends OutputStream
    ObjectOutputStream:对象的序列化流
    作用:把对象以流的方式写入到文件中保存

    构造:
        ObjectOutputStream(OutputStream out) 创建写入指定 OutputStream 的 ObjectOutputStream
        参数:
            OutputStream ou:字节输出流
    特有的成员方法:
        void writeObject(Object obj) 将指定的对象写入 ObjectOutputStream

    使用步骤:
        1. 创建ObjectOutputStream对象,构造方法中传递字节输出流
        2. 使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中
        3. 释放资源
 */
public class Demo01ObjectOutputStream {
    public static void main(String[] args) throws IOException {
        //  1. 创建ObjectOutputStream对象,构造方法中传递字节输出流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("3.txt"));
        //  2. 使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中
        oos.writeObject(new Person("小美女",18));
        //  3. 释放资源
        oos.close();
    }
}

package cn.code.Demo022;

import java.io.Serializable;

/*
    序列化和序列化的时候,会抛出NotSerializableException没有序列化异常
    类通过实现 java.io.Serializable 接口以启用其序列化功能。来实现此接口的类将无法使其任何状态序列化或反序列化
    Serializable接口也叫标记型接口
        要进行序列化和反序列化的类必须实现 Serializable接口,就会给类添加一个标记
        当我们进行序列化和反序列化的时候,就会检测类是否有这个标记
            有:就可以序列化和反序列化
            没有:就会抛出 NotSerializableException 异常
    去市场买肉 --> 肉中有一个蓝色章(检测合格) --> 放心购买 --> 买回来怎么吃随意
 */
public class Person implements Serializable {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    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;
    }
}
19.8.2 对象的反序列化流 ObjectInputStream
package cn.code.Demo022;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

/*
    java.io.ObjectInputStream extends InputStream
    ObjectInputStream:对象的反序列化流
    作用:把文件中保存的对象,以流的方式读取出来使用

    构造方法:
        ObjectInputStream(InputStream in) 创建从指定 InputStream 读取的 ObjectInputStream
        参数:
            InputStream in:字节输入流
    特有的成员方法:
        Object readObject() 从 ObjectInputStream 读取对象

    使用步骤:
        1. 创建ObjectInputStream对象,构造方法中传递字节输入流
        2. 使用ObjectInputStream对象中的方法readObject读取保存对象的文件
        3. 释放资源
        4. 使用读取出来的对象(打印)

    readObject方法声明抛出了ClassNotFoundException(class文件找不到异常)
    当存在对象的class文件是抛出异常
    反序列化的前提:
        1. 类必须实现 Serializable
        2. 必须存在类对应的class文件
 */
public class Demo02ObjectInputStream {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //  1. 创建ObjectInputStream对象,构造方法中传递字节输入流
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("3.txt"));
        //  2. 使用ObjectInputStream对象中的方法readObject读取保存对象的文件
        Object o = ois.readObject();
        //  3. 释放资源
        ois.close();
        //  4. 使用读取出来的对象(打印)
        System.out.println(o);
        Person p = (Person) o;
        System.out.println(p.getName() + p.getAge());
    }
}
19.8.3 transient关键字——瞬态关键字
package cn.code.Demo022;

import java.io.Serializable;

/*
      static 关键字:静态关键字
        静态优先于非静态加载到内存中(静态优先于对象进入到内存中)
        被static修饰的成员变量不能被序列化的,序列化的都是对象
        private static int age;
        oos.writeObject(new Person("小美女",18));
        Object o = ois.readObject();
        Person{name='小美女', age=0}

    transient 关键字:瞬间关键字
        被transient修饰成员变量,不能被序列化(但是没有static的作用)
        private transient int age;
        oos.writeObject(new Person("小美女",18));
        Object o = ois.readObject();
        Person{name='小美女', age=0}
 */
public class Person implements Serializable {
    private String name;
//    private static int age;
    private transient int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
19.8.4 InvalidClassException异常、原理和解决方案

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o8GqkpO7-1573823585028)(C:\Users\Think-Pad\Desktop\java\反序列化异常.jpg)]

19.8.5 序列化集合
package cn.code.Demo022;

import java.io.*;
import java.util.ArrayList;

/*
    练习:序列化集合
        当我们想咋文件中保存多个对象的时候
        可以把多个对象存储到一个集合中
        对集合进行序列化和反序列化
    分析:
        1. 定义一个存储Person对象的ArrarList集合
        2. 往ArrayList集合中存储Person对象
        3. 创建一个序列化流ObjectOutputStream对象
        4. 使用ObjectOutputStream对象中的方法writeObject,对集合进行序列化
        5. 创建一个反序列化ObjectInputStream对象
        6. 使用ObjectInputStream对象中的方法readObject读取文件中保存的集合
        7. 把Object类型的集合转换为ArrayList类型
        8. 遍历ArrayList集合
        9. 释放资源
 */
public class Demo03Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //  1. 定义一个存储Person对象的ArrarList集合
        ArrayList<Person> list = new ArrayList<>();
        //  2. 往ArrayList集合中存储Person对象
        list.add(new Person("张三",18));
        list.add(new Person("李四",19));
        list.add(new Person("王五",20));
        //  3. 创建一个序列化流ObjectOutputStream对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.txt"));
        //  4. 使用ObjectOutputStream对象中的方法writeObject,对集合进行序列化
        oos.writeObject(list);
        //  5. 创建一个反序列化ObjectInputStream对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("1.txt"));
        //  6. 使用ObjectInputStream对象中的方法readObject读取文件中保存的集合
        Object o = ois.readObject();
        //  7. 把Object类型的集合转换为ArrayList类型
        ArrayList<Person> list2 = (ArrayList<Person>) o;
        //  8. 遍历ArrayList集合
        for (Person p : list2) {
            System.out.println(p);
        }
        //  9. 释放资源
        oos.close();
        ois.close();
    }
}
19.9 打印流
package cn.code.Demo022;

import java.io.FileNotFoundException;
import java.io.PrintStream;

/*
    java.io.PrintStream:打印流
        PrintStream 为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式
    PrintStream特点:
        1. 只负责数据的输出,不负责数据的读取
        2. 与其他输出流不同, PrintStream 永远不会抛出 IOException
        3. 有特有的方法:print,println
            void print(任意类型的值)
            void print(任意类型的值并换行)
    构造方法:
        PrintStream(File file):输出的目的地是一个文件
        PrintStream(OutputStream out):输出的目的地是一个字节输出流
        PrintStream(String fileName):输出的目的地是一个文件路径

    PrintStream extends OutputStream
    继承自父类的成员方法:
        public void close(): 关闭此输出流并释放与此流相关的任何系统资源
        public void flush(): 刷新此输出流并强制任何缓冲流的输出字节被写出
        public void write(byte[] b): 将b.length字节从指定的字节数组写入此输出流
        public void write(byte[] b,int off ,int len): 从指定的字节数组写入 len字节,从偏移量 off 开始输出到此输出流
        public abstract void write(int b): 将指定的字节输出流写入
    注意:
        如果使用继承自父类的write方法写数据,那么查看数据的时候会查询编码表 97-->a
        如果使用自己特有的方法print/println写法写数据,写的数据原样输出 97-->97
 */
public class Demo01PrintStream {
    public static void main(String[] args) throws FileNotFoundException {
//        System.out.println("Hello,World!");

        //  创建打印流PrintStream对象,构造方法中绑定要输出的目的地
        PrintStream ps = new PrintStream("4.txt");
        //  如果使用继承自父类的write写法写数据,那么查看数据的时候会查询编码表 97-->a
        ps.write(99);
        //  如果使用自己特有的方法print/println写法写数据,写的数据原样输出 97-->97
        ps.println(99);
        ps.println(9.9);
        ps.println('a');
        ps.println("Hello,World!");
        ps.println(true);

        //  释放资源
        ps.close();
    }
}

package cn.code.Demo022;

import java.io.FileNotFoundException;
import java.io.PrintStream;

/*
    可以改变输出语句的目的地(打印流的流向)
    输出语句,默认在控制台输出
    使用 System.setOut 方法改变输出语句的目的地改为参数中传递的打印流的目的地
        static void setOut(PrintStream out)
          重新分配“标准”输出流
 */
public class Demo02PrintStream {
    public static void main(String[] args) throws FileNotFoundException {
        System.out.println("我是在控制台输出");

        PrintStream ps = new PrintStream("5.txt");
        System.setOut(ps);  //  改变输出语句的目的地改为参数中传递的打印流的目的地
        System.out.println("我在打印流的目的地中输出");

        ps.close();
    }
}

第二十章 网络编程

20.1 端口号

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cp4ipMhy-1573823585029)(C:\Users\Think-Pad\Desktop\java\端口号.jpg)]

20.2 TCP通信

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BibrKovf-1573823585035)(C:\Users\Think-Pad\Desktop\java\tcp的通信.jpg)]

20.2.1 TCP通信 —— 客户端&服务器端代码实现

客户端:

package cn.code.Demo023;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/*
    TCP通信的客户端:向服务器发送链接请求,给服务器发送数据,读取服务器写的数据
    表示客户端的类:
        java.net.Socket:此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器键通信的断点
        套接字:包含了IP地址和端口号的网络单位

    构造方法:
        Socket(String host,int port) 创建一个流套接字并将其连接到指定主机上的指定端口号
        参数:
            String host:服务器主机的名称/服务器的IP地址
            int port:服务器的端口号

    成员方法:
        OutputStream getOutputStream() 返回此套接字的输出流
        InputStream getInputStream() 返回此套接字的输出流
        void close() 关闭此套接字

    实现步骤:
        1. 创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
        2. 使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
        3. 使用网络字节输出流OutputStream对象中的方法write,给服务器发送数据
        4. 使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
        5. 使用网络字节输入流InputStream对象中的方法read,读取服务器回写的数据
        6. 释放资源(Socket)
    注意:
        1. 客户端和服务器端进行交互,必须使用Socket中提供的网络流,不能使用自己曾经的流对象
        2. 当我们曾经客户端对象Ssocket的时候,就会请求服务器和服务器经过3此握手建立连接桐庐
            这时如果服务器没有启动,那么就会抛出异常    ConnectException: Connection refused: connect
            如果服务器已经启动了,那么就可以进行交互了
 */
public class TCPClient {
    public static void main(String[] args) throws IOException {
        //  1. 创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
        Socket socket = new Socket("127.0.0.1",6666);
        //  2. 使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
        OutputStream os = socket.getOutputStream();
        //  3. 使用网络字节输出流OutputStream对象中的方法write,给服务器发送数据
        os.write("你好,服务器!".getBytes());
        //  4. 使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
        InputStream is = socket.getInputStream();
        //  5. 使用网络字节输入流InputStream对象中的方法read,读取服务器回写的数据
        byte[] bytes  = new byte[1024];
        int len = is.read(bytes);
        System.out.println(new String(bytes,0,len));
        //  6. 释放资源(Socket)
        socket.close();
    }
}

服务器端:

package cn.code.Demo023;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/*
    TCP通信的服务器端:接收客户端的请求,读取客户端发送的数据,给客户端回写数据
    表示服务器的类:
        java.net.ServerSoket:此类实现服务器套接字

    构造方法:
        ServerSoket(int port) 创建绑定到的特定端口的服务器套接字

    服务器必须明确一件事情,必须知道是哪个客户端请求的服务器
    所以可以使用accept方法获取到请求到的客户端对象Socket
    成员方法:
        Socket accept() 监听并接收到此套接字的连接

     服务器的实现步骤:
        1. 创建服务器ServerSoket对象和系统要指定的端口号
        2. 使用ServerSoket对象中的方法accept,获取到请求的客户端对象Socket
        3. 使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
        4. 使用网络字节输入流InputStream对象中的方法read,读取客户端发送的数据
        5. 使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
        6. 使用网络字节输出流OutputStream对象中的方法write,给客户端回写数据
        7. 释放资源(Socket, SererSocket)
 */
public class TPCServer {
    public static void main(String[] args) throws IOException {
        //  1. 创建服务器ServerSoket对象和系统要指定的端口号
        ServerSocket server = new ServerSocket(6666);
        //  2. 使用ServerSoket对象中的方法accept,获取到请求的客户端对象Socket
        Socket socket = server.accept();
        //  3. 使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
        InputStream is = socket.getInputStream();
        //  4. 使用网络字节输入流InputStream对象中的方法read,读取客户端发送的数据
        byte[] bytes  = new byte[1024];
        int len = is.read(bytes);
        System.out.println(new String(bytes,0,len));
        //  5. 使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
        OutputStream os = socket.getOutputStream();
        //  6. 使用网络字节输出流OutputStream对象中的方法write,给客户端回写数据
        os.write("收到,谢谢!".getBytes());
        //  7. 释放资源(Socket, SererSocket)
        socket.close();
        server.close();
    }
}
20.3 文件上传案例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BBQSGwq0-1573823585036)(C:\Users\Think-Pad\Desktop\java\文件上传原理.jpg)]

20.3.1 文件上传案例的客户端
package cn.code.Demo023;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/*
    文件上传案例的客户端:读取本地文件,上传到服务器,读取服务器回写的数据

    明确:
        数据源:E:\\IdeaProjects\\1.txt
        目的地:服务器

    实现步骤:
        1. 创建一个本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
        2. 创建一个客户端Socket对象,构造方法中绑定服务器的IP 地址和端口号
        3. 使用Socket中的方法getOutputStream,获取网络字节输出流OutputSream 对象
        4. 使用本地字节谁流FileInputStream对象中的方法read,读取本地文件
        5. 使用网络字节输出流OutputStream的象中的方法write,把读取到的文件上传到服务器
        6. 使用Socket中的方法getInputStream,获取网络字节输出流InputSream 对象
        7. 使用网络字节输入流InputStream对象中的方法read读取服务区回写的数据
        8. 释放资源(FileInputStream,Socket)
 */
public class FileUpload {
    public static void main(String[] args) throws IOException {
        //  1. 创建一个本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
        FileInputStream fis = new FileInputStream("E:\\IdeaProjects\\1.txt");
        //  2. 创建一个客户端Socket对象,构造方法中绑定服务器的IP 地址和端口号
        Socket socket = new Socket("127.0.0.1",8888);
        //  3. 使用Socket中的方法getOutputStream,获取网络字节输出流OutputSream 对象
        OutputStream os = socket.getOutputStream();
        //  4. 使用本地字节谁流FileInputStream对象中的方法read,读取本地文件
        int len = 0;
        byte[] bytes = new byte[1024];
        while ((len = fis.read(bytes)) != -1){
            //  5. 使用网络字节输出流OutputStream的象中的方法write,把读取到的文件上传到服务器
            os.write(bytes,0,len);
        }

        /*
            解决:上传完文件,给服务器写一个结束标记
            void shutdownOutput() 禁用此套接字的输出流
            对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列
         */
        socket.shutdownOutput();

        //  6. 使用Socket中的方法getInputStream,获取网络字节输出流InputSream 对象
        InputStream is = socket.getInputStream();
        //  7. 使用网络字节输入流InputStream对象中的方法read读取服务区回写的数据
        while ((len = is.read(bytes)) != -1){
            System.out.println(new String(bytes,0,len));
        }
        //  8. 释放资源(FileInputStream,Socket)
        fis.close();
        socket.close();
    }
}
20.3.2 文件上传案例的服务端
package cn.code.Demo023;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/*
    文件上传案例服务器端:读取客户端上传的文件,保存到服务器的硬盘,给客户端回写“上传成功”

    明确:
        数据源:客户端上传的文件
        目的地:服务器的硬盘  D:\\upload\\1.txt

    实现步骤:
        1. 创建一个服务器ServerSocket对象,和系统要指定的端口号
        2. 使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
        3. 使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
        4. 判断D:\\upload文件是否存在,不存在则创建
        5. 创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
        6. 使用网络字节输出流InputStream对象中的方法read,读取客户端上传的文件
        7. 使用本地字节输出流FileOutputStream对象中的方法write,把读取到的文件保存到服务器的硬盘上
        8. 使用Socket对象中的方法getOutputStream,获取到网络字节输出流OutputStream对象
        9. 使用网络字节输出流OutputStream对象中的方法write,给客户端回写“上传成功”
        10. 释放资源(FileOutputStream,ServerSocket,Socket)
 */
public class FileUpload02 {
    public static void main(String[] args) throws IOException {
        //  1. 创建一个服务器ServerSocket对象,和系统要指定的端口号
        ServerSocket server = new ServerSocket(8888);
        //  2. 使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
        Socket socket = server.accept();
        //  3. 使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
        InputStream is = socket.getInputStream();
        //  4. 判断D:\\upload文件是否存在,不存在则创建
        File file = new File("D:\\upload");
        if (! file.exists()){
            file.mkdirs();
        }
        //  5. 创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
        FileOutputStream fos = new FileOutputStream(file + "\\1.txt");
        //  6. 使用网络字节输出流InputStream对象中的方法read,读取客户端上传的文件
        int len = 0;
        byte[] bytes = new byte[1024];
        while ((len = is.read(bytes)) != -1){
            //  7. 使用本地字节输出流FileOutputStream对象中的方法write,把读取到的文件保存到服务器的硬盘上
            fos.write(bytes,0,len);
        }
        //  8. 使用Socket对象中的方法getOutputStream,获取到网络字节输出流OutputStream对象
        OutputStream os = socket.getOutputStream();
        //  9. 使用网络字节输出流OutputStream对象中的方法write,给客户端回写“上传成功”
        os.write("上传成功!".getBytes());
        //  10. 释放资源(FileOutputStream,ServerSocket,Socket)
        fos.close();
        server.close();
        socket.close();
    }
}
20.3.3 文件上传案例的阻塞问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-017CFFTb-1573823585037)(C:\Users\Think-Pad\Desktop\java\文件上传案例阻塞问题.jpg)]

第二十一章 函数式接口

21.1 函数式接口的概念、定义和使用

函数式接口:

package cn.code.Demo023;
/*
    函数式接口:有且仅有一个抽象方法的接口,称之为函数式接口
    当然接口中也可以包含其他的方法(默认,静态,私有)

    @FunctionalInterface注释
    作用:
        是:编译成功
        否:编译失败(接口中没有抽象方法、抽象方法多个数 >1个)
 */
@FunctionalInterface
public interface MyFunctionalInterface {
    //  定义一个抽象方法
    public abstract void method();
}

函数式接口的实现类:

package cn.code.Demo023;
/*
    @Override注解
    检查方法是否为重写的方法
        是:编译成功
        否:编译失败
 */
public class MyFunctionalInterfaceImpl implements MyFunctionalInterface{
    @Override
    public void method() {

    }

}

函数式接口的使用:

package cn.code.Demo023;
/*
    函数式接口的使用:一般可以作为方法的参数和返回值类型
 */
public class Test {
    //  定义一个方法,参数使用函数式接口MyFunctionalInterface
    public static void show(MyFunctionalInterface myInter){
        myInter.method();
    }

    public static void main(String[] args) {
        //  调用show方法,方法的参数是一个接口,所以可以传递接口的实现类对象
        show(new MyFunctionalInterfaceImpl());

        //  调用show方法,方法的参数是一个接口,所以我们可以传递接口的匿名内部类
        show(new MyFunctionalInterface() {
            @Override
            public void method() {
                System.out.println("使用匿名内部类重写接口中的抽象方法");
            }
        });

        //  调用show方法,方法的参数是一个函数式接口,所以我们可以lambda表达式
        show(() ->{
            System.out.println("使用Lambda表达式重写接口中的抽象方法");
        });

        //  简化Lambda表达式
        show(() -> System.out.println("使用Lambda表达式重写接口中的抽象方法"));
    }
}

21.1.1 函数式接口作为方法的参数
package cn.code.Demo023;
/*
    例如java.lang.Runnable接口就是一个函数式接口
    假设有一个startThread方法使用该接口作为参数,俺么就可以使用Lambda进行传参
    这种情况其实和Thread类的构造方法参数为Runnable没有本质区别
 */
public class Demo01RUnnable {
    //  定义一个方法startThread,方法的参数使用函数式接口Runnable
    public static void startThread(Runnable run){
        //  开启多线程
        new Thread(run).start();
    }

    public static void main(String[] args) {
        //  调用startThread方法,方法的参数是一个接口,那么我们可以传递这个接口的匿名内部类
        startThread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "-->" + "线程启动了");   //  Thread-0-->线程启动了
            }
        });

        //  调用startThread方法,方法的参数是一个函数式接口,所以可以传递Lambda表达式
        startThread(() -> System.out.println(Thread.currentThread().getName() + "-->" + "线程启动了"));  //  Thread-1-->线程启动了
    }
}
21.1.2 函数式接口作为方法的返回值类型
package cn.code.Demo023;

import java.util.Arrays;
import java.util.Comparator;

/*
    如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式
    当需要通过一个方法来获取一个java.util.Comparator接口类型的对象作为排序器时,就可以调该方法获取
 */
public class Demo02Comparator {
    //  定义一个方法,方法的返回值类型使用函数式接口Comparator
    public static Comparator<String > getComparator(){
        //  方法的返回值类型是一个接口,那么我们就可以返回这个接口的匿名内部类
        /*return new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                //  按照字符串的降序排序
                return o2.length() - o1.length();
            }
        };*/

        //  方法的返回值类型是一个函数式接口,所以我们可以返回一个Lambda表达式
        /*return (String o1, String o2) ->{
            //  按照字符串的降序排序
            return o2.length() - o1.length();
        };*/

        //  继续简化Lambda表达式
        return (o1,o2) -> o2.length() - o1.length();
    }

    public static void main(String[] args) {
        //  创建一个字符串
        String[] s1 = new String[]{"aaaa", "bbbbbb", "rrrrrrrr", "sasd"};
        //  输出排序前的数组
        System.out.println(Arrays.toString(s1));  //  [aaaa, bbbbbb, rrrrrrrr, sasd]
        //  调用Arrays中的sort方法,对字符串数组进行排序
        Arrays.sort(s1,getComparator());
        //  输出排序后的数组
        System.out.println(Arrays.toString(s1));  //  [rrrrrrrr, bbbbbb, aaaa, sasd]
    }
}
21.2 日志案例
package cn.code.Demo023;
/*
    发现以下代码存在的一些性能浪费的问题
    调用showLog方法,传递的第二个参数是一个拼接后的字符串
    先把字符串拼接好,然后再调用showLog方法
    showLog方法中如果传递的日志等级不是1级
    那么就不会是输出拼接后的字符串
    所以感觉字符串就白拼接了,存在了浪费
 */
public class Demo01Logger {
    //  定义一个根据日志的级别,显示日志信息的方法
    public static void showLog(int level,String message) {
        //  对日志的等级进行判断,如果是1级别,那么输出日志信息
        if (level == 1){
            System.out.println(message);
        }
    }

    public static void main(String[] args) {
        //  定义三个信息
        String msg1 = "Hello";
        String msg2 = "World";
        String msg3 = "Java";

        //  调用showLog方法,传递日志级别和日志信息
        showLog(1,msg1+msg2+msg3);
    }
}

优化:

package cn.code.Demo023;
@FunctionalInterface
public interface MessageBuilder {
    //  定义一个拼接信息的抽象方法,返回被拼接的消息
    public abstract String builderMessage();
}
package cn.code.Demo023;
/*
    使用Lambda优化日志案例
    Lambda的特点:延迟加载
    Lambda的使用前提,必须存在函数式接口
 */
public class Demo02Lambda {
    //  定义一个现实日志的方法,方法的参数传递日志的等级和MessageBuilder接口
    public static void showLog(int level,MessageBuilder mb){
        //  对日志的等级进行判断,如果是1级,则调用 MessageBuilder 接口中的 builderMessage方法
        if (level == 1){
            System.out.println(mb.builderMessage());
        }
    }

    public static void main(String[] args) {
        //  定义三个日志信息
        String msg1 = "Hello";
        String msg2 = "World";
        String msg3 = "Java";

        //  调用showLog方法,参数MessageBuilder是一个函数式接口,所以可以使用Lambda表达式
        /*showLog(1,() ->{
            //  返回一个拼接好的字符串
            return msg1+msg2+msg3;
        });*/

        /*
            使用Lambda表达式作为参数传递,仅仅是把参数传递到showLog方法中
            只有满足条件,日志的等级是1级
                才会调用接口MessageBuilder中的方法builderMessage
                才会进行字符串的拼接
            如果条件不满足,日志的等级不是1级
                那么MessageBuilder接口中的方法builderMessage也不会执行
                所以拼接字符串的代码也不会执行
            所以不会存在性能的浪费
         */
        showLog(1,() ->{
            System.out.println("不满足条件不执行!");
            //  返回一个拼接好的字符串
            return msg1+msg2+msg3;
        });
    }
}
21.3 常用的函数式接口 —— Supplier接口
package cn.code.Demo023;

import java.util.function.Supplier;

/*
    常用的函数式接口
    java.util.function.Supplier<T>接口包含一个无参的方法:T get()。 用来获取一个泛型参数指定类型的对象数据

    Supplier<T>接口被称之为成产型接口,指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据
 */
public class Demo01Supplier {
    //  定义一个方法,方法的参数传递Supplier<T>接口,泛型执行String, get方法就会返回一个String
    public static String getString(Supplier<String> sup){
        return sup.get();
    }

    public static void main(String[] args) {
        //  调用getString方法,方法的参数Supplier是一个函数式接口,所以可以使用Lambda表达式
        String s1 = getString(() -> {
            //  生产一个字符串,并返回
            return "胡歌";
        });
        System.out.println(s1);  //  胡歌

        //  优化Lambda表达式
        String s2 = getString(() -> "胡歌");
        System.out.println(s2);  //  胡歌
    }
}
21.3.1 求数组元素的最大值
package cn.code.Demo023;

import java.util.function.Supplier;

/*
    练习:求数组元素最大值
        使用Supplier接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值
        提示:接口的泛型请使用java.lang,Integer类
 */
public class Demo02Test {
    //  定义一个方法,用于获取int类型数组中元素的最大值,方法的参数传递Supplier接口,泛型使用Integer
    public static int getMax(Supplier<Integer> sup){
        return sup.get();
    }

    public static void main(String[] args) {
        //  定义一个int类型的数组,并复制
        int[] arr = {100,845,55,20,10,516};
        //  调用getMax方法,方法的参数Supplier是一个函数式接口,所以可以传递Lambda表达式
        int max1 = getMax(() -> {
            //  获取数组的最大值,并返回
            //  定义一个变量,把数组中的第一个元素赋值给该变量,记录数组中元素的最大值
            int max = arr[0];
            //  遍历数组,获取数组中的其他元素
            for (int i : arr) {
                //  使用其他元素和最大值比较
                if (i > max) {
                    //  如果i大于max,则替换max作为最大值
                    max = i;
                }
            }
            //  返回最大值
            return max;
        });
        System.out.println("数组中元素的最大值是:" + max1);  //  数组中元素的最大值是:845
    }
}
21.4 常用的函数式接口 —— Consumer接口
package cn.code.Demo024;


import java.util.function.Consumer;

/*
    java.util.function.Consumer<T>接口则正好与Supplier接口相反,
        它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定
    Consumer接口中包含抽象方法 void accept(T t),意为消费一个指定泛型的数据

    Consumer接口是一个消费型接口,泛型执行什么类型,就可以使用accept方法消费什么类型的数据
    至于具体怎么消费(使用),需要自定义(输出,计算...)
 */
public class Demo01Consumer {
    /*
        定义一个方法
        方法的参数传递一个字符串的姓名
        方法的参数传递Consumer接口,泛型使用String
        可以使用Consumer接口消费字符串的姓名
     */
    public static void method(String name, Consumer<String> con){
        con.accept(name);
    }

    public static void main(String[] args) {
        // 调用method方法,传递字符串姓名,方法的另一个参数是Consumer接口,是一个函数式接口,所以可以传递Lambda表达式
        method("赵丽颖",(String name) ->{
            //  对传递的字符串进行消费
            //  消费方法:直接输出字符串
//            System.out.println(name);

            //  消费方法:把字符串进行反转输出
            String rename = new StringBuffer(name).reverse().toString();
            System.out.println(rename);
        });
    }
}

21.4.1 Consumer接口中的默认方法 andThen
package cn.code.Demo024;

import java.util.function.Consumer;

/*
    Consumer接口的默认方法andThen
    作用:需要两个Consumer接口,可以把两个Consumer接口组合到一起,再对数据进行消费

    例如:
        Consumer<String> con1;
        Consumer<String> con2;
        String s = "hello";
        con1.accept(s);
        con2.accept(s);
        连接两个Consumer接口,再进行消费
        con1.andThen(con2).accept(s);  谁写在前边谁先消费
 */
public class Demo02AndThen {
    //  定义一个方法,方法的参数传递一个字符串和两个Consumer接口,Consumer接口的泛型使用字符串
    public static void method(String s, Consumer<String> con1, Consumer<String> con2){
//        con1.accept(s);
//        con2.accept(s);
        //  使用andThen方法,把两个Consumer接口连接到一起,再消费数据
        con1.andThen(con2).accept(s);  //  con1连接con2,先执行con1消费数据,再执行con2消费数据
    }

    public static void main(String[] args) {
        //  调用method方法,传递一个字符串,两个Lambda表达式
        method("Hello",
                (t) -> {
                    //  消费方式:把字符串转换为大写输出
                    System.out.println(t.toUpperCase());  //  HELLO
                },
                (t) -> {
                    //  消费方式:把字符串转换为小写输出
                    System.out.println(t.toLowerCase());  //  hello
                });
    }
}
21.4.2 Consumer接口练习 —— 字符串拼接输出
package cn.code.Demo024;

import java.util.function.Consumer;

/*
    练习:
        字符串数组当中存在有多条信息,请按照格式“姓名:xx。性别:xx。” 的格式将信息打印出来
        要求将打印姓名的动作作为第一个Consumer接口的Lambda实例
        将打印性别的动作作为第二个Consumer接口的Lambda实例
        将两个Consumer接口按照顺序“拼接”到一起
 */
public class Demo03Test {
    //  定义一个方法,参数传递String类型的数组和两个Consumer接口,泛型使用String
    public static void printInfo(String[] arr, Consumer<String> con1, Consumer<String> con2){
        //  遍历字符串数组
        for (String s : arr) {
            //  使用andThen方法连接两个Consumer接口,消费字符串
            con1.andThen(con2).accept(s);
        }
    }

    public static void main(String[] args) {
        //  定义一个字符串类型的数组
        String[] arr ={"迪丽热巴,女","邓伦,男","欧阳娜娜,女"};

        //  调用printInfo方法,传递一个字符串数组,和两个Lambda表达式
        printInfo(arr,
                (s) -> {
            //  消费方式:对s进行切割,获取姓名,按照指定的格式输出
                    String name = s.split(",")[0];
                    System.out.println("姓名:" + name);
                },
                (s) -> {
            //  消费方式:对s进行切割,获取性别,按照指定的格式输出
                    String sex = s.split(",")[1];
                    System.out.println("性别:" + sex);
                });
    }
}
21.5 常用的函数式接口 —— Predicate接口
package cn.code.Demo024;

import java.util.function.Predicate;

/*
    java.util.function.predicate<T>接口
    作用:对某种数据类型的数据进行判断,结果返回一个Boolean值

    predicate接口中包含一个抽象方法:
        boolean test(T t): 用来对指定数据类型进行判断的方法
            结果:
                符合条件,返回true
                不符合条件,返回false
 */
public class Demo01Predicate {
    /*
        定义一个方法
        参数传递一个String类型的字符串
        传递一个Predicate接口,泛型使用String
        使用Predicate中的方法test对字符串进行判断,并把判断的结果返回
     */
    public static boolean checkString(String s, Predicate<String> pre){
        return pre.test(s);
    }

    public static void main(String[] args) {
        //  定义一个字符串
        String s = "abcffde";

        //  调用checkString方法对字符串进行校验,参数传递字符串和Lambda表达式
        /*boolean b = checkString(s, (String str) -> {
            //  对参数传递的字符串进行判断,判断字符串的长度是否大于5,并把判断的结果返回
            return str.length() > 5;
        });
        System.out.println(b);*/

        //  优化Lambda表达式
        boolean b = checkString(s,str ->  str.length() > 5);
        System.out.println(b);
    }
}
21.5.1 Predicate接口的默认方法 —— and && or || negate !
package cn.code.Demo024;

import java.util.function.Predicate;

/*
    逻辑表达式:可以连接多个判断条件
    &&:与运算符,有false则false
    ||:或运算符,有true则true
    !:非(取反)运算符,非真则假,非假则真

    需求:判断一个字符串,有两个判断条件
        1. 判断字符串的长度是否大于5
        2. 判断字符串中是否包含a
    两个条件必须同时满足,我们就可以使用&&运算符连接两个条件

    Predicate接口中有一个方法and,表示并且关系,也可以用于连接两个判断条件
    default Predicate<T> and(Predicate<? super T> other){
        Objects.requireNonNull(other);
        return (t) -> this.test(t) && other.test(t);
    }
    方法内部的两个判断条件,也是使用&&运算符连接起来的
 */
public class Demo02Predicate {
    /*
          定义一个方法,方法的参数,传递一个字符串
          传递两个Predicate接口
            一个用于判断字符串的长度是否大于5
            一个用于判断字符串中是否包含a
            两个条件同时满足
     */
    public static boolean checkString(String s, Predicate<String> pre1, Predicate<String> pre2){
//        return pre1.test(s) && pre2.test(s);
//        return pre1.and(pre2).test(s);  //  等价于return pre1.test(s) && pre2.test(s);
//        return pre1.test(s) || pre2.test(s);
//        return pre1.or(pre2).test(s);  //  等价于 return pre1.test(s) || pre2.test(s);
//        return !pre1.test(s);
        return pre1.negate().test(s);  //  等价于 return !pre1.test(s);
    }

    public static void main(String[] args) {
        //  定义一个字符串
        String s = "aferrd";
        //  调用checkString方法,参数传递字符串和两个Lambda表达式
        boolean b = checkString(s, (String str) -> {
            //  判断字符串的长度是否大于5
            return s.length() > 5;
        }, (String str) -> {
            //  判断字符串中是否包含a
            return s.contains("a");
        });
        System.out.println(b);
    }
}
21.5.2 Predicate练习 —— 集合信息筛选
package cn.code.Demo024;

import java.util.ArrayList;
import java.util.function.Predicate;

/*
    练习:集合信息筛选
    数组当中有多条“姓名+性别” 的信息如下,
    String[] arr ={"迪丽热巴,女","邓伦,男","欧阳娜娜,女"};
    请通过Predicate接口的拼装将符合要求的字符串筛选到集合ArrayList中,
    需要同时满足两个条件:
        1. 必须为女生
        2. 姓名为4个字

    分析:
        1. 有两个判断条件,所以需要使用两个Predicate接口,对条件进行判断
        2. 必须同时满足两个条件,所以可以使用and方法连接两个判断条件
 */
public class Demo04Text {
    /*
        定义一个方法
        方法的参数传递一个包含人员信息的数组
        传递两个Predicate接口,用于对数组中的信息进行传递
        把满足条件的信息存到ArrayList集合中并返回
     */
    public static ArrayList<String> filter(String[] arr, Predicate<String> pre1,Predicate<String> pre2){
        //  定义一个ArrayList集合,存储过滤之后的信息
        ArrayList<String> list = new ArrayList<>();
        //  遍历数组,获取数组中的每一条信息
        for (String s : arr) {
            //  使用Predicate接口中的方法test对获取到的字符串进行判断
            boolean b = pre1.and(pre2).test(s);
            //  对得到的布尔值进行判断
            if (b){
                //  条件成立,两个条件都满足,把信息存储到ArrayList集合或者
                list.add(s);
            }
        }
        //  把集合返回
        return list;
    }

    public static void main(String[] args) {
        //  定义一个存储字符串的数组
        String[] arr ={"迪丽热巴,女","邓伦,男","欧阳娜娜,女"};
        //  调用fileter方法,传递字符串数组和两个Lambda表达式
        ArrayList<String> list = filter(arr, (String s) -> {
            //  获取字符串中的性别,判断是否为女
            return s.split(",")[1].equals("女");
        }, (String s) -> {
            //  获取字符串中的姓名,判断传递是否为4个字符
            return s.split(",")[0].length() == 4;
        });
        //  遍历数组
        for (String s : list) {
            System.out.println(s);
        }
    }
}
21.6 常用的函数式接口 —— Function接口
package cn.code.Demo024;

import java.util.function.Function;

/*
    java.util.function.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据,
        前者称为前置条件,后者称为后置条件。
    Function接口中最主要的抽象方法为:R apply(T t),根据类型T的参数获取R的结果。
        使用的场景例如:将String类型转换为Integer类型。
 */
public class Demo01Function {
    /*
        定义一个方法
        方法的参数传递一个字符串类型的整数
        方法的参数传递一个Function接口,泛型使用<String,Integer>
        使用Function接口中的方法apply,把字符串类型的整数,转换为Integer类型的整数
     */
    public static void change(String s, Function<String,Integer> fun){
        Integer in = fun.apply(s);
        System.out.println(in);
    }

    public static void main(String[] args) {
        //  定义一个字符串类型的整数
        String s = "1235";
        //  调用change方法,传递字符串类型的整数,和Lambda表达式
        change(s,(String str) -> {
            //  把字符串类型的整数,转换为Integer类型的整数返回
            return Integer.parseInt(s);
        });
    }
}
21.6.1 Function接口中的默认方法 —— andThen
package cn.code.Demo024;

import java.util.function.Function;

/*
    Function接口中的默认方法andThen:用来进行组合操作

    需求:
        把String类型的"123",转换为Integer类型,把转换后的结果加10
        把增加之后的Integer类型的数据,转换为String类型
    分析:
        转换了两次
        第一次是把String类型转换为了Integer类型
            所以我们可以使用Function<String,Integer>  fun1
                Integer i = fun1.apply("123") + 10;
        第二次是把Integer类型转换为String类型
            所以我们可以使用Function<String,Integer>  fun2
                String s = fun2.apply(i);
        我们可以使用andThen方法,把两次转换组合在一起使用
            String s = fun1.andThen(fun2).apply("123");
            fun1先调用apply方法,把字符串转换为Integer
            fun2再调用apply方法,把Integer转换为字符串
 */
public class Demo02Function_andThen {
    /*
        定义一个方法
        参数串一个字符串类型的整数
        参数再cd两个Function接口
            一个泛型使用Function<String,Integer>
            一个泛型使用Function<Integer,String>
     */
    public static void change(String s, Function<String,Integer> fun1,Function<Integer,String> fun2){
        String ss = fun1.andThen(fun2).apply(s);
        System.out.println(ss);
    }

    public static void main(String[] args) {
        //  定义一个字符串类型的整数
        String s = "123";
       /* //  调用change方法,传递字符串和两个Lambda表达式
        change(s,(String str) -> {
            //  把字符串类型转换为整数 +10
            return Integer.parseInt(str) + 10;
        }, (Integer i) -> {
            //  把整数转换为字符串
            return i+"";
        });
*/
        //  优化Lambda表达式
        change(s,str ->  Integer.parseInt(str) + 10, i -> i+"");
    }
}
21.6.2 Function接口练习 —— 自定义函数模型拼接
package cn.code.Demo024;

import java.util.function.Function;

/*
    练习:自定义函数模型拼接
    题目
    请使用Function进行函数模型的拼接,按照顺序需要执行的多个函数操作为:
        String str = "赵丽颖,20";

    分析:
    1. 将字符串截取数字年龄部分,得到字符串;
        Function<String,String> "赵丽颖,20" -->"20"
    2. 将上一步的int数字累加100,得到结果int数字
        Function<String,Integer>  "20" --> 20
    3. 将上一步的int数字累加100,得到结果int数字
        Function<Integer,Integer>  20 --> 120
 */
public class Demo05Text {
    /*
        定义一个方法
        参数传递包含姓名和年龄的字符串
        参数再传递3个Function接口用于类型转换
     */
    public static int change(String s, Function<String,String> fun1, Function<String,Integer> fun2, Function<Integer,Integer>fun3){
        //  使用andThen方法把三个转换组合到一起
        return fun1.andThen(fun2).andThen(fun3).apply(s);
    }

    public static void main(String[] args) {
        //  定义一个字符串
        String str = "赵丽颖,20";
        //  调用change方法,参数传递字符串和3个Lambda表达式
        int change = change(str, (String s) -> {
            //  "赵丽颖,20" -->"20"
            return str.split(",")[1];
        }, (String s) -> {
            //  "20" --> 20
            return Integer.parseInt(s);
        }, (Integer i) -> {
            //  20 --> 120
            return i + 100;
        });
        System.out.println(change);  //  120
    }
}

第二十二章 Stream流

22.1 使用传统方法&使用Stream流 遍历集合,对集合中的数据进行过滤

传统方式:

package cn.code.Demo025;

import java.util.ArrayList;
import java.util.List;

/*
    使用传统的方式:遍历集合,对集合中的数据进行过滤
 */
public class Demo01List {
    public static void main(String[] args){
        //  创建一个List集合,存储姓名
        List<String> list = new ArrayList<>();
        list.add("陈粒");
        list.add("张杰");
        list.add("陈绮贞");
        list.add("陈奕迅");

        //  对List集合中的元素进行过滤,只要以陈开头的元素,存储到一个新的集合中
        List<String> list1 = new ArrayList<>();
        for (String s : list) {
            if (s.startsWith("陈")){
                list1.add(s);
            }
        }
        //  对lsit1集合进行过滤,只要姓名传递为3的人,存储到一个新集合中
        List<String> list2 = new ArrayList<>();
        for (String s : list1) {
            if (s.length() == 3){
                list2.add(s);
            }
        }

        //  遍历list2集合
        for (String s : list2) {
            System.out.println(s);
        }
    }
}

使用Stream流:

package cn.code.Demo025;

import java.util.ArrayList;
import java.util.List;

/*
    使用Stream流的方式,遍历集合,对集合中的数据进行过滤
    Stream流是JDK1.8之后出现的
    关注的是做什么,而不是怎么做
 */
public class Demo02Stream {
    public static void main(String[] args) {
        //  创建一个List集合,存储姓名
        List<String> list = new ArrayList<>();
        list.add("陈粒");
        list.add("张杰");
        list.add("陈绮贞");
        list.add("陈奕迅");

        //  对List集合中的元素进行过滤,只要以陈开头的元素,存储到一个新的集合中
        //  对lsit1集合进行过滤,只要姓名传递为3的人,存储到一个新集合中
        //  遍历list2集合
        list.stream()
                .filter(name -> name.startsWith("陈"))
                .filter(name -> name.length() == 3)
                .forEach(name -> System.out.println(name));
    }
}
22.2 Stream流中的常用方法 —— forEach
package cn.code.Demo025;

import java.util.stream.Stream;

/*
    Stream流中的常用方法_forEach
    void _forEach(Consumer<? super T> action);
    该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理
    Consumer接口是一个消费型的函数式接口,可以传递Lambda表达式,消费数据

    简单记:
        forEach方法,用来遍历流中的数据
        是一个终结方法,遍历之后就不能继续调用Stream流中的其他方法
 */
public class Demo02Stream_forEach {
    public static void main(String[] args) {
        //  获取一个Stream流
        Stream<String> stream = Stream.of("张三", "李四", "王五", "赵六", "田七");
        //  使用Stream流中的方法forEach对Stream流中的数据进行遍历
        /*stream.forEach((String name) -> {
            System.out.println(name);
        });*/

        stream.forEach(name -> System.out.println(name));
    }
}
22.3 Stream流中的常用方法 —— filter
package cn.code.Demo025;

import java.util.stream.Stream;

/*
    Stream流中的常用方法_filter:用来对Stream流或者的数据进行过滤
    Stream<T> filter(Predicate<? super T> predicate);
    filter方法的参数Predicate是一个函数式接口,所以可以传递Lambda表达式,对数据进行过滤
    Predicate中的抽象方法:
        boolean test(T t);
 */
public class Demo03Stream_filter {
    public static void main(String[] args) {
        //  获取一个Stream流
        Stream<String> stream = Stream.of("张三", "李四", "王五", "赵六", "田七");
        //  对Stream流中的元素进行过滤,只要姓张的人
        Stream<String> stream2 = stream.filter((String name) -> {
            return name.startsWith("张");
        });
        //  遍历stream2流
        stream2.forEach(name -> System.out.println(name));  //  张三
        
        /*
            Stream流属于管道流,只能被消费(使用)一次
            第一个Stream流调用方法完毕,数据就会转到下一个Stream上
            而这时第一个Stream流已经使用完毕,就会关闭了
            所以第一个Stream流就不能再调用方法了
            IllegalStateException: stream has already been operated upon or closed
         */
        //  遍历Stream流
        stream.forEach(name -> System.out.println(name));  //  IllegalStateException: stream has already been operated upon or closed
    }
}
22.4 Stream流中的常用方法 —— map
package cn.code.Demo025;

import java.util.stream.Stream;

/*
    如果需要将流中的元素映射到另一个流中,可以使用map方法
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);
    该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流
    Function中的抽象方法:
        R apply(T t);
 */
public class Demo04Stream_map {
    public static void main(String[] args) {
        //  获取一个String类型的Stream流
        Stream<String> stream = Stream.of("1", "2", "3", "4");
        //  使用map方法,把字符串类型的整数,转换(映射)为Integer类型的整数
        Stream<Integer> stream2 = stream.map((String s) -> {
            return Integer.parseInt(s);
        });
        //  遍历Stream2流
        stream2.forEach(i -> System.out.println(i));
    }
}
22.5 Stream流中的常用方法 —— count
package cn.code.Demo025;

import java.util.ArrayList;

/*
    Stream流中的常用方法_count:用于统计Stream流中的元素个数
    Long count();
    count方法是一个终结方法,返回值是一个Long类型的整数
    所以不能再调用Stream流中的其他方法了
 */
public class Demo05Stream_count {
    public static void main(String[] args) {
        //  获取一个Stream流
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        list.add(6);
        long count = list.stream().count();
        System.out.println(count);  //  6
    }
}
22.6 Stream流中的常用方法 —— limit
package cn.code.Demo025;

import java.util.stream.Stream;

/*
    Stream流中的常用方法_limit:用于截取流中的元素
    limit方法可以对流进行截取,只取用前n个,方法格式:
    Strram<T> limit(long maxSize);
        参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作
    limit方法是一个延迟方法,只是对流中的元素进行截取,返回的是一个新的流,所以可以继续调用Stream流中的其他方法
 */
public class Demo06Stream_limit {
    public static void main(String[] args) {
        //  获取一个Stream流
        String[] arr = {"美羊羊","喜羊羊","懒羊羊","灰太狼","红太狼"};
        Stream<String> stream = Stream.of(arr);
        //  使用limit方法对Stream流中的元素进行截取,只要前3个元素
        Stream<String> stream2 = stream.limit(3);
        //  遍历stream2流
        stream2.forEach(name -> System.out.println(name));
    }
}
22.7 Stream流中的常用的方法 —— skip
package cn.code.Demo025;

import java.util.stream.Stream;

/*
    Stream流中常用方法_skip:用于跳过元素
    如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流:
    Stream<T> skip(long n);
        如果流的当前长度大于n,则跳过前几n个,否则将会得到一个长度为0的空流。
 */
public class Demo07Stream_skip {
    public static void main(String[] args) {
       //  获取一个Stream流
        String[] arr = {"美羊羊","喜羊羊","懒羊羊","灰太狼","红太狼"};
        Stream<String> stream = Stream.of(arr);
        //  使用skip方法跳过前3个元素
        Stream<String> stream2 = stream.skip(3);
        //  遍历stream2流
        stream2.forEach(name -> System.out.println(name));
    }
}
22.8 Stream流中的常用方法 —— concat
package cn.code.Demo025;

import java.util.stream.Stream;

/*
    Stream流中的常用方法_concat:用于把流组合到一起
    如果有两个流,希望合并成为一个流,那么可以使用Stream接口的静态方法concat
    static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
 */
public class Demo08Stream_concat {
    public static void main(String[] args) {
        //  获取一个Stream流
        String[] arr = {"美羊羊","喜羊羊","懒羊羊","灰太狼","红太狼"};
        Stream<String> stream = Stream.of(arr);
        //  获取一个Stream流
        Stream<String> stream2 = Stream.of("张三", "李四", "王五", "赵六", "田七");
        //  把以上两个流组合为一个流
        Stream<String> concat = Stream.concat(stream2, stream);
        //  遍历concat流
        concat.forEach(a -> System.out.println(a));
    }
}

第二十三章 方法引用

23.1 方法引用基本介绍

接口:

package cn.code.Demo026;
/*
    定义一个打印的函数式接口
 */
@FunctionalInterface
public interface Printable {
    //  定义字符串的抽象方法
    public abstract void print(String s);
}


package cn.code.Demo026;

public class Demo01Printable{
    //  定义一个方法,参数传递Printable接口,对字符串进行打印
    public static void printable(Printable p){
        p.print("HelloWorld");
    }
    public static void main(String[] args) {
        //  调用printString方法,方法的参数Printable是一个函数式接口,所以可以传递Lambda
        printable((s) -> System.out.println(s));

        /*
            分析:
                Lambda表达式的目的,打印参数传递的字符串
                把参数s,传递给了System.out对象,调用out对象中的println对字符串进行了输出
                注意:
                    1. System.out对象是已经存在的
                    2. println方法也是已经存在的
                所以我们可以使用方法引用来优化Lambda表达式
                可以使用System.out方法直接引用(调用)println方法
         */
        printable(System.out::println);
    }
}
23.2 通过对象名引用成员方法
package cn.code.Demo026;
/*
    通过对象名引用成员方法
    使用前提是对象名是已经存在的,成员方法也是已经存在
    就可以使用对象名来引用成员方法
 */
public class Demo01ObjectMethodReference {
    //  定义一个方法,方法的参数传递Printable接口
    public static void printString(Printable p){
        p.print("Hello");
    }

    public static void main(String[] args) {
        //  调用printString方法,方法的参数Printable是一个函数式接口,所以可以传递Lambda表达式
        printString((s) ->{
            //  创建MethodRerObject对象
            MethodRerObject obj = new MethodRerObject();
            //  调用MethodRerObject对象中的成员方法printUpperCaseString,把字符串按照大写输出
            obj.printUpperCaseString(s);  //  HELLO
        });

        /*
            使用方法引用优化Lambda
            对象是已经存在的MethodRerObject
            成员方法也是已经存在的printUpperCaseString
            所以我们可以使用对象名引用成员方法
         */
        //  创建MethodRerObject对象
        MethodRerObject obj = new MethodRerObject();
        printString(obj :: printUpperCaseString);  //  HELLO
    }
}

package cn.code.Demo026;

public class MethodRerObject {
    //  定义一个成员方法,传递字符串,把字符串按照大写输出
    public void printUpperCaseString(String str){
        System.out.println(str.toUpperCase());
    }
}


package cn.code.Demo026;
/*
    定义一个打印的函数式接口
 */
@FunctionalInterface
public interface Printable {
    //  定义字符串的抽象方法
    public abstract void print(String s);
}
23.3 通过类名引用静态成员方法
package cn.code.Demo026;
/*
    定义一个计算绝对值的函数式接口
 */
@FunctionalInterface
public interface Calcable {
    //  定义一个抽象方法,传递一个整数,对整数进行绝对值计算并返回
    int calsAbs(int number);
}

package cn.code.Demo026;
/*
    静态类名引用静态方法
    类已经存在,静态成员方法也已经存在
    就可以通过类名直接引用静态成员方法
 */
public class Demo01StaticMethodReference {
    //  定义一个方法,方法的参数传递要计算绝对值的整数,和函数式接口Calcable
    public static int method(int number, Calcable c){
        return c.calsAbs(number);
    }

    public static void main(String[] args) {
        //  调用method方法,传递计算绝对值的整数,和Lambda表达式
        int i = method(-10, (n) -> {
            //  对参数进行绝对值计算并返回结果
            return Math.abs(n);
        });
        System.out.println(i);  //  10


    /*
        使用方法引用优化Lambda表达式
        Math类是存在的
        abs计算绝对值的静态方法也是存在的
        所以我们可以直接通过类名引用静态方法
     */
        int i1 = method(-10, Math::abs);
        System.out.println(i1);  //  10
    }
}
23.4 通过super引用分类的成员方法
package cn.code.Demo026;
/*
    定义见面的函数式接口
 */
@FunctionalInterface
public interface Greatable {
    //  定义一个见面的方法
    void greet();
}

package cn.code.Demo026;
/*
    定义父类
 */
public class Fu {
    //  定义一个sayHello
    public void sayHello(){
        System.out.println("Hello,我是Fu类!");
    }
}

package cn.code.Demo026;
/*
    定义子类
 */
public class Zi extends Fu {
    //  子类重写父类sayHello的方法
    @Override
    public void sayHello(){
        System.out.println("Hello,我是Zi类!");
    }

    //  定义一个方法参数传递Greatable接口
    public void method(Greatable g){
        g.greet();
    }

    public void show(){
        //  调用method方法,方法的参数Greatable是一个函数式接口,所以可以传递Lambda
        /*method(() ->{
            //  创建父类Fu对象
            Fu f =new Fu();
            //  调用父类的sayHello方法
            f.sayHello();
        });*/

        //  因为有子类关系,所以存在一个关键字super,代表父类,所以我们可以直接使用super调用父类的成员方法
        /*method(() ->{
            super.sayHello();
        });*/

        /*
            使用super引用类的成员方法
            super是已经存在的
            父类的成员方法sayHello也是已经存在的
            所以我们可以直接使用super引用父类的成员方法
         */
        method(super :: sayHello);
    }

    public static void main(String[] args) {
        new Zi().show();
    }
}
23.5 通过this引用本类的成员方法
package cn.code.Demo026;
/*
    定义一个富有的函数式接口
 */
@FunctionalInterface
public interface Richable {
    //  定义一个想买什么就买什么的方法
    void buy();
}


package cn.code.Demo026;
/*
    通过this引用本类的成员方法
 */
public class Husband {
    //  定义一个买房子的方法
    public void buyHouse(){
        System.out.println("北京二环内买一套四合院!");
    }

    //  定义一个结婚的方法,参数传递Richable接口
    public void marry(Richable r){
        r.buy();
    }
    //  定义一个非常高兴的方法
    public void soHappy(){
        //  调用结婚的方法,方法的参数Richable是一个函数式接口,传递Lambda表达式
        marry(() ->{
            //  使用this,成员方法,调用本类买房子的方法
            this.buyHouse();
        });

        /*
            使用方法引用优化Lambda表达式
            this是已经存在的
            本类的成员方法buyHouse也是已经存在的
            所以我们可以直接使用this引用本类的成员方法buyHouse
         */
        marry(this::buyHouse);
    }

    public static void main(String[] args) {
        new Husband().soHappy();
    }
}
23.6 类的构造器(构造方法)的引用
package cn.code.Demo026;

public class Person {
    private String name;

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

package cn.code.Demo026;
/*
    定义一个创建Person对象的函数式接口
 */
@FunctionalInterface
public interface PersonBuilder {
    //  定义一个方法,根据传递的姓名,创建Person对象返回
    Person builderPerson(String name);
}

package cn.code.Demo026;
/*
    类的构造器(构造方法)引用
 */
public class Demo {
    //  定义一个方法,参数传递姓名和PersonBuilder接口,方法中通过姓名创建Person对象
    public static void printName(String name, PersonBuilder pb){
        Person person = pb.builderPerson(name);
        System.out.println(person.getName());
    }

    public static void main(String[] args) {
        //  调用printName方法,方法的参数PersonBuilder接口是一个函数式接口,可以传递Lambda
        printName("迪丽热巴",(String name) -> {
            return new Person(name);
        });

        /*
            使用方法引用优化Lambda表达式
            构造方法new Person(String name)已知
            创建对象已知 new
            就可以使用Person引用new创建对象
         */
        printName("迪丽热巴",Person::new);  //  使用Person类的带参构造方法,通过传递的姓名创建对象
    }
}
23.7 数组的构造器引用
package cn.code.Demo026;
/*
    定义一个创建数组的函数式接口
 */
@FunctionalInterface
public interface ArrayBuilder {
    //  定义一个创建int类型数组的方法,参数传递数组的长度,返回创建好的int类型数组
    int[] builderArray(int length);
}

package cn.code.Demo026;

import java.util.Arrays;

/*
    数组的构造器引用
 */
public class Demo02 {
    /*
        定义一个方法
        方法的参数传递创建数组的长度和ArrayBuilder接口
        方法的内部根据传递的长度使用ArrayBuilder中的方法创建数组并返回
     */
    public static int[] creatArray(int length, ArrayBuilder ab){
        return ab.builderArray(length);
    }

    public static void main(String[] args) {
        //  调用creatArray方法,传递数组的长度和Lambda表达式
        int[] arr1 = creatArray(10, (len) -> {
            //  根据数组的长度,创建数组并返回
            return new int[len];
        });
        System.out.println(arr1.length);  //  10

        /*
            使用方法引用优化Lambda表达式
            已知创建的就是int[]数组
            数组的长度也是已知的
            就可以使用方法引用
            int[]引用new,根据参数传递的长度来创建数组
         */
        int[] arr2 = creatArray(10, int[]::new);
        System.out.println(Arrays.toString(arr2));  //  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        System.out.println(arr2.length);  //  10
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值