java学习-io-new io

对于 从jdk 1.7 开始引入新的 文件系统 称为 nio;

和之前io的不同点在于 

  1. 存在新的api java.nio.file.Path 以及新的文件系统 java.nio.file.FileSystem 
  2. 新的工具类: java.nio.file.Files 以及 java.nio.filePaths
  3. 对于 新的 Path 接口集成了 java.nio.file.Watchable提供了便利的文件变更监控API 而不需要再使用java.io.File#lastModified 来判断文件变化
  4. 新的文件读写API,对于io中之前直接使用 byte数组读取方式,提供了 HeapByteBuffer和DirectByteBuffer两种方式, 对于 Heap底层实际还是使用byte数组存储;对于Direct实际是使用的堆外内存

 

ByteBuffer 使用样例

/*
 * Copyright (c) 2020, guoxing, Co,. Ltd. All Rights Reserved
 */
package com.xingguo.nio;

import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ByteChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * ByteChannelDemo
 * {@link java.nio.channels.ByteChannel}
 * 作为 new io 中新提供的操作,实际使用了{@link java.nio.ByteBuffer} 对于 其 concrete class 包含{@link java.nio.HeapByteBuffer} 以及 {@link java.nio.DirectByteBuffer}, 其和 {@link java.io.BufferedInputStream} 的区别在于 对于{@link java.io.BufferedInputStream}由于其使用了{@code byte[]} 因此其只支持 堆内数据 等价于 {@link java.nio.HeapByteBuffer}
 * 对于 {@link java.nio.DirectByteBuffer} 实际就是也是存储到内存中的,只是并不直接存储到堆空间中
 *
 * @author guoxing
 * @date 2020/12/8 9:18 PM
 * @since
 */
public class ByteChannelDemo {
    private static final String USER_DIR = System.getProperty("user.dir");

    public static void main(String[] args) {
        byteBufferReadData();
    }

    private static void byteBufferReadData() {
        /**
         * 测试 使用 {@link Files#newByteChannel(Path, OpenOption...)} 来进行文件读取和写入操作
         * 对于{@link OpenOption}的 concrete class {@link java.nio.file.StandardOpenOption} 中的操作包含了其他语言都通用的文件io操作
         */
        // 读取 USER_DIR/README.md 文件

        try (ByteChannel byteChannel = Files.newByteChannel(Paths.get(USER_DIR, "README.md"))) {
            // 创建一个{@link HeapByteBuffer}
            /**
             * TODO : 对于当前数据读取中文存在乱码的情况; 由于中文字符UTF-8编码集字节长度不固定,因此会导致按照字节读取时出现一个完整的字符字节被中断的情况
             * 对于 当前初始化的 {@link ByteBuffer}中的相关初始化字段
             * {@link ByteBuffer#mark} = -1
             * {@link ByteBuffer#position} = 0
             * {@link ByteBuffer#capacity} = {@link ByteBuffer#allocate(int)} 中的参数
             * {@link ByteBuffer#limit} = {@link ByteBuffer#allocate(int)} 中的参数
             */
            ByteBuffer byteBuffer = ByteBuffer.allocate(16);
            Charset charset = StandardCharsets.UTF_8;
            // 对于 read 的终止条件为 0或-1 代表文件读取结束
            /**
             * 对于 {@link ByteBuffer#put(byte)}操作实际会变更{@link ByteBuffer#position} 记录写入 bytebuffer的字节长度
             */
            while (byteChannel.read(byteBuffer) > 0) {
                /**
                 * rewind 倒带的含义表示 对于bytebuffer每次数据读取都是从头{@link ByteBuffer#position} = 0 开始进行数据读取;由于当写入完成后,{@link ByteBuffer#position}位置发生变化,因此数据读取时要从头开始读取
                 */
                byteBuffer.rewind();
                /**
                 * 对于{@link Charset#decode(ByteBuffer)}操作实际就是读取 {@link ByteBuffer},会调用{@link ByteBuffer#position(int)}修改{@link ByteBuffer#position}字段数据,记录本次读取bytebuffer的字节长度
                 */
                // 使用UTF-8编码集 将 byteBuffer 转换为 CharBuffer
                CharBuffer charBuffer = charset.decode(byteBuffer);
                // 对于当前 操作 默认是按照 bigEndian字节读取方式进行读取数据,因此读取出来的数据会是乱码
//                CharBuffer charBuffer = byteBuffer.asCharBuffer();
                /**
                 * 由于 {@link CharBuffer} {@code implements CharSequence}
                 * 对于 {@link CharBuffer#toString(int, int)} 中的 start 字段 是从 position 开始到 limit 结束
                 */
                System.out.print(charBuffer);
                /**
                 * 由于读取操作导致 {@link ByteBuffer#position} 数据发生变化,
                 * 对于{@link ByteBuffer#limit} 字段实际是通过记录上一次的数据读取的长度
                 *
                 * flip 翻转
                 * {@link ByteBuffer#flip()} 首先将上一次读取的数据长度{@link ByteBuffer#position}赋值给{@link ByteBuffer#limit},并将 {@link ByteBuffer#position}重置为0 ,以及{@link ByteBuffer#mark}重置为-1
                 *
                 * 对于{@link Buffer#flip()}和{@link Buffer#clear()} 的唯一区别在于 对于{@link Buffer#limit}字段的操作
                 * 对于 {@link Buffer#flip()} 操作的好处在于记录了上次读取的最长长度,同时也限制了下一次写入的长度
                 */
                byteBuffer.flip();
//                byteBuffer.clear();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

 

new io 相关 复制和移动操作 :对于复制和移动操作 特别需要注意的一点在于 对于 目标PATH中的中间路径(目录)不允许出现不存在的情况,否则会抛出NoSuchFileException;

相关文件查找API增强 , 支持自定义实现类似 linux find 命令操作

 

/*
 * Copyright (c) 2020, guoxing, Co,. Ltd. All Rights Reserved
 */
package com.xingguo.nio;

import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.util.Objects;

/**
 * DirectoryOperateDemo
 * {@link java.nio.file.Files#createDirectory(Path, FileAttribute[])} 当前操作只能在已存在的目录下创建文件
 * {@link java.nio.file.Files#createDirectories(Path, FileAttribute[])} 当前操作对于不存在的目录会按照父子关系依次创建
 *
 * @author guoxing
 * @date 2020/12/9 3:16 PM
 * @since
 */
public class DirectoryOperateDemo {
    private static final String USER_DIR = System.getProperty("user.dir");

    public static void main(String[] args) throws IOException {
//        directoryBasicOperate();
        // 遍历目录
        /**
         * {@link java.nio.file.FileVisitor}
         * {@link java.nio.file.FileSystem#getPathMatcher(String)} 支持正则匹配文件
         * 利用{@link Files#walkFileTree(Path, FileVisitor)} 进行文件查找
         */
        String javaProjectLocation = getCurrentJavaLocation();
        // 查找当前类所在目录下全部java文件
        findCommand(javaProjectLocation, "*.java");
//        findCommand(USER_DIR, "*.java");
    }

    /**
     * 获取maven工程下当前类的java文件全路径
     *
     * @return 当前java文件路径
     * @author guoxing
     * @date 2020-12-09 5:42 PM
     * @since 1.0.0
     */
    public static String getCurrentJavaLocation() {
        Class<DirectoryOperateDemo> directoryOperateDemoClass = DirectoryOperateDemo.class;
        String path = directoryOperateDemoClass.getResource("/").getPath();
        String projectPath = path.replace("/target/classes", "/src/main/java");
        Package aPackage = directoryOperateDemoClass.getPackage();
        String name = aPackage.getName();
        // 获取当前maven工程 java文件全路径
        return projectPath + name.replace(".", File.separator);
    }

    /**
     * 模拟 find 命令 查找匹配的文件
     *
     * @param pathName 查找根路径
     * @param pattern  正则表达式
     * @author guoxing
     * @date 2020-12-09 4:24 PM
     * @since 1.0.0
     */
    private static void findCommand(String pathName, String pattern) throws IOException {
        Path path = Paths.get(pathName);
        if (!Files.isDirectory(path)) {
            return;
        }
        CustomFileVisitor customFileVisitor = new CustomFileVisitor(pattern);
        try {
            Files.walkFileTree(path, customFileVisitor);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        System.out.printf("%s : 匹配到的文件数量为:%s\n", path, customFileVisitor.getCount());
    }

    /**
     * 自定义 文件查找类
     *
     * @author guoxing
     * @date 2020-12-09 4:30 PM
     * @since 1.0.0
     */
    static class CustomFileVisitor extends SimpleFileVisitor<Path> {
        // sun.nio.fs.UnixFileSystem.GLOB_SYNTAX
        private static final String syntax = "glob";
        // 采用文件正则匹配工具
        private final PathMatcher pathMatcher;
        // 返回匹配到的数据量
        private int count;

        public int getCount() {
            return count;
        }

        public CustomFileVisitor(String pattern) {
            this.pathMatcher = FileSystems.getDefault().getPathMatcher(syntax + ":" + pattern);
        }

        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
            matchFile(dir);
            return FileVisitResult.CONTINUE;
        }

        private void matchFile(Path dir) {
            // dir.getFileName 获取的是当前文件名,而非全路径名
            if (Objects.nonNull(dir) && pathMatcher.matches(dir.getFileName())) {
                System.out.println(dir);
                count++;
            }
        }

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            matchFile(file);
            return FileVisitResult.CONTINUE;
        }
    }


    private static void directoryBasicOperate() throws IOException {
        Path path = Files.createDirectory(Paths.get(USER_DIR, "temp"));
        Files.delete(path);
        // java.nio.file.NoSuchFileException
//        path = Files.createDirectory(Paths.get(USER_DIR, "temp", "secondtemp")); // 当前操作会抛出异常
        // 对于不存在的父级目录,会优先创建
        path = Files.createDirectories(Paths.get(USER_DIR, "temp", "secondtemp"));
        //遍历 目录 下的所有文件数据
        DirectoryStream<Path> paths = Files.newDirectoryStream(path, p -> p.toString().startsWith(USER_DIR));
        for (Path value : paths) {
            System.out.println(value);
        }

        // 由于创建了多级目录,因此需要循环删除
        // TODO:对于这种循环删除一定要确定终止条件
        Path basePath = Paths.get(USER_DIR);
        while (path.startsWith(USER_DIR) && !path.equals(basePath)) {
            Files.delete(path);
            path = path.getParent();
        }
    }
}

 

文件类型

/**
* Basic attributes associated with a file in a file system.
*
* <p> Basic file attributes are attributes that are common to many file systems
* and consist of mandatory and optional file attributes as defined by this
* interface.
*
* <p> <b>Usage Example:</b>
* <pre>
* Path file = ...
* BasicFileAttributes attrs = Files.readAttributes(file, BasicFileAttributes.class); // 使用方法,官方示例
* </pre>
*
* @since 1.7
*
* @see BasicFileAttributeView
*/

java.nio.file.attribute.BasicFileAttributes

 

posted @ 2020-12-09 21:51  郭星  阅读(141)  评论(0)    收藏  举报