13【线程等待、状态、线程池、File类】

追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

导读:本篇文章讲解 13【线程等待、状态、线程池、File类】,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文


上一篇12【多线程、锁机制、lock锁】

下一篇14【IO流概述、字节流、字符流、属性操作】

目录【JavaSE零基础系列教程目录】


13【线程等待、状态、线程池、File类】

一、线程的等待与唤醒

1.1 线程的等待

1.1.1 等待与随机唤醒

  • public final void wait():让当前线程进入等待状态,并且释放锁对象

注意:wait方法是锁对象来调用,调用wait()之后将释放当前锁,并且让当前锁对象对应的线程处于等待(Waiting)状态;

  • public final native void notify():随机唤醒一条锁对象对应线程中的一条(此线程必须是睡眠状态)

注意:notify()也是锁对象来调用,并不是当前线程对象调用

因为wait需要释放锁,所以必须在synchronized中使用,没有锁时使用会抛出IllegalMonitorStateException(正在等待的对象没有锁)

tips:

  1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
  2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
  3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。

案例:线程1执行一次”犯我中华者”,线程2执行一次”虽远必诛”,交替执行

package com.dfbz.demo01;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        Shower s = new Shower();

        new Thread() {
            @Override
            public void run() {
                try {
                    s.show1();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();

        new Thread() {
            @Override
            public void run() {

                try {
                    s.show2();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();


    }
}

class Shower {

    int count = 1;

    public void show1() throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            synchronized (Object.class) {
                while (count != 1) {
                    Object.class.wait();
                }
                Thread.sleep(10);
                System.out.print("犯");
                System.out.print("我");
                System.out.print("中");
                System.out.print("华");
                System.out.print("者");
                System.out.println();
                count = 2;                //count=1
                Object.class.notify();
            }
        }
    }

    public void show2() throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            synchronized (Object.class) {
                while (count != 2) {
                    Object.class.wait();
                }
                Thread.sleep(10);
                System.out.print("虽");
                System.out.print("远");
                System.out.print("必");
                System.out.print("诛");
                System.out.println();
                count = 1;
                Object.class.notify();              //随机唤醒一条当前锁的线程
            }
        }
    }
}

1.1.2 唤醒与全部唤醒

实现需求:线程1执行一次”我是中国人”,线程2执行一次”犯我中华者”,线程3执行一次”虽远必诛”,交替执行

  • public final native void notify():唤醒在当前锁对象中随机的一条线程

  • public final native void notifyAll():唤醒当前锁对象对应的所有线程(效率低)

示例代码:

package com.dfbz.demo02;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */

/**
 * 线程通信
 */
public class Demo01 {
    public static void main(String[] args) {
        Shower s = new Shower();

        new Thread() {
            @Override
            public void run() {

                try {
                    s.show1();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


            }
        }.start();

        new Thread() {
            @Override
            public void run() {

                try {
                    s.show2();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
        new Thread() {
            @Override
            public void run() {

                try {
                    s.show3();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();

    }
}

class Shower {

    int count = 1;

    public void show1() throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            synchronized (Object.class) {
                while (count != 1) {
                    Object.class.wait();
                }
                Thread.sleep(10);
                System.out.print("我");
                System.out.print("是");
                System.out.print("中");
                System.out.print("国");
                System.out.print("人");
                System.out.println();
                count = 2;
                Object.class.notifyAll();    //唤醒该锁对应的全部线程
            }
        }
    }

    public void show2() throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            synchronized (Object.class) {
                while (count != 2) {
                    Object.class.wait();
                }
                Thread.sleep(10);
                System.out.print("犯");
                System.out.print("我");
                System.out.print("中");
                System.out.print("华");
                System.out.print("者");
                System.out.println();
                count = 3;                //count=1
                Object.class.notifyAll();
            }
        }
    }

    public void show3() throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            synchronized (Object.class) {
                while (count != 3) {
                    Object.class.wait();
                }
                Thread.sleep(10);
                System.out.print("虽");
                System.out.print("远");
                System.out.print("必");
                System.out.print("诛");
                System.out.println();
                count = 1;
                Object.class.notifyAll();              // 唤醒该锁对应的全部线程
            }
        }
    }
}

1.2 Lock锁的监视器

上述案例中,通过synchronized同步代码块加上锁对象也可以实现线程间的通信,我们不管下次执行是哪个线程,都是使用notifyAll()唤醒全部线程,即使不是该线程执行也会唤醒当前锁对应的全部线程,我们能不能指定的唤醒某条线程呢?答案是可以的,借助Lock锁实现!

ReentrantLock相关方法如下:

  • public Condition newCondition():获取用于监视线程的监视器;

Condition相关方法如下:

  • void await():让当前执行的线程进行等待(监视器来调用),一旦调用了此方法,该监视器会监视本线程,用于后续的唤醒;
  • void signal():让当前执行的线程唤醒(监视器来调用);

示例代码:

package com.dfbz.demo03;

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

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {

        Printer2 p = new Printer2();
        new Thread() {
            @Override
            public void run() {

                try {
                    p.show1();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }.start();

        new Thread() {
            @Override
            public void run() {

                try {
                    p.show2();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }.start();

        new Thread() {
            @Override
            public void run() {

                try {
                    p.show3();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }.start();
    }
}

class Printer2 {

    //创建锁对象
    ReentrantLock lock = new ReentrantLock();

    //创建三个监视器对象,用于监视三条线程
    Condition c1;
    Condition c2;
    Condition c3;

    public Printer2() {
        c1 = lock.newCondition();
        c2 = lock.newCondition();
        c3 = lock.newCondition();
    }

    int count = 1;

    public void show1() throws InterruptedException {

        for (int i = 0; i < 100; i++) {
            lock.lock();                //开启锁
            while (count != 1) {
                c1.await();             //使用c1监视器让当前线程等待
            }
            Thread.sleep(10);
            System.out.print("我");
            System.out.print("是");
            System.out.print("中");
            System.out.print("国");
            System.out.print("人");
            System.out.println();
            count = 2;
            c2.signal();                //唤醒c2监视器监视的线程
            lock.unlock();              //释放锁
        }

    }

    public void show2() throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            lock.lock();
            while (count != 2) {
                c2.await();                 //使用c2监视器监视该线程
            }
            Thread.sleep(10);
            System.out.print("犯");
            System.out.print("我");
            System.out.print("中");
            System.out.print("华");
            System.out.print("者");
            System.out.println();
            count = 3;
            c3.signal();                    //唤醒c3监视器监视的线程
            lock.unlock();
        }

    }


    public void show3() throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            lock.lock();
            while (count != 3) {
                c3.await();                 //使用c3监视器监视该线程
            }
            Thread.sleep(10);
            System.out.print("虽");
            System.out.print("远");
            System.out.print("必");
            System.out.print("诛");
            System.out.println();
            count = 1;
            c1.signal();                    //唤醒c1监视器监视的线程
            lock.unlock();
        }
    }
}

二、线程状态

2.1 线程状态

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有几种状态呢?在API中java.lang.Thread.State这个枚举中给出了六种线程状态:

这里先列出各个线程状态发生的条件,下面将会对每种状态进行详细解析

线程状态 导致状态发生条件
NEW(新建) 线程刚被创建,但是并未启动。还没调用start方法。
Runnable(可运行) 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。
Blocked(锁阻塞) 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
Waiting(无限等待) 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
Timed Waiting(计时等待) 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
Teminated(被终止) 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

2.2 线程状态变化

线程流程图:

在这里插入图片描述

Runnable被称为可运行状态,一旦线程调用start()方法,线程就处于可运行状态(Runnable)。一个可运行的线程能正在运行也可能没有运行。有些教科书上讲可运行状态分为了就绪状态运行状态,即线程开启后进入就绪状态,当线程抢到CPU执行权后进入运行状态(Java规范没有将正在运行作为一个单独的状态,一个正在运行的线程仍然处于可运行状态)

三、线程池

3.1 线程池概述

创建线程与消耗消除是非常消耗系统资源的操作,如果需要并发的线程非常多,并且每个线程都是执行一个时间很短的任务就结束了,那么这样势必会造成很大资源的浪费(好不容易容易创建出来的线程立马就关了)。

有了线程池之后,当线程使用完毕后,不是立即销毁,而是归还到线程池中,下次需要线程来执行任务时,直接去线程池中获取一条线程即可,这样线程就得到了很大程度上的复用;

总结线程池有如下优点:

  • 1)降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
  • 2)提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;
  • 3)方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的)
  • 4、提供更强大的功能,延时定时线程池。

3.2 线程池的使用

要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在java.util.concurrent.Executors线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。官方建议使用Executors工程类来创建线程池对象。

Executors类中有个创建线程池的方法如下:

  • public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象。

获取到了一个线程池ExecutorService 对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:

  • public Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行

    Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。

示例代码:

package com.dfbz.demo01;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "执行了");
    }
}

线程池测试类:

package com.dfbz.demo01;

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

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        // 创建线程池对象
        ExecutorService threadPool = Executors.newFixedThreadPool(2);//包含2个线程对象
        // 创建Runnable实例对象
        MyRunnable task = new MyRunnable();

        // 从线程池中获取线程对象,然后调用MyRunnable中的run()
        threadPool.submit(task);
        // 再获取个线程对象,调用MyRunnable中的run()
        threadPool.submit(task);
        threadPool.submit(task);
        // 注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。
        // 将使用完的线程又归还到了线程池中
        // 关闭线程池
        threadPool.shutdown();
    }
}

执行效果:

在这里插入图片描述

四、File类

java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作。

4.1 构造方法

  • public File(String pathname) :通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
  • public File(String parent, String child) :从父路径名字符串和子路径名字符串创建新的 File实例。
  • public File(File parent, String child) :从父抽象路径名和子路径名字符串创建新的 File实例。

示例代码:

package com.dfbz.demo01;

import java.io.File;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        // 文件路径名
        String pathname = "D:\\aaa.txt";
        File file1 = new File(pathname);

        // 文件路径名
        String pathname2 = "D:\\aaa\\bbb.txt";
        File file2 = new File(pathname2);

        // 通过父路径和子路径字符串
        String parentDir = "d:\\aaa";
        String childName = "bbb.txt";
        File file3 = new File(parentDir, childName);

        // 通过父级File对象和子路径字符串
        File parentFile = new File("d:\\aaa");
        String child = "bbb.txt";
        File file4 = new File(parentFile, child);
    }
}

4.2 成员方法

4.2.1 获取文件信息方法

  • public String getAbsolutePath() :返回此File的绝对路径名字符串。
  • public String getPath() :将此File转换为路径名字符串。
  • public String getName() :返回由此File表示的文件或目录的名称。
  • public long length() :返回由此File表示的文件的长度,如果是文件夹则返回0。

示例代码:

package com.dfbz.demo01;

import java.io.File;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02 {
    public static void main(String[] args) {
        File f = new File("d:/000/Demo01.java");
        System.out.println("文件绝对路径:" + f.getAbsolutePath());
        System.out.println("文件构造路径:" + f.getPath());
        System.out.println("文件名称:" + f.getName());
        System.out.println("文件长度:" + f.length() + "字节");

        System.out.println("------------");
        File f2 = new File("d:/000");
        System.out.println("目录绝对路径:" + f2.getAbsolutePath());
        System.out.println("目录构造路径:" + f2.getPath());
        System.out.println("目录名称:" + f2.getName());
        System.out.println("目录长度:" + f2.length());
    }
}

运行结果:

在这里插入图片描述

发现getPath()getAbsolutePath()获取的结果一致,getPath用于获取的是构造方法里面输入的路径,有的时候我们在构造方法输入的路径可能是一个相对路径,此时getPath()获取的是构造方法输入的路径,而获取的是此相对路径对应的磁盘绝对路径;

  • 测试:
package com.dfbz.demo01;

import java.io.File;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03 {
    public static void main(String[] args) {
        File f = new File("./abc.java");
        System.out.println("文件绝对路径:" + f.getAbsolutePath());
        System.out.println("文件构造路径:" + f.getPath());
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PXNHng3A-1628159132368)(media/97.png)]

4.2.2 判断文件的方法

  • public boolean exists() :此File表示的文件或目录是否实际存在。
  • public boolean isDirectory() :此File表示的是否为目录。
  • public boolean isFile() :此File表示的是否为文件。

示例代码:

package com.dfbz.demo01;

import java.io.File;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo04 {
    public static void main(String[] args) {
        File f = new File("d:\\aaa\\bbb.java");
        File f2 = new File("d:\\aaa");
        // 判断是否存在
        System.out.println("d:\\aaa\\bbb.java 是否存在:" + f.exists());
        System.out.println("d:\\aaa 是否存在:" + f2.exists());
        // 判断是文件还是目录
        System.out.println("d:\\aaa 文件?:" + f2.isFile());
        System.out.println("d:\\aaa 目录?:" + f2.isDirectory());
    }
}

4.2.3 文件的创建与删除方法

  • public boolean createNewFile() :当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。
  • public boolean delete() :删除由此File表示的文件或目录。
  • public boolean mkdir() :创建由此File表示的目录。
  • public boolean mkdirs() :创建由此File表示的目录,包括任何必需但不存在的父目录。

示例代码:

package com.dfbz.demo01;

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

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo05 {
    public static void main(String[] args) throws IOException {
        // 文件的创建
        File f = new File("aaa.txt");
        System.out.println("是否存在:"+f.exists()); // false
        System.out.println("是否创建:"+f.createNewFile()); // true
        System.out.println("是否存在:"+f.exists()); // true

        // 目录的创建
        File f2= new File("newDir");
        System.out.println("是否存在:"+f2.exists());// false
        System.out.println("是否创建:"+f2.mkdir());	// true
        System.out.println("是否存在:"+f2.exists());// true

        // 创建多级目录
        File f3= new File("newDira\\newDirb");
        System.out.println(f3.mkdir());// false
        File f4= new File("newDira\\newDirb");
        System.out.println(f4.mkdirs());// true

        // 文件的删除
        System.out.println(f.delete());// true

        // 目录的删除
        System.out.println(f2.delete());// true
        System.out.println(f4.delete());// false
    }
}

4.2.4 目录的遍历

  • public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录。

  • public File[] listFiles() :返回一个File数组,表示该File目录中的所有的子文件或目录。

示例代码:

package com.dfbz.demo01;

import java.io.File;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo06 {
    public static void main(String[] args) {
        File dir = new File("d:\\001");

        //获取当前目录下的文件以及文件夹的名称。
        String[] names = dir.list();
        for(String name : names){
            System.out.println(name);
        }
        //获取当前目录下的文件以及文件夹对象,只要拿到了文件对象,那么就可以获取更多信息
        File[] files = dir.listFiles();
        for (File file : files) {
            System.out.println(file);
        }
    }
}

五、递归

5.1 递归概述

  • 递归:指在当前方法内调用自己的这种现象。

  • 递归的分类:

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

    • 递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。
    • 在递归中虽然有限定条件,但是递归次数不能太多。否则也会发生栈内存溢出。
    • 构造方法,禁止递归

5.2 递归使用

5.2.1 案例代码

示例代码:

package com.dfbz.demo01;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        // a();
        b(1);
    }

    /*
     * 3.构造方法,禁止递归
     * 编译报错:构造方法是创建对象使用的,不能让对象一直创建下去
     */
    public Demo01() {
        //Demo01();
    }


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

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

5.2.2 递归图解

  1. main方法进栈执行,调用b(1)方法
  2. b方法进栈,又调用一次b方法,传递2
  3. b2(2)方法进栈,调用b(3)(注意:此时b(1)方法还未执行完毕,就跟main方法还未执行完毕一样)
  4. b2(3)方法进栈,调用b(4)

在这里插入图片描述

注意:递归一定要有条件限定,保证递归能够停止下来,次数不要太多,否则会发生栈内存溢出。

5.3 递归案例

5.3.1 使用递归计算累加和

num的累和 = num + (num-1)的累和,所以可以把累和的操作定义成一个方法,递归调用。

  • 示例代码:
package com.dfbz.demo01;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02 {
    public static void main(String[] args) {
        //计算1~num的和,使用递归完成
        int num = 5;
        // 调用求和的方法
        int sum = getSum(num);
        // 输出结果
        System.out.println(sum);
    }

    /*
      通过递归算法实现.
      参数列表:int 
      返回值类型: int 
    */
    public static int getSum(int num) {
      	/* 
      	   num为1时,方法返回1,
      	   相当于是方法的出口,num总有是1的情况
      	*/
        if (num == 1) {
            return 1;
        }
      	/*
          num不为1时,方法返回 num +(num-1)的累和
          递归调用getSum方法
        */
        return num + getSum(num - 1);
    }
}

5.3.2 使用递归计算阶乘

  • 阶乘:所有小于及等于该数的正整数的积。

n的阶乘:n! = n * (n-1) * (n-2) ...* 3 * 2 * 1

这与累和类似,只不过换成了乘法运算,学员可以自己练习,需要注意阶乘值符合int类型的范围。

条件结束:n! = n * (n-1)!

  • 示例代码:
package com.dfbz.demo01;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03 {
    //计算n的阶乘,使用递归完成
    public static void main(String[] args) {
        int n = 3;
        // 调用求阶乘的方法
        int value = getValue(n);
        // 输出结果
        System.out.println("阶乘为:"+ value);
    }
    /*
        通过递归算法实现.
        参数列表:int 
        返回值类型: int 
      */
    public static int getValue(int n) {
        // 1的阶乘为1
        if (n == 1) {
            return 1;
        }
      	/*
      	  n不为1时,方法返回 n! = n*(n-1)!
          递归调用getValue方法
      	*/
        return n * getValue(n - 1);
    }
}

5.3.3 使用递归打印多级目录

分析:多级目录的打印,就是当目录的嵌套。遍历之前,无从知道到底有多少级目录,所以我们还是要使用递归实现。

  • 示例代码:
package com.dfbz.demo01;

import java.io.File;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo04 {
    public static void main(String[] args) {
        // 创建File对象
        File dir = new File("D:\\aaa");
        // 调用打印目录方法
        printDir(dir);
    }

    public static void printDir(File dir) {
        // 获取子文件和目录
        File[] files = dir.listFiles();
        // 循环打印
      	/*
      	  判断:
      	  当是文件时,打印绝对路径.
      	  当是目录时,继续调用打印目录的方法,形成递归调用.
      	*/
        for (File file : files) {
            // 判断
            if (file.isFile()) {
                // 是文件,输出文件绝对路径
                System.out.println("文件名:" + file.getAbsolutePath());
            } else {
                // 是目录,输出目录绝对路径
                System.out.println("目录:" + file.getAbsolutePath());
                // 继续遍历,调用printDir,形成递归
                printDir(file);
            }
        }
    }
}

5.3.4 使用递归完成文件搜索

搜索D:\001 目录中的.java 文件。

分析

  1. 目录搜索,无法判断多少级目录,所以使用递归,遍历所有目录。
  2. 遍历目录时,获取的子文件,通过文件名称,判断是否符合条件。
package com.dfbz.demo01;

import java.io.File;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo05 {
    public static void main(String[] args) {
        // 创建File对象
        File dir = new File("D:\\001");
        // 调用打印目录方法
        printDir(dir);
    }

    public static void printDir(File dir) {
        // 获取子文件和目录
        File[] files = dir.listFiles();

        // 循环打印
        for (File file : files) {
            if (file.isFile()) {
                // 是文件,判断文件名并输出文件绝对路径
                if (file.getName().endsWith(".java")) {
                    System.out.println("文件名:" + file.getAbsolutePath());
                }
            } else {
                // 是目录,继续遍历,形成递归
                printDir(file);
            }
        }
    }
}

5.3.5 文件过滤器优化

java.io.FileFilter是一个接口,是File的过滤器。 该接口的对象可以传递给File类的listFiles(FileFilter) 作为参数, 接口中只有一个方法。

  • boolean accept(File pathname) :将此目录的每个文件传递给accept方法,此方法返回true则保留此文件,反之剔除;

分析

  1. 接口作为参数,需要传递子类对象,重写其中方法。我们选择匿名内部类方式,比较简单。
  2. accept方法,参数为File,表示当前File下所有的子文件和子目录。保留住则返回true,过滤掉则返回false。保留规则:
    1. 要么是.java文件。
    2. 要么是目录,用于继续遍历。
  3. 通过过滤器的作用,listFiles(FileFilter)返回的数组元素中,子文件对象都是符合条件的,可以直接打印。

代码实现:

package com.dfbz.demo01;

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

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo06 {
    public static void main(String[] args) {
        File dir = new File("D:\\001");
        printDir2(dir);
    }

    public static void printDir2(File dir) {
        // 匿名内部类方式,创建过滤器子类对象
        File[] files = dir.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                return pathname.getName().endsWith(".java")||pathname.isDirectory();
            }
        });
        // 循环打印
        for (File file : files) {
            if (file.isFile()) {
                System.out.println("文件名:" + file.getAbsolutePath());
            } else {
                printDir2(file);
            }
        }
    }
}

上一篇12【多线程、锁机制、lock锁】

下一篇14【IO流概述、字节流、字符流、属性操作】

目录【JavaSE零基础系列教程目录】


记得点赞~!!!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/131772.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!