NIO网络编程(四)—— 文件编程

NIO网络编程(四)—— 文件编程

FileChannel

FileChannel是一个对文件进行操作的通道,需要注意的是,FileChannel只能在阻塞模式下工作,因此不能够搭配Selector。

创建FileChannel

FileChannel没有相应的构造函数,不过通过 FileInputStream、FileOutputStream 或者 RandomAccessFile 来获取 FileChannel,它们都有 getChannel 方法,但各自使用情况不同:

  • 通过 FileInputStream 获取的 FileChannel只能读
  • 通过 FileOutputStream 获取的 FileChannel只能写
  • 通过 RandomAccessFile 获取的FileChannel 是否能读写根据构造 RandomAccessFile 时的读写模式决定
FileChannel f1 = new FileInputStream("data.txt").getChannel();
FileChannel f2 = new FileOutputStream("data.txt").getChannel();
FileChannel f3 = new RandomAccessFile("data.txt", "rw").getChannel();

读取内容

可以通过 FileInputStream 获取文件的 channel,通过read方法将数据写入到ByteBuffer中,read方法的返回值表示读到了多少字节,若读到了文件末尾则返回-1,在实际使用中,可以通过read方法的返回值判断是否读取完毕。

public static void main(String[] args) throws IOException {
        FileChannel f1 = new FileInputStream("data.txt").getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(10000);
        int value = 0;
        while((value = f1.read(buffer))>0)
        {
            System.out.println("\n============="+value);
            buffer.flip();
            while(buffer.hasRemaining())
                System.out.print(((char) buffer.get()));
            buffer.flip();
        }
        f1.close();
    }

写入内容

可以通过 FileOutputStream 获取文件的 channel,通过write方法将buffer中的数据写入到指定文件中,需要注意的是,因为channel也是有大小的,所以 write 方法并不能保证一次将 buffer 中的内容全部写入 channel。必须需要按照以下规则进行写入:

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

其次,一定要使用buffer的flip方法将buffer的模式转换,否则会写乱码。

public static void main(String[] args) throws IOException {
        FileChannel f1 = new FileOutputStream("data2.txt").getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(100);
        buffer.put("hello wolrd!".getBytes(StandardCharsets.UTF_8));
        buffer.flip();
        while(buffer.hasRemaining())
        {
            f1.write(buffer);
        }
        f1.close();
    }

关闭

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

    public static void main(String[] args) throws IOException {
        try (FileInputStream fis = new FileInputStream("stu.txt");
             FileOutputStream fos = new FileOutputStream("student.txt");
             FileChannel inputChannel = fis.getChannel();
             FileChannel outputChannel = fos.getChannel())
              {
            // 执行对应操作
       	 }
    }

FileChannel的位置

在上一篇《Netty编程(二)—— nio.ByteBuffer基础操作》介绍了buffer有个变量position指示的是下一个读写位置的索引,在FileChannel中拥有一个保存读取数据位置的属性position

long pos = channel.position();

可以通过position(int pos)设置channel中position的值,设置当前位置时,如果设置为文件的末尾

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

强制写入

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

两个FileChannel传输数据

transferTo方法

使用transferTo方法可以快速、高效地将一个channel中的数据传输到另一个channel中,但一次只能传输2G的内容。transferTo要传入三个参数:传输channel的起始position,传输大小,目的channel

public static void main(String[] args) throws IOException {
        try(
                FileChannel f1 = new FileInputStream("data.txt").getChannel();
                FileChannel f2 = new FileOutputStream("data2.txt").getChannel();){

            System.out.println("传输前的大小:  f1大小: "+f1.size()+"  f2大小: "+f2.size());
            f1.transferTo(0,f1.size(),f2);
            System.out.println("传输后的大小:  f1大小: "+f1.size()+"  f2大小: "+f2.size());
        }catch (IOException e) { }
    }

结果展示:
在这里插入图片描述

传输超过2G

上面说到transferTo方法一次只能传输不超过2G大小的内容,但是超过2G内容,可以使用循环的方式进行多次传输,知道全部传输完毕:

try(
       FileChannel f1 = new FileInputStream("data.txt").getChannel();
        FileChannel f2 = new FileOutputStream("data2.txt").getChannel();){

    long size = f1.size();
    long capacity = f1.size();
    while(capacity>0)
    {
        long thisTransfor = f1.transferTo(size - capacity , capacity, f2);
        capacity -= thisTransfor;
    }
}catch (IOException e) { }

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

Files

查找

可以使用Files工具类检查文件是否存在

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

创建

如果想要创建一级目录:

Path path = Paths.get("study/java");
Files.createDirectory(path);

需要注意以下两点

  • 如果目录已存在,会抛异常 FileAlreadyExistsException
  • 不能一次创建多级目录,否则会抛异常 NoSuchFileException

如果要创建多级目录:

Path path = Paths.get("study/java/nio");
Files.createDirectories(path);

拷贝移动

拷贝文件可以使用Files工具类里的copy函数,需要注意的是,如果文件已存在,会抛异常 FileAlreadyExistsException

Path source = Paths.get("java/data.txt");
Path target = Paths.get("java/data1.txt");
Files.copy(source, target);

如果希望用 source 覆盖掉 target,需要用 StandardCopyOption 来控制

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

移动文件可以使用Files工具类里的move函数,如果第三个参数使StandardCopyOption.ATOMIC_MOVE 保证文件移动的原子性

Path source = Paths.get("java/data.txt");
Path target = Paths.get("study/data.txt");
Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);

删除

删除文件可以使用Files工具类里的delete方法,需要注意的是,如果文件不存在,会抛异常 NoSuchFileException。

Path target = Paths.get("study/data.txt");
Files.delete(target);

删除目录与删除文件一样,都是使用Files工具类里的delete方法,但是需要注意的是,如果目录还有内容,会抛异常 DirectoryNotEmptyException,所以只能删除空目录,如果想要删除非空目录,可以遍历目录里的所有文件并且删除这些文件以及子目录,然后删除这个目录。不过Files工具类还提供了一个更方便的方法——walkFileTree方法,下一部分会介绍。

Path target = Paths.get("study/java");
Files.delete(target);

walkFileTree方法

使用Files工具类中的walkFileTree(Path, FileVisitor)方法可以更加方便地操作一个目录下的子目录以及文件,他其中需要传入两个参数

  • Path:文件起始路径

  • FileVisitor:文件访问器,这里使用了使用访问者模式,可以传入新建的SimpleFileVisitor并且重写这个类的有四个方法:

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

使用walkFileTree来统计一个目录下所有目录数以及文件数:

public static void main(String[] args) throws IOException {
        Path path = Paths.get("E:\\ppt\\");
        
        Files.walkFileTree(path, new SimpleFileVisitor<Path>(){
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                dicAccount.incrementAndGet();
                System.out.println("===> "+dir);
                return super.preVisitDirectory(dir, attrs);
            }

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                fileAccount.incrementAndGet();
                File f = new File(file.toString());
                Files.delete(file);
                return super.visitFile(file, attrs);
            }

            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {

                return super.visitFileFailed(file, exc);
            }

            @Override
            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                System.out.println(dir+" <===");
                Files.delete(dir);
                return super.postVisitDirectory(dir, exc);
            }
        });
        System.out.println("dicAccount " + dicAccount.get());
        System.out.println("fileAccount "+ fileAccount.get());
    }

结果展示:
在这里插入图片描述
使用walkFileTree方法还可以删除一个目录下所有的文件和子目录,但需要注意的是,刚刚说到Files.delete方法删除目录时只能删除空目录,因此需要在visiteFile中先删除文件,然后在postVisitDirecory方法删除该子目录。

拷贝多级目录

Files.walk(Paths.get(source)).forEach(path1 -> {
            try{
                String name = path1.toString().replace(source,target);
                if(Files.isDirectory(path1))
                {
                    Files.createDirectories(Paths.get(name));
                }
                else if(Files.isRegularFile(path1))
                {
                    Files.copy(path1,Paths.get(target));
                }

            }catch (IOException e)
            {
                e.printStackTrace();
            }
        });
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值