likes
comments
collection
share

Java IO(下篇——文件操作)

作者站长头像
站长
· 阅读数 11

在上篇文章中,主要描述了文件内容的读写数据;而这篇文章则介绍针对文件的读写。

Files

Files类中提供了很多处理文件的方法,不过现在需要先介绍一下Path接口,因为Files的很多静态方法参数都是Path类型

Path path = Paths.get("/Users/c/Desktop/ceshi.txt");
System.out.println("文件名:"+path.getFileName());
System.out.println("父路径:"+path.getParent());
System.out.println("当前路径的根路径:"+path.getRoot());

输出: 文件名:ceshi.txt 父路径:/Users/c/Desktop 当前路径的根路径:/

Paths.get()方法创建一个路径对象 path.getFileName()得到路径上最后一个文件的名称 path.getParent()得到父路径,没有返回null path.getRoot()得到根路径,没有返回null

使用Files读写文件

使用Files读取文件到字节数组

byte[] bytes = Files.readAllBytes(path);

使用Files读取文件文本内容

String s = Files.readString(path, StandardCharsets.UTF_8);

使用Files读取文件到有序序列

List<String> s = Files.readAllLines(path);

使用Files写出字符串到文件

Files.writeString(path,s, StandardOpenOption.APPEND);

使用Files写出二进制数据

Files.createFile(Paths.get("/Users/c/Desktop/1309265.jpeg"));
Files.write(Paths.get("/Users/c/Desktop/1309265.jpeg"),bytes);

使用Files创建文件、目录

//创建目录
Path path = Paths.get("/Users/c/Desktop/hello");
if(!Files.exists(path))
    Files.createDirectory(Paths.get("/Users/c/Desktop/hello"));
//创建文件
//传递一个相对路径与当前路径拼接(如果时绝对路径直接将当前路径修改为传递的绝对路径)
path = path.resolve("www.dat");
if(!Files.exists(path))
    Files.createFile(path);

Files.exists(Path)判断文件路径是否存在 createDirectory(Path)创建一个目录 this.resolve(Path)这里的this表示当前的Path对象,传递一个相对路径与当前路径拼接(如果时绝对路径直接将当前路径修改为传递的绝对路径) createFile(Path)创建一个文件

文件拷贝、移动

//文件拷贝
Path path = Paths.get("/Users/c/Pictures/2309.jpeg");
Files.copy(path,Paths.get("/Users/c/Desktop/1309.jpeg"));
//文件移动
Files.move(path,Paths.get("/Users/c/Desktop/3309.jpeg"));
//文件删除
Files.delete(Paths.get("/Users/c/Desktop/3309.jpeg"));

遍历路径的所有目录

Files.list方法遍历

Path path = Paths.get("/Users/c/Desktop/笔记");
try (Stream<Path> pathStream = Files.list(path)){
    pathStream.forEach(System.out::println);
}

输出: /Users/c/Desktop/笔记/.DS_Store /Users/c/Desktop/笔记/vue /Users/c/Desktop/笔记/oracle /Users/c/Desktop/笔记/java /Users/c/Desktop/笔记/swift /Users/c/Desktop/笔记/计算机 /Users/c/Desktop/笔记/问题记录.rtf /Users/c/Desktop/笔记/c /Users/c/Desktop/笔记/spring

可以发现通过list方法遍历存在一个问题,没有进入到遍历到的目录中去。所以可以说list方法不会进入子目录遍历

Files.walk方法遍历

Path path = Paths.get("/Users/c/Desktop/hello");
try (Stream<Path> pathStream = Files.walk(path)){
    pathStream.forEach(System.out::println);
}

输出: /Users/c/Desktop/hello /Users/c/Desktop/hello/.DS_Store /Users/c/Desktop/hello/world /Users/c/Desktop/hello/world/.DS_Store /Users/c/Desktop/hello/world/www.dat /Users/c/Desktop/hello/www.dat

这个方法在遍历时如果碰到文件夹会进入,并读取文件夹中的内存,然后再接着访问它的同级目录

通过匹配过滤的方式遍历目录

Path path = Paths.get("/Users/c/Desktop/hello");
try (var entries = Files.newDirectoryStream(path,"*.dat")){
    entries.forEach(System.out::println);
}

上面的示例代码只会输出以.dat结尾的文件目录,DirectoryStream是一个目录流,但是他与Stream流没有直接的关系,他只是提供了一个过滤迭代的方法。

访问ZIP压缩文件的目录

Path path = Paths.get("/Users/c/Desktop/e.zip");
FileSystem fs = FileSystems.newFileSystem(path,null);
try (Stream<Path> walk = Files.walk(fs.getPath("/"))){
    walk.forEach(System.out::println);
}

上面的代码会访问指定压缩文件中指定路径/下的所有目录

如何提升文件传输速度

有两种方式可以提高文件读写的速度:内存映射添加缓冲区

内存映射的原理是将文件相关的内容直接映射到内存,这样一来只要去修改内存中的数据,它就会直接映射到文件中去。在nio包中有一个类可以同时完成文件内存映射和产生读写缓冲区的操作FileChannel

示例代码:

//设置一个开始时间点变量
Instant s;
//设置一个结束时间点变量
Instant e;
//这个变量用来计算时间差
Duration time;
s = Instant.now();
//创建一个可读写的‘通道’FileChannel,将文件映射到内存,界限为0——100000
try (FileChannel channel = new RandomAccessFile("/Users/c/Desktop/ceshi.txt","rw").getChannel()){
    MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, 0, 100000);
    for (int i = 0; i < 10000; i++) {
        //将数据推进缓冲区
        map.put("你好吗".getBytes(StandardCharsets.UTF_8));
    }
}
e = Instant.now();
//计算出开始时间和结束时间的间隔(毫秒)
time = Duration.between(s,e);
System.out.println("耗时:"+time.toMillis()+"ms");

s = Instant.now();
try(FileOutputStream outputStream = new FileOutputStream("/Users/c/Desktop/ceshi2.txt");
OutputStreamWriter streamWriter = new OutputStreamWriter(outputStream,StandardCharsets.UTF_8);
PrintWriter writer = new PrintWriter(streamWriter,true)) {
    for (int i = 0; i < 10000; i++) {
        writer.print("你好吗");
    }
}
e = Instant.now();
time = Duration.between(s,e);
System.out.println("耗时:"+time.toMillis()+"ms");

输出: 耗时:14ms 耗时:66ms

在上面的示例代码中,用注释对程序的执行步骤做了说明。通过运行结果可以发现,使用内存映射+缓冲区的方式可以大大提高文件读写的速度,并且数据量越大,它的运行速度与普通的读写方式的速度差距越大。

除了读写速度快,FileChannel还有一个功能,它可以为文件加锁。可以调用这个类的locktryLock方法,前者在获取不到锁时会阻塞;后者在获取不到锁时会返回null。有两种情况会释放锁,第一种情况是FileChannel对象关闭,第二种情况是调用了lock.release方法。

代码示例:

try (FileChannel channel = new RandomAccessFile("/Users/c/Desktop/ceshi.txt", "rw").getChannel();
     FileLock lock = channel.lock()) {
    MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, 0, 100);
    map.put("原谅我这一生不羁放纵爱自由1".getBytes(StandardCharsets.UTF_8));
} catch (FileNotFoundException e) {
    throw new RuntimeException(e);
} catch (IOException e) {
    throw new RuntimeException(e);
}

FileLock lock = channel.lock()放在try中,它会帮我们自动解锁。需要注意的是,这里的锁是针对进程的,对多个线程是无效的。