Netty(二)文件编程

发布时间 2023-11-13 15:11:37作者: Tod4

Netty(二)文件编程


1 FileChannel

  • 不能够直接打开FileChannel,只能够通过FileInputStreamFIleOutPutStreamRandomAccessFile的getChannel()方法来获取FileChannel
  • FileInputStream获得的channel只能读
  • FIleOutPutStream获得的channel只能写
  • RandomAccessFile是否能读写需要根据构造RandomAccessFile时的读写模式决定
1.1 读取
  • read会从channel中读取数据填充到ByteBuffer中,然后返回读取了多少字节的数量,-1表示文件读取结束

            try (FileChannel channel = new RandomAccessFile("words", "r").getChannel()){
                ByteBuffer buffer = ByteBuffer.allocate(100);
    //            19
                int read = channel.read(buffer);
                System.out.println(read);
                debugAll(buffer);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
    
1.2 写入
  • channel的写入能力是有限的,因此channel的write不能够保证ByteBuffer中的全部数据一次性写入

  • 因此正确的写入的姿势如下,需要使用hasRemaining判断bytebuffer有没有剩余数据:

        @Test
        public void fileChannelWriteTest() {
            try (FileChannel channel = new RandomAccessFile("words", "rw").getChannel()) {
                ByteBuffer byteBuffer = ByteBuffer.allocate(11);
                byteBuffer.put("hello world".getBytes());
                // 切换为读模式
                byteBuffer.flip();
                while(byteBuffer.hasRemaining()) {
                    channel.write(byteBuffer);
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    
1.3 关闭

​ channel必须关闭,并且调用FileInputStreamFIleOutPutStreamRandomAccessFile的close方法后会间接调用channel的close方法

1.4 位置
  • position()获取当前位置,position(index)设置channel的position指针

        public static void main(String[] args) {
            try (FileChannel channel = new RandomAccessFile("words", "r").getChannel()){
                ByteBuffer buffer = ByteBuffer.allocate(100);
                int read = channel.read(buffer);
    //            11
                System.out.println(channel.position());
    //            12
                channel.position(12);
                System.out.println(channel.position());
    
    //            debugAll(buffer);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    
1.5 大小
  • channel.size()获取文件大小

        public static void main(String[] args) {
            try (FileChannel channel = new RandomAccessFile("words", "r").getChannel()){
    //            11
                System.out.println(channel.size());
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    
1.6 强制写入
  • 操作系统出于性能的考虑会将数据缓存而不是立刻写入磁盘,可以调用channel.force(true)方法立刻将文件内容写入磁盘,参数true表示同时写入文件的元数据信息(文件的权限等信息)

        @Test
        public void fileChannelWriteTest() {
            try (FileChannel channel = new RandomAccessFile("words", "rw").getChannel()) {
                ByteBuffer byteBuffer = ByteBuffer.allocate(11);
                byteBuffer.put("hello world".getBytes());
                // 切换为读模式
                byteBuffer.flip();
                while(byteBuffer.hasRemaining()) {
                    channel.write(byteBuffer);
                    channel.force(true);
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    
1.7 FileChannel数据传输
  • FileChannel的文件船速速率要高于流传输,因为会操作系统底层的零拷贝进行优化

        @Test
        public void testFileChannelTransferTo() {
            try (
                    FileChannel from = new FileInputStream("words").getChannel();
                    FileChannel to = new FileOutputStream("wordsTo").getChannel();
            ) {
                from.transferTo(0, from.size(), to);
            } catch (IOException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
    
  • 但是传输大小上限为2g,如果想要传输大文件,需要进行多次传输

        @Test
        public void testFileChannelTransferTo() {
            try (
                    FileChannel from = new FileInputStream("words").getChannel();
                    FileChannel to = new FileOutputStream("wordsTo").getChannel();
            ) {
                long size = from.size();
                for (var left = size; left > 0; ) {
                    left -= from.transferTo(size - left, left, to);
                }
            } catch (IOException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
    

2 Path & Files

2.1 Path

​ jdk7引入了Path和Paths

  • Path用来表示文件路径

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

    	Path source = Paths.get("1.txt"); 
    
  • path.normalize():格式化路径,防止出现相对路径..的写法

2.2 Files
  • Fles.exists:检查文件是否存在

        @Test
        public void testFiles() {
            var path = Paths.get("helloWorld.txt");
            // false
            System.out.println(Files.exists(path));
        }
    
  • Files.createDirectory():创建一级目录

        @Test
        public void testFiles() {
            var path = Paths.get(".\\HelloWorld");
            try {
                Files.createDirectory(path);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    
    • 如果文件已经存在,会抛出FileAlreadyExistsException
    • 如果创建了多级目录,则会抛出NoSuchFileException,意思是没有一级目录
  • Files.createDirectories:创建多级目录

        @Test
        public void testFiles() {
            var path = Paths.get(".\\HelloWorld\\d");
            try {
                Files.createDirectories(path);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    
  • Files.copy(source, target):文件拷贝

    • 如果目标文件已经存在则会抛出FileAlreadyExistsException

    • 如果希望覆盖掉target,则可以使用StandardCopyOption来控制

          @Test
          public void testFiles() {
              var source = Paths.get("words");
              var target = Paths.get("wordsTo");
      
              try {
                  Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
              } catch (IOException e) {
                  throw new RuntimeException(e);
              }
          }
      
  • Files.move(source, target):文件移动

    • 需要目标路径存在
    • ATOMIC_MOVE能够保证移动操作的原子性
        @Test
        public void testFiles() {
            var source = Paths.get("words");
            var target = Paths.get("Hello\\wordsTo");
    
            try {
                Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    
  • Files.delete(source):删除文件或者目录

    • 如果文件不存在,则会抛出NoSuchFileException
    • 如果目录中还有内容,则会抛出DirectoryNotEmptyException
        @Test
        public void testFiles() {
            var source = Paths.get("words");
            var target = Paths.get("Hello\\wordsTo");
    
            try {
                Files.delete(target);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    
2.3 Files文件夹遍历
  • Files.walkFileTree:遍历文件夹下的所有文件和目录

  • 采用访问者模式,即需要传入一个SimpleFileVisitor遍历逻辑由Files实现,而遍历到文件、遍历到文件夹、访问文件失败和遍历文件夹后的操作需要传入一个匿名内部类指定实现

        public static void main(String[] args) throws IOException {
            var path = Paths.get("E:\\Program_workspace\\jdk\\jdk-21.0.1");
            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);
            System.out.println(fileCount);
        }
    

    上面程序在对文件、文件夹计数的时候,必须使用AtomicInteger进行,因为匿名内部类引用的时候必须为final,因此无法使用基本数据类型

2.4 Files删除多级目录
  • 如果直接使用Files.delete()删除需要保证目录下文件为空

  • 可以借助Files.walkFileTree()重写访问文件时和访问文件夹后的方法,删除文件和文件夹即可完成多级目录的删除

  • 程序中的删除是不会走电脑回收站的,谨慎使用

        private static void fileDel() {
            try {
    //            Files.delete(Paths.get("E:\\leidian - 副本"));
                Files.walkFileTree(Paths.get("E:\\\\leidian - 副本"), new SimpleFileVisitor<Path>() {
                    @Override
                    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                        System.out.println("====>" + dir);
                        return super.preVisitDirectory(dir, attrs);
                    }
    
                    @Override
                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                        System.out.println(file);
                        Files.delete(file);
                        return super.visitFile(file, attrs);
                    }
    
                    @Override
                    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                        System.out.println("<====" + dir);
                        Files.delete(dir);
                        return super.postVisitDirectory(dir, exc);
                    }
                });
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    
2.5 Files多级目录拷贝
    private static void fileCopy() throws IOException {
        var source = "E:\\leidian";
        var target = "E:\\leidian2";

        Files.walk(Paths.get(source)).forEach(path -> {
            String replace = path.toString().replace(source, target);

            try {
                Files.copy(path, Paths.get(replace));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });

    }