Java NIO

NIO:New IO

Java新IO概述

新IO采用内存映射文件的方式来处理输入/输出,新IO文件或文件的一段区域映射到内存中,这样就可以访问内存一样来访问文件了(这种方式模拟了操作系统上的虚拟内存的概念),通过这种方式来进行输入/输出比传统的输入/输出要快得多

Java中NIO相关的包如下:

  • java.nio包:主要提供了一些和Buffer相关的类

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

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

  • java.nio.channels.spi包:主要包含提供Channel服务的类

  • java.nio.charset.spi包:主要包含提供字符集服务的相关类

Channel(通道)和Buffer(缓冲)是新IO中的两个核心对象,Channel是对传统输入/输出系统中的模拟,在新IO系统中所有数据都需要通过通道传输;Channel与传统的InputStream、OutputStream最大的区别在于它提供了一个map方法,通过该map方法可以直接将“一块数据”映射到内存中。如果说传统的输入/输出系统是面向流的处理,而新IO则是面向块的处理

Buffer可以被理解成一个容器,它的本质是一个数组,发送到Channel中的所有对象都必须首先放到Buffer中,而从Channel中读取的数据也必须先读到Buffer中。此处的Buffer有点类似于前面我们介绍的“竹筒”,但该Buffer既可以像前面那样一次、一次去Channel中取水,也允许使用Channel直接将文件的某块数据映射成Buffer

除了Channel和Buffer之外,新IO还提供了用于将UNICODE字符串映射成字节序列以及逆映射操作的Charset类,还提供了用于支持非阻塞式输入/输出的Selector类

使用Buffer

Buffer类没有提供构造器,通过使用如下方法来得到一个Buffer对象:

  • static XxxBuffer allocate(int capacity):创建一个容量为capacity的XxxBuffer对象

使用较多的是ByteBuffer和CharBuffer。其中ByteBuffer类还有一个子类:MappedByteBuffer,它用于表示Channel将磁盘文件的部分或全部内容映射到内存中后得到的结果,通常MappedByteBuffer对象由Channel的map()方法返回

Buffer三个重要概念:容量(capacity)、界限(limit)、位置(position)

  • 容量(capacity):缓冲区的容量(capacity)表示该Buffer的最大数据容量,即最多可以存储多少数据。缓冲区的容量不可能为负值,创建后不能改变
    界限(limit)

  • 界限(limit):第一个不应该被读写或者写入的缓冲区位置索引。也就是说,位于limit后的数据既不可被读,也不可被写

  • 位置(position):用于指明下一个可以被读出或者写入的缓冲区位置索引(类似于IO流中的记录指针)。当使用Buffer从Channel中读取数据时,position的值恰好等于已经读到了多少数据。当刚刚新建一个Buffer对象时,其position为0;如果从Channel中读取了2个数据到该Buffer中,则position为2,指向Buffer中的第三个(第1个位置的索引为0)位置

  • 标记(mark):Buffer里还支持一个可选的标记(mark,类似于传统IO流中的mark),Buffer允许直接将position定位到该mark处。

0 <= mark <= position <= limit <= capacity

Buffer的主要作用就是装入数据,然后输出数据,开始时Buffer的position为0,limit为capacity,程序可通过put()方法向Buffer中放入一些数据(或从channel获取数据),每放入一些数据,position向后移动一些位置

当Buffer装入数据结束后,调用filp()方法,该方法将limit设置为position所在位置,将position设置为0。这样使得从Buffer中读取数据总是从0开始。读完所有装入的数据即结束,也就是说,Buffer调用filp后,Buffer为输出数据做好了准备

当Buffer输出数据结束后,调用clear方法。将position置为0,将limit置为capacity,这样为再次向Buffer中装载数据做好准备

Buffer抽象类常用方法:

  • int capacity():返回Buffer的capacity大小

  • boolean hasRemaining():判断当前位置(position)和界限(limit)之间是否有元素可供处理

  • int limit():返回Buffer的界限(limit)的位置

  • Buffer limit(int newLimit):重新设置界限(limit)的值,并返回一个具有新的limit的缓冲区对象

  • int position():返回Buffer的position值

  • Buffer position(new position):设置Buffer的position值,并返回position被修改后的Buffer对象

  • int remaining():返回当前位置和界限(limit)之间的元素个数

  • Buffer reset():将位置(position)转到mark所在的位置

  • Buffer rewind():将位置(position)设置为0,取消设置的mark

put()和get()方法,用于向Buffer中放入数据和从Buffer中取出数据。当使用put()和get()。Buffer既支持对单个数据的访问,也支持对批量数据的访问(以数组作为参数)

当使用put()和get()来访问Buffer中的数据时,分为相对和绝对两种:

  • 相对(Relative):从Buffer当前位置读取或写入数据,然后将位置(position)的值按处理元素个数增加

  • 绝对(Absolute):直接根据索引来向Buffer中读取或写入数据,使用绝对方式来访问Buffer里的数据,并不会影响position的值

import java.nio.*;

public class BufferTest
{
    public static void main(String[] args)
    {
        // 创建Buffer
        CharBuffer buff = CharBuffer.allocate(8);    // ①
        System.out.println("capacity: "    + buff.capacity());
        System.out.println("limit: " + buff.limit());
        System.out.println("position: " + buff.position());
        // 放入元素
        buff.put('a');
        buff.put('b');
        buff.put('c');      // ②
        System.out.println("加入三个元素后,position = " + buff.position());
        // 调用flip()方法
        buff.flip();      // ③
        System.out.println("执行flip()后,limit = " + buff.limit());
        System.out.println("position = " + buff.position());
        // 取出第一个元素
        System.out.println("第一个元素(position=0):" + buff.get());  // ④
        System.out.println("取出一个元素后,position = " + buff.position());
        // 调用clear方法
        buff.clear();     // ⑤
        System.out.println("执行clear()后,limit = " + buff.limit());
        System.out.println("执行clear()后,position = " + buff.position());
        System.out.println("执行clear()后,缓冲区内容并没有被清除:" + "第三个元素为:" +  buff.get(2));    // ⑥
        System.out.println("执行绝对读取后,position = " + buff.position());
    }
}

运行结果:

capacity: 8
limit: 8
position: 0
加入三个元素后,position = 3
执行flip()后,limit = 3
position = 0
第一个元素(position=0):a
取出一个元素后,position = 1
执行clear()后,limit = 8
执行clear()后,position = 0
执行clear()后,缓冲区内容并没有被清除:第三个元素为:c
执行绝对读取后,position = 0

代码①:新分配的CharBuffer对象:

代码②:向Buffer中放入3个对象后

代码③:执行Buffer的flip()方法后

代码⑤:执行clear()后的Buffer

使用Channel

Channel类似于传统的流对象,但与传统的流对象有两个主要区别:

  • Channel可以直接将指定文件的部分或全部直接映射成Buffer

  • 程序不能直接访问Channel中的数据,包括读、写入都不行,Channel只能与Buffer进行交互。也就是说,如果要从Channel中取得数据,必须先用Buffer从Channel中取出一些数据,然后让程序从Buffer中取出这些数据;如果要将程序中的数据写入Channel,一样先让程序将谁放入Buffer中,程序再将Buffer里的数据写入Channel中

所有的Channel都不应该通过构造器来直接创建,而是通过传统的节点InputStream、OutputStream的getChannel()方法来返回对应的Channel,不同的节点流获得的Channle不一样

Channel中最常用的三类方法是map()、read()和write()

map()方法用于将Channel对应的部分或全部数据映射成ByteBuffer;而read()或write()方法都有一系列重载形式,这些方法用于从Buffer中读取数据或向Buffer里写入数据

map方法的方法签名为:MappedByteBuffer map(FileChannel.MapMode mode, long position, long size),第一个参数执行映射时的模式,分别有只读,读写模式,而第二个,第三个参数用于控制将Channel的哪些数据映射成ByteBuffer

以下是直接将FileChannel的全部数据映射成ByteBuffer的效果的代码。使用FileInputStream、FileOutputStream来获取FileChannel。代码①直接将指定Channel中的全部数据映射成ByteBuffer,代码②直接将整个ByteBuffer的全部数据写入一个输出FileChannel中,完成文件复制。使用Charset类和CharsetDecoder类将ByteBuffer转换成CharBuffer

import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;

public class FileChannelTest
{
    public static void main(String[] args)
    {
        File f = new File("FileChannelTest.java");
        try(
            // 创建FileInputStream,以该文件输入流创建FileChannel
            FileChannel inChannel = new FileInputStream(f).getChannel();
            // 以文件输出流创建FileBuffer,用以控制输出
            FileChannel outChannel = new FileOutputStream("a.txt").getChannel())
        {
            // 将FileChannel里的全部数据映射成ByteBuffer
            MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, f.length());   // ①
            // 使用GBK的字符集来创建解码器
            Charset charset = Charset.forName("GBK");
            // 直接将buffer里的数据全部输出
            outChannel.write(buffer);     // ②
            // 再次调用buffer的clear()方法,复原limit、position的位置
            buffer.clear();
            // 创建解码器(CharsetDecoder)对象
            CharsetDecoder decoder = charset.newDecoder();
            // 使用解码器将ByteBuffer转换成CharBuffer
            CharBuffer charBuffer =  decoder.decode(buffer);
            // CharBuffer的toString方法可以获取对应的字符串
            System.out.println(charBuffer);
        }
        catch (IOException ex)
        {
            ex.printStackTrace();
        }
    }
}

RandomAccessFile中也包含getChannel()方法,返回的FileChannel()读写类型取决于RandomAccessFile打开文件的模式。以下代码将对a.txt文件的内容进行复制,追加到该文件后面:

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

public class RandomFileChannelTest
{
    public static void main(String[] args) throws IOException
    {
        File f = new File("a.txt");
        try(
            // 创建一个RandomAccessFile对象
            RandomAccessFile raf = new RandomAccessFile(f, "rw");
            // 获取RandomAccessFile对应的Channel
            FileChannel randomChannel = raf.getChannel())
        {
            // 将Channel中所有数据映射成ByteBuffer
            ByteBuffer buffer = randomChannel.map(FileChannel.MapMode.READ_ONLY, 0, f.length());
            // 把Channel的记录指针移动到最后
            randomChannel.position(f.length());
            // 将buffer中所有数据输出
            randomChannel.write(buffer);
        }
    }
}

randomChannel.position(f.length()); 代码将Channel的记录指针移动到该Channel的最后,从而可以让程序将指定ByteBuffer的数据追加到该Channel后面。每次运行上面程序,都会把a.txt文件的内容复制一份,并将全部内容追加到该文件的后面

使用map()方法一次将所有的文件内容映射到内存中引起性能下降,可以使用Channel和Buffer传统的“用竹筒多次重复取水”的方式:

import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;

public class ReadFile
{
    public static void main(String[] args)
        throws IOException
    {
        try(
            // 创建文件输入流
            FileInputStream fis = new FileInputStream("ReadFile.java");
            // 创建一个FileChannel
            FileChannel fcin = fis.getChannel())
        {
            // 定义一个ByteBuffer对象,用于重复取水
            ByteBuffer bbuff = ByteBuffer.allocate(256);
            // 将FileChannel中数据放入ByteBuffer中
            while( fcin.read(bbuff) != -1 )
            {
                // 锁定Buffer的空白区
                bbuff.flip();
                // 创建Charset对象
                Charset charset = Charset.forName("GBK");
                // 创建解码器(CharsetDecoder)对象
                CharsetDecoder decoder = charset.newDecoder();
                // 将ByteBuffer的内容转码
                CharBuffer cbuff = decoder.decode(bbuff);
                System.out.print(cbuff);
                // 将Buffer初始化,为下一次读取数据做准备
                bbuff.clear();
            }
        }
    }
}

Buffer提供了flip()和clear()两个方法,每次读取数后调用flip()方法将没有数据的区域“封印”起来,避免程序从Buffer中取出null值;数据取出后立即调用clear()方法将Buffer的position设0,为下一次读取数据做准备

字符集和Charset

编码Encode:把明文的字符序列换成计算机理解的二进制序列
解码Decode:把二进制序列转换成明文字符串

Java默认使用Unicode字符集,但很多操作系统并不使用Unicode字符集,那么当从系统中读取数据到Java程序中时,就可能出现乱码等问题

JDK1.4提供了Charset来处理字节序列和字符序列(字符串)之间的转换关系,该类包含了用于创建编码和解码的的方法,还提供了获取所有Charset所支持的字符集的方法,Charset类是不可变的

availableCharset()的静态方法用于获取当前JDK所支持的所有字符集

import java.nio.charset.*;
import java.util.*;

public class CharsetTest
{
    public static void main(String[] args)
    {
        // 获取Java支持的全部字符集
        SortedMap  map = Charset.availableCharsets();
        for (String alias : map.keySet())
        {
            // 输出字符集的别名和对应的Charset对象
            System.out.println(alias + "----->"
                + map.get(alias));
        }
    }
}

常用字符串别名:

  • GBK:简体中文字符串

  • BIG5:繁体中文字符串

  • ISO-8859-1:ISO拉丁字母表No.1

  • UTF-8:8位UCS转换格式

  • UTF-16BE:16位的UCS转换格式,Big-endian(最低地址存放高位字节)字节顺序

  • UTF-16LE:16位的UCS转换格式,Little-endian(最高地址存放低位字节)字节顺序

  • UTF-16:16位的UCS转换格式,字节顺序由可选的字节顺序标记来标识

调用Charset的forName()方法创建字符串别名对应的Charset对象,forName()方法的参数就是相应字符集的别名:
Charset cs = Charset.forName("ISO-8859-1");

获得Charset对象之后,通过该对象的newDecoder()、newEncoder()方法分别返回CharsetDecoder和CharsetEncoder对象,代表该Charset的解码器和编码器。调用CharsetDecoder的decode()方法可以将ByteBuffer(字节序列)转换成CharBuffer(字符序列),调用CharsetEncoder的encode()方法可以将CharBuffer或String(字符序列)转换成ByteBuffer(字节序列)

import java.nio.*;
import java.nio.charset.*;

public class CharsetTransform
{
    public static void main(String[] args)
        throws Exception
    {
        // 创建简体中文对应的Charset
        Charset cn = Charset.forName("GBK");
        // 获取cn对象对应的编码器和解码器
        CharsetEncoder cnEncoder = cn.newEncoder();
        CharsetDecoder cnDecoder = cn.newDecoder();
        // 创建一个CharBuffer对象
        CharBuffer cbuff = CharBuffer.allocate(8);
        cbuff.put('内');
        cbuff.put('马');
        cbuff.put('尔');
        cbuff.flip();
        // 将CharBuffer中的字符序列转换成字节序列
        ByteBuffer bbuff = cnEncoder.encode(cbuff);
        // 循环访问ByteBuffer中的每个字节
        for (int i = 0; i < bbuff.capacity() ; i++)
        {
            System.out.print(bbuff.get(i) + " ");
        }
        // 将ByteBuffer的数据解码成字符序列
        System.out.println("\n" + cnDecoder.decode(bbuff));
    }
}

Charset类提供了如下三个方法:

  • CharBuffer decode(ByteBuffer bb):将ByteBuffer中的字节序列转换成字符序列的便捷方法

  • ByteBuffer encode(CharBuffer cb):将CharBuffer中的字节序列转换成字符序列的便捷方法

  • ByteBuffer encode(String str):将String中的字节序列转换成字符序列的便捷方法

获取Charset对象后,如果仅仅需要进行简单的编码、解码操作,实则无须创建CharsetDecoder和CharsetEncoder对象,直接调用Charset的encode()和decode()方法进行编码、解码即可

文件锁

如果多个运行的程序需要并发修改同一个文件时,程序之间需要某种机制来进行通信,使用文件锁可以有效阻止多个进程并发修改同一个文件

Java提供了FileLock类支持文件锁功能,在FileChannel中提供的lock()/tryLock()方法可以获得文件锁FileLock对象,从而锁定文件

lock()方法和trylock()方法的区别是:当lock()试图锁定某个文件时,如果无法得到文件锁,程序将一直阻塞,而tryLock()方法时尝试锁定文件,它将直接返回而不是阻塞,如果获得了文件锁,该方法则返回该文件锁,否则将返回null

  • lock(long position, long size, boolean shared):对文件从position开始,长度为size的内容加锁,该方法是阻塞式的

  • tryLock(long position, long size, boolean shared):非阻塞式的加锁方法

当shared为true时,表明该锁是一个共享锁,它将允许多个进程来读取文件,但不允许其他进程将其设为排他锁;当shared为false的时,表明这是一个排他锁,它将锁住对该文件的读写

通过调用FileLock的isShared来判断获得的锁是否为共享锁

直接使用lock()或tryLock()方法获取的文件锁是排他锁

处理完成之后,调用FileLock的release()方法释放文件锁

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

public class FileLockTest
{
    public static void main(String[] args)
        throws Exception
    {

        try(
            // 使用FileOutputStream获取FileChannel
            FileChannel channel = new FileOutputStream("a.txt").getChannel())
        {
            // 使用非阻塞式方式对指定文件加锁
            FileLock lock = channel.tryLock();
            // 程序暂停10s
            Thread.sleep(10000);
            // 释放锁
            lock.release();
        }
    }
}

Java7的NIO2

NIO的改进主要包括如下方面:

  • 提供了全面的文件IO和文件系统访问支持

  • 基于异步Channel的IO

Path、Paths和File核心API

Paths提供了get(String first, String... more)方法来获取Path对象,Paths会将给定的多个字符串连缀成路径,如Paths.get("D:", "java","code")将返回D:javacode路径;getNameCount()返回Path路径所包含的路径名

import java.io.*;
import java.net.*;
import java.nio.file.*;

public class PathTest
{
    public static void main(String[] args)
        throws Exception
    {
        // 以当前路径来创建Path对象
        Path path = Paths.get(".");
        System.out.println("path里包含的路径数量:" + path.getNameCount());
        System.out.println("path的根路径:" + path.getRoot());
        // 获取path对应的绝对路径
        Path absolutePath = path.toAbsolutePath();
        System.out.println(absolutePath);
        // 获取绝对路径的根路径
        System.out.println("absolutePath的根路径:" + absolutePath.getRoot());
        // 获取绝对路径所包含的路径数量
        System.out.println("absolutePath里包含的路径数量:" + absolutePath.getNameCount());
        System.out.println(absolutePath.getName(3));
        // 以多个String来构建Path对象
        Path path2 = Paths.get("D:", "java","code");
        System.out.println(path2);
    }
}

运行结果:

path里包含的路径数量:1
path的根路径:null
D:\Development\eclipse\workspace\CrazyJava\.
absolutePath的根路径:D:\
absolutePath里包含的路径数量:5
CrazyJava
D:\java\code

以下代码示范Files工具类的用法:

import java.nio.file.*;
import java.nio.charset.*;
import java.io.*;
import java.util.*;

public class FilesTest
{
    public static void main(String[] args)
        throws Exception
    {
        // 复制文件
        Files.copy(Paths.get("FilesTest.java"), new FileOutputStream("a.txt"));
        // 判断FilesTest.java文件是否为隐藏文件
        System.out.println("FilesTest.java是否为隐藏文件:"+ Files.isHidden(Paths.get("FilesTest.java")));
        // 一次性读取FilesTest.java文件的所有行
        List lines = Files.readAllLines(Paths.get("FilesTest.java"), Charset.forName("gbk"));
        System.out.println(lines);
        // 判断指定文件的大小
        System.out.println("FilesTest.java的大小为:" + Files.size(Paths.get("FilesTest.java")));
        List poem = new ArrayList<>();
        poem.add("感时花溅泪");
        poem.add("恨别鸟惊心");
        // 直接将多个字符串内容写入指定文件中
        Files.write(Paths.get("pome.txt"), poem, Charset.forName("gbk"));
        // 使用Java 8新增的Stream API列出当前目录下所有文件和子目录
        Files.list(Paths.get(".")).forEach(path -> System.out.println(path));
        // 使用Java 8新增的Stream API读取文件内容
        Files.lines(Paths.get("FilesTest.java"), Charset.forName("gbk")).forEach(line -> System.out.println(line));
        FileStore cStore = Files.getFileStore(Paths.get("C:"));
        // 判断C盘的总空间,可用空间
        System.out.println("C:共有空间:" + cStore.getTotalSpace());
        System.out.println("C:可用空间:" + cStore.getUsableSpace());
    }
}

使用FileVisitor遍历文件和目录

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

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

  • walkFileTree(Path start, Set options, int maxDepth, FileVisitor visitor):遍历start路径下的所有文件和子目录,最多遍历maxDepth深度的文件

FileVisitor代表一个文件访问器,walkFileTree()方法会自动遍历start路径下的所有文件和子目录,遍历文件和子目录都会触发FileVisitor中相应的方法。这四个方法在下面的代码中出现。FileVisitor中定义了如下4个方法:

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

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

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

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

FileVisitResult对象,它是一个枚举类,代表访问之后的后续行为,它有如下几种后续行为:

  • CONTINUE:代表“继续访问”的后续行为

  • TERMINATE:代表“终止访问”的后续行为

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

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

以下程序使用Files工具类的walkFileTree()方法遍历g:publishcodes15目录下的所有文件和子目录,直到找到的文件以“FileVisitorTest.java”结尾,则程序停止遍历。实现了对指定目录进行搜索,直到找到指定文件为止

import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.*;

public class FileVisitorTest
{
    public static void main(String[] args)
        throws Exception
    {
        // 遍历D:\coding\Java路径\CrazyJava\codes\15目录下的所有文件和子目录
        Files.walkFileTree(Paths.get("D:", "coding", "Java路径", "CrazyJava", "codes", "15"), 
        new SimpleFileVisitor()
        {
            // 访问文件时候触发该方法
            @Override
            public 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;
            }
            // 开始访问目录时触发该方法
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException
            {
                System.out.println("正在访问:" + dir + " 路径");
                return FileVisitResult.CONTINUE;
            }
        });
    }
}

使用WatchService监控文件变化

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

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

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

  • WatcheKey poll(long timeout, TimeUnit unit):尝试等待timeout时间去获取下一个WatchKey

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

如果程序需要一直监控,则应该选择使用take()方法,如果程序只需要监控指定时间,则使用poll方法

import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.*;

public class WatchServiceTest
{
    public static void main(String[] args) throws Exception
    {
        // 获取文件系统的WatchService对象
        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()+ "事件!");
            }
            // 重设WatchKey
            boolean valid = key.reset();
            // 如果重设失败,退出监听
            if (!valid)
            {
                break;
            }
        }
    }
}

访问文件属性

Java7的NIO.2在java.nio.file.attribute包下提供了大量工具类,可以简单地读取、修改文件属性。分为如下两类:

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

  • XxxAttributes:代表某种文件属性的“集合”,程序一般通过XxxAttributeView对象获取XxxAttributes

FileAttributeView是其他XxxAttributeView的父接口

  • AclFileAttributeView: 为特定文件设置ACL(Access Control List)及文件所有者属性。getAcl()方法返回List对象,该返回值代表该文件的权限集,setAcl(List)方法修改该文件的ACL

  • BaseFileAttributeView:获取或修改文件的基本属性。readAttributes()方法返回一个BaseFileAttributeView对象,对文件夹基本属性的修改是通过BaseFileAttributeView对象完成的

  • DosFileAttributeView:获取或修改文件DOS相关属性。readAttributes()方法返回一个DosFileAttributeView对象,对文件夹属性的修改通过DosFileAttributeView对象完成的

  • FileOwnerAttributeView:获取或修改文件的所有者。getOwner()方法返回一个UserPrincipal对象来代表文件所有者;也可调用setOwner(UserPrincipal owner)方法来改变文件的所有者

  • PosixFileAttributeView:获取文件或修改POSIX(Portable Operating System Interface of INIX)属性。readAttributes()方法返回一个PosixFileAttributeView对象,该对象用于获取或修改文件的所有者、组所有者、访问权限信息。仅在UNIX、Linux等系统上有用

  • UserDefinedFileAttributeView:开发者自定义文件属性

你可能感兴趣的:(java,nio)