NIO-文件编程

简介: NIO-文件编程

文件编程

FileChannel

工作模式

FileChannel只能在阻塞模式下工作,所以无法搭配Selector

获取

不能直接打开FileChannel,必须通过FileInputStream、FileOutputStream或者RandomAccessFile来获取FileChannel,它们都有getChannel方法

  • 通过FileInputStream获取的channel只能读
  • 通过FileOutputStream获取的channel只能写
  • 通过RandomAccessFile获取的channel能否读写需要根据构造RandomAccessFile时的读写模式决定
读取

通过 FileInputStream 获取channel,通过read方法将数据写入到ByteBuffer中

read方法的返回值表示读到了多少字节,若读到了文件末尾则返回-1

int readBytes = channel.read(buffer);

可根据返回值判断是否读取完毕

while(channel.read(buffer) > 0) {
    // 进行对应操作
    ...
}
写入

因为channel也是有大小的,所以 write 方法并不能保证一次将 buffer 中的内容全部写入 channel。必须需要按照以下规则进行写入

// 通过hasRemaining()方法查看缓冲区中是否还有数据未写入到通道中
while(buffer.hasRemaining()) {
    channel.write(buffer);
}
关闭

通道需要close,一般情况通过try-with-resource进行关闭,最好使用以下方法获取stream以及channel,避免某些原因使得资源未被关闭

public class TestChannel {
    public static void main(String[] args) throws IOException {
        try (FileInputStream fis = new FileInputStream("xx.txt");
             FileOutputStream fos = new FileOutputStream("xxx.txt");
             FileChannel inputChannel = fis.getChannel();
             FileChannel outputChannel = fos.getChannel()) {
            ...
        }
    }
}
位置

position

channel也拥有一个保存读取数据位置的属性,即position

long pos = channel.position();

可以通过position(int pos)设置channel中position的值

long newPos = ...;
channel.position(newPos);

设置当前位置时,如果设置为文件的末尾

  • 这时读取会返回 -1
  • 这时写入,会追加内容,但要注意如果 position 超过了文件末尾,再写入时在新内容和原末尾之间会有空洞(00)
强制写入

操作系统出于性能的考虑,会将数据缓存,不是立刻写入磁盘,而是等到缓存满了以后将所有数据一次性的写入磁盘。可以调用 force(true) 方法将文件内容和元数据(文件的权限等信息)立刻写入磁盘

两个Channel传输数据

transferTo方法

使用transferTo方法可以快速、高效的将一个channel中的数据传输到另一个channel中,但是一次只能传输2g的内容

transferTo底层使用了零拷贝技术

public class TestChannelTransferTo {
    public static void main(String[] args) {
        try (FileChannel channel = new RandomAccessFile("b.txt", "r").getChannel();
             FileChannel channel1 = new RandomAccessFile("g.txt", "rw").getChannel()
        ) {
            channel.transferTo(0,channel.size(),channel1);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

当传输的文件大于2G时,需要通过以下方法进行多次传输

/**
 * @apiNote 解决transferTo方法一次只能写入2G数据
 */
@Slf4j
public class TestChannelTransferToGt2G {
    public static void main(String[] args) {
        try (FileChannel channel = new RandomAccessFile("b.txt", "r").getChannel();
             FileChannel channel1 = new RandomAccessFile("d.txt", "rw").getChannel()
        ) {
            long size = channel.size();
            long hasNotReadSize = size;
            while (hasNotReadSize > 0) {
                hasNotReadSize -= channel.transferTo((size - hasNotReadSize), hasNotReadSize, channel1);
                log.info("has not read size:{} post:{}", hasNotReadSize, (size - hasNotReadSize));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Path与Paths

  • Path用来表示文件路径
  • Paths是工具类,用来获取Path实例
Path source = Paths.get("1.txt"); // 相对路径 不带盘符 使用 user.dir 环境变量来定位 1.txt

Path source = Paths.get("d:\\1.txt"); // 绝对路径 代表了  d:\1.txt 反斜杠需要转义

Path source = Paths.get("d:/1.txt"); // 绝对路径 同样代表了  d:\1.txt

Path projects = Paths.get("d:\\data", "projects"); // 代表了  d:\data\projects
  • .代表当前路径
  • ..代表了上一级路径
Path path = Paths.get("d:\\data\\projects\\a\\..\\b");
System.out.println(path);
System.out.println(path.normalize()); // 正常化路径 会去除 . 以及 ..

Files

查找

检查文件是否存在

Path path = Paths.get("a.txt");
System.out.println(Files.exists(path));
创建

创建一级目录

Path path= Path.get("C:/files");
Files.createDirectory(path);
  • 如果目录已存在,会抛异常 FileAlreadyExistsException
  • 不能一次创建多级目录,否则会抛异常 NoSuchFileException

创建多级目录

Path path = Paths.get("C:/f1/f2");
Files.createDirectories(path);
拷贝和移动

拷贝文件

Path source = Paths.get("helloword/data.txt");
Path target = Paths.get("helloword/target.txt");
Files.copy(source, target);
  • 如果文件已存在,会抛异常 FileAlreadyExistsException

如果希望覆盖掉文件,需要使用StandardCopyOption 来控制

Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING)

移动文件

Path source = Paths.get("helloword/data.txt");
Path target = Paths.get("helloword/data.txt");
Files.move(source, target, StandardCopyOption.ATOMIC_MOVE)
  • StandardCopyOption.ATOMIC_MOVE 保证文件移动的原子性
删除

删除文件

Path target = Paths.get("t.txt");
Files.delete(target);
  • 如果文件不存在,会抛异常 NoSuchFileException

删除目录

Path target = Paths.get("d/d1");
Files.delete(target);
  • 如果目录还有内容,会抛异常 DirectoryNotEmptyException
遍历

可以使用Files工具类中的walkFileTree(Path, FileVisitor)方法,其中需要传入两个参数

  • Path:文件起始路径
  • FileVisitor:文件访问器,使用访问者模式,可以使用实现类SimpleFileVisitor

    • preVisitDirectory:访问目录前的操作
    • visitFile:访问文件的操作
    • visitFileFailed:访问文件失败时的操作
    • postVisitDirectory:访问目录后的操作

统计文件数量

@Slf4j
public class TestFilleWalkFileTree {
    public static void main(String[] args) throws IOException {
        AtomicInteger dirCount=new AtomicInteger();
        AtomicInteger fileCount=new AtomicInteger();
        Files.walkFileTree(Paths.get("C:\\Java\\jdk1.8.0_291"),new SimpleFileVisitor<Path>(){
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                dirCount.incrementAndGet();
                log.info("=====> dir:{}",dir);
                return super.preVisitDirectory(dir, attrs);
            }

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                log.info("file:{}",file);
                fileCount.incrementAndGet();
                return super.visitFile(file, attrs);
            }
        });
        log.info("dir_count:{} file_count:{}",dirCount,fileCount);
    }
}

批量删除文件

@Slf4j
public class TestFilesWalkFileTree {
    //执行顺序 1.preVisitDirectory  2.visitFile  3.postVisitDirectory
    public static void main(String[] args) throws IOException {
        Files.walkFileTree(Paths.get("E:\\by_delete"),new SimpleFileVisitor<Path>(){
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                log.info("enter:{}",dir);
                return super.preVisitDirectory(dir, attrs);
            }

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                log.info("delete file:{}",file);
                Files.delete(file);
                return super.visitFile(file, attrs);
            }

            @Override
            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                log.info("delete dir:{}",dir);
                Files.delete(dir);
                return super.postVisitDirectory(dir, exc);
            }
        });
    }
}
相关文章
|
7月前
|
Java 关系型数据库 MySQL
Elasticsearch【问题记录 01】启动服务&停止服务的2类方法【及 java.nio.file.AccessDeniedException: xx/pid 问题解决】(含shell脚本文件)
【4月更文挑战第12天】Elasticsearch【问题记录 01】启动服务&停止服务的2类方法【及 java.nio.file.AccessDeniedException: xx/pid 问题解决】(含shell脚本文件)
697 3
|
3月前
NIO-编程实战(二)
NIO-编程实战(二)
|
2月前
|
Java Linux 应用服务中间件
【编程进阶知识】高并发场景下Bio与Nio的比较及原理示意图
本文介绍了在Linux系统上使用Tomcat部署Java应用程序时,BIO(阻塞I/O)和NIO(非阻塞I/O)在网络编程中的实现和性能差异。BIO采用传统的线程模型,每个连接请求都会创建一个新线程进行处理,导致在高并发场景下存在严重的性能瓶颈,如阻塞等待和线程创建开销大等问题。而NIO则通过事件驱动机制,利用事件注册、事件轮询器和事件通知,实现了更高效的连接管理和数据传输,避免了阻塞和多级数据复制,显著提升了系统的并发处理能力。
69 0
|
3月前
|
缓存 Java Linux
NIO-编程实战(一)
NIO-编程实战(一)
|
5月前
|
Java
Java中的NIO编程详解
Java中的NIO编程详解
|
5月前
|
Java
Java中的NIO编程详解
Java中的NIO编程详解
|
6月前
|
存储 监控 Java
Java中的NIO编程实践精华
Java中的NIO编程实践精华
|
6月前
|
存储 监控 Java
深入探索Java BIO与NIO输入输出模型:基于文件复制和socket通信
深入探索Java BIO与NIO输入输出模型:基于文件复制和socket通信
|
6月前
|
存储 监控 Java
了解Java中的NIO编程
了解Java中的NIO编程
|
6月前
|
Scala
scala 读取文件(中文)异常 thread "main" java.nio.charset.MalformedInputException: Input length = 1
scala 读取文件(中文)异常 thread "main" java.nio.charset.MalformedInputException: Input length = 1
57 0