JAVA基础-NIO

  • 传统IO:使用阻塞IO模型,基于字节流和字符流,进行文件读写,使用Socket和ServerSocket进行网络传输。
    • 存在的问题:
      • 当线程在等待IO完成的时候,是无法执行其他任务,导致资源空等浪费。
      • 一个Socket对应一个线程,Socket是基于字节流进行文件传输,如果大量的Socket,就会存在大量的上下文切换和阻塞,降低系统性能
  • NIO:使用非阻塞模型,基于通道(Channel)和缓冲区(Buffer)进行文件读写,以及使用SocketChannel和ServerSocketChannel进行网络传输
    • 优势:允许线程在等待IO的时候执行其他任务。这种模式通过使用选择器(Selector)来监控多个通道上的事件,实现更高的性能和伸缩性
  • 实际上传统的IO包已经使用NIO重新实现过,即使我们不显示的使用NIO变成,也能从中获益

    package org.example.code_case.javaIo;

    import org.junit.jupiter.api.Test;

    import java.io.*;
    import java.nio.ByteBuffer;
    import java.nio.channels.FileChannel;

    public class 传统IO和NIO的对比 {

    复制代码
      String sourcePath = "/src/main/java/org/example/code_case/javaIo/testFile.iso";
      String targetPath1 = "/src/main/java/org/example/code_case/javaIo/testFileIO.iso";
      String targetPath2 = "/src/main/java/org/example/code_case/javaIo/testFileNIO.iso";
    
      @Test
      public void testDemo() {
    
          Thread ioThread = new Thread(() -> {
              long startTime = System.currentTimeMillis();
              try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(sourcePath));
                   BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(targetPath1));) {
    
                  byte[] bytes = new byte[8192];
                  int len;
                  while ((len = bufferedInputStream.read(bytes)) != -1) {
                      bufferedOutputStream.write(bytes, 0, len);
                  }
              } catch (IOException e) {
                  throw new RuntimeException(e);
              }
    
              long endTime = System.currentTimeMillis();
              System.out.println("传统IO time:" + (endTime - startTime));
          });
    
          Thread nioThread = new Thread(() -> {
              long startTime = System.currentTimeMillis();
              try (RandomAccessFile readRw = new RandomAccessFile(sourcePath, "rw");
                   RandomAccessFile writeRw = new RandomAccessFile(targetPath2, "rw");) {
                  try (FileChannel readChannel = readRw.getChannel();
                       FileChannel writeChannel = writeRw.getChannel();) {
                      // 创建并使用 ByteBuffer 传输数据
                      ByteBuffer byteBuffer = ByteBuffer.allocate(8192);
                      while (readChannel.read(byteBuffer) > 0) {
                          byteBuffer.flip();
                          writeChannel.write(byteBuffer);
                          byteBuffer.clear();
                      }
                  }
              } catch (IOException e) {
                  throw new RuntimeException(e);
              }
              long endTime = System.currentTimeMillis();
              System.out.println("NIO time:" + (endTime - startTime));
          });
    
          ioThread.start();
          nioThread.start();
          try {
              ioThread.join();
              nioThread.join();
          } catch (InterruptedException e) {
              throw new RuntimeException(e);
          }
    
    
      }

    }

NIO time:24266

传统IO time:24267

当文件越大,传统IO和NIO的速度差异越小

  • 既然如此可以不使用NIO吗?
    • 答案:不行,IO应用的场景:文件IO、网络IO
    • 而NIO的主战场为网络IO
  • NIO设计的目的就是为了解决传统IO在大量并发连接时的性能瓶颈问题。传统IO在网络通信中使用阻塞式IO模型,并且会为每个连接分配一个线程,所以系统资源称为关键瓶颈。而NIO采用非阻塞式IO和IO多路复用,可以在单个线程中处理多个并发连接,从而提高网络传输的性能
  • NIO特点:
    • 非阻塞IO:在等待IO完成时,不会阻塞,可以执行其他任务,节省系统资源
      • NIO的非阻塞性体现在:读或者写的时候,如果通道无数据,则会先返回,当数据准备好后告知selector,不会在读或写时由于数据未准备好而阻塞。
    • IO多路复用:一个线程能够同时监控多个IO通道,,并在IO事件准备好的时处理他们
    • 提供ByteBuffer类,可以高效的管理缓冲区

NIO 和传统 IO 在网络传输中的差异

复制代码
package org.example.code_case.javaIo.NIO和IO的对比;

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

public class IOServer {

    public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(8888);) {
            while (true) {
                try (Socket accept = serverSocket.accept();
                     InputStream in = accept.getInputStream();
                     OutputStream out = accept.getOutputStream();) {


                    byte[] bytes = new byte[1024];
                    int len;
                    while ((len = in.read( bytes)) != -1){
                        //模拟操作耗时
                        TimeUnit.MILLISECONDS.sleep(10);
                        out.write(bytes, 0, len);
                    }
                }
                catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
}

package org.example.code_case.javaIo.NIO和IO的对比;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class NIOServer {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        try (ServerSocketChannel open = ServerSocketChannel.open();) {
            //绑定端口
            open.bind(new InetSocketAddress(9999));
            //设置非阻塞模式
            open.configureBlocking(false);

            //创建selector
            Selector selector = Selector.open();
            open.register(selector, SelectionKey.OP_ACCEPT);//事件类型:服务端接受客户端连接


            //处理事件
            while (true) {
                //阻塞等待事件发生
                selector.select();
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                //遍历就绪事件
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    //处理完成之后删除
                    iterator.remove();

                    //判断事件类型
                    if (key.isAcceptable()) {
                        //有新连接请求
                        ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                        SocketChannel accept = channel.accept();
                        //设置为非阻塞模式
                        accept.configureBlocking(false);
                        //为新连接附加写队列
                        Queue<ByteBuffer> byteBufferQueue = new ConcurrentLinkedQueue<>();
                        accept.register(selector, SelectionKey.OP_READ, byteBufferQueue);//事件类型:通道可读
                    }
                    if (key.isReadable()) {
                        //通道可读
                        SocketChannel channel = (SocketChannel) key.channel();
                        ByteBuffer allocate = ByteBuffer.allocate(1024);
                        //从SocketChannel中读取数据写入缓冲区,
                        //当通道没有数据可读立即返回0,而读取完返回-1
                        int read = channel.read(allocate);
                        if (read > 0) {
                            //切换读模式
                            allocate.flip();
                            byte[] data = new byte[allocate.remaining()];
                            allocate.get(data);
                            executorService.execute(() -> processData(data, channel, key));
                        }
                        if (read == -1) {
                            //关闭通道
                            closeChannel(key);
                        }
                    }
                    if (key.isValid() && key.isWritable()) {
                        //通道可写
                        SocketChannel channel = (SocketChannel) key.channel();
                        Queue<ByteBuffer> queue = (Queue<ByteBuffer>) key.attachment();
                        synchronized (queue) {
                            ByteBuffer buffer;
                            while ((buffer = queue.peek()) != null) {
                                channel.write(buffer);
                                if (buffer.hasRemaining()) break;//未写完,等待下次写事件
                                queue.poll();//已写完,移除缓冲区
                            }
                            if (queue.isEmpty()) {
                                key.interestOps(SelectionKey.OP_READ);//取消写事件监听,监听读事件
                                closeChannel(key);
                            }
                        }

                    }


                }

            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static void processData(byte[] data, SocketChannel channel, SelectionKey key) {
        try {
            //模拟耗时操作
            TimeUnit.MILLISECONDS.sleep(10);
            //生成相应数据
            ByteBuffer wrap = ByteBuffer.wrap(data);
            Queue<ByteBuffer> queue = (Queue<ByteBuffer>) key.attachment();
            synchronized (queue) {
                queue.add(wrap);
                key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);//同时监听写事件和读事件,OP_READ是因为数据可没有一次性读完
            }
            key.selector().wakeup();//唤醒selector立即处理新事件
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

    }

    private static void closeChannel(SelectionKey key) {
        try {
            key.cancel();
            key.channel().close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

@Test
public void ioTest() throws InterruptedException {
    ExecutorService ioService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    Runnable ioThread = () -> {
        try (Socket socket = new Socket("127.0.0.1", 8888);
             InputStream in = socket.getInputStream();
             OutputStream out = socket.getOutputStream();) {

            out.write("hello world,".getBytes());

            in.read(new byte[1024]);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    };


    long start = System.currentTimeMillis();
    for (int i = 0; i < 10000; i++) {
        ioService.execute(ioThread);
    }
    ioService.shutdown();
    ioService.awaitTermination(1, TimeUnit.DAYS);
    long end = System.currentTimeMillis();
    System.out.println("传统IO,处理10000个请求 time:" + (end - start));
}

@Test
public void nioTest() throws InterruptedException {
    ExecutorService nioService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    Runnable nioThread = () -> {
        try (SocketChannel open = SocketChannel.open();) {
            open.connect(new InetSocketAddress("localhost", 9999));

            //给服务端发消息
            ByteBuffer buffer = ByteBuffer.wrap("hello world,".getBytes());
            open.write(buffer);
            buffer.clear();
            //接收服务端消息
            open.read(buffer);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    };
    
    long start1 = System.currentTimeMillis();
    for (int i = 0; i < 10000; i++) {
        nioService.execute(nioThread);
    }
    nioService.shutdown();
    nioService.awaitTermination(1, TimeUnit.DAYS);
    long end1 = System.currentTimeMillis();
    System.out.println("NIO,处理10000个请求 time:" + (end1 - start1));
}

传统IO,处理10000个请求 time:122803ms

NIO,处理10000个请求 time:23950ms

从运行结果能够很明显的看出NIO在处理网络高并发请求的优势

JavaIO:BIO、NIO、AIO分别是什么?有什么区别?

  • I/O:即输入/输出,通常用于文件读写或网络通信
  • 随着Java的发展,共有3种I/O模型,BIO、NIO、AIO
  • BIO:同步阻塞IO模型,也称传统IO,是面向数据流的,即一个流产生一个字节数据。效率低、网络并发低
    • 同步阻塞IO模型:发送请求,等待对方应答,期间不能做任何事情
    • 适用场景:连接少,并发低的架构,例如文件读写
  • NIO:同步非阻塞IO模型,也称NewIO,是面向数据块的,即每次操作在一步中产生或消费一个数据块。引进Channel通道、BtyeBuffer缓冲区、Selector选择器组件。效率高、网络并发高
    • 同步非阻塞IO模型:发送请求,通过轮询和选择器检查IO状态,期间可以做其他事情。
    • 适用场景:连接数目多,但连接时间短的场景,例如聊天室
  • AIO:异步非阻塞IO模型,建立在NIO的基础上实现的异步
    • 异步非阻塞IO模型:发送请求,然后去其他事情,请求完成自动通知

    • 适用场景:连接数目多,但连接时间长的场景,例如图片服务器

      @Test
      public void test2() throws IOException, ExecutionException, InterruptedException {
      //读取文件
      AsynchronousFileChannel open = AsynchronousFileChannel.open(path, StandardOpenOption.READ);
      ByteBuffer allocate = ByteBuffer.allocate(1024);
      open.read(allocate, 0, allocate, new CompletionHandler<Integer, ByteBuffer>() {

      复制代码
          @Override
          public void completed(Integer result, ByteBuffer attachment) {
              attachment.flip();
              System.out.println("读取到的数据" + StandardCharsets.UTF_8.decode(attachment));
              attachment.clear();
              try {
                  open.close();
              } catch (IOException e) {
                  throw new RuntimeException(e);
              }
          }
      
          @Override
          public void failed(Throwable exc, ByteBuffer attachment) {
              System.out.println("读取失败");
              exc.printStackTrace();
          }
      });
      
      
      //写入文件
      AsynchronousFileChannel open1 = AsynchronousFileChannel.open(path, StandardOpenOption.CREATE,StandardOpenOption.WRITE);
      
      ByteBuffer wrap = ByteBuffer.wrap("hello".getBytes(StandardCharsets.UTF_8));
      
      Future<Integer> write = open1.write(wrap, 0);
      
      write.get();
      open1.close();
      System.out.println("写入完成");

      }

Buffer和Channel

  • 在NIO中有两个重要的组件:Buffer缓冲区和Channel通道
  • 其中Channel就像轨道,只负责运送。Buffer就像火车,负责承载数据
Buffer
  • Buffer:IntBuffer、CharBuffer、ByteBuffer、LongBuffer
  • 缓冲区的核心方法就是put和get方法
  • Buffer类维护了4个核心参数:
    • 容量Capacity:缓冲区的最大容量,在创建时确定,不能改变,底层是数组
    • 上界limit:缓冲区中数据的总数,代表当前缓冲区有多少数据,默认为0,只有在调用flip()方法之后才有意义
    • 位置Position:下一个要被读或写的位置,Position会自动由相应的get和put方法更新
    • 标记mark:用于记录上一次读写的位置
  • 如果想要从Buffer中读取数据,需要调用flip(),此方法将Postion的值给了limit,将Postion置为0
Channel
  • 通道分为两类:文件通道和套接字通道
  • FileChannel:用于文件IO的通道,支持对文件的读写和追加操作。无法设置非阻塞模式
  • SocketChannel:用于TCP套接字的网络通信,支持非阻塞模式,与Selector一同使用提供高效的网络通信。与之匹配的是ServerSocketChannel,用于监听TCP套接字的通道,也支持非阻塞模式,ServerSocketChannel负责监听新的连接请求,接受到请求后创建一个新的SocketChannel处理数据传输
  • DatagramChannel:用于 UDP 套接字 I/O 的通道。DatagramChannel 支持非阻塞模式,可以发送和接收,数据报包,适用于无连接的、不可靠的网络通信。
文件通道FileChannel
  • 使用内存映射文件MappedByteBuffer当方式实现文件复制功能:

    /**

    • MappedByteBuffer 使用ByteBuffer的子类,它可以直接将文件映射到内存中,以便通过直接操作内存来实现对文件的读写。

    • 这种方式可以提高文件 I/O 的性能,因为操作系统可以直接在内存和磁盘之间传输数据,无需通过Java 应用程序进行额外的数据拷贝。

    • 需要注意:MappedByteBuffer进行文件操作时,数据的修改不会立即写入磁盘,可以通过调用force()方法将数据立即写入
      */
      @Test
      public void test3() throws IOException {
      try (FileChannel sourceChannel = FileChannel.open(path, StandardOpenOption.READ);
      FileChannel targetChannel = FileChannel.open(targetPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.READ);) {

      复制代码
       long size = sourceChannel.size();
       MappedByteBuffer sourceMappedByteBuffer = sourceChannel.map(FileChannel.MapMode.READ_ONLY, 0, size);
       MappedByteBuffer targetMappedByteBuffer = targetChannel.map(FileChannel.MapMode.READ_WRITE, 0, size);
      
      
       targetMappedByteBuffer.put(sourceMappedByteBuffer);

      }
      }

  • 通道之间通过transferTo()实现数据的传输:

    /**

    • 这种传输方式可以避免将文件数据在用 户空间和内核空间之间进行多次拷贝,提高了文件传输的性能。

    • transferTo()方法可以将数据从一个通道传输到另一个通道,而不需要通过中间缓冲区。

    • position:源文件中开始传输的位置

    • count:传输的字节数

    • targetChannel:目标通道

    • 注意:transferTo()可能无法一次传输所有请求的字节,需要使用循环来确保所有的字节都被传输
      */
      @Test
      public void test4() throws IOException {
      try (FileChannel sourceChannel = FileChannel.open(path, StandardOpenOption.READ);
      FileChannel targetChannel = FileChannel.open(targetPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.READ);) {
      long size = sourceChannel.size();
      long position = 0;

      复制代码
       while (position < size) {
           long offer = sourceChannel.transferTo(position, size - position, targetChannel);
           position += offer;
       }

      }
      }

直接缓冲区与非直接缓冲区
  • 直接缓冲区:
    • ByteBuffer.allocateDirect(int size)FileChannel.map()创建的MappedByteBuffer都是直接缓冲区
    • 直接分配在JVM堆内存之外的本地内存中,由操作系统直接管理
    • 在读写时直接操作本地内存,避免了数据复制,提高性能,但是创建和销毁成本高,占用物理内存
    • 适用场景:网络IO操作、频繁的IO操作
  • 非直接缓冲区
    • ByteBuffer.allocate(int size)
    • 分配在JVM的堆内存中,由JVM管理
    • 在读写操作时,需要将数据从堆内存复制到操作系统的本地内存,在进行I/O操作,但是创建成本低,内存管理简单
    • 适用场景:临时数据处理、小文件操作、数据转换处理
异步文件通道AsynchronousFileChannel
  • AsynchronoursFileChannel是一个异步文件通道,提供了使用异步的方式对文件读写、打开关闭等操作

    Path file = Paths.get("example.txt");
    AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(file,
    StandardOpenOption.READ, StandardOpenOption.WRITE);

  • 两种异步操作:

    • Future方式:使用Future对象来跟踪异步操作的完成情况,当我们调用read()和write()方法的时候,会返回一个Future对象,可以检查任务是否完成,进而操作结果

      ByteBuffer buffer = ByteBuffer.allocate(1024);
      long position = 0;
      Future<Integer> result = fileChannel.read(buffer, position);
      while (!result.isDone()) {
      // 执行其他操作
      }
      int bytesRead = result.get();
      System.out.println("Bytes read: " + bytesRead);

    • CompletionHandler方法:需要实现CompletionHandler接口来定义异步操作成功或失败的操作。相当于回调函数

      //读取文件
      AsynchronousFileChannel open = AsynchronousFileChannel.open(path, StandardOpenOption.READ);
      ByteBuffer allocate = ByteBuffer.allocate(1024);

      AtomicInteger position = new AtomicInteger(0);
      CountDownLatch countDownLatch = new CountDownLatch(1);
      open.read(allocate, position.get(), allocate, new CompletionHandler<Integer, ByteBuffer>() {

      复制代码
      @Override
      public void completed(Integer result, ByteBuffer attachment) {
          try {
              TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e) {
              throw new RuntimeException(e);
          }
          if(result > 0){
              position.addAndGet(result);
              attachment.flip();
              System.out.println("读取到的数据" + StandardCharsets.UTF_8.decode(attachment));
              attachment.clear();
              open.read(allocate, position.get(), allocate, this);
          }else{
              try {
                  countDownLatch.countDown();
                  System.out.println("关闭");
                  open.close();
              } catch (IOException e) {
                  throw new RuntimeException(e);
              }
          }
      
      
      }
      
      @Override
      public void failed(Throwable exc, ByteBuffer attachment) {
          System.out.println("读取失败");
          exc.printStackTrace();
      }

      });

      countDownLatch.await();

Paths类

  • Paths类主要用于操作文件和目录的路径。

    //获取当前工作路径
    String currentPath = System.getProperty("user.dir");
    System.out.println("当前工作目录:" + currentPath);
    //获取指定路径
    Path resolve = Paths.get(currentPath).resolve("src/main/resources/不同IO的使用方式.txt");
    System.out.println("获取文件名:" + resolve.getFileName());
    System.out.println("获取父目录:" + resolve.getParent());
    System.out.println("获取根目录:" + resolve.getRoot());
    System.out.println("简化路径:" + resolve.normalize());
    System.out.println("将相对路径转化为绝对路径:" + resolve.toAbsolutePath());

Files类

  • Files类提供了大量的静态方法,用于处理文件系统中的文件和目录。包括文件的创建、删除、复制、移动等操作,以及读取和设置文件属性
  • exists(Path path, LinkOption... options);检查文件或目录是否存在
    • LinkOption是一个枚举类,定义了如何处理文件系统的符号链接的选项。符号链接是一种特殊类型的文件,在Unix和类Unix系统常见,在Win系统中类似快捷方式
  • createFile(Path path, FileAttribute<?>... attrs);创建一个新的空文件
    • FileAttribute是一个泛型接口,用于处理各种不同类型的属性

      Path parentPath = Paths.get(System.getProperty("user.dit")).resolve("src/main/resources");
      Path resolve = parentPath.resolve("FileTest.txt");

      //设置文件权限:
      //rw- 文件所有者可读写不能执行
      //r-- 文件所在组可读不能写不能执行
      //--- 其他用户不能读写不能执行
      Set<PosixFilePermission> posixFilePermissions = PosixFilePermissions.fromString("rw-r-----");
      FileAttribute<Set<PosixFilePermission>> fileAttribute = PosixFilePermissions.asFileAttribute(posixFilePermissions);
      Files.createFile(resolve, fileAttribute);

  • createDirectory(Path dir, FileAttribute<?>... attrs);创建一个新的文件夹

  • delete(Path path);删除文件或目录

  • copy(Path source, Path target, CopyOption... options);复制文件或目录

    • 在Java NIO中,有两个实现了CopyOption接口的枚举类:StandardCopyOption和LinkOption
    • StandardCopyOption:REPLACE_EXISTING如果文件已存在,copy会替换目标文件,如果不指定,则抛异常。COPY_ATTRIBUTES复制时同样复制文件属性,如果不指定,则文件具有默认属性
  • move(Path source, Path target, CopyOption... options);移动或重命名文件或目录
  • readAllLines(Path path, Charset cs);读取文件的所有行到一个字符串列表
  • write(Path path, Iterable<? extends CharSequence> lines, Charset cs, OpenOption... options);将字符串列表写入文件
    • OpenOption是一个用于配置文件操作的接口,它提供了在使用 Files.newByteChannel() 、 Files.newInputStream() 、 Files.newOutputStream() 、 AsynchronousFileChannel.open() 和 FileChannel.open() 方法时定制行为的选项。
    • StandardOpenOption实现了OpenOption接口的枚举类:
      • read、write、append、truncate_existing、create、create_new、delete_on_close、sparse、sync、dsync
  • newBufferedReader(Path path, Charset cs) 和 newBufferedWriter(Path path, Charset cs, OpenOption... options);创建BufferedrReader和BufferedWrite

  • Files.walkFileTree();递归访问目录结构中的所有文件和目录,并允许对这些文件和目录执行自定义操作

    @Test
    public void test4() throws IOException {
    Path parentPathDir = Paths.get(System.getProperty("user.dir")).resolve("src/main/java/org/example/code_case");

    复制代码
      Files.walkFileTree(parentPathDir, new SimpleFileVisitor<Path>() {
          //在访问目录之前调用的
          @Override
          public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
              System.out.println("访问目录之前:" + dir);
              return super.preVisitDirectory(dir, attrs);
          }
    
          //在访问目录之后调用的
          @Override
          public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
              System.out.println("访问目录之后:" + dir);
              return super.postVisitDirectory(dir, exc);
          }
    
          //在访问文件时调用的
          @Override
          public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
              System.out.println("访问文件:" + file);
              return super.visitFile(file, attrs);
          }
    
          //在访问文件时发生异常时调用的
          @Override
          public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
              System.out.println("访问文件时发生异常:" + file);
              return super.visitFileFailed(file, exc);
          }
      });

    }

    • 其中FileVisitResult枚举包括:continue、terminate、skip_siblings(跳过兄弟节点,然后继续)、skip_subtree(跳过子树,然后继续)仅在preVisitDirectory方法返回时才有意义

      @Test
      public void test5() throws IOException {
      //文件搜索
      Path parentPathDir = Paths.get(System.getProperty("user.dir")).resolve("src/main/java/org/example/code_case");
      class MyFileVisitor extends SimpleFileVisitor<Path> {

      复制代码
          public boolean exsits = false;
      
          final String fileName = "README.txt";
      
          @Override
          public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
              String currentFileName = file.getFileName().toString();
              if (fileName.equalsIgnoreCase(currentFileName)) {
                  exsits = true;
                  return FileVisitResult.TERMINATE;
              }
              return super.visitFile(file, attrs);
          }
      };
      MyFileVisitor myFileVisitor = new MyFileVisitor();
      Files.walkFileTree(parentPathDir, myFileVisitor);
      if(myFileVisitor.exsits){
          System.out.println("文件存在");
      }else {
          System.out.println("文件不存在");
      }

      }