目录
5.1扫描指定的目录,并找到名称中包含指定字符串的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件.
1.文件
1.1狭义上的文件
狭义的文件指的是存储在计算机存储设备上的数据集合,具有名称,扩展名,内容,属性等基本特征,是操作系统管理的基本单位
1.2广义上的文件
广义的文件不仅包括狭义的文件,还涵盖逻辑上可被当作文件处理的抽象数据单元,甚至包括内存中的临时数据、网络传输的数据流、设备接口的虚拟文件等。泛指计算机中很多软硬件资源.
2.文件路径
计算机中,文件路径用于唯一标识文件或目录在存储设备上的位置,不同操作系统的路径表示方式略有不同,但是核心逻辑相似
2.1路径组成
根目录:路径的起点(如C:\或/)
目录分隔符:
Windows:反斜杠 \
(如 C:\Users\Alice
),但是Windows也支持/作为分隔符
Linux/macOS:正斜杠 /
(如 /home/alice
)
2.2绝对路径
从根目录开始,完整描述文件位置.适用于精准定位文件,不受当前工作目录的影响
2.3相对路径
基于当前工作目录(每个程序运行的时候,都有一个工作目录),描述文件的相对位置.
常用的符号:
- .表示当前目录(如 ./file.txt)
- ..表示上一级目录
../images/photo.jpg
示例:
假设当前的工作目录,是d:/tmp,我们需要定位到111这个文件
- 如果工作目录是d:/ 相对路径写成 ./tmp/111
- 如果工作目录是d:/tmp 相对路径写成 ./111
- 如果工作目录是d:/tmp/222 相对路径写成 ../111 (..表示当前目录的上级目录)
- 如果国祚目录是d:/tmp/222/bb 相对路径写成 ../../111
2.4文件类型
2.4.1按用途分类
类型 | 常见扩展名 | 说明 | 示例程序 |
---|---|---|---|
文本文档 | .txt , .docx | 存储纯文本或格式化文字 | 记事本、Word |
图片文件 | .jpg , .png | 存储图像数据 | Photoshop、画图 |
音频文件 | .mp3 , .wav | 存储声音 | 音乐播放器、Audacity |
视频文件 | .mp4 , .avi | 存储视频 | VLC、PotPlayer |
可执行文件 | .exe , .app | 运行程序或安装软件 | Windows/macOS 系统 |
压缩文件 | .zip , .rar | 打包和压缩多个文件 | WinRAR、7-Zip |
网页文件 | .html , .css | 网站代码 | 浏览器、VS Code |
数据库文件 | .sql , .db | 存储结构化数据 | MySQL、SQLite |
脚本文件 | .py , .sh | 存储可执行脚本 | Python、Bash |
2.4.2按存储方式分类
类型 | 说明 | 示例 |
---|---|---|
文本文件 | 以ASCII/Unicode编码存储 | .txt , .csv , .json |
二进制文件 | 以机器可读的二进制格式存储 | .exe , .jpg , .mp3 |
3.java中操作文件
java中分别可以针对文件系统操作和文件内容操作
3.1File文件概述
3.1.1属性
修饰符及类型 | 属性 | 说明 |
static String | pathSepartor | 依赖于系统的分隔符路径 |
static char | pathSepartor | 依赖于系统的分隔符路径 |
pathSepartor:File里面的一个静态变量,就是/或者\,跟着系统走的
3.1.2构造方法
构造方法 | 说明 |
---|---|
File(String pathname) | 通过路径字符串创建 File 对象(可以是文件或目录)。 |
File(String parent, String child) | 在父目录路径 parent 下,指定子路径 child 创建文件。 |
File(File parent, String child) | 在父 File 对象下,指定子路径 child 创建文件。 |
3.1.3常用方法
文件目录的基本信息
方法 | 说明 |
---|---|
boolean exists() | 判断文件或目录是否存在。 |
boolean isFile() | 判断是否是文件(不是目录)。 |
boolean isDirectory() | 判断是否是目录。 |
String getName() | 返回文件名(含扩展名,如 example.txt )。 |
String getPath() | 返回路径字符串(构造时的路径)。 |
String getAbsolutePath() | 返回绝对路径。 |
long length() | 返回文件大小(字节数),目录返回 0 。 |
long lastModified() | 返回最后修改时间(时间戳)。 |
文件/目录操作
方法 | 说明 |
---|---|
boolean createNewFile() | 创建新文件(若文件已存在则返回 false )。 |
boolean delete() | 删除文件或空目录。 |
boolean mkdir() | 创建单级目录。 |
boolean mkdirs() | 创建多级目录(如 a/b/c )。 |
boolean renameTo(File dest) | 重命名或移动文件。 |
目录内容列表
方法 | 说明 |
---|---|
String[] list() | 返回目录下的文件名数组。 |
File[] listFiles() | 返回目录下的 File 对象数组。 |
权限与属性
方法 | 说明 |
---|---|
boolean canRead() | 文件是否可读。 |
boolean canWrite() | 文件是否可写。 |
boolean canExecute() | 文件是否可执行。 |
boolean setReadOnly() | 设置为只读。 |
示例:
1.文件操作方法演示
package io;
import java.io.File;
import java.io.IOException;
public class IODemo1 {
public static void main(String[] args) throws IOException {
File file = new File("./test.txt");//不要求这里真的有test.txt
System.out.println(file.getName());
System.out.println(file.getParent());
System.out.println(file.getPath());
System.out.println(file.getAbsolutePath());
System.out.println(file.getCanonicalPath());
}
}
- 第一行,返回的是文件名(不包含路径)。
- 第二行,返回的是父目录路径。
- 第三行,返回的是构造
File
对象时的路径字符串。 - 第四行,返回绝对路径(但不会解析
.
或..
)。 - 第五行,返回规范化的绝对路径(解析
.
和..
,并去除冗余符号)。
2.创建一个新文件
自动创建了test.txt文件
3. 删除文件
package io;
import java.io.File;
public class IODemo3 {
public static void main(String[] args) {
File file = new File("./test.txt");
file.delete();
}
}
删除了文件
4.创建多级目录
package io;
import java.io.File;
public class IODemo4 {
public static void main(String[] args) {
File dir = new File("./text/aaa/bbb");
//mkdir只能创建一级目录
//mkdirs 可以创建多级目录
dir.mkdirs();
}
}
我们发现,会自动帮我创建一个多级目录
5.文件改名
package io;
import java.io.File;
public class IODemo5 {
public static void main(String[] args) {
File file = new File("./text");
File dest = new File("./textAAA");
file.renameTo(dest);
}
}
4.文件内容的读写--数据流
文件读写时编程中的基础操作,涉及数据的输入输出流
输入流:从文件或其他来源读取数据的通道
输出流:向文件或者其他目标写入数据的通道
java标准库的流对象从类型上可以分为两大类:
- 字节流,操作二进制数据
- InputStream FileInputStream
- OutputStream FileOutputStream
- 字节流,操作文本数据
- Reader FileReader
- Writer FileWriter
4.1 InputStream
方法
修饰符及返回值类
型
| 方法签名 名 | 说明 |
int
| int read() | 读取一个字节的数据,返回 -1 代表已经完全读完了 |
int
|
int read(byte[] b)
|
最多读取
b.length
字节的数据到
b
中,返回实际读到的数
量;
-1
代表以及读完了
|
int
|
int read(byte[] b,
int off, int len)
|
最多读取
len - off
字节的数据到
b
中,放在从
off
开始,返回实际读到的数量;
-1
代表以及读完了
|
void
| void close() | 关闭字节流 |
package io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class IODemo6 {
//字节流读取文件
public static void main(String[] args) throws IOException {
//创建InputStream对象的时候,使用绝对路径和相对路径都是一样的
InputStream inputStream = new FileInputStream("d:/test.txt");
while(true){
//如果第一轮没有读完,则循环下一轮继续读取
//但是第二轮读的数据,会覆盖第一轮读的数据
byte[] buffer = new byte[1024];
//变量名buffer(缓冲区),buffer存在就是为了提高IO操作的效率
int len = inputStream.read(buffer);
//这里传入的是一个刚准备的数组,把这个数组交给read,让read方法对该数组内部进行一个填充
//read的返回值,read尽可能把参数传进来的数组给填满
//read尽可能读取1024个字节,填充到数组里
//但是文件剩余长度有限,如果文件剩余长度超过1024,则会填满
//如果剩余不足,则会有多少就填写多少,返回当前实际读取长度
System.out.println("len: " + len);
if (len == -1){
break;
}
for (int i = 0; i < len; i++) {
System.out.printf("%x\n" , buffer[i]);
}
}
inputStream.close();
}
}
注意:read方法会返回当前实际读取的长度
如代码所示,我们使用read从test.txt文件中读取数据
我们可以看到返回了hello world中每一个字母的ascii码表.(16进制)
4.2 OutputStream
方法
方法 | 功能说明 | 示例 |
---|---|---|
| 写入单个字节(低8位有效,高24位忽略) |
|
| 写入整个字节数组 |
|
| 写入字节数组的指定范围(从 |
|
| 强制将缓冲区数据写入目标(如磁盘/网络) |
|
| 关闭流并释放资源(必须调用) |
|
package io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class IODemo7 {
//写文件
public static void main(String[] args) throws IOException {
OutputStream outputStream = new FileOutputStream("d:/test.txt");
//OutputStream默认情况下,打开一个文件,会先清空文件原来有的内容
/*
如果不想清空,流对象还提供了一个"追加写"对象
通过这个把新的内容追加写在后面
*/
outputStream.write(97);
outputStream.write(98);
outputStream.write(99);
outputStream.write(100);
outputStream.close();
}
}
- 这里的close操作含义是关闭文件,线程中的PCB中有一个重要的文件,文件描述表,记录该进程打开了哪些文件 ,打开文件操作,就会在文件描述符表里,申请一个位置,把这个信息放进去,每次关闭文件操作,也会把这个表对应的表项给释放掉
- 如果没写close,则这个表项没有及时释放,虽然java有GC,但是回收不一定及时;如果不手动释放,可能会把表项很快占满,因为这个数组不能自动扩容,他是存在上限的
- close一般来时是要执行的,但是一个程序,这里的文件对象自始至终要使用,不关闭也问题不大,随着进程结束,PCB销毁,文件描述表也就销毁了,对应资源也就自动回收了
注意: Input(输入)和 Output(输出) 的命名源于 程序视角 而非用户视角。这种命名约定是为了统一描述数据流动方向
方向 | 程序视角 | 用户视角 | 类比 |
---|---|---|---|
Input | 数据流入程序(读操作) | 程序“读取”外部数据 | 像用吸管喝水(吸入) |
Output | 数据流出程序(写操作) | 程序“写入”外部存储 | 像用喷壶浇水(喷出) |
那么,在这段代码中我们考虑这样几个问题,
1.如果我们忘记写close,会发生什么情况
如果没哟close,对应的表项没有及时释放,虽然java有GC,GC操作会在回收这个OutputStream对象的时候去完成这个释放操作,但是这个GC不一定及时,所以不手动释放,意味着文件描述符很可能很快就被沾满
2. 如果我们的程序出现bug,无法执行到close怎么办?
我们提出一种更加推荐的写法,在java中被称为 try with resources,
这是 Java 7 引入的一种异常处理语法,用于自动管理资源(如文件流、数据库连接等需要关闭的资源)。它简化了资源管理代码,避免了传统 try-catch-finally 块中的繁琐关闭操作。
但是这个有使用条件:资源类必须实现AutoCloseable或者Closeable接口,可以在try后的括号里声明多个资源,用分号分隔
package io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class IODemo7_1 {
public static void main(String[] args) throws IOException {
try(OutputStream outputStream = new FileOutputStream("d:/testt.txt")){
//实现了Closeable接口的类才可以放到try的()中被自动关闭
//这个接口提供的方法就是close
outputStream.write(97);
outputStream.write(98);
outputStream.write(99);
outputStream.write(100);
}
}
}
4.3 字符流
用法和字节流基本差不多
package io;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
public class IODemo8 {
public static void main(String[] args) {
try (Reader reader = new FileReader("d:/test.txt")){
while(true){
int ch = reader.read();
if (ch == -1){
break;
}
System.out.println("" + (char)ch);
}
}catch (IOException e){
e.printStackTrace();
}
}
}
package io;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class IODemo9 {
public static void main(String[] args) {
try (Writer writer = new FileWriter("d:/test.txt")) {
writer.write("hello world");
//写操作其实是先写到缓冲区里面(缓冲区有多种,自己代码里面,标准库里面,操作系统内核里面)
//写操作执行完了,内容还可能在缓冲区里面,还没有真的进入硬盘
writer.close();
//close操作,会触发缓冲区的刷新
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.4 Scanner进行字符读取
对字符类型直接用InputStream进行读取是非常麻烦并且是非常困难的,所以我们可使用Scanner完成这项工作.
我们这里仍然对d:test.txt的数据进行读取操作
package io;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
public class IODemo10 {
public static void main(String[] args) {
//Scanner scanner = new Scanner(System.in);
File file = new File("d:test.txt");
if (!file.exists()) {
System.out.println("文件不存在!");
return;
}
try(InputStream inputStream = new FileInputStream("d:test.txt")){
Scanner scanner = new Scanner(inputStream);
//此时内容就从文件中读取了
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
System.out.println(line);
}
//由于在try就已经对inputStream对象进行了关闭,里面的scanner不关闭也没事
}catch (IOException e){
e.printStackTrace();
}
}
}
- hasNextLine():检查输入源中是否还有下一行(包括空行,如果有返回true,如果没有返回false
- nextLine():读取当前行的剩余内容(包括换行符前的所有字符),并将指针移动到下一行开头,返回类型为String
我们这里可以看到,在控制台直接输出了我们当前文档中的数据 .
5.案例
5.1扫描指定的目录,并找到名称中包含指定字符串的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件.
package io;
import java.io.File;
import java.util.Scanner;
//扫描指定目录,找到包含指定字符的文件,并且询问用户是否要删除
public class IODemo11 {
private static Scanner scanner = new Scanner(System.in);
public static void main(String[] args) {
System.out.println("请输入要搜索的路径:");
String basePath = scanner.next();
//针对输入的简单判定
File root = new File(basePath);
if (!root.isDirectory()){
//如果路径不存在
System.out.println("输入的目录有误!");
return;
}
//再让用户输入一个要删除的文件名
System.out.println("请输入要删除的文件名:");
String nameToDelete = scanner.next();
//针对指定的路径进行扫描,递归操作
//先从根目录出发.(root)
//先判断当前这个目录里面看看是否包含要删除的文件,如果是就删除,否则就跳过下一个
//如果当前这里包含了一些目录,在针对子目录进行递归
scanDir(root,nameToDelete);
}
private static void scanDir(File root, String nameToDelete) {
System.out.println("[scan.dir]" + root.getAbsolutePath());
//先列出root下面的文件和目录
File[] files = root.listFiles();
//如果当前目录下没有东西,是一个空目录
//结束继续递归
if (files == null){
return;
}
for (File file : files){
if (file.isDirectory()){
scanDir(file,nameToDelete);
}else {
if (file.getName().contains(nameToDelete)){
System.out.println("确认是否要删除 " + file.getAbsolutePath() + "吗?");
String choice = scanner.next();
if (choice.equals("Y") || choice.equals("y")){
file.delete();
System.out.println("删除成功!");
}else {
System.out.println("删除取消");
}
}
}
}
}
}
代码执行流程:
- 入口检查
- 打印当前扫描目录路径:[scan.dir] + 绝对路径
- 调用listFiles()获取目录下所有文件和子目录,如果目录为空或不可访问(files == null),立即返回
- 遍历每个条目:for(File file : files){}
- 如果是目录:
if (file.isDirectory()){ scanDir(file,nameToDelete);
- 如果是文件
if (file.getName().contains(nameToDelete)){ //代码逻辑 }
- 如果是目录:
- 文件删除确认流程:发现匹配文件时:打印确认提示,等待用户输入,判断输入
- 递归控制:每层递归处理完当前目录所有条目后自动返回
5.2进行普通文件的复制
package io;
import java.io.*;
import java.util.Scanner;
public class IODemo12 {
public static void main(String[] args) {
//先输入两个路径 源路径和目标路径
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要拷贝那个文件:");
String srcPath = scanner.next();
System.out.println("请输入要被放到哪个地方");
String destPath = scanner.next();
File srcFile = new File(srcPath);
if (!srcFile.isFile()){
//如果源不是一个文件,就不做任何操作
System.out.println("您当前输入的源路径有误!");
return;
}
File destFile = new File(destPath);
if (destFile.isFile()){
//如果目标文件已经存在,就不能拷贝
System.out.println("您当前输入的目标路径已经存在!");
return;
}
//进行拷贝
try(InputStream inputStream = new FileInputStream(srcFile);
OutputStream outputStream = new FileOutputStream(destFile)){
//OutputStream 在写文件的时候,文件不存在,就会抛出异常
//进行拷贝操作
while (true){
int b = inputStream.read();
if (b == -1){
break;
}
outputStream.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
代码执行流程:
- 资源初始化阶段
try(InputStream inputStream = new FileInputStream(srcFile); OutputStream outputStream = new FileOutputStream(destFile)){
- 先创建输入流(读取源文件)
- 再创建输出流(写入目标文件,若目标文件不存在会自动创建)
- 若目标文件不存在,会抛出异常
- 字节拷贝阶段
- read()每次读取1字节,返回int,到达文件末尾时返回-1
- write(int b)写入低8位,忽略高24位,数据会先写入缓冲区,满后会刷入磁盘
- 资源关闭阶段
- 自动关闭,因为我们的InputStream和OutputStream,符合try with resource特性
- 先关闭后声明的OutputStream再关闭InputStream
6.小结
文件的基本概念:文件是存储数据的基本单位,包含文件名、扩展名、内容及属性(如大小、权限)。
文件路径:分为绝对路径和相对路径,不同操作系统(Windows/Linux/macOS)的路径表示方式不同。
文件类型:通过扩展名区分(如 .txt、.jpg),决定了文件的打开方式和用途。
文件操作:通过编程语言(如Java的File类)可以获取文件信息(路径、大小)、创建/删除文件、遍历目录等。
文件读写与数据流:
Java 使用 InputStream(输入流)读取文件,OutputStream(输出流)写入文件。
字节流(FileInputStream/FileOutputStream)适合二进制文件,字符流(Reader/Writer)适合文本文件。
缓冲流(如 BufferedInputStream)能大幅提升 I/O 性能。
try-with-resources:
Java 7+ 引入的语法,自动管理资源(如文件流),避免手动 close(),代码更简洁安全。
资源需实现 AutoCloseable 接口(如所有流类)。