认识文件
1. 文件和目录
文件就是存储在硬盘中的数据集合。
文件除了数据以外,还有文件名,文件类型,文件大小等信息。
目录通俗的讲就是文件夹,目录是专门用来存放管理信息的特殊文件。
目录是有层级结构的,也就是树型结构。
2. 文件路径
路径表示一个 文件/目录 的具体位置。
Windows系统中 一个路径 对应一个 文件/目录。
绝对路径
从根结点开始,一路往下找到目标文件/目录,中间经过的目录就是绝对路径了
路径中,每个目录用 \
或 /
来分割,\
只在Windows中适用,在代码中要写成 \\
或 /
相对路径
相对路径需要明确一个基准目录(工作目录)
假设基准目录为:C:/Test/123/hello
一个文件的绝对路径为:C:/Test/123/test.java
那么这个的相对路径为:../test.java
../
表示当前目录(基准目录)的上一层
../../
表示当前目录(基准目录)的上上层
./
表示在当前目录
3. 文件分类
文件分为 文本文件 和 二进制文件。
文本文件存储的数据,遵守 ASCII码 或者 UTF-8等字符集编码。
.txt
.java
.c
都是文本文件
二进制文件存储的是二进制数据。
.class
.exe
.jpg
.mp3
等都是二进制文件。
一个简单的区分方法:
将一个文件拖入记事本中,如果看得懂,就是文本文件。
如果看不懂,都是乱码,就是二进制文件。
拖入了一个 .xls
文件系统操作
文件系统操作就是创建文件,删除文件,创建目录,重命名等操作,不涉及文件内容的读写操作。
Java 中 java.io.File
类就是对一个文件进行抽象,创建这个类的实例,就可以对文件系统进行操作。
1. File 类的属性
static String | pathSeparator | 依赖于系统的路径分隔符,String 类型的表示 |
---|---|---|
static char | pathSeparatorChar | 依赖于系统的路径分隔符,char 类型的表示 |
2. File 类的构造方法
注意: File 实例, 不能代表该文件真实存在。
示例:
在 D:/Test_IO 目录下使用这三个构造方法创建文件, 开始这个目录没有文件。
代码:
public static void main(String[] args) throws IOException {
// 根据路径创建一个File实例, 绝对路径或者相对路径都可以
File file1 = new File("D:/Test_IO/test1.txt");
File directory = new File("D:/Test_IO");
// 父目录 + 孩子文件路径, 父目录是一个 File 对象, 而且必须是目录
File file2 = new File(directory, "test2.txt");
// 父目录 + 孩子文件路径, 父目录用路径表示
File file3 = new File("D:/Test_IO/Test", "../test3.txt");
// exists 文件是否真实存在
System.out.println(file1.exists());
System.out.println(file2.exists());
System.out.println(file3.exists());
// createNewFile 根据 File 对象, 创建一个空文件
file1.createNewFile();
file2.createNewFile();
file3.createNewFile();
System.out.println(file1.exists());
System.out.println(file2.exists());
System.out.println(file3.exists());
}
结果:
成功创建了三个文件
3. File 类常用方法
3.1 get方法
返回值类型 | 方法 | 说明 |
---|---|---|
String | getParent() | 返回 File 对象的父目录文件路径 |
String | getName() | 返回 FIle 对象的纯文件名称 |
String | getPath() | 返回 File 对象的文件路径 |
String | getAbsolutePath() | 返回 File 对象的绝对路径 |
String | getCanonicalPath() | 返回 File 对象的修饰过的绝对路径 |
代码示例:
public static void main(String[] args) throws IOException {
// 使用相对路径
File file1 = new File("../test.txt"); // 不要求文件真实存在
System.out.println(file1.getParent()); // 返回 File 对象的父目录文件路径
System.out.println(file1.getName()); // 返回 File 对象的文件名称
System.out.println(file1.getPath()); // 返回 File 对象的文件路径, 就是构造时传入的参数
System.out.println(file1.getAbsolutePath()); // 返回 File 对象的绝对路径
System.out.println(file1.getCanonicalPath()); // 返回 File 对象的修饰过的绝对路径
System.out.println();
// 使用绝对路径
File file2 = new File("D:/Test_IO/test.txt"); // 不要求文件真实存在
System.out.println(file2.getParent());
System.out.println(file2.getName());
System.out.println(file2.getPath());
System.out.println(file2.getAbsolutePath());
System.out.println(file2.getCanonicalPath());
}
结果:
3.2 创建、删除普通文件
返回值类型 | 方法 | 说明 |
---|---|---|
boolean | exists() | 判断 File 对象描述的文件是否真实存在 |
boolean | isDirectory() | 判断 File 对象代表的文件是否是一个目录 |
boolean | isFile() | 判断 File 对象代表的文件是否是一个普通文件 |
boolean | createNewFile() | 根据 File 对象,自动创建一个空文件。成功创建后返回 true |
boolean | delete() | 根据 File 对象,删除该文件。成功删除后返回 true |
void | deleteOnExit() | 根据 File 对象,标注文件将被删除,删除动作会到JVM 运行结束时才会进行 |
代码示例:
public static void main(String[] args) throws IOException {
File file = new File("D:/Test_IO/test.txt");
System.out.println("是否存在: " + file.exists());
System.out.println("是否是目录: " + file.isDirectory());
System.out.println("是否是普通文件: " + file.isFile() + '\n');
System.out.println("创建文件: " + file.createNewFile() + '\n');
System.out.println("是否存在: " + file.exists());
System.out.println("是否是目录: " + file.isDirectory());
System.out.println("是否是普通文件: " + file.isFile() + '\n');
System.out.println("删除文件: " + file.delete() + "\n");
System.out.println("是否存在: " + file.exists());
System.out.println("创建文件: " + file.createNewFile() + '\n');
file.deleteOnExit();
System.out.println("deleteOnExit()");
System.out.println("是否存在: " + file.exists());
}
结果:
注意事项:deleteOnExit()
在运行结束才会删除文件。
3.3 创建目录
返回值类型 | 方法 | 说明 |
---|---|---|
boolean | mkdir() | 创建 File 对象代表的目录 |
boolean | mkdirs() | 创建 File 对象代表的目录,如果必要,会创建中间目录 |
创建一级目录:
public static void main(String[] args) {
File directory = new File("D:/Test_IO/Test");
System.out.println("是否存在: " + directory.exists());
System.out.println("是否是目录: " + directory.isDirectory());
System.out.println("是否是普通文件: " + directory.isFile() + '\n');
System.out.println("创建一级目录: " + directory.mkdir() + '\n');
System.out.println("是否存在: " + directory.exists());
System.out.println("是否是目录: " + directory.isDirectory());
System.out.println("是否是普通文件: " + directory.isFile() + '\n');
}
结果:
创建多级目录:
public static void main(String[] args) {
File directory = new File("D:/Test_IO/Test/Hello");
System.out.println("是否存在: " + directory.exists());
System.out.println("是否是目录: " + directory.isDirectory());
System.out.println("是否是普通文件: " + directory.isFile() + '\n');
System.out.println("创建多级目录: " + directory.mkdirs() + '\n');
System.out.println("是否存在: " + directory.exists());
System.out.println("是否是目录: " + directory.isDirectory());
System.out.println("是否是普通文件: " + directory.isFile() + '\n');
}
结果:
注意:创建一级目录可以用 mkdir()
或 mkdirs()
, 创建多级目录必须用 mkdirs()
, 目录的删除与普通文件一致
3.4 文件重命名
返回值类型 | 方法 | 说明 |
---|---|---|
boolean | renameTo(File dest) | 进行文件改名,也可以视为我们平时的剪切、粘贴操作 |
示例:
public static void main(String[] args) {
File file = new File("D:/Test_IO/test.txt"); // 要求 test.txt 得存在,可以是普通文件,可以是目录
File dest = new File("hello.txt"); // 要求 hello.txt 不存在
System.out.println(file.exists()); // true
System.out.println(dest.exists()); // false
System.out.println(file.renameTo(dest));// 进行文件改名, 成功返回 true
System.out.println(file.exists()); // false
System.out.println(dest.exists()); // true
}
结果:
3.5 其他操作
返回值类型 | 方法 | 说明 |
---|---|---|
boolean | canRead() | 判断用户是否对文件有可读权限 |
boolean | canWrite() | 判断用户是否对文件有可写权限 |
String[] | list() | 返回 File 对象代表的目录下的所有文件名 |
File[] | listFiles() | 返回 File 对象代表的目录下的所有文件,以 File 对象表示 |
文件内容操作
对文件内容进行读取和写入。
一、字节流
1. 读取文件
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 是一个抽象类, 要搭配具体的实现类,我们要对文件操作,所以搭配 FileInputStream 使用
FileInputStream
构造方法 | 说明 |
---|---|
FileInputStream(File file) | 利用 File 构造文件输入流 |
FileInputStream(String name) | 利用文件路径构造文件输入流 |
代码示例
public static void main(String[] args) throws IOException {
// 相当于打开文件
// InputStream 是抽象类
// FileInputStream 构造文件输入流
// 文件必须存在
try (InputStream inputStream = new FileInputStream("D:/Test_IO/test.txt")) {
while (true) {
// read 一次读取一个字节, 但是 read 的返回值是 int
int b = inputStream.read(); // 读取一个字节的数据,返回 -1 代表已经完全读完了
if (b == -1) {
break;
}
char ch = (char)b;
System.out.print(ch);
}
}
// 使用 try(){} 这个写法会自动关闭字节流
// inputStream.close();
}
public static void main(String[] args){
try (InputStream inputStream = new FileInputStream("D:/Test_IO/test.txt")) {
byte[] buf = new byte[1024];
int len;
while (true) {
len = inputStream.read(buf); // 最多读取 b.length 字节的数据到 b 中, 返回实际读到的数量. -1 代表以及读完了
// 一次最多读1024个字节,尽量填满数组
// 读到末尾返回 -1
if (len == -1) {
break;
}
for (int i = 0; i < len; i++) {
System.out.print((char)buf[i]);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
搭配 Scanner 可以读取字符
构造方法 | 说明 |
---|---|
Scanner(InputStream is, String charset) | 使用 charset 字符集进行 is 的扫描读取 |
代码示例:
public static void main(String[] args) {
try (InputStream inputStream = new FileInputStream("D:/Test_IO/test.txt")) {
try (Scanner scanner = new Scanner(inputStream, "UTF-8")) {
while (scanner.hasNext()) {
String s = scanner.nextLine();
System.out.println(s);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
结果:
2. 写入文件
OutputStream
修饰符及返回值类型 | 方法签名 | 说明 |
---|---|---|
void | write(int b) | 写入要给字节的数据 |
void | write(byte[] b) | 将 b 这个字符数组中的数据全部写入 os 中 |
int | write(byte[] b, int off, int len) | 将 b 这个字符数组中从 off 开始的数据写入 os 中,一共写 len 个 |
void | close() | 关闭字节流 |
void | flush() | 重要:OutputStream 为提高效率,会将数据暂时放在内存中, 等到数据足够多了,才会真正的将数据写入硬盘,调用 flush(刷新)操作,将数据写入硬盘。 |
OutputStream 也是一个抽象类, 要搭配具体的实现类,我们要对文件操作,所以搭配 FileOutputStream 使用
FileOutputStream
构造方法 | 说明 |
---|---|
FileOutputStream(File file) | 利用 File 构造文件输出流 |
FileOutputStream(String name) | 利用文件路径构造文件输出流 |
代码示例
public static void main(String[] args) {
// 文件可以不存在, 会自动创建
try (OutputStream outputStream = new FileOutputStream("D:/Test_IO/output.txt")) {
outputStream.write('H');// 写入一个字节
outputStream.write('e');
outputStream.write('l');
outputStream.write('l');
outputStream.write('o');
outputStream.flush();
}catch (IOException e) {
e.printStackTrace();
}
}
结果:
写入中文字符
public static void main(String[] args) throws IOException {
try (OutputStream os = new FileOutputStream("D:/Test_IO/output.txt")) {
String s = "你好";
byte[] b = s.getBytes("utf-8");
os.write(b);
os.flush();
}
}
结果:
搭配 PrintWriter 进行字符写入
代码示例:
public static void main(String[] args) {
try (OutputStream os = new FileOutputStream("D:/Test_IO/output.txt")) {
try (OutputStreamWriter outputStreamWriter = new OutputStreamWriter(os,"utf-8")) {
try (PrintWriter printWriter = new PrintWriter(outputStreamWriter)) {
printWriter.println("hello");
printWriter.print("你好\r\n");
printWriter.printf("1 + 1 = %d\r\n", 1 + 1);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
结果:
二、字符流
1. 读取文件
Reader
Reader 抽象类,读取文件需要搭配 FileReader 使用
代码示例:
public static void main(String[] args) {
// 文件必须存在
try (Reader reader = new FileReader("D:/Test_IO/test.txt")) {
while (true) {
int c = reader.read();
if (c == -1) {
break;
}
System.out.print((char)c);
}
} catch (IOException e) {
e.printStackTrace();
}
}
结果:
2. 写入文件
Writer
Writer 抽象类,读取文件需要搭配 FileWriter 使用
代码示例:
public static void main(String[] args) {
try (Writer writer = new FileWriter("D:/Test_IO/output.txt")) {
writer.write('a');
writer.write('b');
writer.write('\n');
writer.write('你');
writer.write('好');
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
结果:
练习案例
1. 文件名查找,删除文件
扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件。
代码:
public class IODemo12 {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入要扫描的根目录 (绝对路径 或 相对路径): ");
String rootDirPath = scanner.next();
// 创建 File 实例
File rootDir = new File(rootDirPath);
if (!rootDir.isDirectory()) {
// 不是目录, 或者不存在
System.out.println("您输入的根目录不存在或者不是目录! ");
return;
}
System.out.print("请输入要找出的文件名中的字符: ");
String token = scanner.next();
// 存放结果
List<File> result = new ArrayList<>();
// 递归扫描
scanDir(rootDir, token, result);
System.out.println("共找到了符合条件的文件" + result.size() + "个, 分别是");
for (File file : result) {
System.out.println(file.getCanonicalPath() + " 您是否要删除改文件? y/n");
String in = scanner.next();
if (in.toLowerCase().equals("y")) {
// 输出文件
file.delete();
}
}
}
// 递归遍历
private static void scanDir(File rootDir, String token, List<File> result) {
File[] files = rootDir.listFiles();// 获取当前目录下的所有文件
if (files == null) {
return;
}
for (File file : files) {
if (file.isDirectory()) {
// 是目录
scanDir(file, token, result);
} else if (file.isFile()) {
// 是普通文件
if (file.getName().contains(token)) {
// 文件名包含指定字符
result.add(file);
}
} else {
// 不是目录和普通文件, 跳过
continue;
}
}
}
}
演示:
2. 普通文件复制
进行普通文件复制
代码:
public class IODemo13 {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入要复制的文件 (绝对路径 或 相对路径): ");
String sourcePath = scanner.next();
// 创建 File 实例
File sourceFile = new File(sourcePath);
if (!sourceFile.exists()) {
System.out.println("文件不存在, 请确认路径是否正确!");
return;
}
if (!sourceFile.isFile()) {
// 不是普通文件, 或者不存在
System.out.println("文件不是普通文件, 请确认路径是否正确!");
return;
}
System.out.print("请输入要复制的目标路径 (绝对路径 或 相对路径): ");
String destPath = scanner.next();
File destFile = new File(destPath);
if (destFile.exists()) {
// 目标存在
if (destFile.isFile()) {
System.out.println("目标路径已经存在, 是否要进行覆盖?y/n");
String ans = scanner.next();
if (!ans.toLowerCase().equals("y")) {
System.out.println("停止复制");
return;
}
} else {
System.out.println("目标路径已存在, 并且不是普通文件, 请确认路径是否正确!");
}
}
try (InputStream inputStream = new FileInputStream(sourceFile)) {
try (OutputStream outputStream = new FileOutputStream(destFile)) {
while (true) {
byte[] b = new byte[1024];
int len = inputStream.read(b);// 读取
if (len == -1) {
break;
}
outputStream.write(b, 0, len);// 写入
outputStream.flush();
}
}
}
System.out.println("复制已完成");
}
}
演示:
3. 文件名和内容中查找
扫描指定目录,并找到名称或者内容中包含指定字符的所有普通文件(不包含目录)
这个方案性能较差,不要在大文件下扫描,可能会内存不够
代码:
public class IODemo14 {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入要扫描的根目录(绝对路径 或 相对路径):");
String rootDirPath = scanner.next();
File rootDir = new File(rootDirPath);
if (!rootDir.isDirectory()) {
System.out.println("您输入的根目录不存在或不是目录!");
return;
}
System.out.print("请输入要找的字符:");
String token = scanner.next();
// 存放结果
List<File> result = new ArrayList<>();
// 递归
scanDirWithContent(rootDir, token, result);
System.out.println("以下 " + result.size() + " 个文件包含 \'" + token +"\':");
// 输出结果
for (File file : result) {
System.out.println(file.getCanonicalPath());
}
}
private static void scanDirWithContent(File rootDir, String token, List<File> result) {
// 获取当前目录下的所有文件
File[] files = rootDir.listFiles();
if (files == null) {
return;
}
for (File file : files) {
System.out.println("当前搜索到:" + file.getAbsolutePath());
if (file.isDirectory()) {
// 是目录
scanDirWithContent(file, token, result);
} else if (file.isFile()) {
// 是普通文件
if (file.getName().contains(token)) {
// 文件名是否包含
result.add(file);
continue;
}
if (isContentContains(file, token)) {
// 文件内容
result.add(file);
}
} else {
// 不是目录和普通文件, 跳过
continue;
}
}
}
// 使用字节流 搭配 Scanner
private static boolean isContentContains(File file, String token) {
StringBuilder stringBuilder = new StringBuilder();
try (InputStream inputStream = new FileInputStream(file)) {
try (Scanner scanner = new Scanner(inputStream, "utf-8")) {
while (scanner.hasNextLine()) {
// 一次读一行
stringBuilder.append(scanner.nextLine());
stringBuilder.append("\r\n");
}
}
} catch (IOException e) {
e.printStackTrace();
}
return stringBuilder.indexOf(token) != -1;
}
}
演示: