阅读 95

netty(三)nio之文件编程(netty使用教程)

本篇文章主要讨论一下NIO中的文件变成,主要是FileChannel的用法。

一、FileChannel常用操作

1.1 获取FileChannel

有一个文件text.txt,其内容如下:

abcdef 复制代码

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

1.1.1 通过 FileInputStream 获取

    public static void main(String[] args) throws Exception {         //使用FileInputStream获取channel         FileInputStream fileInputStream = new FileInputStream(new File("C:\\Users\\P50\\Desktop\\text.txt"));         FileChannel channel1 = fileInputStream.getChannel();         ByteBuffer buffer= ByteBuffer.allocate(10);         //channel1.write(buffer);         channel1.read(buffer);         buffer.flip();         System.out.println((print(buffer)));     }     static String print(ByteBuffer b) {         StringBuilder stringBuilder = new StringBuilder();         for (int i = 0; i < b.limit(); i++) {             stringBuilder.append((char) b.get(i));         }         return stringBuilder.toString();     } 复制代码

结果:

abcdef 复制代码

通过 FileInputStream 获取的 channel 只能读,如果使用写入write方法,会抛出异常:

Exception in thread "main" java.nio.channels.NonWritableChannelException at sun.nio.ch.FileChannelImpl.write(FileChannelImpl.java:201) at com.cloud.bssp.nio.FileChannel.GetFileChannel.main(GetFileChannel.java:21) 复制代码

1.1.2 通过 FileOutputStream 获取

    public static void main(String[] args) throws Exception {         //使用FileOutputStream获取channel         FileOutputStream fileOutputStream = new FileOutputStream(new File("C:\\Users\\P50\\Desktop\\text.txt"),true);         FileChannel channel2 = fileOutputStream.getChannel();         ByteBuffer buffer= ByteBuffer.allocate(10);         buffer.put(StandardCharsets.UTF_8.encode("helloworld"));         buffer.flip();         channel2.write(buffer);     }     static String print(ByteBuffer b) {         StringBuilder stringBuilder = new StringBuilder();         for (int i = 0; i < b.limit(); i++) {             stringBuilder.append((char) b.get(i));         }         return stringBuilder.toString();     } 复制代码

文件被写入,这里注意FileOutputStream 的属性append,如果是true,表示追加,否则覆盖。本文使用的追加。

abcdefhelloworld 复制代码

通过 FileOutputStream 获取的 channel 只能写,如果使用read方法,会抛出异常:

Exception in thread "main" java.nio.channels.NonReadableChannelException at sun.nio.ch.FileChannelImpl.read(FileChannelImpl.java:149) at com.cloud.bssp.nio.FileChannel.GetFileChannel.main(GetFileChannel.java:28) 复制代码

1.1.3  通过 RandomAccessFile获取

    public static void main(String[] args) throws Exception {         //使用RandomAccessFile获取channel         RandomAccessFile file = new RandomAccessFile("C:\\Users\\P50\\Desktop\\text.txt", "rw");         FileChannel channel3 = file.getChannel();         ByteBuffer buffer = ByteBuffer.allocate(15);         //读取文件内容到buffer         channel3.read(buffer);         buffer.flip();         System.out.println(print(buffer));         // 切换为写模式,并且清空buffer         buffer.clear();         //写入helloworld到文件         buffer.put(StandardCharsets.UTF_8.encode("helloworld"));         buffer.flip();         channel3.write(buffer);     }         // 切换为写模式,并且清空buffer         buffer.clear();         //写入helloworld到文件         buffer.put(StandardCharsets.UTF_8.encode("helloworld"));         buffer.flip();         channel3.write(buffer); 复制代码

结果:这里读取的少了一个字节,因为我指定的buffer只有15,文档中是16,只读取了一次,。

abcdefhelloworl 复制代码

文档内容被修改为如下,将channel读取到的内容以及新加入的内容拼接在了一起

abcdefhelloworlhelloworld 复制代码

通过 RandomAccessFile 是否能读写根据构造 RandomAccessFile 时的读写模式决定,指定rw(读写模式)。

1.2 读取和写入

1.2.1 读取

在前面的获取例子中已经给出了关于读取的方式,如下所示,会返回int类型,从 channel 读取数据填充ByteBuffer,返回值表示读到了多少字节,返回值为-1 表示到达了文件的末尾。

int readBytes = channel.read(buffer); 复制代码

仍然使用上面的第一个例子,如果文档是空的话,则会返回-1

 int read = channel1.read(buffer);  System.out.println(read); 复制代码

-1 复制代码

1.2.2 写入

如上一章节的例子,已经演示了如何写入数据,利用write方法,将buffer的数据写入channel,但是正确的写入方式应该如下所示:

while(buffer.hasRemaining()) {     channel.write(buffer); } 复制代码

hasRemaining()是buffer的一个方法,判断position是否小于limit,是则返回true,表示buffer仍然有未读取的数据。

在 while 中调用 channel.write 是因为 write 方法并不能保证一次将 buffer 中的内容全部写入 channel。

1.2.3 强制写入

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

public abstract void force(boolean metaData) throws IOException; 复制代码

1.3 关闭

像我们上面写的代码实际上都没有去关闭流和channel的,这如果在生产环境都是会产生严重的问题。

channel是必须要关闭的,不过调用了 FileInputStream、FileOutputStream 或者 RandomAccessFile 的 close() 方法会间接地调用 channel 的 close 方法。

看下FileInputStream的close方法:

    public void close() throws IOException {         synchronized (closeLock) {             if (closed) {                 return;             }             closed = true;         }         if (channel != null) {            channel.close();         }         fd.closeAll(new Closeable() {             public void close() throws IOException {                close0();            }         });     } 复制代码

1.4 FileChannel的位置

获取当前位置

long pos = channel.position(); 复制代码

设置当前位置

long newPos = ...; channel.position(newPos); 复制代码

如下获取文件channel:

        // 文件内容为10个字节的helloworld         RandomAccessFile file = new RandomAccessFile("C:\\Users\\P50\\Desktop\\text.txt", "rw");         FileChannel channel = file.getChannel(); 复制代码

打印不同设置时的位置:

        // 打印位置,没有读取时是0         System.out.println(channel.position());         // 读取后是文件的长度         ByteBuffer buffer = ByteBuffer.allocate(10);         channel.read(buffer);         System.out.println(channel.position());         // 设置位置后的长度         FileChannel position = channel.position(5);         System.out.println(position.position()); 复制代码

结果:

0 10 5 复制代码

1.5 获取文件大小

channel.size(); 复制代码

二、channel的相互传输

channel提供两个用来channel相互传输数据的方法:

/**   * 将一个channel的数据传输到target这个channel中,其中position,count,都是调用此方法的channel的   * in.transferTo(0, in.size(), out);   */ transferTo(long position, long count, WritableByteChannel target) 复制代码

/**   * 一个channel从src这个channel获取数据,其中position,count,都是src这个channel的   * out.transferFrom(in,0,in.size());   */ transferFrom(ReadableByteChannel src, long position, long count) 复制代码

使用例子如下:

public class TestCopyFileByNIO {     public static void fileChannelCopy(String sfPath, String tfPath) {         FileInputStream fi = null;         FileOutputStream fo = null;         FileChannel in = null;         FileChannel out = null;         try {             fi = new FileInputStream(new File(sfPath));             fo = new FileOutputStream(new File(tfPath));             in = fi.getChannel();//得到对应的文件通道             out = fo.getChannel();//得到对应的文件通道             in.transferTo(0, in.size(), out);//连接两个通道,并且从in通道读取,然后写入out通道         } catch (Exception e) {             e.printStackTrace();         } finally {             try {                 fi.close();                 fo.close();             } catch (Exception e) {                 e.printStackTrace();             }         }     }     public static void main(String[] args) {         long start = System.currentTimeMillis();         String sPath = "E:\\workspace\\comprehend-service.rar";         String tPath = "E:\\workspace\\comprehend-service-" + System.currentTimeMillis() + "-bak.rar";         fileChannelCopy(sPath, tPath);         long end = System.currentTimeMillis();         System.out.println("用时为:" + (end - start) + "ms");     } } 复制代码

结果:

用时为:194ms 复制代码

2.1 channel的最大传输值

channel的传输是有大小限制的,最大为2个g,超过会导致数据丢失。所以需要使用循环去多次传输数据。

public class TestCopyFileByNIO {     public static void fileChannelCopy(String sfPath, String tfPath) {         FileInputStream fi = null;         FileOutputStream fo = null;         FileChannel in;         FileChannel out;         try {             fi = new FileInputStream(new File(sfPath));             fo = new FileOutputStream(new File(tfPath));             in = fi.getChannel();             out = fo.getChannel();             // 总文件大小             long size = in.size();             // left 剩余文件的数量             for (long left = size; left > 0;){                 System.out.println("position = " + (size - left) + ",left = " + left);                 // transferTo返回传输的数量,剩余的减去传输的,就是当前剩余的数量                 left -= in.transferTo((size -left), left, out);             }         } catch (Exception e) {             e.printStackTrace();         } finally {             try {                 fi.close();                 fo.close();             } catch (Exception e) {                 e.printStackTrace();             }         }     }     public static void main(String[] args) {         long start = System.currentTimeMillis();         String sPath = "E:\\workspace\\workspace.zip";         String tPath = "E:\\workspace\\workspace-" + System.currentTimeMillis() + "-bak.zip";         fileChannelCopy(sPath, tPath);         long end = System.currentTimeMillis();         System.out.println("用时为:" + (end - start) + "ms");     } 复制代码

结果:

position = 0,left = 2925330022 position = 2147483647,left = 777846375 用时为:13664ms 复制代码

三、Path 和 Paths 类

jdk7 引入了 Path 和 Paths 类

  • Path 用来表示文件路径

  • Paths 是工具类,用来获取 Path 实例

// 相对路径 使用 user.dir 环境变量来定位 1.txt Path source = Paths.get("1.txt");  // 绝对路径 代表了  d:\1.txt Path source = Paths.get("d:\\1.txt");  // 绝对路径 同样代表了  d:\1.txt Path source = Paths.get("d:/1.txt");   // 代表了  d:\data\projects Path projects = Paths.get("d:\\data", "projects"); 复制代码

  • . 代表了当前路径

  • .. 代表了上一级路径

例如目录结构如下

d: |- data |- projects |- a |- b 复制代码

代码

Path path = Paths.get("d:\\data\\projects\\a\\..\\b"); System.out.println(path); // 正常化路径 System.out.println(path.normalize());  复制代码

会输出

d:\data\projects\a\..\b d:\data\projects\b 复制代码

四、Files类

检查文件是否存在

Path path = Paths.get("helloword/data.txt"); System.out.println(Files.exists(path)); 复制代码

创建一级目录

Path path = Paths.get("helloword/d1"); Files.createDirectory(path); 复制代码

  • 如果目录已存在,会抛异常 FileAlreadyExistsException

  • 不能一次创建多级目录,否则会抛异常 NoSuchFileException

创建多级目录用

Path path = Paths.get("helloword/d1/d2"); Files.createDirectories(path); 复制代码

拷贝文件

Path source = Paths.get("helloword/data.txt"); Path target = Paths.get("helloword/target.txt"); Files.copy(source, target); 复制代码

  • 如果文件已存在,会抛异常 FileAlreadyExistsException

如果希望用 source 覆盖掉 target,需要用 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("helloword/target.txt"); Files.delete(target); 复制代码

  • 如果文件不存在,会抛异常 NoSuchFileException

删除目录

Path target = Paths.get("helloword/d1"); Files.delete(target); 复制代码

  • 如果目录还有内容,会抛异常 DirectoryNotEmptyException

遍历目录文件

public static void main(String[] args) throws IOException {     Path path = Paths.get("C:\\Program Files\\Java\\jdk1.8.0_91");     AtomicInteger dirCount = new AtomicInteger();     AtomicInteger fileCount = new AtomicInteger();     Files.walkFileTree(path, new SimpleFileVisitor<Path>(){         @Override         public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)              throws IOException {             System.out.println(dir);             dirCount.incrementAndGet();             return super.preVisitDirectory(dir, attrs);         }         @Override         public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)              throws IOException {             System.out.println(file);             fileCount.incrementAndGet();             return super.visitFile(file, attrs);         }     });     System.out.println(dirCount); // 133     System.out.println(fileCount); // 1479 } 复制代码

统计 jar 的数目

Path path = Paths.get("C:\\Program Files\\Java\\jdk1.8.0_91"); AtomicInteger fileCount = new AtomicInteger(); Files.walkFileTree(path, new SimpleFileVisitor<Path>(){     @Override     public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)          throws IOException {         if (file.toFile().getName().endsWith(".jar")) {             fileCount.incrementAndGet();         }         return super.visitFile(file, attrs);     } }); System.out.println(fileCount); // 724 复制代码

删除多级目录

Path path = Paths.get("d:\\a"); Files.walkFileTree(path, new SimpleFileVisitor<Path>(){     @Override     public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)          throws IOException {         Files.delete(file);         return super.visitFile(file, attrs);     }     @Override     public FileVisitResult postVisitDirectory(Path dir, IOException exc)          throws IOException {         Files.delete(dir);         return super.postVisitDirectory(dir, exc);     } }); 复制代码

删除是危险操作,确保要递归删除的文件夹没有重要内容

拷贝多级目录

long start = System.currentTimeMillis(); String source = "D:\\Snipaste-1.16.2-x64"; String target = "D:\\Snipaste-1.16.2-x64aaa"; Files.walk(Paths.get(source)).forEach(path -> {     try {         String targetName = path.toString().replace(source, target);         // 是目录         if (Files.isDirectory(path)) {             Files.createDirectory(Paths.get(targetName));         }         // 是普通文件         else if (Files.isRegularFile(path)) {             Files.copy(path, Paths.get(targetName));         }     } catch (IOException e) {         e.printStackTrace();     } }); long end = System.currentTimeMillis(); System.out.println(end - start); 复制代码


关于NIO文件编程此处就写到这了,有帮助的话朋友个点个赞


作者:我犟不过你
链接:https://juejin.cn/post/7028776620500451335


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐