文件操作 —— IO
1. 文件的属性
- 文件内容
- 文件大小
- 文件路径
- 文件名称
2. 文件的管理
采用树形结构进行管理。
3. 文件路径
分为两种:相对、绝对路径。
- 相对路径:相对于当前位置的路径,以“./xxx.xxx”为标志
- 绝对路径:以从盘符开始到某一级的路径
4. 输入和输出——Input/Output
输入和输出是以CPU的角度进行观察。
输出:
- 向屏幕上打印,就是打印内容从CPU流出,外设,所以是 输出。
- 向文件中写内容,就是将内容从CPU流出,到文件,所以是 输出。
输入:
- 从键盘上输入字符,就是字符从外设流入到CPU,所以是 输入。
- 从文件中读取内容,就是内容从文件流入到CPU,所以是 输入。
5. 字节和字符
按照读取输入的方式不同可以分为按照字符流、字节流两种情况。
- 字符流,按照字符编码规则,每次读取一个字符,根据指定的编码规则对读取到的字节进行解析,最终正确显示字符
- 字节流,按照字节进行读取,每次读取一个字节(取决于给的byte[]是多长的,在read方法中传入的参数是多大)
方式 问题 字符流 只能操作文本文件 字节流 操作任意类型的文件
- 但是字符流如果编码格式与文件不同步,那么也可能造成乱码问题。
6. deleteOnExit
退出时删除文件,常用于临时文件的删除。
会在程序退出时将文件删除。
7. 文件内容的读写 —— 数据流
7.1 数据流的分类
数据流分为两大类,分为字符流(Reader和Writer)、字节流(InputStream和OutputStream)。
其中字符流适合使用于纯文本文件(使用记事本打开不是乱码的文件),字节流适用于任何文件。
因为字符流每次读一个字符,**根据编码规则能够自动选取读几个字节,**图像等类文件不适合;
但是字节流不论是什么文件每次就是一个字节,原原本本的进行读出。
7.2 读写文件内容
读写文件存在一种固定套路:
- 打开文件、流
- 读取文件
- 关闭文件、流
代码示例:
import java.io.*; public class test3 { public static void main(String[] args) throws FileNotFoundException { File file = new File(".\test.txt"); // try with resources try(FileInputStream fis = new FileInputStream(file); FileOutputStream fos = new FileOutputStream(file)){ // 写入 String s = "qyy is the best."; fos.write(s.getBytes()); // 读出 int len = -1; byte[] bytes = new byte[1024]; System.out.println(file.getAbsoluteFile() + "的内容为:"); while((len = fis.read(bytes, 0, bytes.length)) != -1) { System.out.println(new String(bytes, 0, len)); } } catch (IOException e) { throw new RuntimeException(e); } // 退出时删除——测试文件 file.deleteOnExit(); } }
上述代码并没有使用close去关闭流,因为在这里使用了一种try with rescoures的语法,简单来说就是之前只是简单地使用try…catch进行捕获异常,这里的try语法在try后面加上了一堆括号,在里面写上需要释放资源的代码。
释放资源也可以将.close()写在finally{}中,但是上述语法更加简洁。
值得一提的是,只有实现了Closeable接口的类,才能放到try()中。比如上述的InputStream:
7.3 read方法——输出型参数
输出型参数本来是一个c/c++中常用的概念。输出型参数在c/c++中是将参数的引用写在参数位置,能够在函数运行完毕后不依赖于返回值的方式得到这个变量的值。好处:
- 能够携带多个返回值
- 避免在调用函数的过程中,函数未能正常返回退栈而导致的资源泄露(资源总是能够在上一级进行释放)
这里的
其有三个参数:
参数 | 说明 |
---|---|
byte[] b | 存放文件内容的输出型参数 |
int off | 从byte[]中第几个位置开始存放,是一个偏移量(offset) |
int len | 读取字节的实际长度,便于构建String变量将文件内容进行输出 |
7.4 Scanner读取文件
Scanner在之前的使用中,均是以
import java.io.FileInputStream; import java.io.FileNotFoundException; import java.util.Scanner; public class test4 { public static void main(String[] args) throws FileNotFoundException { Scanner scanner = new Scanner(new FileInputStream("test.txt")); while (scanner.hasNext()) { System.out.println(scanner.next()); } } }
运行结果:
7.4 几点说明
-
write是首先会将文件内容全部都清空,即使你并没有往文件中输入任何内容,除非添加追加参数
true -
Reader与Writer方法的使用与InputStream和OutputStream相同,不再列出
8. 文件读写基本操作
8.1 文件的查找
因为文件是以“树形”结构构建,所以文件的查找本质上是一种多叉树的遍历。
所以就少不了使用**“递归”**。
代码示例:
import java.io.File; import java.util.regex.Pattern; public class test5 { public static void main(String[] args) { // 文件的查找 // 使用正则表达式进行匹配文件 String regex = "qyy.*"; Pattern pattern = Pattern.compile(regex); File path = new File("D:\code\0java\J2024_01_19");// 从D:code javaJ2024_01_19开始找 searchFile(pattern, path); } private static void searchFile(Pattern targetFile, File path) { File[] files = path.listFiles(); assert files != null;// 路径不能为空 for (File file : files) { if (file.isFile()) { // 是文件——看是否匹配要查找的文件 if (targetFile.matcher(file.getName()).matches()) { System.out.println("Found: " + file.getAbsolutePath()); } } else if (file.isDirectory()) { // 是目录——递归 searchFile(targetFile, file); }else { // null } } } }
值得一提的是,这段代码中使用了Pattern类,利用这个类可以进行正则表达式的匹配。
compile(String regex): 静态方法,用于将给定的正则表达式编译为
Pattern 对象。matcher(CharSequence input): 用于创建一个
Matcher 对象,该对象用于在输入字符序列上执行匹配操作。
8.2 文件的复制
import java.io.*; public class test6 { public static void main(String[] args) { // 实现文件复制 // 从test.txt复制到同级目录下的test2.txt File src = new File("./test.txt"); File dest = new File("./test2.txt"); try(FileInputStream fis = new FileInputStream(src); FileOutputStream fos = new FileOutputStream(dest)) { int len = -1; byte[] bytes = new byte[1024]; while ((len = fis.read(bytes, 0 ,bytes.length)) != -1) { String s = new String(bytes, 0, len); System.out.println(s); // 一边读,一边写 fos.write(s.getBytes()); } } catch (IOException e) { throw new RuntimeException(e); } } }