前言

  • Java进阶课程的第三篇,文件操作相关内容。

  • 学习完基础之后就是进阶的内容了。


包含的知识

  1. File类的操作
  • 创建File对象来表示文件或目录,并通过该对象获取文件信息(如长度、名称、是否为文件等)。
  • 检查文件是否存在,创建新文件,删除文件。
  • 创建单级或多级目录。
  1. 文件和目录的信息获取
  • 使用File对象的方法来获取文件或目录的相关信息,例如:length(), getName(), isFile(), isDirectory(), getAbsoluteFile()等。
  1. 遍历文件夹
  • 列出指定目录下的所有文件和子目录的名字或File对象数组。
  • 递归遍历目录树,查找特定文件(如QQ.exe),并打印其绝对路径。
  1. 字符编码与解码
  • 展示了字符串到字节数组的编码过程,以及从字节数组到字符串的解码过程,指定了具体的字符集(如GBK)以确保正确的编码转换。
  1. 文件搜索
  • 定义了一个静态方法searchFile,用于在给定目录及其子目录中搜索特定文件名的文件。
  1. 文件输入输出流
  • 文件字节输入流InputStream和输出流OutputStream用于读取和写入文件的原始字节。
  • 文件字符输入流Reader和输出流Writer用于处理基于字符的数据,包括读取和写入文本文件。
  • 缓冲流BufferedReader和BufferedWriter用于提高性能,提供按行读取和写入的功能。
  1. 特殊数据流
  • DataOutputStream用于写出基本类型数据到文件中,如字节、整数、浮点数等,还支持写出UTF格式的字符串。
  • DataInputStream用于从文件中读取这些基本类型的数据,确保数据类型的正确性。
  1. 打印流
  • 使用PrintStream或PrintWriter向文件写入不同类型的值,并自动处理换行符。
  1. 文件复制
  • 通过传统的输入输出流进行文件复制。
  • 使用try-with-resources语句简化资源管理,自动关闭流。
  • 使用缓冲流BufferedInputStream和BufferedOutputStream提升文件复制性能。
  • 提供了三种不同的文件复制方法copyFile0、copyFile1和copyFile2,展示了如何使用基础的输入输出流、try-with-resources语句以及缓冲流来进行文件复制。
  1. 异常处理
  • 使用try-catch块捕获可能发生的I/O异常,保证程序的健壮性。
  1. 资源管理
  • 创建实现了Closeable接口的自定义资源类MyConn,使用try-with-resources语句确保流和其他资源在使用完毕后能够被正确关闭,避免资源泄露。
  1. 第三方库的使用
  • 引用了org.apache.commons.io.FileUtils用于简化文件复制和删除操作。
  • 使用FileUtils.copyFile方法快速复制文件。
  • FileUtils.deleteDirectory用于递归删除目录及其内容。

具体代码

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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
package ADV_0;

import org.apache.commons.io.FileUtils;

import java.io.*;
import java.util.Arrays;

public class ADVZc2 {
public static void main(String[] args) throws Exception {
// 创建File对象对文件进行操作
//先在D盘创建一个java开发.jpg的图片文件
System.out.println("======创建File对象获取信息======");
File f1 = new File("M:\\java开发.jpg");//绝对路径,相对路径:不带盘符,默认在工程下寻找文件
System.out.println(f1.length()); // 字节个数
System.out.println(f1.getName());// 文件名
System.out.println(f1.isFile()); // 是否为文件 true
System.out.println(f1.isDirectory()); // 是否为目录 false
System.out.println(f1.getAbsoluteFile()); // 获取绝对路径

System.out.println("======创建不存在的文件和删除======");
File f2 = new File("D:\\wg.txt");
System.out.println(f2.exists()); // 判断是否存在
System.out.println(f2.createNewFile()); // 把这个文件创建出来
System.out.println(f2.delete()); // 删除文件,只能删除空文件,不能删除非空文件夹

System.out.println("======文件夹======");
File f3 = new File("D:\\resource\\aaa");
System.out.println(f3.mkdir()); // mkdir只能创建一级文件夹
File f4 = new File("D:\\ccc\\ddd\\eee");
System.out.println(f4.mkdirs()); // mkdirs可以创建多级文件夹

System.out.println("======获取某个目录下的全部一级文件名称======");
File f5 = new File("D:\\ccc\\ddd\\eee");
String[] names = f5.list(); //获取该目录下的所有文件和子目录的名称数组
for (String name : names) {
System.out.println(name);
}
File[] files = f5.listFiles(); //获取该目录下的所有文件和子目录的File对象数组
for (File file : files) {
System.out.println(file.getAbsoluteFile()); // 获取绝对路径
}

System.out.println("======File遍历一级文件对象的操作======");
File f6 = new File("D:\\ccc\\ddd\\eee");
// 是文件,或者路径不存在时,返回null
// 是空文件夹时,返回一个长度为0的数组[]
// 是一个有内容的文件夹时,将里面所有一级文件和文件夹的路径放在File数组中返回
// 是一个文件夹,且里面有隐藏文件时,将里面所有文件和文件夹的路径放在File数组中返回,包含隐藏文件
// 是一个文件夹,但是没有权限访问该文件夹时,返回null
File[] f7 = f6.listFiles();
System.out.println(Arrays.toString(f7));//[]

System.out.println("--------字符编码和解码--------");
// 1、编码
String name = "噔噔哒哒wgzc777";
// byte[] bytes = name.getBytes(); // idea的UTF-8编码的。
byte[] bytes = name.getBytes("GBK"); // 指定GBK进行编码。
System.out.println(bytes.length);
System.out.println(Arrays.toString(bytes));

// 2、解码
// String name2 = new String(bytes); // 平台的UTF-8解码的。
String name2 = new String(bytes, "GBK");// 指定GBK进行解码
System.out.println(name2);

// 目标:完成文件搜索。找出D:盘下的QQ.exe的位置。
try {
File dir = new File("D:/");
searchFile(dir, "QQ.exe");
} catch (Exception e) {
e.printStackTrace();
}

System.out.println("-----文件字节输入流0-----");
// 文件字节输入流,读取文件中的字节数组到内存中
// 1、创建文件字节输入流管道于源文件接通
// InputStream is = new FileInputStream(new File("src\ADV_0\wgzc00.txt"));
InputStream is0 = new FileInputStream("src\\ADV_0\\wgzc00.txt"); // 简化写法
// 2、每次读取文件中的一个字节并输出
int b;// 定义一个变量记住每次读取的一个字节
while ((b = is0.read()) != -1) {
System.out.print((char) b);
}
// 问题:性能较差,读取汉字输出一定会乱码
is0.close();// 关闭管道

System.out.println("-----文件字节输入流1-----");
InputStream is1 = new FileInputStream("src\\ADV_0\\wgzc00.txt");

// 每次读取文件中的多个字节并输出
byte[] buffer = new byte[3];// 定义一个字节数组用于每次读取字节
int len;// 定义一个变量记住每次读取了多少个字节
while ((len = is1.read(buffer)) != -1) {
// 把读取到的字节数组转换成字符串输出
String str = new String(buffer,0, len);
System.out.print(str);
}
// 问题:性能得到提升,但无法避免读取汉字输出乱码,存在截断汉字字节的可能
is1.close();

System.out.println("-----文件字节输入流2-----");
InputStream is2 = new FileInputStream("src\\ADV_0\\wgzc00.txt"); // 简化写法
// 一次性读完文件的全部字节:可避免读取汉字输出乱码的问题
byte[] bytes1 = is2.readAllBytes();
String rs = new String(bytes1);
System.out.println(rs);
is2.close();


System.out.println("-----文件字节输出流-----");
// OutputStream os = new FileOutputStream("src/ADV_0/wgzc01.txt"); // 覆盖
OutputStream os = new FileOutputStream("src/ADV_0/wgzc01.txt", true); // 追加

// 写入数据
// public void write(int b)
os.write(97); // 写入一个字节数据
os.write('b'); // 写入一个字符数据
os.write("\r\n".getBytes()); // 换行

// 写入字节数组
// public void write(byte[] b)
byte[] bytes0 = "山舞银蛇,原驰蜡象,欲与天公试比高".getBytes();
os.write(bytes0);
os.write("\r\n".getBytes()); // 换行

// 写入字节数组的一部分
// public void write(byte[] b, int pos, int len)
os.write(bytes0, 0, 3);
os.write("\r\n".getBytes()); // 换行
os.close(); // 关闭管道 释放资源

System.out.println("-----文件字节流完成文件复制操作-----");
copyFile0("E:\\wj_old.jpg", "D:\\wj_new.jpg");

System.out.println("-----文件字符输入流-----");
// 目标:掌握资源的新方式:try-with-resource
// 文件字符输入流
try (Reader fr = new FileReader("src\\ADV_0\\wgzc00.txt");) {
// 定义字符数组,每次读多个字符
char[] chs = new char[3];
int lens; // 用于记录每次读取了多少个字符
while ((lens = fr.read(chs)) != -1){
// 每次读取多个字符,并把字符数组转换成字符串输出
String str = new String(chs,0,lens);
System.out.print(str);
}
// 文件字符输入流每次读取多个字符,读取中文
}catch (Exception e){
e.printStackTrace();
}

// 缓冲字符输入流读取字符内容:性能提升了,多了按照行读取文本的能力
try (
// 创建文件字符输入流与源文件接通
Reader fr = new FileReader("src\\ADV_0\\wgzc00.txt");
// 创建缓冲字符输入流包装低级的字符输入流
BufferedReader br = new BufferedReader(fr);
) {
// // 定义一个字符数组,每次读多个字符
// char[] chs = new char[1024];
// int len; // 用于记录每次读取了多少个字符
// while ((len = br.read(chs)) != -1){
// // 3、每次读取多个字符,并把字符数组转换成字符串输出
// String str = new String(chs,0,len);
// System.out.print(str);
// }

// System.out.println(br.readLine());
// System.out.println(br.readLine());
// System.out.println(br.readLine());
// System.out.println(br.readLine());
// System.out.println(br.readLine());
// System.out.println(br.readLine()); // null

// 使用循环改进,来按照行读取数据。
// 定义一个字符串变量用于记住每次读取的一行数据
String line;
while ((line = br.readLine()) != null){
System.out.println(line);
}
// 目前读取文本最优雅的方案:性能好,不乱码,可以按照行读取。
}catch (Exception e){
e.printStackTrace();
}

System.out.println("-----文件字符输出流-----");
try ( //文件字符输出流
// Writer fw = new FileWriter("src\ADV_0\wgzc01.txt"); // 覆盖
Writer fw = new FileWriter("src\\ADV_0\\wgzc01.txt", true); // 追加
){
// public void write(int c)
fw.write('a');
fw.write(98);
fw.write('微');
fw.write("\r\n"); // 换行

// public void write(String str)
fw.write("java");
fw.write(" 大风起兮云飞扬。\n" +
" 威加海内兮归故乡。\n" +
" 安得猛士兮守四方!\n");
fw.write("\r\n"); // 换行

// public void write(String str, int off, int len)
fw.write("java", 1, 2);//av
fw.write("\r\n"); // 换行

// public void write(char[] cbuf)
char[] chars = "java".toCharArray();
fw.write(chars);//java
fw.write("\r\n"); // 换行

// public void write(char[] cbuf, int off, int len)
fw.write(chars, 1, 2);//av
fw.write("\r\n"); // 换行

// fw.flush(); // 刷新缓冲区,将缓冲区中的数据全部写出去,刷新后流可以继续使用
// fw.close(); // 关闭资源,关闭包含刷新,关闭后流不能继续使用
} catch (Exception e) {
e.printStackTrace();
}

// 缓冲字符输出流:提升了字符输出流的写字符的性能,多了换行功能
try (
// Writer fw = new FileWriter("src\ADV_0\wgzc01.txt"); // 覆盖
Writer fw = new FileWriter("src\\ADV_0\\wgzc01.txt", true); // 追加

// 创建一个缓冲字符输出流对象,把字符输出流对象作为构造参数传递给缓冲字符输出流对象
BufferedWriter bw = new BufferedWriter(fw);
){

// 2. 写一个字符出去: public void write(int c)
bw.write('a');
bw.write(98);
bw.write('光');
bw.newLine(); // 换行

// 3、写一个字符串出去: public void write(String str)
bw.write("java");
bw.write("我爱Java,但是Java要找不到工作了");
bw.newLine(); // 换行

// 4、写字符串的一部分出去: public void write(String str, int off, int len)
bw.write("java", 1, 2);
bw.newLine(); // 换行

// 5、写一个字符数组出去: public void write(char[] cbuf)
char[] chars = "java".toCharArray();
bw.write(chars);
bw.newLine(); // 换行

// 6、写字符数组的一部分出去: public void write(char[] cbuf, int off, int len)
bw.write(chars, 1, 2);
bw.newLine(); // 换行
} catch (Exception e) {
e.printStackTrace();
}

System.out.println("-----不同编码读取乱码-----");
// 代码:UTF-8 文件 UTF-8 读取不乱码
// 代码:UTF-8 文件 GBK 读取乱码
try (
// 1、创建文件字符输入流与源文件接通
Reader fr = new FileReader("day03-file-io\\src\\dlei09.txt");
// 2、创建缓冲字符输入流包装低级的字符输入流
BufferedReader br = new BufferedReader(fr);
) {
// 定义一个字符串变量用于记住每次读取的一行数据
String line;
while ((line = br.readLine()) != null){
System.out.println(line);
}
}catch (Exception e){
e.printStackTrace();
}

// 使用字符输入转换流InputStreamReader解决不同编码读取乱码的问题
try (
// 先提取文件的原始字节流
InputStream is = new FileInputStream("day03-file-io\\src\\dlei09.txt");
// 指定字符集把原始字节流转换成字符输入流
Reader isr = new InputStreamReader(is, "GBK");
// 2、创建缓冲字符输入流包装低级的字符输入流
BufferedReader br = new BufferedReader(isr);
) {
// 定义一个字符串变量用于记住每次读取的一行数据
String line;
while ((line = br.readLine()) != null){
System.out.println(line);
}
}catch (Exception e){
e.printStackTrace();
}

System.out.println("-----特殊数据流-----");
try (
DataOutputStream dos = new DataOutputStream(new FileOutputStream("day03-file-io\\src\\data.txt"));
){
dos.writeByte(34);
dos.writeUTF("你好");
dos.writeInt(3665);
dos.writeDouble(9.9);
}catch (Exception e){
e.printStackTrace();
}

// 目标:特殊数据流的使用。
try (
DataInputStream dis = new DataInputStream(new FileInputStream("day03-file-io\\src\\data.txt"));
){
System.out.println(dis.readByte());
System.out.println(dis.readUTF());
System.out.println(dis.readInt());
System.out.println(dis.readDouble());
}catch (Exception e){
e.printStackTrace();
}

// 目标:打印流的使用。
try (
// PrintStream ps = new PrintStream("day03-file-io/src/ps.txt");
PrintStream ps = new PrintStream(new FileOutputStream("day03-file-io/src/ps.txt", true));
// PrintWriter ps = new PrintWriter("day03-file-io/src/ps.txt");
){
ps.println(97);
ps.println('a');
ps.println("黑马");
ps.println(true);
ps.println(99.9);
}catch (Exception e){
e.printStackTrace();
}

// 目标:IO框架
FileUtils.copyFile(new File("E:\\wj_old.jpg"), new File("D:\\wj_new.jpg"));

//JDK 7提供的
//Files.copy(Path.of("E:\\wj_old.jpg"), Path.of(""D:\\wj_new.jpg"));

FileUtils.deleteDirectory(new File("E:\\resource\\图片"));//删除
}

/**
* 搜索文件
* @param dir 搜索的目录
* @param fileName 搜索的文件名称
*/
public static void searchFile(File dir, String fileName) throws Exception {
// 1、判断极端情况
if (dir == null || !dir.exists() || dir.isFile()) {
return; // 不搜索
}

// 2、获取目录下的所有一级文件或者文件夹对象
File[] files = dir.listFiles();

// 3、判断当前目录下是否存在一级文件对象,存在才可以遍历
if (files != null && files.length > 0) {
// 4、遍历一级文件对象
for (File file : files) {
// 5、判断当前一级文件对象是否是文件
if (file.isFile()) {
// 6、判断文件名称是否和目标文件名称一致
if (file.getName().contains(fileName)) {
System.out.println("找到目标文件:" + file.getAbsolutePath());
Runtime r = Runtime.getRuntime();
r.exec(file.getAbsolutePath());
}
} else {
// 7、如果当前一级文件对象是文件夹,则继续递归调用
searchFile(file, fileName);
}
}
}
}

public static void copyFile0(String srcPath, String destPath) {
// 文件字节输入流管道与源文件接通
InputStream fis = null;
OutputStream fos = null;
try {
fis = new FileInputStream(srcPath);
fos = new FileOutputStream(destPath);
// 2、读取一个字节数组,写入一个字节数组 1024 + 1024 + 3
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len); // 读取多少个字节,就写入多少个字节
}
System.out.println("复制成功!");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 最后一定会执行一次:即便程序出现异常!
// 3、释放资源
try {
if(fos != null) fos.close();
} catch (Exception e) {
e.printStackTrace();
}
try {
if(fis != null) fis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

public static void copyFile1(String srcPath, String destPath) {
// 文件字节输入流管道与源文件接通
try (
// 这里只能放置资源对象,用完后,最终会自动调用其close方法关闭!!
InputStream fis = new FileInputStream(srcPath);
OutputStream fos = new FileOutputStream(destPath);
MyConn conn = new MyConn(); // 自定义的资源对象 最终会自动调用其close方法关闭!!
){
// 读取一个字节数组,写入一个字节数组 1024 + 1024 + 3
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len); // 读取多少个字节,就写入多少个字节
}
System.out.println("复制成功!");
} catch (Exception e) {
e.printStackTrace();
}
}

public static void copyFile2(String srcPath, String destPath) {
//文件字节输入流管道与源文件接通
try (
// 这里只能放置资源对象,用完后,最终会自动调用其close方法关闭!!
InputStream fis = new FileInputStream(srcPath);
// 把低级的字节输入流包装成高级的缓冲字节输入流
InputStream bis = new BufferedInputStream(fis);

OutputStream fos = new FileOutputStream(destPath);
// 把低级的字节输出流包装成高级的缓冲字节输出流
OutputStream bos = new BufferedOutputStream(fos);
){
// 读取一个字节数组,写入一个字节数组 1024 + 1024 + 3
byte[] buffer = new byte[1024];
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len); // 读取多少个字节,就写入多少个字节
}
System.out.println("复制成功!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
class MyConn implements Closeable {
@Override
public void close() throws IOException {
System.out.println("资源关闭!");
}
}