3.文件编程
3.1.FileChannel
FileChannel只能工作在非阻塞模式下面,不能和selector一起使用
获取
不能直接打开FIleChannel,必须通过FileInputSream,或者FileOutputSetream ,或者RandomAccessFile来获取FileChannel
- 通过FileInputSream获取的channel只能读
- 通过FileOutputSetream 获取的channel只能写
- 通过RandomAccessFile 是否能读写,根据构造时指定的读写模式相关(“r”,“w”)
读取
会从channel读取数据填充到ByteBuffer中,返回的值,表示读到了多少字节,-1表示到达了文件的末尾
int read = channel.read(buffer);
写入
在while中调用 write方法,是因为 write方法并不能保证一次将buffer中的内容全部写入channel中
public void test4(){
try (FileChannel channel = new RandomAccessFile("data.txt", "rw").getChannel()) {
ByteBuffer b = ByteBuffer.allocate(10);
b.put((byte) 'a'); // 存入数据
b.put((byte) 'b'); // 存入数据
b.put((byte) 'c'); // 存入数据
b.flip(); // 切换为读模式
// 在while中调用 write方法,是因为 write方法并不能保证一次将buffer中的内容全部写入channel中
while (b.hasRemaining()){
// 写入数据
channel.write(b);
}
} catch (IOException e) {
}
}
关闭
channel必须关闭,不过调用了FileInoutStream,FileOutputStream,或者RandomAccessFile的close方法会间接的调用channle的close方法
位置
channel.position
是 Java NIO 中用于获取通道(Channel)当前的位置的方法。通道的位置表示从数据源(如文件或网络连接)的开头到当前位置之间的字节数。
@Test
@DisplayName("测试channel.position()方法")
public void test5(){
try (FileChannel channel = new RandomAccessFile("data.txt", "r").getChannel()) {
// 获取当前通道的位置
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put((byte) 'a');
buffer.put((byte) 'a');
buffer.put((byte) 'a');
// 切换为读模式
buffer.flip();
channel.read(buffer);
long position = channel.position();
logger.error("Current position: {}", position);
// 在文件中移动位置,假设移动到文件的开头
channel.position(0);
// 再次获取当前通道的位置
position = channel.position();
logger.error("Current position: {}", position);
} catch (IOException e) {
e.printStackTrace();
}
}
channel.position()
返回当前通道的位置。channel.position(0)
将通道的位置移动到文件的开头。- 通过调用
position()
方法,你可以控制从文件的哪个位置读取数据,或者从哪个位置开始写入
大小
使用size可以获取文件的大小
long size = channel.size();
强制写入
强制写入操作可以看作是将缓冲区中的数据内容直接写入到磁盘上,而
不依赖于操作系统的延迟写入策略
(因为出于性能考虑,操作系统会将数据进行缓存,而不是立刻写入磁盘)。这样可以保证写入数据的即时性和持久性,但同时也会增加写入操作的开销和系统的负载。
// 假设 channel 是一个 FileChannel 对象
channel.force(true); // 执行强制写入操作
3.2.两个Channel之间传递数据
transferTo()
方法是 Java NIO 中的一个用于通道之间数据传输的方法。这个方法允许将数据从一个通道直接传输到另一个通道,而不需要中间缓冲区。
在Java NIO中,数据可以在通道之间直接传输,而不必经过缓冲区。这种直接传输的方式在大数据量传输时能够提高性能并降低内存消耗。
transferTo()
方法通常用于将一个通道的数据传输到另一个通道,例如将一个文件通道的内容传输到网络套接字通道,或者将一个输入流传输到输出流。
零拷贝,transferTo()
底层就是使用了零拷贝进行优化
- 当调用
transferTo()
方法时,底层操作系统会尝试将数据直接从源通道传输到目标通道,而不需要经过用户空间的缓冲区。 - 操作系统会使用DMA(直接内存访问)技术,从源文件的内核缓冲区中直接读取数据,并将数据直接写入目标文件的内核缓冲区中。
- 这样,数据不需要经过用户空间的缓冲区,也不需要额外的数据复制操作,从而实现了零拷贝的数据传输
@Test
@DisplayName("两个Channel之间传递数据")
public void test6(){
try(FileChannel FROM = new FileInputStream("data.txt").getChannel();
FileChannel TO = new FileOutputStream("data2.txt").getChannel();) {
// 1.从FROM中读取数据 TO中写入数据 但是最大只能传输2G
// 2.left变量表示还剩余多少字节没有传输
long size = FROM.size();
for (long left = size; left > 0;){
// 每次传输的字节大小 会返回
long l = FROM.transferTo((size-left), left, TO);
// 3.每次传输完毕后,更新left的值
left -= l;
}
} catch (IOException e) {
e.printStackTrace();
}
}
3.3.Path
JDK 7 以后引入 Path 和 Paths两个类
-
**Path:**用来表示文件的路径
-
创建路径:
Paths.get(String first, String... more)
:创建路径实例。Path resolve(String other)
:解析相对路径。
-
获取路径信息:
Path getFileName()
:获取路径中的文件名部分。Path getParent()
:获取路径中的父路径部分。int getNameCount()
:获取路径的名称元素数量。Path getName(int index)
:获取路径中指定索引位置的名称元素。
-
判断路径属性:
boolean isAbsolute()
:判断路径是否为绝对路径。boolean startsWith(String other)
/boolean endsWith(String other)
:判断路径是否以指定字符串开始或结束。
-
转换路径:
Path toAbsolutePath()
:将路径转换为绝对路径。Path relativize(Path other)
:获取当前路径相对于另一路径的相对路径。
-
比较路径:
int compareTo(Path other)
:比较两个路径的字典顺序。
-
判断文件系统操作:
boolean exists()
:判断路径所代表的文件或目录是否存在。boolean isRegularFile()
/boolean isDirectory()
:判断路径表示的是否为普通文件或目录。boolean isReadable()
/boolean isWritable()
/boolean isExecutable()
:判断文件是否可读、可写、可执行。
-
操作路径:
Path normalize()
:规范化路径,解析.
和..
等符号。Path resolveSibling(Path other)
:返回当前路径的父路径与给定路径的相对路径组合而成的路径。void createDirectory()
:创建一个目录。void createFile()
:创建一个文件。
-
遍历目录:
DirectoryStream<Path> newDirectoryStream(Path dir)
:返回目录中的条目的目录流。
-
读取文件内容:
byte[] readAllBytes()
:读取文件的所有字节并返回一个字节数组。List<String> readAllLines()
:读取文件的所有行并返回一个字符串列表。
-
删除文件或目录:
-
boolean deleteIfExists()
:删除指定的文件或目录。
-
-
**Paths:**是工具类,用来获取Path的实例
// 相对路径,根据user.dir 环江变量来定位 data.txt
Path path = Paths.get("data.txt");
logger.error("path: {}", path.toAbsolutePath());
// 绝对路径
Paths.get("D:\\dcjet\\java_base_study\\data.txt");
logger.error("path: {}", path.toAbsolutePath());
// 绝对路径
Paths.get("D:/dcjet/java_base_study/data.txt");
logger.error("path: {}", path.toAbsolutePath());
// 绝对路径
Paths.get("D:\\dcjet\\java_base_study\\", "data.txt");
logger.error("path: {}", path.toAbsolutePath());
.
和 ..
是用于表示目录结构中的当前目录和父目录的特殊符号。
.
:表示当前目录,即当前所在位置的目录。..
:表示父目录,即当前目录的上一级目录。
root/
├── documents/
│ ├── file1.txt
├── pictures/
└── videos/
在 documents
目录中,.
表示 documents
目录本身,..
表示 root
目录
3.4.Files
-
复制文件或目录:
Path source = Paths.get("source.txt"); Path target = Paths.get("target.txt"); Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
-
移动文件或目录:
Path source = Paths.get("source.txt"); Path target = Paths.get("target.txt"); Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
-
删除文件或目录:
Path path = Paths.get("file.txt"); // 如果目录还有文件 会有报错 // 如果删除的目录 不存在 也会报错 Files.delete(path);
-
创建文件或目录:
// 可以创建多级目录 Path dir = Paths.get("test/d1/d2"); // 只能创建一级目录 Files.createDirectory(dir); // 创建多级目录 Files.createDirectories(dir);
-
读取文件内容:
Path path = Paths.get("file.txt"); byte[] bytes = Files.readAllBytes(path);
-
写入文件内容:
Path path = Paths.get("file.txt"); List<String> lines = Arrays.asList("Hello", "World"); Files.write(path, lines, StandardCharsets.UTF_8);
-
判断文件或目录属性:
Path path = Paths.get("file.txt"); boolean exists = Files.exists(path); boolean isRegularFile = Files.isRegularFile(path);
-
比较文件内容:
Path path1 = Paths.get("file1.txt"); Path path2 = Paths.get("file2.txt"); boolean isSameFile = Files.isSameFile(path1, path2);
-
获取文件或目录属性:
Path path = Paths.get("file.txt"); FileStore fileStore = Files.getFileStore(path);
-
遍历目录:
Path dir = Paths.get("directory"); try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) { for (Path entry : stream) { System.out.println(entry.getFileName()); } } catch (IOException e) { e.printStackTrace(); }
-
其他操作:
遍历文件夹
@Test @DisplayName("遍历文件夹") public void test8() { Path path = Paths.get("D:\\dcjet\\java_base_study\\src\\main\\java\\com\\hrfan\\java_se_base\\netty\\nio"); try { // 遍历文件夹(访问者模式应用) Files.walkFileTree(path, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { // 遍历之前 logger.error("file: {}", file); return FileVisitResult.CONTINUE; } }); } catch (IOException e) { e.printStackTrace(); } }
-
删除多级目录
在这个案例中使用
Files.walkFileTree()
方法遍历了目录树,并在visitFile()
方法中删除了每个文件,在postVisitDirectory()
方法中删除了每个目录。注意,删除操作会递归删除目录中的所有文件和子目录。@Test @DisplayName("删除目录") public void test9() { AtomicInteger atomicInteger = new AtomicInteger(0); Path path = Paths.get("D:\\aa\\adsa"); try { // 遍历文件夹 Files.walkFileTree(path, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); // 删除文件 logger.error("delete file: {}", file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { // 删除目录(退出时删除目录因为此时目录中没有文件了) Files.delete(dir); logger.error("delete dir: {}", dir); return super.postVisitDirectory(dir, exc); } }); // 最终遍历完成 logger.error("get file number: {}", atomicInteger.get()); } catch (IOException e) { e.printStackTrace(); } }
-