NIO流(多路复用技术)

目录

  • 什么是NIO
        • 使用场景
  • NIO(new IO)相关包路径
  • NIO的实现基础
    • NIO的核心组件
      • Buffer
      • 缓冲区详解
        • 数据如何从磁盘读到用户进程
      • Channel
        • Channel的使用
    • 其他组件
      • 字符集和Charset
      • 文件锁
      • NIO工具类
        • 使用Files的FileVisitor遍历文件和目录
        • 使用WatchService监控文件变化
        • 访问文件属性

什么是NIO

NIO是Java提供的一种基于Channel和Buffer的IO操作方式,即:利用内存映射文件方式处理输入和输出。NIO具有更加强大和灵活的IO操作能力,提供了非阻塞IO、多路复用等特性,特别适合需要处理大量连接的网络编程场景

  • 在JDK1.4时提出了NIO(New I/O),在BIO模型(Blocking IO)的基础上,增加了NIO模型(Non-Blocking IO),即同步非阻塞方式

  • JDK7时在NIO包中增加了AIO,NIO也随之被称为NIO2.0(即NIO+AIO),NIO是同步非阻塞的,AIO是异步非阻塞的。

NIO官方叫法为New I/O,但是由于后续加入了AIO,导致New IO已经不能表达已有的IO模型,因此NIO也被业界称为Non-blocking I/O,即非阻塞IO

本文只对Non-blocking IO进行探讨,AIO不做过多赘述,AIO详情请参考AIO(异步IO)
想要详细了解IO多路复用模型原理参考:IO多路复用模型原理

使用场景
  • 对于低负载、低并发的应用程序,可以使用同步阻塞IO来提升开发效率和维护性

  • 但是对于高负载、高并发的网络应用,应该使用NIO的非阻塞模式来开发

NIO(new IO)相关包路径

其中包下的常用类后续会详细说明

  • java.nio:主要包含各种与Buffer相关的类

  • java.nio.channels:主要包含与Channel和Selector相关的类

    套接字的特别说明(因为也在这个包下)

    在该包路径下,NIO提供了与传统IO模型中SocketServerSocket相对应的SocketChannelServerSocketChannel两种不同的套接字通道实现

    新增的这两种通道都支持阻塞和非阻塞方式

  • java.nio.charset:主要包含与字符集相关的类

  • java.nio.file:主要包含文件处理的工具类

  • java.nio.channels.spi:主要包含与Channel相关的服务提供者编程接口

  • java.nio.charset.spi:主要包含与字符集相关的服务提供者编程接口

NIO的实现基础

NIO是基于Linux IO模型的IO多路复用模型实现的,netty、tomcat5及以后的版本的实现都是基于NIO,想要理解Linux的IO模型参考:Linux的五种网络IO模型
IO复用模型图解
IO复用模型图解

这里的多路是指N个连接,每一个连接对应一个channel,或者说多路就是多个channel,是指多个连接复用了一个线程或者少量线程(在tomcat中是少量线程)

NIO的核心组件

JavaNIO主要包含三个核心组件:

**Selector:**多路复用器(选择器),是NIO的基础,也可以称为轮询代理器、事件订阅器或Channel容器管理器。Selector提供选择已经就绪的任务的能力,允许一个线程同时监听多个通道上的事件,即:单线程同时管理多个网络连接,并在某个通道上有数据可读或可写时及时做出响应,是Java NIO实现非阻塞IO的关键组件

**Channel:**是所有数据的传输通道,通道可以是文件、网络连接等。Channel提供了一个map()方法,通过该map()方法可以直接将“一块数据”映射到内存中

**Buffer:**是一个容器(类似数组),发送到Channel中的所有对象都必须首先放到Buffer中,从Channel中读取的数据也会先放到Buffer中

为什么要将传统IO模型中stream的概念换成channel+buffer的概念?

  • Stream与Channel对比

    • 传统的阻塞IO模型中,stream是用于在程序和数据源之间进行数据传输的抽象概念,流的特点就是顺序的、逐个访问。

    • Java NIO中提出的Channel也是进行数据传输的抽象概念,区别在于stream是单向数据传输,而channel是双向数据传输,

    从这个角度看,Channel是全双工通信,Stream是单工通信,那么Channel必然就会比Stream更加高效

  • 为什么传统IO没有提出Buffer的概念?

    缓冲区以及缓冲区是如何工作的,这是所有IO实现的基础,即输入和输出就是把数据移进 or 移出缓冲区

    进程执行IO操作,就是向操作系统发出请求,将数据从缓冲区取出(写操作),或者将数据写入缓冲区(读操作)

    既然Buffer是所有IO实现的基础,传统IO模型并没有Buffer,是不是说错了或者传统IO并不是IO?

    其实并不是,只是传统IO中Buffer是开发者自己创建的,也就是byte[]数组,这个byte数组设置多大都是开发者自己决定,因此没有提出Buffer的概念
    Java byte[] input = new byte[1024]; /* 读取数据 */ socket.getInputStream().read(input);
    而在JavaNIO中,缓冲区是一个固定大小的,连续内存块,用于暂时存储数据,为缓冲区也提供了一系列的操作API,因此在NIO特意强调了Buffer的概念

NIO注册、轮询等待、读写操作协作关系如下图:
在这里插入图片描述

Buffer

Buffer是Channel操作读写的组件,包含了写入和读取得数据。在NIO库中,所有数据都是用缓冲区处理的,缓冲区实际上就是一个数组,并提供了对数据结构化以及维护读写位置等信息。
Buffer是一个抽象类,我们在网络传输中大多数都是使用ByteBuffer,它可以在底层字节数组上进行get/set操作。除了ByteBuffer之外,对应于其他基本数据类型(boolean除外)都有相应的Buffer类(CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer)。

这些Buffer类没有提供构造器访问,因此创建Buffer类就必须使用静态方法allocate(int capacity),即:ByteBuffer buffer = ByteBuffer.allocate(10)表示创建容量为10的ByteBuffer对象

ByteBuffer类的子类:MappedByteBuffer用于表示Channel将磁盘文件的部分或全部内容映射到内存中后得到的结果。MappedByteBuffer对象是由Channel的map()方法返回

  1. 容量(capacity):缓冲区的容量,表示该Buffer的最大数据容量,创建后不可改变,不能为负值

  2. 界限(limit):第一个不应该被读出或写入的缓冲区位置索引,位于limit后的数据既不可被读也不可被写

  3. 位置(position):用于指明下一个可以被读出或写入的缓冲区位置索引,索引从0开始,即如果从Channel中读了两个数据后(0,1),position指向的索引应该是2(第三个即将读取数据的位置)

position可以自己设置,即设置从索引为mark处读取数据
在这里插入图片描述

/*** XxxBuffer方法:*      put():向Buffer中放入一些数据--一般不使用,都是从Channel中获取数据*      get():向Buffer中取出数据*      flip():当Buffer装入数据结束后,调用该方法可以设置limit为当前位置,避免后续数据的添加--为输出数据做准备*      clear():对界限、位置进行初始化,limit置为capacity,position设置为0,为下一次取数做准备*      int capacity():返回Buffer的容量大小*      boolean hasRemaining():判断当前位置和界限之间是否还有元素可供处理*      int limit():返回Buffer的界限(limit)的位置*      Buffer mark():设置Buffer的标记(mark)位置,只能在0-position之间做标记*      int position():返回Buffer中的position值*      Buffer position(int newPs):设置Buffer的position,并返回position被修改后的Buffer对象*      int remaining():返回当前位置和界限之间的元素个数*      Buffer reset():将位置转到mark所在的位置*      Buffer rewind():将位置设置为0,取消设置的mark* @Param: * @return: void*/ 
public void BufferTest(){//创建一个CharBuffer缓冲区,设置容量为20CharBuffer buff= CharBuffer.allocate(20);//测试方法://获取当前容量、界限、位置System.out.println("初始值:"+buff.capacity()+"\n"+buff.limit()+"\n"+buff.position());//20、20、0buff.put('1');buff.put('2');buff.put('3');buff.position(1).mark();//标记位置索引处buff.rewind();//将position设置为0,并将mark清除,此时再调用reset()将会报错java.nio.InvalidMarkExceptionbuff.mark().reset();//将position转移到标记处buff.put("abc");buff.put("java");//abcjava//设置界限值buff.limit(buff.position());System.out.println("添加数据后:"+buff.capacity()+"\n"+buff.limit()+"\n"+buff.position());//20、7、7//初始化容量、界限、位置int position = buff.position();buff.position(0);System.out.println("修改后:"+buff.capacity()+"\n"+buff.limit()+"\n"+buff.position());//20、7、0//遍历Buffer数组的数据for (int i = 0; i < position; i++) {System.out.print(buff.get());}System.out.println();//hasRemaining判断是否可继续添加元素,position >= limit返回false,position < limit返回trueSystem.out.println(buff.remaining());//0System.out.println(buff.hasRemaining());//falsebuff.limit(15);System.out.println(buff.hasRemaining());//trueSystem.out.println(buff.position());//7System.out.println("remaining="+buff.remaining());//8 还可以添加8个元素buff.clear();System.out.println("clear后:"+buff.capacity()+"\n"+buff.limit()+"\n"+buff.position());//20、20、0
}

Buffer的缺点:

  1. XxxBuffer使用allocate()方法创建的Buffer对象是普通的Buffer–创建在Heap上的一块连续区域–间接缓冲区

  2. ByteBuffer还有一个allocateDirect()方法创建的Buffer是直接Buffer–创建在物理内存上开辟空间–直接缓冲区

1. 间接缓冲区:易于管理,垃圾回收器可回收,但是空间有限,读写文件速度较慢(从磁盘读到内存)2. 直接缓冲区:空间较大,读写速度快(从磁盘读到磁盘的速度),但是不受垃圾回收器的管理,创建和销毁都极耗性能
  1. 直接Buffer的创建成本高于间接Buffer,所以直接Buffer只用于生存期长的Buffer。

  2. 直接Buffer只有ByteBuffer才能创建,因此如果需要其他的类型,只能使用创建好的ByteBuffer转型为其他类型

重要注意事项:

flip()方法可以将Buffer从写模式切换到读模式,flip()方法会将position设回到0,并将limit设置成之前position的值

缓冲区详解

数据如何从磁盘读到用户进程

在这里插入图片描述

上图解析

该图描述了数据从外部磁盘向运行中的进程的内存区域移动的过程

  1. 进程使用read()系统调用,要求从指定目标处获取数据

  2. 此时CPU会通过特定的指令将磁盘控制器初始化,包括设置数据传输的起始地址、目的地址、数据长度等

  3. 外部设备发起直接内存访问请求,请求磁盘控制器来执行数据传输操作

  4. 磁盘控制器根据初始化的参数直接在外设和内核内存缓冲区之间进行数据传输,不需要CPU干预

  5. 当数据传输完成后,磁盘控制器会发送一个信号中断给CPU,通知传输完成

  6. 一旦内核的内存缓冲区数据传输完成,内核就会立即把数据从内核空间的临时缓冲区拷贝到用户进程执行read()系统调用时指定的缓冲区内

Channel

Channel表示打开到IO设备的连接,可以直接将指定的文件的部分或全部直接映射为Buffer–映射,程序不能直接访问Channel中的数据(读取、写入都不行),必须通过Buffer进行承载后从Buffer中操作这些数据

Channel有两种实现:SelectableChannel1用于网络读写;FileChannel用于文件操作
其中SelectableChannel有以下几种实现:

  • ServerSocketChannel:应用服务器程序的监听通道。只有通过了这个通道,应用程序才能向操作系统注册支持多路复用IO的端口监听。同时支持UDP协议和TCP协议

  • SocketChannel:TCP Socket套接字的监听通道,一个Socket套接字对应了一个客户端IP:端口 → 服务端IP:端口的通信连接

  • DatagramChannel:UDP数据报文的监听通道

Channel相比于IO中的Stream流更加高效2,但是必须和Buffer一起使用。

Channel的使用
  1. Channel不能使用构造器来创建,只能通过字节流InputStream,OutputStream(节点流)来调用getChannel()方法来创建

  2. 不同的节点流调用getChannel()方法创建的Channel对象不一样。
    如:FileInputStream/FileOutputStream->FileChannel
    PipedInputStream/PipedOutputStream->Pip.SinkChannel/Pip.SourceChannel

  3. Channel常用的三个方法:

    • MappedByteBuffer map(FileChannel.MapMode mode,long position,long size)

      将Channel对应的部分或全部的数据映射成ByteBuffer

      参数说明:

      mode:映射模式-三种:READ_ONLY(只读)、PRIVATE(私有(写时复制))、READ_WRITE(读写)

      position:Buffer的初始化位置

      size:Buffer的容量

    • read():用于读取Buffer中的数据

    • write():用于将数据写入Buffer

FileChannel inChannel = null;
FileChannel outChannel = null;
try {//1.创建文件对象--指定读取和写入的文件File src=new File("E:\\Documents\\java.txt");File dest=new File("E:\\Documents\\java1.txt");//2.使用FileInputStream进行文件读取、FileOutputStream进行文件写入//不一样的是采用管道的方式--这里就需要getChannel()创建Channel对象inChannel = new FileInputStream(src).getChannel();//只能读outChannel = new FileOutputStream(dest).getChannel();//只能写//3.将管道数据通过map()方法传递给MappedByteBuffer对象进行缓冲承载MappedByteBuffer buffer=inChannel.map(FileChannel.MapMode.READ_ONLY, 0, src.length());//4.将获取的内容buffer交给Channel写回到指定文件java1.txt中outChannel.write(buffer);//将文件内容打印到控制台//1.初始化position和limitbuffer.clear();//2.设置输出编码格式Charset charset=Charset.forName("UTF-8");//3.将ByteBuffer转换成字符集的BufferCharsetDecoder decoder=charset.newDecoder();CharBuffer cb=decoder.decode(buffer);//4.输出字符集bufferSystem.out.println(cb);
} catch (IOException e) {e.printStackTrace();
} finally {try {inChannel.close();} catch (IOException e) {e.printStackTrace();}try {outChannel.close();} catch (IOException e) {e.printStackTrace();}
}

使用RandomAccessFile创建Channel对象

  1. 管道的写数据的方式是追加–但是重新执行程序就是覆盖–这种情况需要修改position的位置

  2. 源文件的随机访问对象创建的管道必须可读,目标文件的随机访问对象创建的管道必须可写

  3. 源文件的随机访问对象创建的管道使用map()方法生成buffer后,目标文件的随机访问对象创建的管道使用write()方法写出buffer

  4. 最后一定要关闭流

FileChannel channel = null;
FileChannel channel1 = null;
try {File srcPath=new File("E:\\Documents\\java.txt");File destPath=new File("E:\\Documents\\java1.txt");channel = new RandomAccessFile(srcPath,"r").getChannel();channel1 = new RandomAccessFile(destPath,"rw").getChannel();ByteBuffer map = channel.map(FileChannel.MapMode.READ_ONLY, 0, srcPath.length());channel1.position(destPath.length());channel1.write(map);
} catch (IOException e) {e.printStackTrace();
} finally {try {channel.close();} catch (IOException e) {e.printStackTrace();}
}
File src=new File("E:/Documents/java.txt");
FileChannel channel = new FileInputStream(src).getChannel();
ByteBuffer bf=ByteBuffer.allocate(256);
int len=0;
while ((len=channel.read(bf))!=-1) {//limit设置为position,避免操作空白区域。如果覆盖到指定位置后,后续还有内容也不可读取,这样避免覆盖不完全出现错误数据bf.flip();System.out.println(bf);Charset charset=Charset.forName("UTF-8");CharsetDecoder decoder = charset.newDecoder();CharBuffer cb = decoder.decode(bf);System.out.println(cb);//重置buffer的参数,内容依旧是采用覆盖的方式,clear不会修改Buffer中的内容bf.clear();
}

其他组件

**Charset类:**用于将Unicode字符映射成为字节序列以及逆映射操作

字符集和Charset

由于计算机的文件、数据、图片文件底层都是二进制存储的(全部都是字节码)

编码:将明文的字符序列转换成计算机理解的二进制序列称为编码

解码:将二进制序列转换成明文字符串称为解码

在这里插入图片描述

java默认使用Unicode字符集,当从操作系统中读取数据到java程序容易出现乱码

当A程序使用A字符集进行数据编码(二进制)存储到硬盘,B程序采用B字符集进行数据解码,解码的二进制数据转换后的字符与A字符集转换后的字符不一致就出现了乱码的情况。

JDK1.4提供了Charset来处理字节序列和字符序列之间的转换关系

  • 该类包含了用于创建编码器和解码器的方法

  • 获取Charset所支持的字符集的方法availableCharsets()

forName(String charsetName):创建Charset对应字符集的对象实例
newDecoder():通过Charset对象获取对应的解码器
newEncoder():通过Charset对象获取对应的编码器CharBuffer encode(ByteBuffer bb):将ByteBuffer中的字节序列转换为字符序列
ByteBuffer decode(CharBuffer cb):将CharBuffer中的字符序列转换为字节序列
ByteBuffer encode(String str):将String中的字符序列转换为字节序列
//      SortedMap<String, Charset> stringCharsetSortedMap = Charset.availableCharsets();
//      stringCharsetSortedMap.forEach((key,value)-> System.out.println(key+"<->"+value));Charset charset = Charset.forName("UTF-8");ByteBuffer bb = charset.encode("中文字符");System.out.println(bb);//java.nio.HeapByteBuffer[pos=0 lim=12 cap=19]//编码解码方式一:CharBuffer decode1 = charset.decode(bb);System.out.println(decode1);//中文字符ByteBuffer encode1 = charset.encode(decode1);System.out.println(encode1);//java.nio.HeapByteBuffer[pos=0 lim=12 cap=19]//编码解码方式二:CharsetDecoder decoder = charset.newDecoder();CharsetEncoder encoder = charset.newEncoder();CharBuffer decode = decoder.decode(encode1);System.out.println(decode);//中文字符ByteBuffer encode = encoder.encode(decode);System.out.println(encode);//java.nio.HeapByteBuffer[pos=0 lim=12 cap=19]}

文件锁

  • 文件锁是在多个运行的程序需要并发修改同一个文件时所必须的

  • 使用文件锁可以有效地阻止多个进程并发的修改同一个文件,但是并不是所有平台都提供了文件锁机制

  • 文件锁能控制文件的全部或部分字节的访问

  • 文件锁在不同的操作系统的差别较大

NIO中java提供了FileLock来支持文件锁定功能,在FileChannel中提供的lock()/tryLock()方法可以获取文件锁FileLock对象

  • lock(long position,long size,boolean shared):如果未获取文件锁,则会导致线程阻塞

  • tryLock(long position,long size,boolean shared):如果未获取文件锁直接返回null,获取返回该文件锁

    • 上述两个方法参数解析:

      • position:从文件的position位置开始

      • size:给长度为size的内容加锁

      • shared:true表示为共享锁:允许多个进程来读取该文件,但是其他进程获得该文件的排他锁;false表示该锁为排他锁,自己读取时其他线程不能获取锁

直接使用lock()或tryLock()方法获取的文件锁是排他锁,即shared默认值为false

FileLock fileLock = null;
try {FileOutputStream fileOutputStream = new FileOutputStream("E:/Documents/java.txt");FileChannel channel = fileOutputStream.getChannel();fileLock = channel.tryLock();//创建锁以后,其他程序将无法对该文件进行修改Thread.sleep(1000);
} catch (IOException e) {e.printStackTrace();
} catch (InterruptedException e) {e.printStackTrace();
} finally {try {fileLock.release();} catch (IOException e) {e.printStackTrace();}
}

虽然文件锁可以用于控制并发访问,但是还是推荐使用数据库来保存程序信息,而不是文件

注意:

  1. 对于部分平台,文件锁即使可以被获取,文件依旧是可以被其他线程操作的

  2. 对于部分平台,不支持同步地锁定一个文件并把它映射到内存中

  3. 文件锁是由java虚拟机持有的,如果两个java程序使用同一个java虚拟机,则不能对同一个文件进行加锁操作

  4. 对于部分平台,关闭FileChannel时,会释放java虚拟机在该文件上的所有锁,因此应该避免对同一个被锁定的文件打开多个FileChannel

NIO工具类

NIO问题:

  1. File类功能有限

  2. File类不能利用特定文件系统的特性

  3. File类的方法性能不高

  4. File类大多数方法出错时不会提供异常信息

升级NIO.2:

  • 提供了Path接口和Paths实现工具类

  • 提供了Files工具类

public class Nio2Test {@Testpublic void pathsTest(){Path path = Paths.get("E:/Documents/java.txt");//path包含的路径数量System.out.println(path.getNameCount());//2=>(Document,java.txt)//获取根目录System.out.println(path.getRoot());//E:\//获取绝对路径System.out.println(path.toAbsolutePath());//E:\Documents\java.txtPath path1 = Paths.get("E:", "Documents", "java.txt");System.out.println(path1);//E:\Documents\java.txt}@Testpublic void File() throws IOException {//复制文件Files.copy(Paths.get("E:","Documents","java1.txt"), new FileOutputStream("E:/Documents/java2.txt"));//检查文件是否为隐藏文件System.out.println("Nio2Test.java:"+Files.isHidden(Paths.get("out.txt")));//falseList<String> list = Files.readAllLines(Paths.get("E:/Documents/java2.txt"), Charset.forName("UTF-8"));System.out.println(list);//获取文件大小long size = Files.size(Paths.get("E:/Documents/java2.txt"));System.out.println(size);//写数据到文件中ArrayList<String> poem = new ArrayList<>();poem.add("今天搞完IO没得问题吧");poem.add("明天搞完网络编程第一章没得问题吧");poem.add("后天搞完网络编程第二章搞完IO没得问题吧");poem.add("大后天搞完网络编程第三章搞完IO没得问题吧");Path write = Files.write(Paths.get("E:/Documents/java2.txt"), poem, Charset.forName("UTF-8"));//覆盖System.out.println(write);//按行获取文件内容,使用Stream接口中的forEache方法遍历Files.lines(Paths.get("E:/Documents/java2.txt"),Charset.forName("UTF-8")).forEach(ele-> System.out.println(ele));//获取目录下文件,使用Stream接口中的forEache方法遍历Files.list(Paths.get("E:/Documents")).forEach(ele-> System.out.println(ele));//获取当前文件的根目录别名FileStore fileStore = Files.getFileStore(Paths.get("E:/Documents/java2.txt"));System.out.println(fileStore);//E盘总空间long totalSpace = fileStore.getTotalSpace();System.out.println(totalSpace);//E盘可用空间long unallocatedSpace = fileStore.getUnallocatedSpace();System.out.println(unallocatedSpace);}
}
使用Files的FileVisitor遍历文件和目录

不使用Files,通常想要遍历指定目录下的所有文件和子目录都是使用递归的方式,这种方式不仅复杂,灵活性也不高

在Files类中提供了两个方法来遍历文件和子目录

walkFileTree(Path start,FileVisitor<? super Path> visitor):遍历start路径下的所有文件和子目录

walkFileTree(Path start,Set options,int maxDepth,FileVisitor<? super Path> visitor):遍历start路径下的所有文件和子目录,但是可根据maxDepth控制遍历深度

两个方法都使用了FileVisitor作为入参,FileVisitor代表一个文件访问器,walkFileTree()方法会自动遍历start路径下的所有文件和子目录,遍历文件和子目录都会触发FileVisitor中相应的方法

FileVisitor中定义的四个方法:

FileVisitResult postVisitDirectory(T dir,IOException exc):访问子目录之后触发该方法

FileVisitResult preVisitDirectory(T dir,BasicFileAttributes attrs):访问子目录之前触发该方法

FileVisitResult visitFile(T file,BasicFileAttributes attrs):访问file文件时触发该方法

FileVisitResult visitFileFailed(T file,IOException exec):访问file文件失败时触发该方法

FileVisitResult是一个枚举类,代表访问之后的后续行为:

CONTINUE:代表继续访问

SKIP_SIBLINGS:代表继续访问,但不访问该文件或目录的兄弟文件或目录

SKIP_SUBTREE:代表继续访问,但不访问该文件或目录的子目录树

TERMINATE:代表中止访问

如果想要实现自己的文件访问器,可以通过继承SimpleFileVisitor来实现,SimpleFileVisitor是FileVisitor的实现类,这样就可以根据需要、选择性的重写指定方法了

public class FileVisitorTest{public static void main(String[] args) throws Exception{Files.walkFileTree(Paths.get("G:","publish","codes","15"),new SimpleFileVisitor<Path>(){@Overridepublic FileVisitResult visitFile(Path file,BasicFileAttributes attrs) throws IOException{System.out.println("正在访问"+file+"文件");//找到了FileVisitorTest.java文件if(file.endsWith("FileVisitorTest.java")){System.out.println("--已经找到目标文件--");return FileVisitResult.TERMINATE;}return FileVisitResult.CONTINUE;}@Overridepublic FileVisitResult preVisitDirectory(Path dir,BasicFileAttributes attrs) throws IOException{System.out.println("正在访问"+dir+"路径");return FileVisitResult.CONTINUE;}})}
}
使用WatchService监控文件变化

不使用WatchService的情况下,想要监控文件的变化,则需要考虑启动一条后台线程,这条后台线程每隔一段实践去遍历一次指定目录的文件,如果发现此次遍历结果与上次遍历结果不同,则认为文件发生了变化,这种方式不仅十分繁琐,而且性能也不好

Path类提供了一个方法用于监听文件系统的变化

register(WatchService watcher,WatchEvent.Kind<?>… events):用watcher监听该path代表的目录下的文件变化。events参数指定要监听哪些类型的事件

这个方法最核心的就是WatchService,它代表一个文件系统监听服务,它负责监听path代表的目录下的文件变化,一旦使用register()方法完成注册之后,接下来就可以调用WatchService的如下三个方法来获取监听目录的文件变化事件

WatchKey poll():获取下一个WatchKey,如果没有WatchKey发生就立即返回null;

WatchKey poll(long timeout,TimeUnit unit):尝试等待timeout实践去获取下一个WatchKey;

WatchKey take():获取下一个WatchKey,如果没有WatchKey发生就一直等待

public class WatchServiceTest{public static void main(String[) args) throws Exception{//获取文件系统atchService对象WatchService watchService = FileSystems.getDefault() .newWatchService(); //为C:盘根路径注册监昕Paths.get("C:/").register(watchService , StandardWatchEventKinds.ENTRY_CREATE , StandardWatchEventKinds.ENTRY_MODIFY , StandardWatchEventKinds.ENTRY_DELETE) ; while(true){//获取下一个文件变化事件WatchKey key = watchService.take(); //①for (WatchEvent<?> event : key.pollEvents()){System.out.println(event.context() + "文件发生了" + event.kind() + "事件!" ) ; }//重设 WatchKeyboolean valid = key.reset(); // 如果重设失败 退出监听if (!valid){break;}}}
}

代码说明:

在①处试图获取下一个WatchKey,如果没有发生就等待,因此C盘路径下的每次文件的变化都会被该程序监听到

访问文件属性

在未使用NIO工具类的情况下,以前的File类可以访问一些简单的文件属性,比如文件大小、修改时间、文件是否隐藏、是文件还是目录等。如果程序需要获取或修改更多的文件属性,必须利用运行所在的平台的特定代码来实现。

NIO.2在java.nio.file.attribute包下提供了大量的工具类,通过这些工具类,开发者可以非常简单的读取、修改文件属性。这些工具类主要分为两类:

  • XxxAttributeView:代表某种文件属性的视图

  • XxxAttributes:代表某种文件属性的集合,一般通过XxxAttributeView获取XxxAttributes

FileAttributeView是其他XxxAttributeView的父接口,以下是一些常用的FileAttributeView的实现类

AclFileAttributeView:通过AclFileAttributeView,可以为特定文件设置ACL(Access Control List)及文件所有者属性。其中getAcl()方法返回List对象,代表了该文件的权限集合;通过setAcl(List)方法可以修改该文件的ACL

BasicFileAttributeView:它可以获取或修改文件的基本属性,包括文件的最后修改时间、最后访问时间、创建时间、大小、是否为目录、是否为符号链接等。其中readAttributes()方法返回一个BasicFileAttributes对象,对文件夹基本属性的修改是通过BasicFileAttributes对象来完成的

DosFileAttributeView:它主要用于获取或修改文件的DOS相关属性,比如文件是否只读、是否隐藏、是否为系统文件、是否为存档文件等。其中readAttributes()方法返回一个DosFileAttributes对象,对这些属性的修改是通过DosFileAttributes对象来完成的

FileOwnerAttributeView:它主要用于获取或修改文件的所有者。其中getOwner()方法返回一个UserPrincipal对象来代表文件所有者,也可以调用setOwner(UserPrincipal owner)方法来改变文件的所有者

PosixFileAttributeView:它主要用于获取或修改POSIX(Portable Operating System Interface of INIX)属性,其中readAttributes()方法返回一个PosixFileAttributes对象,该对象可用于获取或修改文件的所有者、组所有者、访问权限信息(可以看作是UNIX中chmod所作的事情)。注意:这个View只在Unix、Linux等系统上有用

UserDefinedFileAttributeAttributeView:它可以让开发者为文件设置一些自定义属性


  1. 所有被Selector注册的通道,只能是继承了SelectableChannel类的子类 ↩︎

  2. 底层操作系统的通道一般都是全双工的,可以异步双向传输,所以全双工的Channel比流能更好的映射底层操作系统的API ↩︎

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

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

相关文章

什么样的无线麦克风好?一文看懂哪种麦克风降噪效果好

作为视频创作者&#xff0c;拍摄视频除了要注意拍摄的画质外&#xff0c;声音的录制也很重要。声音录制的清晰度也会直接影响整个作品的整体水平&#xff0c;要想录的声音清晰&#xff0c;有专业级录制效果&#xff0c;必须选好麦克风&#xff0c;而无线领夹麦克风&#xff0c;…

【工具分享】Annabelle勒索病毒解密工具

前言 Annabelle勒索病毒灵感来自恐怖电影系列 Annabelle。除了文件加密功能外&#xff0c;Annabelle 勒索软件还会试图禁用防火墙&#xff0c;强制停止一系列正在运行程序&#xff0c;通过连接的 USB 驱动器进行传播。 特征 勒索内容&#xff1a; Annabelle 使用 AES256 CBC 加…

【Linux】线程同步和生产者-消费者模型

目录 一. 线程同步1. 条件变量2. 条件变量接口条件变量的创建及初始化条件变量的销毁条件变量等待条件变量唤醒 3. 条件变量同步解决抢占问题 二. 生产者-消费者模型1. 什么是生产者-消费者模型2. 为什么要使用生产者-消费者模型3. 生产者-消费者模型特点4. 基于阻塞队列实现生…

技术前沿:三品PLM系统引领工程变更管理新趋势

引言 在当今快速变化的制造行业&#xff0c;产品生命周期管理&#xff08;PLM&#xff09;系统已成为企业不可或缺的工具之一。PLM系统不仅帮助企业优化产品开发流程&#xff0c;还对工程变更管理&#xff08;ECM&#xff09;起着至关重要的作用。本文将探讨PLM系统在工程变更…

解决ssh报错,.ssh/id_rsa: No such file or directory Permission denied (publickey)

拉取依赖或者代码时说没有权限 首先我们可以看到的是这个报错但是我们的远程确实配置ssh密钥 首先我们可以看到的是这个报错 但是我们的远程确实配置ssh密钥 我们可以在我们项目路径下添加一下我们的私钥如&#xff1a; 首先确定我们ssh是正常启动的eval $(ssh-agent)我们可以…

AC/DC电源模块:提供高质量的电力转换解决方案

BOSHIDA AC/DC电源模块&#xff1a;提供高质量的电力转换解决方案 AC/DC电源模块是一种电力转换器件&#xff0c;可以将交流电转换为直流电。它通常用于各种电子设备和系统中&#xff0c;提供高质量的电力转换解决方案。 AC/DC电源模块具有许多优点。首先&#xff0c;它能够提…

让大模型变得更聪明:人工智能的未来发展之路

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

以JVM新特性看Java的进化之路:从Loom到Amber的技术篇章

引言&#xff1a; JVM的最新特性通过在效率、功能和易用性方面的创新&#xff0c;对Java的未来发展产生了深远的影响。以下是几个关键特性如何塑造了Java的未来&#xff1a; 正文&#xff1a; 轻量级并发 - 项目Loom&#xff1a; 项目Loom通过引入虚拟线程&#xff08;也被称为…

江苏职称申报大揭秘:你所不知道的那些细节

大家好&#xff01;今天我将带大家深入探索江苏职称申报的一些你可能从未关注过的细节。对于在江苏工作的工程类小伙伴们来说&#xff0c;这些信息或许能助你一臂之力&#xff0c;让你在职称申报的道路上更加顺畅。 我们要明确的是&#xff0c;江苏省的工程类职称申报主要有三种…

每日一题——只需一行Python秒杀:PAT乙级1009 说反话!但不能故步自封!(举一反三+思想解读+逐步优化)

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 目录 我的写法 各部分功能分析&#xff1a; 综合时间复杂度 综合空间复杂度 总结 思路…

读人工智能时代与人类未来笔记15_改变人类经验

1. 认识世界的方式 1.1. 信仰 1.2. 理性 1.2.1. 理性不仅革新了科学&#xff0c;也改变了我们的社会生活、艺术和信仰 1.2.2. 在其浸染之下&#xff0c;封建等级制度瓦解了&#xff0c;而民主&#xff0c;即理性的人应该自治的理念崛起了 1.3. 人工智能 1.3.1. 这种转变将…

大数据开发面试题【Kafka篇】

83、介绍下Kafka&#xff0c;Kafka的作用?Kafka的组件?适用场景? kafka是一个高吞吐量、可扩展的分布式消息传递系统&#xff0c;在处理实时流式数据&#xff0c;并能够保证持久性和容错性 可用于数据管道、流分析和数据继承和关键任务应用&#xff08;发布/订阅模式&#…

Vue3+Ant design 实现Select下拉框一键全选/清空

最近在做后台管理系统项目的时候&#xff0c;产品增加了一个让人非常苦恼的需求&#xff0c;让在Select选择器中添加一键全选和清空的功能&#xff0c;刚开始听到的时候真是很懵&#xff0c;他又不让在外部增加按钮&#xff0c;其实如果说在外部增加按钮实现全选或者清空的话&a…

3、python安装-linux系统下

安装前置依赖软件&#xff0c;安装完成后&#xff0c;打开官网&#xff0c;下载linux系统下的python安装包&#xff1a; 选择最新的版本 点击最新版本&#xff0c;进入版本对应的界面&#xff0c; 选择第一个进行源码的编译&#xff0c;右键选择复制连接地址&#xff0c; 回到终…

HTML+CSS+JS(web前端大作业)~致敬鸟山明简略版

HTMLCSSJS【动漫网站】网页设计期末课程大作业 web前端开发技术 web课程设计 文章目录 一、网站题目 二、网站描述 三、网站介绍 四、网站效果 五、 网站代码 文章目录 一、 网站题目 动漫网站-鸟山明-龙珠超 二、 网站描述 页面分为页头、菜单导航栏&#xff08;最好可下拉&…

CDC 数据实时同步入湖的技术、架构和方案(截至2024年5月的现状调研)

近期&#xff0c;对 “实时摄取 CDC 数据同步到数据湖” 这一技术主题作了一系列深入的研究和验证&#xff0c;目前这部分工作已经告一段落&#xff0c;本文把截止目前&#xff08;2024年5月&#xff09;的研究结果和重要结论做一下梳理和汇总。为了能给出针对性的技术方案&…

ESP32-C6接入巴法云,Arduino方式

ESP32-C6接入巴法云&#xff0c;Arduino方式 第一、ESP32-C6开发环境搭建第一步&#xff1a;安装arduino IDE 软件第二步&#xff1a;安装esp32库第三&#xff1a;arduino 软件设置 第二&#xff1a;简单AP配网程序第一步&#xff1a;程序下载第二步&#xff1a;程序使用第三步…

电脑微信群发 500 1000人以上怎么群发,微信营销群发助手软件,本人亲测,增加十倍业绩!!!

今天给大家推荐一款我们目前在使用的电脑群发工具掘金小蜜&#xff0c;不仅可以无限多开&#xff0c;方便你同时管理多个账号&#xff0c;群发功能更是十分强大&#xff0c;轻松释放你的双手。 掘金小蜜&#xff08;只支持Win7及以上操作系统&#xff0c;没有推Mac版和手机客户…

[码蹄集新手训练营]MT1016-MT1020

目录 题号MT1016 宽度与对齐MT1017 左右对齐MT1018 输入宽度MT1020 %s格式符 题号 MT1016 宽度与对齐 #include<stdio.h> int main() { printf("%-5d %5d\n%-5d %5d\n%-5d %5d",455,455,-123,-123,987654,987654);return 0; }MT1017 左右对齐 #include<s…

Mac | macOs系统安装Monuty解决外接u盘ntfs读写问题

问题 mac电脑的macOs系统无法将文件读写入外接u盘或硬盘中&#xff1b; 解决方案 安装Monuty 官网&#xff1a;mounty官网 下载软件 安装其他配置 macbook:~ uwe$ brew install --cask macfuse macbook:~ uwe$ brew install gromgit/fuse/ntfs-3g-mac macbook:~ uwe$ brew…