Java IO(下篇——文件操作)
在上篇文章中,主要描述了文件内容的读写数据;而这篇文章则介绍针对文件的读写。
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
还有一个功能,它可以为文件加锁。可以调用这个类的lock
或tryLock
方法,前者在获取不到锁时会阻塞;后者在获取不到锁时会返回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
中,它会帮我们自动解锁。需要注意的是,这里的锁是针对进程的,对多个线程是无效的。
转载自:https://juejin.cn/post/7236736926640111671