文件操作和IO
文章目录
- @[toc]
- 一、文件的含义
- 1. 狭义的文件
- 2. 广义的文件
- 3. Java中的标准输入输出
- 二、文件的类型
- 1. 文本文件 vs 二进制文件
- 2. 判断文件类型的方法
- 3. 编码方式(字符集)
- 4. C语言文件操作举例
- 5. Java对文本文件的处理
- 三、文件的操作分类
- 1. 文件系统操作
- 2. 文件内容操作
- 3. 各语言支持
- 四、Java File类详解
- 1. File类的作用
- 2. 常用构造方法
- 3. 常用API方法
- 4. 路径与分隔符
- 5. 相对路径的基准目录
- 五、权限与临时文件
- 六、目录递归遍历
- 七、文件内容操作(IO流基础)
- 1. IO流的概念
- 2. IO流分类
- 八、文件内容的操作
- 1. 打开文件
- 2. 关闭文件
- 关闭文件的三种写法
- 3. 读取文件
- InputStream
- FileInputStream
- Reader
- Scanner
- OutputStream
- 九、小程序练习
- 1. 扫描指定⽬录,并找到名称中包含指定字符的所有普通⽂件(不包含⽬录),并且后续询问⽤⼾是否要删除该⽂件
- 2. 普通⽂件的复制
- 3. 扫描指定⽬录,并找到名称或者内容中包含指定字符的所有普通⽂件(不包含⽬录)
- 十、OJ题高效输入方案(IO优化)
- 1. 问题背景
- 2. 优化思路
- 3. 快速输入类模板
- 4. 优化原理
- 5. 实际应用
文章目录
- @[toc]
- 一、文件的含义
- 1. 狭义的文件
- 2. 广义的文件
- 3. Java中的标准输入输出
- 二、文件的类型
- 1. 文本文件 vs 二进制文件
- 2. 判断文件类型的方法
- 3. 编码方式(字符集)
- 4. C语言文件操作举例
- 5. Java对文本文件的处理
- 三、文件的操作分类
- 1. 文件系统操作
- 2. 文件内容操作
- 3. 各语言支持
- 四、Java File类详解
- 1. File类的作用
- 2. 常用构造方法
- 3. 常用API方法
- 4. 路径与分隔符
- 5. 相对路径的基准目录
- 五、权限与临时文件
- 六、目录递归遍历
- 七、文件内容操作(IO流基础)
- 1. IO流的概念
- 2. IO流分类
- 八、文件内容的操作
- 1. 打开文件
- 2. 关闭文件
- 关闭文件的三种写法
- 3. 读取文件
- InputStream
- FileInputStream
- Reader
- Scanner
- OutputStream
- 九、小程序练习
- 1. 扫描指定⽬录,并找到名称中包含指定字符的所有普通⽂件(不包含⽬录),并且后续询问⽤⼾是否要删除该⽂件
- 2. 普通⽂件的复制
- 3. 扫描指定⽬录,并找到名称或者内容中包含指定字符的所有普通⽂件(不包含⽬录)
- 十、OJ题高效输入方案(IO优化)
- 1. 问题背景
- 2. 优化思路
- 3. 快速输入类模板
- 4. 优化原理
- 5. 实际应用
一、文件的含义
1. 狭义的文件
- 定义:硬盘上的文件(如
.txt
、.docx
)及其保存文件的目录(即“文件夹”)。 - 文件夹 ≈ 目录
2. 广义的文件
- 定义:操作系统中,很多硬件设备和软件资源都被当作“文件”来管理。
- 举例:
- 标准输入(键盘):stdin
- 标准输出(控制台):stdout
- 打印机、网卡等设备
3. Java中的标准输入输出
- 标准输入:
System.in
- 标准输出:
System.out
二、文件的类型
1. 文本文件 vs 二进制文件
类型 | 特点 | 举例 |
---|---|---|
文本文件 | 只包含可直接阅读的字符,能用记事本等直接打开 | .txt 、.java 、.c |
二进制文件 | 内容不可直接阅读,包含非文本数据,直接打开会乱码 | .exe 、.mp3 、.docx 、.class |
- 本质:所有文件底层都是二进制数据,文本文件能通过字符编码表(如UTF-8、GBK)正确转换为字符。
2. 判断文件类型的方法
- 简单粗暴法:用记事本打开,能正常阅读为文本文件,否则为二进制文件。
3. 编码方式(字符集)
- 文本文件需指定字符编码(如UTF-8/GBK)才能正确解析内容。
4. C语言文件操作举例
fopen("c:/test.txt", "r")
:文本模式fopen("c:/test.txt", "rb")
:二进制模式
5. Java对文本文件的处理
- Java读取文本文件时会自动进行编码转换,将底层二进制内容转为字符。
- 读取二进制文件则没有这种转换。
三、文件的操作分类
1. 文件系统操作
- 创建/删除文件、创建/删除目录、重命名、判断文件是否存在等。
2. 文件内容操作
- 读文件、写文件。
3. 各语言支持
- C语言:标准库不直接支持文件系统操作,需调用操作系统API。
- C++:2017年后标准库支持文件系统操作。
- Java:
File
类专门用于文件系统操作。
四、Java File类详解
1. File类的作用
- 用于表示硬盘上的文件或目录(无论是否存在)。
2. 常用构造方法
构造方法 | 说明 |
---|---|
File(String pathname) | 通过路径创建File对象(可绝对、相对路径) |
File(File parent, String child) | 父目录对象+子路径 |
File(String parent, String child) | 父目录路径+子路径 |
3. 常用API方法
方法 | 说明 |
---|---|
getParent() | 返回父目录路径 |
getName() | 返回文件名 |
getPath() | 返回文件路径 |
getAbsolutePath() | 返回绝对路径 |
getCanonicalPath() | 返回规范化绝对路径 |
exists() | 判断文件是否真实存在 |
isDirectory() | 是否为目录 |
isFile() | 是否为普通文件 |
createNewFile() | 创建文件 |
delete() | 删除文件 |
deleteOnExit() | JVM结束时删除文件(常用于临时文件) |
list() | 返回目录下所有文件名字符串数组 |
listFiles() | 返回目录下所有File对象数组 |
mkdir() /mkdirs() | 创建目录/连同中间目录 |
renameTo(File dest) | 文件重命名 |
canRead() /canWrite() | 判断读/写权限 |
4. 路径与分隔符
- Windows:
\
- Linux/Mac:
/
- Java提供
File.separator
自动适配不同系统。
5. 相对路径的基准目录
- IDEA中运行:项目根目录
- 命令行运行:当前命令行所在目录
- 被其他进程调用:父进程目录
- 可用API修改:可通过相关API手动指定
五、权限与临时文件
- 常见异常:
- 空间不足(硬盘满)
- 权限不足(无读/写权限)
- deleteOnExit():常用于临时文件,JVM结束时自动删除。
- 实际应用:如word未保存时自动生成的临时文件。
六、目录递归遍历
list()
、listFiles()
:仅遍历当前目录- 递归遍历:要遍历所有子目录及文件,需递归实现
void listAll(File dir) {File[] files = dir.listFiles();for(File f : files) {if(f.isDirectory()) {listAll(f); // 递归} else {System.out.println(f.getAbsolutePath());}}
}
七、文件内容操作(IO流基础)
1. IO流的概念
-
流:数据像水流一样流动,按需分批读写。
-
把数据视为池中的水
- 写 - 视为通过水龙头灌水 - 输出流
- 读 - 视为水龙头接水 - 输入流
-
计算机中的输入输出,都是以 cpu 视角来谈的
- 数据远离 cpu 就是输出
- 数据靠近 cpu 就是输入
2. IO流分类
分类 | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
适用场景 | 二进制数据(图片、音频等) | 文本数据(txt、csv等) |
-
以
InputStream
/OutputStream
结尾:字节流 -
以
Reader
/Writer
结尾:字符流 -
资源泄露问题:不易察觉,长期积累会导致系统异常,养成良好关闭习惯。
八、文件内容的操作
1. 打开文件
InputStream inputStream = new FileInputStream("./test.txt");
- 每打开一个文件,进程的文件描述符表会增加一个表项,资源有限。
- 未及时关闭文件会导致“文件资源泄露”,影响长期运行的程序。
2. 关闭文件
- **close()**方法释放文件描述符,防止资源泄露。
关闭文件的三种写法
(1) 直接close
适用于简单场景,进程即将结束。
InputStream inputStream = new FileInputStream("./test.txt");
inputStream.close();
(2) try-finally保证关闭
严谨做法,确保异常时也能释放资源。
InputStream inputStream = null;
try {inputStream = new FileInputStream("./test.txt");// 读写操作
} catch (IOException e) {e.printStackTrace();
} finally {try {if (inputStream != null) inputStream.close();} catch (IOException e) {throw new RuntimeException(e);}
}
(3) try-with-resources(推荐)
自动调用close,简洁安全,前提是实现了Closeable
接口。
try() 里面可以写多个对象的构造过程
try (InputStream inputStream = new FileInputStream("./test.txt")) {// 读写操作
} catch (IOException e) {e.printStackTrace();
}
3. 读取文件
InputStream
方法
修饰符及返回值类型 | 方法签名 | 说明 |
---|---|---|
int | read() | 读取一个字节的数据,返回 -1 代表已经完全读完了 |
int | read(byte[] b) | 最多读取 b.length 字节的数据到 b 中,返回实际读到的数量;-1 代表以及读完了 |
int | read(byte[] b, int off, int len) | 最多读取 len - off 字节的数据到 b 中,放在从 off 开始,返回实际读到的数量;-1 代表以及读完了 |
void | close() | 关闭字节流 |
InputStream 只是一个抽象类,要使用还需要具体的实现类。
关于 InputStream 的实现类有很多,基本可以认为不同的输入设备都可以对应一个 InputStream 类。我们现在只关心从文件中读取,所以使用 FileInputStream。
FileInputStream
构造方法
签名 | 说明 |
---|---|
FileInputStream(File file) | 利用 File 构造文件输入流 |
FileInputStream(String name) | 利用文件路径构造文件输入流 |
读取文件的两种方法.相比较而言,后一种的IO次数更好,性能更好.
public static void main(String[] args) throws IOException {try(InputStream inputStream = new FileInputStream("./test.txt")) {while (true){int b=inputStream.read();if (b==-1){break;}System.out.printf("0x%x\n",b);}}
}
public static void main(String[] args) throws IOException {try(InputStream inputStream=new FileInputStream("./test.txt")){while (true){byte[] buffer=new byte[1024];int n= inputStream.read(buffer);if (n==-1){break;}for (int i = 0; i < n; i++) {System.out.printf("0x%x\n",buffer[i]);}}}
}
Reader
Reader也是相同的用法,一次读一个字符
public static void main(String[] args) throws IOException {try(Reader reader=new FileReader("./test.txt")){while (true){char[] buffer=new char[1024];int n= reader.read(buffer);if (n==-1){break;}for (int i = 0; i < n; i++) {System.out.println(buffer[i]);}}}
}
Reader是按字节读取(UTF-8编码)
- 每个汉字是 3 个字节。
而保存是 Java 中的 char
-
每个 char 是 2 个字节。
-
当用 char 表示汉字时,不再使用 UTF-8,而是使用 Unicode。
-
在 Unicode 中,一个汉字就是 2 个字节。
-
使用字符流读取数据时,Java 标准库会自动根据数据的编码进行转码。
Scanner
Scanner 里面可以填写文件的 InputStream
public static void main(String[] args) throws IOException {try(InputStream inputStream=new FileInputStream("./test.txt")){try (Scanner scanner =new Scanner(inputStream)){while (scanner.hasNext()){String s=scanner.next();System.out.println(s);}}}
}
OutputStream
修饰符及返回值类型 | 方法签名 | 说明 |
---|---|---|
void | write(int b) | 写入一个字节的数据 |
void | write(byte[] b) | 将字节数组 b 中的所有数据写入输出流 |
int | write(byte[] b, int off, int len) | 将字节数组 b 从 off 开始的 len 个数据写入输出流 |
void | close() | 关闭字节流 |
void | flush() | 刷新缓冲区,将数据真正写入设备中。 说明:I/O 速度慢,OutputStream 通常会用缓冲区临时存储数据,只有缓冲区满或调用 flush 时才真正写入设备。写完或合适时需调用 flush 以防数据遗留在缓冲区。 |
- OutputStream 只是一个抽象类,实际使用时需要具体实现类。
- 目前只关心写入文件,则使用
FileOutputStream
。
public static void main(String[] args) throws IOException {// 要写入的数据String data = "你好,OutputStream!";// 将字符串转换为字节数组(UTF-8编码)byte[] bytes = data.getBytes();// 创建输出流,写入到文件 "output.txt"try (FileOutputStream fos = new FileOutputStream("output.txt")) {// 1. 写入一个字节(这里只写第一个字节)fos.write(bytes[0]);// 2. 写入整个字节数组fos.write(bytes);// 3. 写入字节数组的一部分(比如从第2个字节开始,写3个字节)fos.write(bytes, 1, 3);// 4. 刷新缓冲区fos.flush();// 5. 关闭流(try-with-resources会自动关闭)System.out.println("写入完成!");} catch (IOException e) {e.printStackTrace();}
}
默认情况下,FileOutputStream
会覆盖原有文件内容。如果你希望\追加内容而不是覆盖,可以使用它的一个特殊构造方法(append)。
FileOutputStream fos = new FileOutputStream("output.txt", true);
九、小程序练习
1. 扫描指定⽬录,并找到名称中包含指定字符的所有普通⽂件(不包含⽬录),并且后续询问⽤⼾是否要删除该⽂件
public static void scan(File directory,String key,Scanner scanner){File[] files=directory.listFiles();for (File file:files){if (file==null){return;}// 如果是目录,则继续遍历if (file.isDirectory()){scan(file,key,scanner);}else{// 如果是文件,则开始查询String name=file.getName();if (name.contains(key)){System.out.println(name+"是否要删除此文件:Y/N");String choise=scanner.next();if (choise.equals("Y")||choise.equals("y")){file.delete();System.out.println("文件已删除");}else{System.out.println("文件未删除");}}}}}public static void main(String[] args) throws IOException {File directory =new File("C:\\Users\\24314\\Desktop\\test");try(Scanner scanner=new Scanner(System.in)){scan(directory,"test",scanner);}}
2. 普通⽂件的复制
public static void main(String[] args) throws IOException {// 普通文件的复制File srcFile = new File("C:\\Users\\24314\\Desktop\\test\\怠美.jpg");File destFile = new File("C:\\Users\\24314\\Desktop\\test\\怠美-复制.jpg");// 判断源文件是否存在if (!srcFile.exists()){System.out.println("源文件不存在");return;}// 判断复制文件是否存在if (destFile.exists()){System.out.println("目标文件已存在,将被覆盖");return;}// 读取源文件try(InputStream inputStream= new FileInputStream(srcFile);OutputStream outputStream=new FileOutputStream(destFile)){byte[] buffer=new byte[1024];while (true){int n=inputStream.read(buffer);if (n==-1){break;}outputStream.write(buffer,0,n);}}
}
3. 扫描指定⽬录,并找到名称或者内容中包含指定字符的所有普通⽂件(不包含⽬录)
public static void scan(File directory,String key) throws IOException {File[] files=directory.listFiles();if (files==null){return;}for (File file:files){if (file.isDirectory()){scan(file,key);}else{try(Reader reader=new FileReader(file)){char[] buffer=new char[1024];StringBuilder tmp=new StringBuilder();while (true){int n= reader.read(buffer);if (n==-1){break;}tmp.append(buffer,0,n);}if (tmp.toString().contains(key)){System.out.println("文件"+file.getName()+":"+tmp);System.out.println(file.getName());}}}}
}
public static void main(String[] args) throws IOException {File directory =new File("C:\\Users\\24314\\Desktop\\test");if (!directory.isDirectory()){System.out.println("目录不存在");return;}String key="test";scan(directory,key);
}
十、OJ题高效输入方案(IO优化)
1. 问题背景
- 使用
Scanner
时,数据量大容易超时——因为每次next
都是一次IO操作。
2. 优化思路
- 用
BufferedReader
一次读入一整行,再用StringTokenizer
分词,大幅减少IO次数。
3. 快速输入类模板
class Read {StringTokenizer st = new StringTokenizer("");BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));String next() throws IOException {while (!st.hasMoreTokens()) {st = new StringTokenizer(bf.readLine());}return st.nextToken();}String nextLine() throws IOException {return bf.readLine();}int nextInt() throws IOException {return Integer.parseInt(next());}long nextLong() throws IOException {return Long.parseLong(next());}double nextDouble() throws IOException {return Double.parseDouble(next());}
}
4. 优化原理
- Scanner:每次
next
都是一次IO,慢。 - BufferedReader+StringTokenizer:一次IO可获得多条数据,极大提升效率。
5. 实际应用
- OJ题、数据量大的输入场景,推荐使用该方案。