跳至主要内容

Java NIO File API 使用总结 2

Welcome file

简介

在之前的文章中总结了NIO的Path接口和Files工具类的使用,本文继续总结一些其他的新功能和概念

NIO的基础概念和基本使用

不同于传统的Java File I/O的Stream流的概念,NIO基于Buffer和Channel重新设计了一套API。Buffer的API相对来说比较难用,不过具有零拷贝的功能,可以大大提高文件传输速度。

创建和写入文件

public static void createAndWriteFile(){
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    byteBuffer.put("HelloWorld".getBytes(StandardCharsets.UTF_8));
    byteBuffer.flip();
    try ( FileChannel fileChannel = FileChannel.open(Path.of("/tmp/test.txt"),
            StandardOpenOption.CREATE,StandardOpenOption.WRITE) ){    
        fileChannel.write(byteBuffer);
    } catch (IOException e) {
        e.printStackTrace();
    }
    System.out.println("write success.");
}

可以看到,NIO写入文件前,需要先创建ByteBuffer对象,之后再往里面写入数据。写入之后必须调用flip方法重新设置position和limit,这也是Buffer中的概念。

之后再创建一个Channel,往Channel中写入数据。

读取文件

public static void readFile(){
    Charset utf8 = StandardCharsets.UTF_8;
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    try ( FileChannel fileChannel = FileChannel.open(Path.of("/tmp/test.txt"),
            StandardOpenOption.READ) ){
        fileChannel.read(byteBuffer);
        byteBuffer.flip();
    } catch (IOException e) {
        e.printStackTrace();
    }
    CharBuffer charBuffer = utf8.decode(byteBuffer);
    System.out.print("read result:");
    System.out.println(new String(charBuffer.array()));
}

可以看到读取文件也是类似,先创建ByteBuffer对象再读取数据,读取完之后再调用flip方法才能decode。从这里可以看到ByteBuffer确实比较难用,一不小心忘记调用flip,就会出现各种意想不到的BUG。

File NIO的扩展

Java在1.7版本对NIO再次进行了扩展,使Java对文件系统的处理更接近底层,包括监听目录的变化等,通常也被叫做NIO2。

FileSystemProvider,FileSystem和FileStore

这三个类提供了对特定文件系统更底层的支持:

public static void basic() throws IOException {
    List<FileSystemProvider> providers = FileSystemProvider.installedProviders();
    System.out.println(providers);
    FileSystem defaultFileSystem = FileSystems.getDefault();
    System.out.println(defaultFileSystem);
    Iterable<Path> rootDirectories = defaultFileSystem.getRootDirectories();
    System.out.println(rootDirectories.iterator().next());
    Iterable<FileStore> fileStores = defaultFileSystem.getFileStores();
    FileStore fileStore = fileStores.iterator().next();
    System.out.println(fileStore.name());
    System.out.println(fileStore.type());
  //        defaultFileSystem.provider().createDirectory(Path.of("/tmp/testDir"));
}
  • FileSystemProvider.installedProviders()可以查看支持的文件系统
  • FileSystem defaultFileSystem = FileSystems.getDefault()拿到默认的文件系统

JarFileSystem

FileSystem中,还提供了对Jar文件的支持,使得在Jar文件中的操作更加容易:

public static void jarFileSystem() throws IOException {
    URI jarUri = URI.create("jar:file:///tmp/test.jar");

    Map<String, String> options = new HashMap<>();
    // Create
    options.put("create", "true");
    try( FileSystem jarFs = FileSystems.newFileSystem(jarUri, options);){
        // Write file into jar
        Path dir = jarFs.getPath("files");
        jarFs.provider().createDirectory(dir);

        Path textTxt = jarFs.getPath("files/text.txt");
        Files.writeString(textTxt, "HelloWorld Jar File", StandardCharsets.UTF_8,
                StandardOpenOption.CREATE, StandardOpenOption.WRITE);

        // Read file in jar
        Path path = jarFs.getPath("files/text.txt");
        String result = Files.readString(path);
        System.out.println(result);
    }

}

常见的创建、写入文件和读取文件示例。

文件系统事件监听

在1.7之前的Java中,如果要知道目录或文件是不是有改动,需要不断的主动查询这些目录或文件的状态。

现在NIO2对文件系统事件的监听也有了支持:

public static void watchEvent() throws IOException, InterruptedException {
    Path watchPath = Path.of("/tmp");
    try (FileSystem fileSystem = FileSystems.getDefault();
         WatchService watchService = fileSystem.newWatchService();) {

        // only watch current dir , not include sub-dir
        WatchKey key = watchPath.register(watchService,
                StandardWatchEventKinds.ENTRY_CREATE,
                StandardWatchEventKinds.ENTRY_MODIFY,
                StandardWatchEventKinds.ENTRY_DELETE,
                StandardWatchEventKinds.OVERFLOW
        );

        while (key.isValid()) {
            WatchKey watchKey = watchService.take();
            List<WatchEvent<?>> watchEvents = watchKey.pollEvents();
            for (WatchEvent<?> event : watchEvents) {
                WatchEvent.Kind<?> kind = event.kind();
                Path context = (Path) event.context();
                if (kind == StandardWatchEventKinds.OVERFLOW) {
                    System.out.println("event too much");
                } else if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
                    System.out.println("file created , path=" + context + ",type=" + Files.probeContentType(context));
                } else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
                    System.out.println("file modified , path=" + context + ",type=" + Files.probeContentType(context));
                } else if (kind == StandardWatchEventKinds.ENTRY_DELETE) {
                    System.out.println("file deleted , path=" + context + ",type=" + Files.probeContentType(context));
                }
                // reset or never get event
                watchKey.reset();
            }
        }
        System.out.println("watch key is invalid");

    }
}

现在在/tmp下对文件的操作都会触发Event,对需要的Event进行不同的操作。

不过有几个需要注意的点:

  • WatchService只会对当前注册的目录进行监听,不会对子目录监听,如果需要监听子目录,需要把子目录都注册一遍
  • StandardWatchEventKinds.OVERFLOW的Event表示事件丢失等情况,通常是由于事件太多,此时没有什么特别的好办法,只能日志记录一下
  • 使用完WatchKey之后,需要调用watchKey.reset()进行重置
  • 当目录已经不存在或者被监听取消之类的情况时,key.isValid()方法就会返回False

总结

示例代码可以在Github上找到。

参考资料

[1]Oracle的Java Doc文档:https://docs.oracle.com/javase/8/docs/api/index.html

评论

此博客中的热门博文

国密SM2签名封装成PKCS7格式

在国内做金融行业,难免会有被强制使用国密算法的情况,而且一般还会指定必须使用硬件加密机之类的设备,所以我也稍微的研究了一下国密算法,使用软算法签名并封装 PKCS7 格式(文档中的一个交互)。 以下是基于 Bouncy Castle 的示例,密钥对的生成可以参考 Bouncy Castle 中 test 包下 SM2 相关代码 public static String sign ( ) throws Exception { //加载公钥 byte [ ] plainText = "hello, world" . getBytes ( ) ; FileInputStream input = new FileInputStream ( "F:\\certificate\\public.cer" ) ; CertificateFactory certificateFactory = new CertificateFactory ( ) ; X509Certificate certificate = ( X509Certificate ) certificateFactory . engineGenerateCertificate ( input ) ; input . close ( ) ; //加载私钥,private为换成实际的私钥 PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec ( "private" . getBytes ( ) ) ; //SM2算法实际上为ECC算法,并指定了一些参数值,所以这里的参数是EC KeyFactory factory = KeyFactory . getInstance ( "EC" , "BC" ) ; PrivateKey privateKey = factory . generatePrivate ( spec ) ; //以下为签名并封装成PKCS7格式 byte [ ] signedMessag

Spring Boot Actuator 2 示例

Welcome file 简介 Spring Boot Actuator为应用程序提供了各种开箱即用的运维特性,可以与应用方便的交互和监控。 使用环境:Java 11 和 Spring Boot 2.4.3.RELEASE 集成Spring Boot Actuator 在Spring Boot中集成Spring Boot Actuator与集成其他的框架类似,在 pom.xml 里引入相关的starter就可以: < dependency > < groupId > org.springframework.boot </ groupId > < artifactId > spring-boot-starter-actuator </ artifactId > </ dependency > < dependency > < groupId > org.springframework.boot </ groupId > < artifactId > spring-boot-starter-web </ artifactId > </ dependency > 由于大部分的使用场景还是web,所以这里也用Spring MVC做示例。 配置好 pom.xml 后,默认actuator仅暴露一些基本功能,实际使用中,根据需求暴露对应功能。为了简便测试,这里在 application.yml 中配置暴露全部功能: management : endpoints : web : exposure : include : "*" endpoint : health : enabled : true show-details : always probes : enabled : true shutdown : enabled : true metr

NextCloud数据目录迁移

最近服务器的环境坏了,所以迁移了NextCloud的数据目录。不过在迁移过程中有点小问题。 环境: Ubuntu 18.04 Docker 19.03.7 1.NextCloud页面不正常,Docker日志显示XX目录permission denied 参考了 这里 的做法,不过是把  /var/www/html/   整个目录的权限都修改为  chown -R www-data:www-data ,之后就不再报权限问题了。 2.数据库配置修改 因为NextCloud是在初始化时填的数据库连接信息,所以直接迁移数据目录的情况下,会导致应用连不到新的数据库环境。此时可以找到数据目录下的  config/config.php 文件,直接修改数据库连接配置。