Java语法进阶

news2025/2/7 1:11:35

目录:


  1. Object类、常用API
  2. Collection、泛型
  3. List、Set、数据结构、Collections
  4. Map与斗地主案例
  5. 异常、线程
  6. 线程、同步
  7. 等待与唤醒案例、线程池、Lambda表达式
  8. File类、递归
  9. 字节流、字符流
  10. 缓冲流、转换流、序列化流、Files
  11. 网络编程 十二、函数式接口
  12. Stream流、方法引用

一、Object类、常用API


二、Collection、泛型


三、List、Set、数据结构、Collections


四、Map与斗地主案例


五、异常、线程


目标:
1、说出进程的概念
2、说出线程的概念
3、能够理解并发与并行的区别
4、能够开启新线程


4.1 并发与并行

  • 并发:指两个或多个事件在同一个时间段内发生。
  • 并行:指两个或多个事件在同一时刻发生(同时发生)。

4.2 线程与进程

  • 进程:
    是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。

  • 进程:
    线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程

理解版:

  • 进程: 进入到内存的程序,叫作“进程”。
    在ROM(永久)存储的应用程序通过点击运行,进入RAM(临时)内存中

  • 进程: 点击运行应用程序or功能就会开启一条应用程序到CPU的执行路径,这个路径有个名字,叫作“线程”。


进程:
进程


线程:
线程图


线程调度

  • 分时调度:
    所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

  • 抢占式调度:
    优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

抢占式调度详解
大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,dos窗口等软件。此时,这些程序是在同时运行,”感觉这些软件好像在同一时刻运行着“。
实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。
其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。


主线程: 执行主方法(main)的线程

单线程程序:Java程序中只有一个线程,执行从main方法开始,从上到下
主线程


4.3 创建线程类之一

Java使用 java.lang.Thread 类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。Java中通过继承Thread类来创建并启动多线程的步骤如下:

  1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
  2. 创建Thread子类的实例,即创建了线程对象
  3. 调用线程对象的start()方法来启动该线程

第一种方法:
将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。

实现步骤:
1、创建一个Thread的子类(自定一个继承thread的类)。
2、在Thread的子类中重写run方法,设置线程任务(开启线程要做什么)。
3、创建Thread类的对象
4、调用Thread类中的start方法,开启新的线程,执行run方法

void start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
结果是两个线程并发地运行;当前线程(main线程)和另一个线程(执行其 run 方法)。
多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。(start完就没了,不能够继续start)

Java程序属于抢占式调度,哪个线程优先级高,哪个线程优先执行;同一个优先级,随机选择执行(优先级相等,谁线抢占cpu谁就先执行)。

Demo:

Thread的子类

public class Mythread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i <20 ; i++) {
            System.out.println("run:"+i);
        }
    }
}

测试类

public class MyThread_Demo {
    public static void main(String[] args) {
        Mythread run = new Mythread();
        run.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("main:"+i);
        }
    }
}

结果:main线程和run线程的执行顺序是随机的,不是固定的。
因为他们的线程优先级是一样的,所以抢占cpu资源的机会是平分的,谁先抢到了,谁就先执行。


六、线程、同步


目标:

  • 能够描述Java中多线程运行原理
  • 能够使用继承类的方式创建多线程
  • 能够使用实现接口的方式创建多线程
  • 能够说出实现接口方式的好处
  • 能够解释安全问题的出现的原因
  • 能够使用同步代码块解决线程安全问题
  • 能够使用同步方法解决线程安全问题
  • 能够说出线程6个状态的名称

1.1 多线程原理(结合上面的Demo代码)

多线程原理:随机打印的结果
-------------------------------------------------------------不明白?那就看一下详细的吧!----------------------------------------------------------------------------

Demo Code:
自定义类

public class MyThread extends Thread{
    /*
     * 利用继承中的特点
     * 将线程名称传递 进行设置
     */
    public MyThread(String name){
        super(name);
    }
    /*
     * 重写run方法
     * 定义线程要执行的代码
     */
    public void run(){
        for (int i = 0; i < 20; i++) {
//getName()方法 来自父亲
            System.out.println(getName()+i);
        }
    }
}

测试类

public class Demo {
	public static void main(String[] args) {
		System.out.println("这里是main线程");
		MyThread mt = new MyThread("小强");
		mt.start();//开启了一个新的线程
		for (int i = 0; i < 20; i++) {
		System.out.println("旺财:"+i);
		}
	}
}

流程图:
在这里插入图片描述
说明:
程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用mt的对象的start方法,另外一个新的线程也启动了,这样,整个应用就在多线程下运行。

通过这张图我们可以很清晰的看到多线程的执行流程,那么为什么可以完成并发执行呢?我们再来讲一讲原理。
在这里插入图片描述
简易版本:
在这里插入图片描述
请注意mt.run()与mt.start()的区别。


1.2 Thread类之获取线程名称

Thread的子类

/*
    获取线程名称:
        1、使用THread类中的getName方法
         String getName() 返回该线程的名称。
        2、可以获取到当前正在执行的线程,使用线程中的方法getName()获取线程名称
        static Thread currentThread() 返回对当前正在执行的线程对象的引用。 
 */
//定义一个Thread的子类
public class Mythread extends Thread{
    //重写Thread中run方法,设置线程任务
    @Override
    public void run() {
        //获取线程名称
        //第一种:getName方法
        String name = getName();
        System.out.println(name);
        //第二种:currentThread静态方法
        Thread t = Thread.currentThread();//为什么可以使用类名调用该方法?因为是静态方法
        System.out.println(t);
        //第三种:链式编程
        System.out.println(Thread.currentThread().getName());//currentThread()返回的是线程对象的引用
                                                                //所以可以调用getName方法
    }

测试类

/*
    线程名称:
        主线程:main
        新线程:Thread-0、Thread-1、Thread-2
 */
public class MyThread_Demo {
    public static void main(String[] args) {
        //创建Thread子类的对象
        Mythread mt = new Mythread();
        //调用start方法,开启新线程,执行run方法
        mt.start();//Thread-1
        //使用匿名对象调用start
        new Mythread().start();//Thread[Thread-2,5,main]    //start一个就新创建一个线程
        new Mythread().start();//Thread[Thread-0,5,main]
        //第三种:链式编程
        System.out.println(Thread.currentThread().getName());
    }
}

1.2 Thread类之设置线程名称(了解)

Thread的子类

/*
    设置线程名称:(了解)
        1、使用Thread类中的方法setName(名字)
         void setName(String name) 改变线程名称,使之与参数 name 相同。
        2、创建一个带参构造方法,参数传递线程的名称;调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子子线程起一个名字
        Thread(String name) 分配新的 Thread 对象。
 */
public class Mythread extends Thread{
    //方法二:带参构造
    public Mythread(){
    };
    public Mythread(String name){
        super(name);
    };
    //方法一:
    @Override
    public void run() {
        System.out.println(getName());
    }
}

测试类

public class MyThread_Demo {
    public static void main(String[] args) {
        Mythread mt = new Mythread();
        // 方法一:开启线程
        mt.setName("小强");
        mt.start();
        //方法二:开启线程
        new Mythread("旺财").start();
    }
}

1.2 Thread类之常用方法

Thread常用方法
利用sleep方法制作秒表:

public class MyThread_Demo {
    public static void main(String[] args) {
        //模拟秒表
        for (int i = 1; i < 60; i++) {
            System.out.println(i);
            //使用Thread类的sleep方法让程序睡眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

1.3 创建线程之二(尽量使用)

采用 java.lang.Runnable 也是非常常见的一种,我们只需要重写run方法即可。
步骤如下:

  1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。(也就是,让Runnable的实现类对象作为Thread的参数传递进去,这样的Thread才变成线程对象)
  3. 调用线程对象的start()方法来启动线程。

Runnable实现类

/*
    创建多线程程序的第二种实现方式:实现Runnable     (只有run方法)
     java.lang.Runnable
        Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法。
     java.lang.Thread
        Thread(Runnable target) 分配新的 Thread 对象。
        Thread(Runnable target, String name) 分配新的 Thread 对象。
实现步骤:
    1-创建一个Runnable接口的实现类
    2-在实现类中重写Runnable接口的run方法,设置线程任务
    3-创建一个Runnable接口的实现类对象
    4-创建Thread类对象,构造方法中传递Runnable接口的实现类对象
    5-调用Thread类中的start方法,开启新的线程执行run方法
 */
public class RunnableDemo implements Runnable{  //1-创建一个Runnable接口的实现类
    //2-在实现类中重写Runnable接口的run方法,设置线程任务
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}

测试类

public class Runnable_Demo {
    public static void main(String[] args) {
        //3-创建一个Runnable接口的实现类对象
        RunnableDemo run = new RunnableDemo();
        //4-创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t = new Thread(run,"小强");
        //5-调用Thread类中的start方法,开启新的线程执行run方法
        t.start();
        //主线程
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}

1.4 Thread和Runnable的区别

实现Runnable接口比继承Thread类所具有的优势:

  1. 适合多个相同的程序代码的线程去共享同一个资源。
  2. 可以避免java中的单继承的局限性。
  3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
  4. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。

解释:
1-避免单继承的局限性
一个类只能有一个父类(一个人只有一个亲爸),子类继承了Thread类就不能继承其他类了。
2-增强了程序的扩展性,降低了程序的耦合性(解耦)
实现Runnable接口的方式,把【设置线程任务】和【开启新的线程】进行了分离。(解耦)

  • Runnable实现类中,重写了run方法:用来设置线程任务

  • 测试类中创建Thread类对象,把不同的Runnable实现类对象作为参数传给Thread对象调用不同的run方法。

一个重写run,另一个调用run,开启新的线程。

扩充:
在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进程。

1.5 匿名内部类方式实现线程的创建

匿名:没有名字
内部类:写在其他类内部的类
匿名内部类的作用:简化代码
过程:

  • 把【子类继承父类、重写父类的方法、创建子类对象】合一完成

  • 把【实现类接口、重写接口中的方法、创建实现类对象】合成完成

内部类的最终产物是【子类/实现类对象】,而这个类没有名字。

格式:
new 父类/接口{
重写父类/接口的方法
};

Demo Dode:

public class Runnable_Demo {
    public static void main(String[] args) {
        //线程的父类是Thread
        //之前是MyThread mt = new MyThread();等于Thread t = new MyThread();也等于Thread t = new Thread();[多态]
        new Thread() {
            //重写run,设置线程任务
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName() + "-->" + "我是Thread");
                }
            }
        }.start();

        //线程的接口是Runnable
        //Runnable r = new RunnbaleImpl();[同上:多态]
        Runnable r = new Runnable() {
            //重写run,设置线程任务
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName() + "-->" + "我是Runnable");
                }
            }
        };
        new Thread(r).start();

        //再一次简化Runnable接口的方式
        new Thread(new Runnable() {     //把参数内相当于new Thread(r).start()的r
            //重写run,设置线程任务
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName() + "-->" + "我是Runnable的加强简化");
                }
            }
        }).start();
    }
}

2.1 线程安全

线程安全的概述:
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
多线程共享资源,出现线程安全
多线程代码实现
Runnable实现类

public class RunnableImpl implements Runnable {
    //定义一个多线程共享的票源
    int ticket = 100;
    //设置线程任务:卖票
    @Override
    public void run() {
        //死循环,让卖票操作重复运行
        while (true){
            //判断是否有票
            if (ticket>0){
                //提高安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
                ticket--;
            }
        }
    }
}

测试类

/*
    模拟卖票案例
    创建3个线程,同时开启,对共享的票进行售出
 */
public class Demo2Ticket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runbable接口的实现类
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }
}

多线程原理
多线程原理
有意思的是,在开始的时候,如果t0抢到了cpu的执行权进入到run方法,遇到sleep也会失去cpu执行权,然后,t1、t2再开始抢夺,到sleep的时候也会陆续失去cpu执行权。当3个线程睡醒了,他们继续开始抢夺执行权。而且,继续执行的时候不是重头开始,而是在哪里睡着了,就在哪里醒来接着跑往下跑。(所以,下面跳过了if判断,出现0,-1)
图中,假设3个线程共享资源为1票,而t2优先抢夺成功并卖票,则资源变成0票,停止循环执行;然后,t1、t2会跟着抢夺执行,执行的时候是以资源为0执行,所以才出现0票和-1票。
怎么会出现开头出现3个线程同时打印3个100,下面都没有重复呢?
因为3个线程同时进入run方法时,从打印输出卖出几张票执行到ticket减减的时候,是需要时间的。


2.2 线程同步

线程同步的3种方式:

  1. 同步代码块。2. 同步方法。3. 锁机制。

2.3 同步代码块

同步代码块: synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
格式:
synchronized(同步锁){
需要同步操作的代码
}
同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
2. 锁对象 可以是任意类型。
3. 多个线程对象 要使用同一把锁。

注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。

在这里插入图片描述
在这里插入图片描述
需要注意的是:

  1. Object lock = new Object();一定要设置在修改共享资源的外边。(为了只有1个锁对象)
  2. synchronized设置在哪里
    把访问共享资源代码包起来。

同步技术原理
在这里插入图片描述
图中,t0抢到了执行权拿到了lock对象,进入sleep的时候,t1、t2再一次进行抢夺,但是发现没有对象锁的时候,只能进入阻塞状态,进行在外面等待t0执行完毕。


2.4 同步方法

在这里插入图片描述
Demo Code :
在这里插入图片描述
在这里插入图片描述
同步方法的对象是this,
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
静态同步方法:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


2.5 Lock锁

在这里插入图片描述
在这里插入图片描述
还可以配合finally这么玩:不管有没有出现一次,都会释放锁
在这里插入图片描述


3.1 线程的状态

在这里插入图片描述
在这里插入图片描述


3.2 Timed Waiting(计时等待)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.3 BLOCKED(锁阻塞)

在这里插入图片描述

3.4 Waiting(无限等待)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
等待唤醒案例的分析:
在这里插入图片描述
等待唤醒案例:

等待唤醒案例:线程之间的通信
    创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
    创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子
    
注意:
    顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
    同步使用的锁对象必须保证唯一
    只有锁对象才能调用wait和notify方法

Obejct类中的方法
void wait()
      在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
void notify()
      唤醒在此对象监视器上等待的单个线程。
      会继续执行wait方法之后的代码


public class Demo01WaitAndNotify {
    public static void main(String[] args) {
        //创建锁对象,保证唯一
        Object obj = new Object();
        // 创建一个顾客线程(消费者)
        new Thread(){
            @Override
            public void run() {
               //一直等着买包子
               while(true){
                   //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                   synchronized (obj){
                       System.out.println("告知老板要的包子的种类和数量");
                       //调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
                       try {
                           obj.wait();
                       } catch (InterruptedException e) {
                           e.printStackTrace();
                       }
                       //唤醒之后执行的代码
                       System.out.println("包子已经做好了,开吃!");
                       System.out.println("---------------------------------------");
                   }
               }
            }
        }.start();

        //创建一个老板线程(生产者)
        new Thread(){
            @Override
            public void run() {
                //一直做包子
                while (true){
                    //花了5秒做包子
                    try {
                        Thread.sleep(5000);//花5秒钟做包子
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                    synchronized (obj){
                        System.out.println("老板5秒钟之后做好包子,告知顾客,可以吃包子了");
                        //做好包子之后,调用notify方法,唤醒顾客吃包子
                        obj.notify();
                    }
                }
            }
        }.start();
    }
}

3.5 Object类中wait带参方法和notifyAll方法

进入到TimeWaiting(计时等待)有两种方式
1.使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态

唤醒的方法:
     void notify() 唤醒在此对象监视器上等待的单个线程。
     void notifyAll() 唤醒在此对象监视器上等待的所有线程。


public class Demo02WaitAndNotify {
    public static void main(String[] args) {
        //创建锁对象,保证唯一
        Object obj = new Object();
        // 创建一个顾客线程(消费者)
        new Thread(){
            @Override
            public void run() {
                //一直等着买包子
                while(true){
                    //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                    synchronized (obj){
                        System.out.println("顾客1告知老板要的包子的种类和数量");
                        //调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //唤醒之后执行的代码
                        System.out.println("包子已经做好了,顾客1开吃!");
                        System.out.println("---------------------------------------");
                    }
                }
            }
        }.start();

        // 创建一个顾客线程(消费者)
        new Thread(){
            @Override
            public void run() {
                //一直等着买包子
                while(true){
                    //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                    synchronized (obj){
                        System.out.println("顾客2告知老板要的包子的种类和数量");
                        //调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //唤醒之后执行的代码
                        System.out.println("包子已经做好了,顾客2开吃!");
                        System.out.println("---------------------------------------");
                    }
                }
            }
        }.start();

        //创建一个老板线程(生产者)
        new Thread(){
            @Override
            public void run() {
                //一直做包子
                while (true){
                    //花了5秒做包子
                    try {
                        Thread.sleep(5000);//花5秒钟做包子
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                    synchronized (obj){
                        System.out.println("老板5秒钟之后做好包子,告知顾客,可以吃包子了");
                        //做好包子之后,调用notify方法,唤醒顾客吃包子
                        //obj.notify();//如果有多个等待线程,随机唤醒一个
                        obj.notifyAll();//唤醒所有等待的线程
                    }
                }
            }
        }.start();
    }
}

3.5 补充知识点

在这里插入图片描述

七、等待与唤醒案例、线程池、Lambda表达式


1.1 线程间通信

在这里插入图片描述

1.2 等待与唤醒机制

1.3 生产者与消费者问题

在这里插入图片描述

2.1 线程池思想概述

2.2 线程池概念

在这里插入图片描述

2.3 线程池的使用

线程池: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销毁线程池(不建议执行)

3.1 函数式编程思想概

3.2 冗余的Runnable代码

3.3 编程思想转换

3.4 体验Lambda的更优写法

3.5 回顾匿名内部类

3.6 Lambda的格式

Lambda表达式的标准格式:
    由三部分组成:
        a.一些参数
        b.一个箭头
        c.一段代码
    格式:
        (参数列表) -> {一些重写方法的代码};
    解释说明格式:
        ():接口中抽象方法的参数列表,没有参数,就空着;有参数就写出参数,多个参数使用逗号分隔
        ->:传递的意思,把参数传递给方法体{}
        {}:重写接口的抽象方法的方法体

3.7 练习:使用Lambda标准格式(无参无返回)

3.8 Lambda的参数和返回值

3.9 练习:使用Lambda标准格式(有参有返回)

3.10 Lambda省略格式

Lambda表达式:是可推导,可以省略
凡是根据上下文推导出来的内容,都可以省略书写
可以省略的内容:
    1.(参数列表):括号中参数列表的数据类型,可以省略不写
    2.(参数列表):括号中的参数如果只有一个,那么类型和()都可以省略
    3.{一些代码}:如果{}中的代码只有一行,无论是否有返回值,都可以省略({},return,分号)
        注意:要省略{},return,分号必须一起省略

3.11 练习:使用Lambda省略格式

八、File类、递归


1.1 概述

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

重点:记住这三个单词
    file:文件
    directory:文件夹/目录
    path:路径

File类的静态成员:

        static String pathSeparator 与系统有关的路径分隔符,为了方便,它被表示为一个字符串。
        static char pathSeparatorChar 与系统有关的路径分隔符。

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

        操作路径:路径不能写死了
        C:developaa.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:冒号:

    String separator = File.separator;
    System.out.println(separator);// 文件名称分隔符 windows:反斜杠  linux:正斜杠/

绝对路径和相对路径

路径:
    绝对路径:是一个完整的路径
        以盘符(c:,D:)开始的路径
            c:\a.txt
            C:\Usersitcast\IdeaProjects\shungyuan\123.txt
            D:\demo\b.txt
    相对路径:是一个简化的路径
        相对指的是相对于当前项目的根目录(C:\Usersitcast\IdeaProjects\shungyuan)
        如果使用当前项目的根目录,路径可以简化书写
        C:\Usersitcast\IdeaProjects\shungyuan\123.txt-->简化为: 123.txt(可以省略项目的根目录)
    注意:
        1.路径是不区分大小写
        2.路径中的文件名称分隔符windows使用反斜杠,反斜杠是转义字符,两个反斜杠代表一个普通的反斜杠

1.2 构造方法

public static void main(String[] args) {

     /*
   
        File类的构造方法
  
     */
   
    //show02("c:\","a.txt");//c:a.txt
   
    //show02("d:\","a.txt");//d:a.txt
   
    show03();

    File f = new File("C:\Users\itcast\IdeaProjects\shungyuan");
    long length = f.length();
    System.out.println(length);
}

/*
    File(File parent, String child) 根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。
    参数:把路径分成了两部分
        File 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);//c:hello.java
}

/*
    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);//c:a.txt
}

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

    File f2 = new File("C:\Users\itcast\IdeaProjects\shungyuan");
    System.out.println(f2);//C:UsersitcastIdeaProjectsshungyuan

    File f3 = new File("b.txt");
    System.out.println(f3);//b.txt
}

}

1.3 常用方法

创建删除的方法

/*   
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\itcast\IdeaProjects\shung");

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

    File f2 = new File("C:\Users\itcast\IdeaProjects\shungyuan");
    if(f2.exists()){
        System.out.println(f2.isDirectory());//true
        System.out.println(f2.isFile());//false
    }

    File f3 = new File("C:\Users\itcast\IdeaProjects\shungyuan\shungyuan.iml");
    if(f3.exists()){
        System.out.println(f3.isDirectory());//false
        System.out.println(f3.isFile());//true
    }
}

/*
    public boolean exists() :此File表示的文件或目录是否实际存在。
    用于判断构造方法中的路径是否存在
        存在:true
        不存在:false
 */
private static void show01() {
    File f1 = new File("C:\Users\itcast\IdeaProjects\shungyuan");
    System.out.println(f1.exists());//true

    File f2 = new File("C:\Users\itcast\IdeaProjects\shung");
    System.out.println(f2.exists());//false

    File f3 = new File("shungyuan.iml");//相对路径 C:UsersitcastIdeaProjectsshungyuanshungyuan.iml
    System.out.println(f3.exists());//true

    File f4 = new File("a.txt");
    System.out.println(f4.exists());//false
	}
}

File类的创建删除方法
/*
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("08_FileAndRecursion\新建文件夹");
    boolean b1 = f1.delete();
    System.out.println("b1:"+b1);

    File f2 = new File("08_FileAndRecursion\abc.txt");
    System.out.println(f2.delete());
}

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

    File f2 = new File("08_FileAndRecursion\111\222\333\444");
    boolean b2 = f2.mkdirs();
    System.out.println("b2:"+b2);

    File f3 = new File("08_FileAndRecursion\abc.txt");
    boolean b3 = f3.mkdirs();//看类型,是一个文件
    System.out.println("b3:"+b3);

    File f4 = new File("08_F\ccc");
    boolean b4 = f4.mkdirs();//不会抛出异常,路径不存在,不会创建
    System.out.println("b4:"+b4);
}

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

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

    File f2 = new File("08_FileAndRecursion\2.txt");
    System.out.println(f2.createNewFile());

    File f3 = new File("08_FileAndRecursion\新建文件夹");
    System.out.println(f3.createNewFile());//不要被名称迷糊,要看类型

    File f4 = new File("08_FileAndRecursi\3.txt");
    System.out.println(f4.createNewFile());//路径不存在,抛出IOException
	 }
}

1.4 遍历文件夹(目录)功能

/*

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("C:\Users\itcast\IdeaProjects\shungyuan\08_FileAndRecursion");
    File[] files = file.listFiles();
    for (File f : files) {
        System.out.println(f);
    }
}

/*
    public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录。
    遍历构造方法中给出的目录,会获取目录中所有文件/文件夹的名称,把获取到的多个名称存储到一个String类型的数组中
 */
private static void show01() {
    //File file = new File("C:\Users\itcast\IdeaProjects\shungyuan\08_FileAndRecursion\1.txt");//NullPointerException
    //File file = new File("C:\Users\itcast\IdeaProjects\shungyuan\08_Fi");//NullPointerException
    File file = new File("C:\Users\itcast\IdeaProjects\shungyuan\08_FileAndRecursion");
    String[] arr = file.list();
    for (String fileName : arr) {
        System.out.println(fileName);
    }
 }
}

2.1 递归概念&分类&注意事项

/*

递归:方法自己调用自己

- 递归的分类:

  - 递归分为两种,直接递归和间接递归。
 
  - 直接递归称为方法自身调用自己。
 
  - 间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法。

- 注意事项:
  
  - 递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢

出。
 
  - 在递归中虽然有限定条件,但是递归次数不能太多。否则也会发生栈内存
 
  溢出。 
  
  - 构造方法,禁止递归

递归的使用前提:
  
    当调用方法的时候,方法的主体不变,每次调用方法的参数不同,可以使用递        
归

*/

public class Demo01Recurison {

public static void main(String[] args) {

    //a();
  
    b(1);

}

/*
    构造方法,禁止递归
        编译报错:构造方法是创建对象使用的,一直递归会导致内存中有无数多个对象,直接编译报错
 */
public Demo01Recurison() {
    //Demo01Recurison();
}

/*
        在递归中虽然有限定条件,但是递归次数不能太多。否则也会发生栈内存溢出。
        11157
            Exception in thread "main" java.lang.StackOverflowError
     */
private static void b(int i) {
    System.out.println(i);
    if(i==20000){
        return; //结束方法
    }
    b(++i);
}

/*
    递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。
    Exception in thread "main" java.lang.StackOverflowError
 */
private static void a() {
    System.out.println("a方法!");
    a();
}
}

九、字节流、字符流


第二章 字节流

------------------------------字节输出流

  /*      
   
    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对象中的方法write,把数据写入到文件中
    3.释放资源(流使用会占用一定的内存,使用完毕要把内存清空,提供程序的效率)
  */

Demo :

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

一次读取一个字节:

package com.itheima.demo01.OutputStream;

import java.io.File;
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 {
        //创建FileOutputStream对象,构造方法中绑定要写入数据的目的地
        FileOutputStream fos = new FileOutputStream(new File("09_IOAndProperties\b.txt"));
        //调用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();
    }
}

在这里插入图片描述

一次读取一个字节:
注意:
1、new FileInputStream()之后会指向文件源,而且指针是指向文件的第1个字节,也就是a
2、调用fis.read()方法后,原本指向a的指针会往后跑1个字节,也就是跑到b
其中,read方法找JVM,JVM找OS,OS调用方法把a传给JVM,JVM再返回给read()
一次性读取多字节:
注意:
1:new FileInputStream()指向文件源,且指针指向文件源中第1个字节
2:创建了1个长度为2的空字节数组
3:read(bytes)读取的字节存入数组中,存进2个字节,即“A,B”,
再调用获取的是下2个(C,D),这时候ABCDE中只有E没有获取
再调用获取的是E,就是单单的E,至于为什么输出的时候是ED
是因为重复调用read(bytes)获取是把新的字符覆盖掉已存有的字节。
图中,获取C、D之后,只剩E,再调用获取,E只是把C给覆盖了,D没有被覆盖,所以还是存在,所以打印的时候输出E、D

补充:window系统在文件系统会有一个结束标记(看不见的),当指针指向“结束标记”的时候,就返回-1,返回-1后,read()方法执行结束

数据追加续写

追加写/续写:使用两个参数的构造方法
FileOutputStream(String name, boolean append)创建一个向具有指定 name 的文件中写入数据的输出文件流。
FileOutputStream(File file, boolean append) 创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
参数:
String name,File file:写入数据的目的地
boolean append:追加写开关
true:创建对象不会覆盖源文件,继续在文件的末尾追加写数据
false:创建一个新文件,覆盖源文件
写换行:写换行符号
windows:
linux:/n
mac:/r

Demo:

public class Demo03OutputStream {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("09_IOAndProperties\c.txt",true);
        for (int i = 1; i <=10 ; i++) {
            fos.write("你好".getBytes());
            fos.write("
".getBytes());
        }

        fos.close();
    }
}

----------------------------------字节输入流

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.释放资源

一次读取多个字节

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

String类的构造方法
    String(byte[] bytes) :把字节数组转换为字符串
    String(byte[] bytes, int offset, int length) 把字节数组的一部分转换为字符串 offset:数组的开始索引 length:转换的字节个数

文件复制

文件复制练习:一读一写

明确:
    数据源: c:\1.jpg
    数据的目的地: d:\1.jpg

文件复制的步骤:
    1.创建一个字节输入流对象,构造方法中绑定要读取的数据源
    2.创建一个字节输出流对象,构造方法中绑定要写入的目的地
    3.使用字节输入流对象中的方法read读取文件
    4.使用字节输出流中的方法write,把读取到的字节写入到目的地的文件中
    5.释放资源

第三章 字符流

----------------------------输入流

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

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

java.io.FileReader extends InputStreamReader extends 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("09_IOAndProperties\c.txt");
        //2.使用FileReader对象中的方法read读取文件
        //int read() 读取单个字符并返回。
        /*int len = 0;
        while((len = fr.read())!=-1){
            System.out.print((char)len);
        }*/

        //int read(char[] cbuf)一次读取多个字符,将字符读入数组。
        char[] cs = new char[1024];//存储读取到的多个字符
        int len = 0;//记录的是每次读取的有效字符个数
        while((len = fr.read(cs))!=-1){
            /*
                String类的构造方法
                String(char[] value) 把字符数组转换为字符串
                String(char[] value, int offset, int count) 把字符数组的一部分转换为字符串 offset数组的开始索引 count转换的个数
             */
            System.out.println(new String(cs,0,len));
        }

        //3.释放资源
        fr.close();
    }
}

----------------------------输入出

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

共性的成员方法:
    - void write(int c) 写入单个字符。
    - 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写的字符个数。
    - 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中的方法write,把数据写入到内存缓冲区中(字符转换为字节的过程)
    3.使用FileWriter中的方法flush,把内存缓冲区中的数据,刷新到文件中
    4.释放资源(会先把内存缓冲区中的数据刷新到文件中)

Flush与close区别

flush方法和close方法的区别
    - flush :刷新缓冲区,流对象可以继续使用。
    - close:  先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。

Demo :

public class Demo02CloseAndFlush {
    public static void main(String[] args) throws IOException {
        //1.创建FileWriter对象,构造方法中绑定要写入数据的目的地
        FileWriter fw = new FileWriter("09_IOAndProperties\e.txt");
        //2.使用FileWriter中的方法write,把数据写入到内存缓冲区中(字符转换为字节的过程)
        //void write(int c) 写入单个字符。
        fw.write(97);
        //3.使用FileWriter中的方法flush,把内存缓冲区中的数据,刷新到文件中
        fw.flush();
        //刷新之后流可以继续使用
        fw.write(98);

        //4.释放资源(会先把内存缓冲区中的数据刷新到文件中)
        fw.close();

        //close方法之后流已经关闭了,已经从内存中消失了,流就不能再使用了
        fw.write(99);//IOException: Stream closed
    }
}

字节输出流的其他方法

字符输出流写数据的其他方法
    - 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写的字符个数。

Demo:

public class Demo03Writer {
    public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("09_IOAndProperties\f.txt");
        char[] cs = {'a','b','c','d','e'};
        //void write(char[] cbuf)写入字符数组。
        fw.write(cs);//abcde

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

        //void write(String str)写入字符串。
        fw.write("传智播客");//传智播客

        //void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数。
        fw.write("黑马程序员",2,3);//程序员

        fw.close();
    }
}

数据追加续写:

续写和换行
续写,追加写:使用两个参数的构造方法
    FileWriter(String fileName, boolean append)
    FileWriter(File file, boolean append)
    参数:
        String fileName,File file:写入数据的目的地
        boolean append:续写开关 true:不会创建新的文件覆盖源文件,可以续写; false:创建新的文件覆盖源文件
 换行:换行符号
    windows:

    linux:/n
    mac:/r


public class Demo04Writer {
    public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("09_IOAndProperties\g.txt",true);
        for (int i = 0; i <10 ; i++) {
            fw.write("HelloWorld"+i+"
");
        }

        fw.close();
    }
}

第四章 IO异常的处理

JDK7前

    在jdk1.7之前使用try catch finally 处理流中的异常
    格式:
        try{
            可能会产出异常的代码
        }catch(异常类变量 变量名){
            异常的处理逻辑
        }finally{
            一定会指定的代码
            资源释放
        }
        ---------------------------------------------------------------
        public class Demo01TryCatch {
    public static void main(String[] args) {
        //提高变量fw的作用域,让finally可以使用
        //变量在定义的时候,可以没有值,但是使用的时候必须有值
        //fw = new FileWriter("09_IOAndProperties\g.txt",true); 执行失败,fw没有值,fw.close会报错
        FileWriter fw = null;
        try{
            //可能会产出异常的代码
            fw = new FileWriter("w:\09_IOAndProperties\g.txt",true);
            for (int i = 0; i <10 ; i++) {
                fw.write("HelloWorld"+i+"
");
            }
        }catch(IOException e){
            //异常的处理逻辑
            System.out.println(e);
        }finally {
            //一定会指定的代码
            //创建对象失败了,fw的默认值就是null,null是不能调用方法的,会抛出NullPointerException,需要增加一个判断,不是null在把资源释放
            if(fw!=null){
                try {
                    //fw.close方法声明抛出了IOException异常对象,所以我们就的处理这个异常对象,要么throws,要么try catch
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

JDK7后

    JDK7的新特性
    在try的后边可以增加一个(),在括号中可以定义流对象
    那么这个流对象的作用域就在try中有效
    try中的代码执行完毕,会自动把流对象释放,不用写finally
    格式:
        try(定义流对象;定义流对象....){
            可能会产出异常的代码
        }catch(异常类变量 变量名){
            异常的处理逻辑
        }
----------------------------------------------------------------
public class Demo02JDK7 {
    public static void main(String[] args) {
        try(//1.创建一个字节输入流对象,构造方法中绑定要读取的数据源
            FileInputStream fis = new FileInputStream("c:\1.jpg");
            //2.创建一个字节输出流对象,构造方法中绑定要写入的目的地
            FileOutputStream fos = new FileOutputStream("d:\1.jpg");){

            //可能会产出异常的代码
            //一次读取一个字节写入一个字节的方式
            //3.使用字节输入流对象中的方法read读取文件
            int len = 0;
            while((len = fis.read())!=-1){
                //4.使用字节输出流中的方法write,把读取到的字节写入到目的地的文件中
                fos.write(len);
            }

        }catch (IOException e){
            //异常的处理逻辑
            System.out.println(e);
        }


    }
}

JDK9后

    JDK9新特性
    try的前边可以定义流对象
    在try后边的()中可以直接引入流对象的名称(变量名)
    在try代码执行完毕之后,流对象也可以释放掉,不用写finally
    格式:
        A a = new A();
        B b = new B();
        try(a,b){
            可能会产出异常的代码
        }catch(异常类变量 变量名){
            异常的处理逻辑
        }

public class Demo03JDK9 {
    public static void main(String[] args) throws IOException {
        //1.创建一个字节输入流对象,构造方法中绑定要读取的数据源
        FileInputStream fis = new FileInputStream("c:\1.jpg");
        //2.创建一个字节输出流对象,构造方法中绑定要写入的目的地
        FileOutputStream fos = new FileOutputStream("d:\1.jpg");

        try(fis;fos){
            //一次读取一个字节写入一个字节的方式
            //3.使用字节输入流对象中的方法read读取文件
            int len = 0;
            while((len = fis.read())!=-1){
                //4.使用字节输出流中的方法write,把读取到的字节写入到目的地的文件中
                fos.write(len);
            }
        }catch (IOException e){
            System.out.println(e);
        }

        //fos.write(1);//Stream Closed

    }
}

第五章 属性集

---------------------------------------------------------------------------------
    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) throws IOException {
        show03();
    }

    /*
        可以使用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("09_IOAndProperties\prop.txt"));
        //prop.load(new FileInputStream("09_IOAndProperties\prop.txt"));
        //3.遍历Properties集合
        Set<String> set = prop.stringPropertyNames();
        for (String key : set) {
            String value = prop.getProperty(key);
            System.out.println(key+"="+value);
        }
    }

---------------------------------------------------------------------------------
        可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
        void store(OutputStream out, String comments)
        void store(Writer writer, String comments)
        参数:
            OutputStream out:字节输出流,不能写入中文
            Writer writer:字符输出流,可以写中文
            String comments:注释,用来解释说明保存的文件是做什么用的
                    不能使用中文,会产生乱码,默认是Unicode编码
                    一般使用""空字符串

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

        //2.创建字节输出流/字符输出流对象,构造方法中绑定要输出的目的地
        //FileWriter fw = new FileWriter("09_IOAndProperties\prop.txt");

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

        //4.释放资源
        //fw.close();

        prop.store(new FileOutputStream("09_IOAndProperties\prop2.txt"),"");
    }

---------------------------------------------------------------------------------
        使用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("迪丽热巴","165");
        prop.setProperty("古力娜扎","160");
        //prop.put(1,true);

        //使用stringPropertyNames把Properties集合中的键取出,存储到一个Set集合中
        Set<String> set = prop.stringPropertyNames();

        //遍历Set集合,取出Properties集合的每一个键
        for (String key : set) {
            //使用getProperty方法通过key获取value
            String value = prop.getProperty(key);
            System.out.println(key+"="+value);
        }
    }
}

十、缓冲流、转换流、序列化流、Files


十一、网络编程


十二、函数式接口


十三、Stream流、方法引用


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2294029.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《chatwise:DeepSeek的界面部署》

ChatWise&#xff1a;DeepSeek的界面部署 摘要 本文详细描述了DeepSeek公司针对其核心业务系统进行的界面部署工作。从需求分析到技术实现&#xff0c;再到测试与优化&#xff0c;全面阐述了整个部署过程中的关键步骤和解决方案。通过本文&#xff0c;读者可以深入了解DeepSee…

单节锂电池外部供电自动切换的电路学习

文章目录 前言一、原理分析&#xff1a;①当VBUS处有外部电源输入时②当VBUS处无外部电源输入时 二、器件选择1、二极管2、MOS管3、其他 总结 前言 学习一种广泛应用的锂电池供电自动切换电路 电路存在外部电源时&#xff0c;优先使用外部电源供电&#xff0c;并为电池供电&…

数据结构-堆和PriorityQueue

1.堆&#xff08;Heap&#xff09; 1.1堆的概念 堆是一种非常重要的数据结构&#xff0c;通常被实现为一种特殊的完全二叉树 如果有一个关键码的集合K{k0,k1,k2,...,kn-1}&#xff0c;把它所有的元素按照完全二叉树的顺序存储在一个一维数组中&#xff0c;如果满足ki<k2i…

如何打造一个更友好的网站结构?

在SEO优化中&#xff0c;网站的结构往往被忽略&#xff0c;但它其实是决定谷歌爬虫抓取效率的关键因素之一。一个清晰、逻辑合理的网站结构&#xff0c;不仅能让用户更方便地找到他们需要的信息&#xff0c;还能提升搜索引擎的抓取效率 理想的网站结构应该像一棵树&#xff0c;…

每日Attention学习20——Group Shuffle Attention

模块出处 [MICCAI 24] [link] LB-UNet: A Lightweight Boundary-Assisted UNet for Skin Lesion Segmentation 模块名称 Group Shuffle Attention (GSA) 模块作用 轻量特征学习 模块结构 模块特点 使用分组(Group)卷积降低计算量引入External Attention机制更好的学习特征S…

VUE之组件通信(二)

1、v-model v-model的底层原理&#xff1a;是:value值和input事件的结合 $event到底是啥&#xff1f;啥时候能.target 对于原生事件&#xff0c;$event就是事件对象 &#xff0c;能.target对应自定义事件&#xff0c;$event就是触发事件时&#xff0c;所传递的数据&#xff…

[x86 ubuntu22.04]进入S4失败

目录 1 问题描述 2 解决过程 2.1 查看内核日志 2.2 新建一个交换分区 2.3 指定交换分区的位置 1 问题描述 CPU&#xff1a;G6900E OS&#xff1a;ubuntu22.04 Kernel&#xff1a;6.8.0-49-generic 使用“echo disk > /sys/power/state”命令进入 S4&#xff0c;但是无法…

idea隐藏无关文件

idea隐藏无关文件 如果你想隐藏某些特定类型的文件&#xff08;例如 .log 文件或 .tmp 文件&#xff09;&#xff0c;可以通过以下步骤设置&#xff1a; 打开设置 在菜单栏中选择 File > Settings&#xff08;Windows/Linux&#xff09;或 IntelliJ IDEA > Preference…

文献阅读 250205-Global patterns and drivers of tropical aboveground carbon changes

Global patterns and drivers of tropical aboveground carbon changes 来自 <Global patterns and drivers of tropical aboveground carbon changes | Nature Climate Change> 热带地上碳变化的全球模式和驱动因素 ## Abstract: Tropical terrestrial ecosystems play …

【数据结构】循环链表

循环链表 单链表局限性单向循环链表判断链表是否有环思路code 找到链表入口思路代码结构与逻辑 code 单链表局限性 单链表作为一种基本的数据结构&#xff0c;虽然在很多场景下都非常有用&#xff0c;但它也存在一些局限性&#xff1a; 单向访问&#xff1a;由于每个节点仅包含…

ImGui 学习笔记(二)—— 多视口

在计算机图形学中&#xff0c;视口&#xff08;Viewport&#xff09;是一个可观察的多边形区域。 将物体渲染至图像的过程中&#xff0c;会用两种区域表示。世界坐标窗口是用户所关注的区域&#xff08;即用户想要可视化的东西&#xff09;&#xff0c;坐标系由应用程序确定。…

安装和卸载RabbitMQ

我的飞书:https://rvg7rs2jk1g.feishu.cn/docx/SUWXdDb0UoCV86xP6b3c7qtMn6b 使用Ubuntu环境进行安装 一、安装Erlang 在安装RabbitMQ之前,我们需要先安装Erlang,RabbitMQ需要Erlang的语言支持 #安装Erlang sudo apt-get install erlang 在安装的过程中,会弹出一段信息,此…

Apache HttpClient

HttpClient是apache组织下面的一个用于处理HTTP请求和响应的来源工具&#xff0c;是一个在JDK基础类库是做了更好的封装的类库。 HttpClient 使用了连接池技术来管理 TCP 连接&#xff0c;这有助于提高性能并减少资源消耗。连接池允许 HttpClient 复用已经建立的连接&#xff0…

Golang 并发机制-6:掌握优雅的错误处理艺术

并发编程可能是提高软件系统效率和响应能力的一种强有力的技术。它允许多个工作负载同时运行&#xff0c;充分利用现代多核cpu。然而&#xff0c;巨大的能力带来巨大的责任&#xff0c;良好的错误管理是并发编程的主要任务之一。 并发代码的复杂性 并发编程增加了顺序程序所不…

react使用DatePicker日期选择器

1、引入&#xff1a;npm i day 2、页面引入&#xff1a; import dayjs from dayjs; 3、使用 <DatePicker onChange{onChange} value{datas ? dayjs(datas) : null} /> 4、事件 const onChange (date, dateString) > {setInput(dateString)setDatas(dateString)…

深度学习 Pytorch 基础网络手动搭建与快速实现

为了方便后续练习的展开&#xff0c;我们尝试自己创建一个数据生成器&#xff0c;用于自主生成一些符合某些条件、具备某些特性的数据集。 导入相关的包 # 随机模块 import random# 绘图模块 import matplotlib as mpl import matplotlib.pyplot as plt# 导入numpy import nu…

保姆级教程Docker部署KRaft模式的Kafka官方镜像

目录 一、安装Docker及可视化工具 二、单节点部署 1、创建挂载目录 2、运行Kafka容器 3、Compose运行Kafka容器 4、查看Kafka运行状态 三、集群部署 四、部署可视化工具 1、创建挂载目录 2、运行Kafka-ui容器 3、Compose运行Kafka-ui容器 4、查看Kafka-ui运行状态 …

51单片机看门狗系统

在 STC89C52 单片机中&#xff0c;看门狗控制寄存器的固定地址为 0xE1。此地址由芯片厂商在硬件设计时确定&#xff0c;但是它在头文件中并未给出&#xff0c;因此在使用看门狗系统时需要声明下这个特殊功能寄存器 sfr WDT_CONTR 0xE1; 本案将用一个小灯的工作状况来展示看门…

RNN/LSTM/GRU 学习笔记

文章目录 RNN/LSTM/GRU一、RNN1、为何引入RNN&#xff1f;2、RNN的基本结构3、各种形式的RNN及其应用4、RNN的缺陷5、如何应对RNN的缺陷&#xff1f;6、BPTT和BP的区别 二、LSTM1、LSTM 简介2、LSTM如何缓解梯度消失与梯度爆炸&#xff1f; 三、GRU四、参考文献 RNN/LSTM/GRU …

Android记事本App设计开发项目实战教程2025最新版Android Studio

平时上课录了个视频&#xff0c;从新建工程到打包Apk&#xff0c;从头做到尾&#xff0c;没有遗漏任何实现细节&#xff0c;欢迎学过Android基础的同学参加&#xff0c;如果你做过其他终端软件开发&#xff0c;也可以学习&#xff0c;快速上手Android基础开发。 Android记事本课…