文件操作工具类
# 文件操作工具类完整
# FileUtil
package com.scholar.utils;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.file.*;
/**
* 文件处理工具类,提供文件的创建、读取、写入、删除、复制、输出等功能
* @author WuYimin
*/
@Slf4j
public class FileUtil {
/**
* 创建文件
* @param filePath 文件路径
* @return 如果创建成功返回true,文件已存在返回false
*/
public static boolean createFile(String filePath) {
File file = new File(filePath);
try {
if (!file.exists()) {
// 检查父目录是否存在,不存在则创建
file.getParentFile().mkdirs();
return file.createNewFile();
}
return false;
} catch (IOException e) {
log.error("创建文件失败: {}", e.getMessage());
return false;
}
}
/**
* 创建目录
* @param directoryPath 目录路径
* @return 如果创建成功返回true,目录已存在返回false
*/
public static boolean createDirectory(String directoryPath) {
File directory = new File(directoryPath);
if (!directory.exists()) {
return directory.mkdirs();
}
return false;
}
/**
* 写入文本到文件
* @param filePath 文件路径
* @param content 写入的文本内容
* @param append 是否追加内容
* @return 写入成功返回true
*/
public static boolean writeFile(String filePath, String content, boolean append) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath, append))) {
writer.write(content);
writer.newLine(); // 换行
return true;
} catch (IOException e) {
log.error("写入文件失败: {}", e.getMessage());
return false;
}
}
/**
* 读取文件为字符串
* @param filePath 文件路径
* @return 文件内容
*/
public static String readFileToString(String filePath) {
try {
return new String(Files.readAllBytes(Paths.get(filePath)));
} catch (IOException e) {
log.error("读取文件失败: {}", e.getMessage());
return null;
}
}
/**
* 删除文件
* @param filePath 文件路径
* @return 删除成功返回true
*/
public static boolean deleteFile(String filePath) {
File file = new File(filePath);
if (file.exists()) {
return file.delete();
}
return false;
}
/**
* 复制文件
* @param sourcePath 源文件路径
* @param destinationPath 目标文件路径
* @return 复制成功返回true
*/
public static boolean copyFile(String sourcePath, String destinationPath) {
try {
Files.copy(Paths.get(sourcePath), Paths.get(destinationPath), StandardCopyOption.REPLACE_EXISTING);
return true;
} catch (IOException e) {
log.error("复制文件失败: {}", e.getMessage());
return false;
}
}
/**
* 移动文件
* @param sourcePath 源文件路径
* @param destinationPath 目标文件路径
* @return 移动成功返回true
*/
public static boolean moveFile(String sourcePath, String destinationPath) {
try {
Files.move(Paths.get(sourcePath), Paths.get(destinationPath), StandardCopyOption.REPLACE_EXISTING);
return true;
} catch (IOException e) {
log.error("移动文件失败: {}", e.getMessage());
return false;
}
}
/**
* 获取文件大小
* @param filePath 文件路径
* @return 文件大小(以字节为单位)
*/
public static long getFileSize(String filePath) {
File file = new File(filePath);
if (file.exists()) {
return file.length();
}
return -1;
}
/**
* 判断文件是否存在
* @param filePath 文件路径
* @return 文件存在返回true
*/
public static boolean fileExists(String filePath) {
File file = new File(filePath);
return file.exists();
}
/**
* 读取文件并输出到 HttpServletResponse
* 用于文件下载功能
* @param response HttpServletResponse对象
* @param filePath 文件路径
*/
public static void readFile(HttpServletResponse response, String filePath) {
if (!pathIsOk(filePath)) {
log.warn("文件路径不合法: {}", filePath);
return;
}
OutputStream out = null;
FileInputStream in = null;
try {
File file = new File(filePath);
if (!file.exists()) {
log.warn("文件不存在: {}", filePath);
return;
}
in = new FileInputStream(file);
byte[] byteData = new byte[1024];
out = response.getOutputStream();
int len;
while ((len = in.read(byteData)) != -1) {
out.write(byteData, 0, len);
}
out.flush();
} catch (Exception e) {
log.error("读取文件时发生异常: {}", e.getMessage());
} finally {
closeStream(out);
closeStream(in);
}
}
/**
* 复制源目录下的所有文件到目标目录,保持目录结构
* @param sourceDirPath 源目录路径
* @param destinationDirPath 目标目录路径
*/
public static void copyDirectory(String sourceDirPath, String destinationDirPath) {
Path sourceDir = Paths.get(sourceDirPath);
Path destinationDir = Paths.get(destinationDirPath);
try {
// 检查目标目录是否存在,不存在则创建
if (!Files.exists(destinationDir)) {
Files.createDirectories(destinationDir);
}
// 遍历源目录中的所有文件和子目录
Files.walk(sourceDir).forEach(sourcePath -> {
try {
// 计算目标路径
Path targetPath = destinationDir.resolve(sourceDir.relativize(sourcePath));
// 如果是目录,创建对应的目录
if (Files.isDirectory(sourcePath)) {
if (!Files.exists(targetPath)) {
Files.createDirectories(targetPath);
}
}
// 如果是文件,复制文件
else {
Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING);
log.info("文件复制成功: {} -> {}", sourcePath, targetPath);
}
} catch (IOException e) {
log.error("文件复制失败: {}", e.getMessage());
}
});
} catch (IOException e) {
log.error("遍历目录时发生异常: {}", e.getMessage());
}
}
/**
* 关闭流的方法,避免资源泄漏
* @param closeable 需要关闭的流对象
*/
private static void closeStream(AutoCloseable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (Exception e) {
log.error("关闭流时发生异常: {}", e.getMessage());
}
}
}
/**
* 检查文件路径是否合法
* @param path 文件路径
* @return 路径合法返回true
*/
private static boolean pathIsOk(String path) {
return path != null && !path.trim().isEmpty();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# 功能介绍
# 1. 创建文件 (createFile
)
public static boolean createFile(String filePath)
1
- 功能:根据指定的文件路径创建一个新的文件。如果文件已经存在,则不会重新创建。
- 参数:
filePath
:要创建的文件的路径。
- 返回值:成功创建返回
true
,文件已存在或创建失败返回false
。
# 2. 创建目录 (createDirectory
)
public static boolean createDirectory(String directoryPath)
1
- 功能:根据指定路径创建一个新的目录。如果目录已经存在,则不会重新创建。
- 参数:
directoryPath
:要创建的目录路径。
- 返回值:成功创建返回
true
,目录已存在或创建失败返回false
。
# 3. 写入文本到文件 (writeFile
)
public static boolean writeFile(String filePath, String content, boolean append)
1
- 功能:将文本内容写入指定文件,可以选择是追加模式还是覆盖模式。
- 参数:
filePath
:文件路径。content
:要写入的文本内容。append
:true
表示追加模式,false
表示覆盖模式。
- 返回值:成功写入返回
true
,失败返回false
。
# 4. 读取文件为字符串 (readFileToString
)
public static String readFile
ToString(String filePath)
1
2
3
2
3
- 功能:将指定路径的文件内容读取为字符串。
- 参数:
filePath
:要读取的文件路径。
- 返回值:返回文件内容的字符串。如果读取失败,返回
null
。
# 5. 删除文件 (deleteFile
)
public static boolean deleteFile(String filePath)
1
- 功能:根据指定的文件路径删除文件。
- 参数:
filePath
:要删除的文件路径。
- 返回值:成功删除返回
true
,文件不存在或删除失败返回false
。
# 6. 复制文件 (copyFile
)
public static boolean copyFile(String sourcePath, String destinationPath)
1
- 功能:将源文件复制到目标文件位置。如果目标文件已存在,将会被覆盖。
- 参数:
sourcePath
:源文件路径。destinationPath
:目标文件路径。
- 返回值:成功复制返回
true
,失败返回false
。
# 7. 移动文件 (moveFile
)
public static boolean moveFile(String sourcePath, String destinationPath)
1
- 功能:将源文件移动到目标路径。如果目标路径中有同名文件,将会被覆盖。
- 参数:
sourcePath
:源文件路径。destinationPath
:目标文件路径。
- 返回值:成功移动返回
true
,失败返回false
。
# 8. 获取文件大小 (getFileSize
)
public static long getFileSize(String filePath)
1
- 功能:获取指定文件的大小,单位为字节。
- 参数:
filePath
:文件路径。
- 返回值:返回文件大小(字节数),如果文件不存在,返回
-1
。
# 9. 判断文件是否存在 (fileExists
)
public static boolean fileExists(String filePath)
1
- 功能:检查指定路径的文件是否存在。
- 参数:
filePath
:文件路径。
- 返回值:文件存在返回
true
,否则返回false
。
# 10. 读取文件并输出到 HttpServletResponse
(readFile
)
public static void readFile(HttpServletResponse response, String filePath)
1
- 功能:将指定文件的内容读取并输出到 HTTP 响应流中,用于文件下载。
- 参数:
response
:HttpServletResponse
对象。filePath
:文件路径。
- 返回值:无。
# 11. 复制目录及其内容 (copyDirectory
)
public static void copyDirectory(String sourceDirPath, String destinationDirPath)
1
- 功能:将源目录下的所有文件和子目录复制到目标目录,保持原目录结构。
- 参数:
sourceDirPath
:源目录路径。destinationDirPath
:目标目录路径。
- 返回值:无。
# 12. 关闭流的方法 (closeStream
)
private static void closeStream(AutoCloseable closeable)
1
- 功能:通用的流关闭方法,确保在使用完流后及时关闭,避免资源泄漏。
- 参数:
closeable
:需要关闭的流对象。
- 返回值:无。
# 13. 检查文件路径是否合法 (pathIsOk
)
private static boolean pathIsOk(String path)
1
- 功能:检查文件路径是否为空或非法。
- 参数:
path
:文件路径。
- 返回值:合法返回
true
,否则返回false
。
# 文件拷贝方式
在文件操作中,直接复制文件与读取文件内容并写入到另一个文件的性能差异主要取决于实现方式和使用的 I/O 操作类型。
# 1. 直接文件复制(Files.copy
或 FileChannel.transferTo
)
Files.copy()
是 Java NIO 提供的文件复制方法,它背后可能会使用操作系统级别的文件复制功能(如 FileChannel.transferTo
或者基于零拷贝技术)。操作系统级别的复制通常比手动读取和写入快得多,因为它们能够减少用户空间和内核空间之间的数据拷贝次数,提升 I/O 效率。
优点:
- 更高效:由于
Files.copy
可能使用了底层的文件系统 API 或零拷贝(zero-copy),可以减少 I/O 操作中的开销(如上下文切换、缓冲区复制等)。 - 代码简洁:调用一个方法即可实现复制,逻辑清晰。
缺点:
- 有限的控制:相比手动读写文件,
Files.copy
对于文件内容处理的控制较少。例如,如果你需要对文件内容进行修改或过滤,可能就需要手动读取和写入。
# 2. 手动读取文件并写入文件
手动读取文件内容并写入到另一个文件中,通常使用 FileInputStream
和 FileOutputStream
或者通过 BufferedReader
和 BufferedWriter
。这种方法需要你逐个字节或块地读取文件并将其写入目标文件,性能会依赖于读写的缓冲区大小和操作系统对小块数据处理的效率。
优点:
- 灵活性:你可以在文件读取和写入时处理内容(例如过滤、修改数据等)。
- 更细粒度的控制:你可以控制读写的块大小、缓冲区管理、异常处理等。
缺点:
- 性能可能较差:相比直接调用操作系统 API,手动读取和写入会涉及更多的 I/O 操作和数据拷贝(从磁盘到内存,再从内存到目标文件),增加上下文切换,降低效率。
- 代码复杂性:手动处理文件需要编写更多代码,涉及流的打开、关闭、异常处理等。
性能对比
直接复制的效率(
Files.copy
)- 直接复制使用了底层文件系统 API,如
FileChannel.transferTo
或操作系统的文件复制命令,这使得它的性能优于逐行或逐块读取、写入的方案。 - 当文件非常大时,直接复制文件更为高效,因为它减少了 CPU 参与的数据拷贝工作。
- 直接复制使用了底层文件系统 API,如
手动读取与写入
- 当你需要处理文件内容时,手动读取和写入是必须的。但是,如果仅仅是文件内容的转移而无需修改,手动读写通常比直接复制慢。
- 手动操作会涉及多次磁盘 I/O 和内存之间的数据交换,而直接复制可能在内核空间内完成,避免了不必要的上下文切换。
# 实验比较
如果你想通过实验来比较这两种方法的性能,以下是两种方法的示例代码:
# 方法 1:使用 Files.copy()
进行直接文件复制
public static void copyFileUsingFilesCopy(String sourcePath, String destPath) {
Path source = Paths.get(sourcePath);
Path destination = Paths.get(destPath);
try {
Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING);
System.out.println("文件复制完成");
} catch (IOException e) {
e.printStackTrace();
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 方法 2:手动读取并写入文件
public static void copyFileUsingStreams(String sourcePath, String destPath) {
try (FileInputStream in = new FileInputStream(new File(sourcePath));
FileOutputStream out = new FileOutputStream(new File(destPath))) {
byte[] buffer = new byte[1024]; // 使用1KB的缓冲区
int bytesRead;
// 逐块读取并写入
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
System.out.println("文件复制完成");
} catch (IOException e) {
e.printStackTrace();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 性能测试
为了比较性能,你可以使用 System.nanoTime()
或 System.currentTimeMillis()
来测量两个方法的执行时间:
public static void main(String[] args) {
String sourcePath = "/path/to/sourceFile";
String destPath1 = "/path/to/destinationFile1";
String destPath2 = "/path/to/destinationFile2";
long startTime = System.nanoTime();
copyFileUsingFilesCopy(sourcePath, destPath1);
long duration = System.nanoTime() - startTime;
System.out.println("使用 Files.copy 耗时: " + duration + " 纳秒");
startTime = System.nanoTime();
copyFileUsingStreams(sourcePath, destPath2);
duration = System.nanoTime() - startTime;
System.out.println("使用手动流复制耗时: " + duration + " 纳秒");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
结论:
对于大文件:
Files.copy
更快,因为它可能调用了底层操作系统的优化机制,减少了用户空间和内核空间之间的数据拷贝。- 手动读取和写入 的性能较差,尤其当文件非常大时,这种方法会因为频繁的 I/O 操作和上下文切换而降低性能。
对于小文件:
- 差距可能不会很明显,特别是在 I/O 操作较快的情况下。
功能需求:
- 如果你只需要简单的文件复制,
Files.copy()
是首选。 - 如果你需要在复制过程中处理文件内容(如过滤、修改),你只能使用手动读取和写入的方式。
- 如果你只需要简单的文件复制,
如果你不需要处理文件内容,直接使用 Files.copy()
是更好的选择,尤其是在文件较大或需要频繁复制时。
编辑此页 (opens new window)
上次更新: 2024/12/28, 18:32:08