文章目录
一、File 概述
Java 中通过 java.io.File 类来对一个文件(包括目录)进行抽象的描述.注意,有 File 对象,并不代表真实存在该文件
1.1 属性
1.2 构造方法
注意:
提到相对路径就需要一基准路径.当new一个File对象时如 File file = new File(“./test.txt”);此时它的基准路径是啥呢?如果是通过IDEA来运行程序,此时基准路径就是当前java项目所在的路径,如
1.3 方法
1.4 案例
- 观察 get 系列的特点和差异
import java.io.File;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
File file = new File("..\\hello-world.txt"); // 并不要求该文件真实存在
System.out.println(file.getParent());
System.out.println(file.getName());
System.out.println(file.getPath());
System.out.println(file.getAbsolutePath());
System.out.println(file.getCanonicalPath());
}
}
- 普通文件的创建、删除
public class Main {
public static void main(String[] args) throws IOException {
File file = new File("hello-world.txt"); // 要求该文件不存在,才能看到相同的现象
System.out.println(file.exists());
System.out.println(file.isDirectory());
System.out.println(file.isFile());
System.out.println(file.createNewFile());
System.out.println(file.exists());
System.out.println(file.isDirectory());
System.out.println(file.isFile());
System.out.println(file.createNewFile());
}
}
注意:
使用 createNewFile() 方法时,若该文件不存在就在此直接创建一个空的文件然后返回true,若已经存在该文件就直接返回false.
创建前,没有该文件
创建后
- 普通文件的删除
public class Main {
public static void main(String[] args) throws IOException {
File file = new File("some-file.txt"); // 要求该文件不存在,才能看到相同的现象
System.out.println(file.exists());
System.out.println(file.createNewFile());
System.out.println(file.exists());
System.out.println(file.delete());
System.out.println(file.exists());
}
}
- 观察目录的创建
public class Main {
public static void main(String[] args) throws IOException {
File dir = new File("some-dir"); // 要求该目录不存在,才能看到相同的现象
System.out.println(dir.isDirectory());
System.out.println(dir.isFile());
System.out.println(dir.mkdir());
System.out.println(dir.isDirectory());
System.out.println(dir.isFile());
}
}
- 观察目录创建2
public class Main {
public static void main(String[] args) throws IOException {
File dir = new File("some-parent\\some-dir"); // some-parent 和 somedir 都不存在
System.out.println(dir.isDirectory());
System.out.println(dir.isFile());
System.out.println(dir.mkdirs());
System.out.println(dir.isDirectory());
System.out.println(dir.isFile());
}
}
注意: mkdir() 的时候,如果中间目录不存在,则无法创建成功; mkdirs() 可以解决这个问题.即要想创建多级目录就要使用 mkdirs()
- 观察文件重命名
public class Main {
public static void main(String[] args) throws IOException {
File file = new File("some-file.txt"); // 要求 some-file.txt 得存在,可以是普通文件,可以是目录
File dest = new File("dest.txt"); // 要求 dest.txt 不存在
System.out.println(file.exists());
System.out.println(dest.exists());
System.out.println(file.renameTo(dest));
System.out.println(file.exists());
System.out.println(dest.exists());
}
}
二、文件内容的读写 —— 数据流
2.1 InputStream 概述
说明:
InputStream 只是一个抽象类,要使用还需要具体的实现类。关于 InputStream 的实现类有很多,基本可以认为不同的输入设备都可以对应一个 InputStream 类,我们现在只关心从文件中读取,所以使用 FileInputStream
2.1.1 FileInputStream 概述
构造方法:
2.1.1.1 如何按字节进行数据读
代码示例:
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
//指定打开文件的路径,此处可以是绝对路径也可以是相对路径,还可以是File对象
//try 会帮我们自动调用close方法,但是条件是要实现Closeable接口.所有刘对象都实现了
try (InputStream is = new FileInputStream("hello.txt")) {
byte[] buf = new byte[1024];
int len;
while (true) {
//把读的结果放入到指定参数数组中,并返回读到的字节数
len = is.read(buf);
if (len == -1) {
//代表文件已经全部读完
break;
}
for (int i = 0; i < len; i++) {
System.out.printf("%c", buf[i]);
}
}
}
catch(IOException e){
e.printStackTrace();
}
//finally{
// 关闭文件资源
// is.close;}
}
}
注意:
- 此方法 IO 次数更少,性能更好.
- 为何这里 len == -1 的时候就表示结束,还需要观察 read 方法的源码!!
- 上述代码不需要在 finally 里面调用close方法来关闭文件资源,try语句里面会自动帮我们调用.但前提是要实现Closeable接口,所有的流对象都实现了该接口
2.1.2 利用 Scanner 进行字符读取
先举个例子:
把文件内容中填充中文看看,注意,写中文的时候使用 UTF-8 编码。hello.txt 中填写 “你好中
国”
// 需要先在项目目录下准备好一个 hello.txt 的文件,里面填充 "你好中国" 的内容
public class Main {
public static void main(String[] args) throws IOException {
try (InputStream is = new FileInputStream("hello.txt")) {
byte[] buf = new byte[1024];
int len;
while (true) {
len = is.read(buf);
if (len == -1) {
// 代表文件已经全部读完
break;
}
// 每次使用 3 字节进行 utf-8 解码,得到中文字符
// 利用 String 中的构造方法完成
// 这个方法了解下即可,不是通用的解决办法
for (int i = 0; i < len; i += 3) {
String s = new String(buf, i, 3, "UTF-8");
System.out.printf("%s", s);
}
}
}
}
}
注意: 这里我利用了这几个中文的 UTF-8 编码后长度刚好是 3 个字节和长度不超过 1024 字节的现状,但这种方式并不是通用的
上述例子中,我们看到了对字符类型直接使用 InputStream 进行读取是非常麻烦且困难的,所以,我们使用一种我们之前比较熟悉的类来完成该工作,就是 Scanner 类
代码示例:
public class Main {
public static void main(String[] args) throws IOException {
try (InputStream is = new FileInputStream("hello.txt")) {
try (Scanner scanner = new Scanner(is, "UTF-8")) {
while (scanner.hasNext()) {
String s = scanner.next();
System.out.print(s);
}
}
}
}
}
除此之外,我们还可以通过 Reader 类的 read 方法来进行字符读取
2.2 OutputStream 概述
方法:
说明:
OutputStream 同样只是一个抽象类,要使用还需要具体的实现类。我们现在还是只关心写入文件中,所以使用 FileOutputStream
2.2.1 如何按字节进行数据写
try (OutputStream os = ...) {
byte[] buf = new byte[1024];
while (/* 还有未完成的业务数据 */) {
// 将业务数据填入 buf 中,长度为 n
int n = ...;
os.write(buf, 0, n);
}
os.flush(); // 进行数据刷新操作
}
2.2.2 利用 OutputStreamWriter 进行字符写入
不推荐做法示例:
public class Main {
public static void main(String[] args) throws IOException {
try (OutputStream os = new FileOutputStream("output.txt")) {
String s = "Nothing";
byte[] b = s.getBytes();
os.write(b);
// 不要忘记 flush
os.flush();
}
}
}
利用 PrintWriter 找到我们熟悉的方法
上述,我们其实已经完成输出工作,但总是有所不方便,我们接来下将 OutputStream 处理下,使用PrintWriter 类来完成输出,因为PrintWriter 类中提供了我们熟悉的 print/println/printf 方法
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
try (OutputStream os = new FileOutputStream("output.txt")) {
// 告诉它,我们的字符集编码是 utf-8 的
try (OutputStreamWriter osWriter = new OutputStreamWriter(os, "UTF-8"){
try (PrintWriter writer = new PrintWriter(osWriter)) {
// 接下来我们就可以方便的使用 writer 提供的各种方法了
writer.println("我是第一行");
writer.print("我的第二行\r\n");
writer.printf("%d: 我的第三行\r\n", 1 + 1);
writer.flush();
}
}
}
}
注意上述代码含义:
注意:除此之外,我们还可以通过 Writer 类的 write 方法来进行字符写入
三、文件操作案例
3.1 删除文件
public class Demo12 {
public static void main(String[] args) {
// 1. 先输入要扫描的目录, 以及要删除的文件名
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要扫描的路径: ");
String rootDirPath = scanner.next();
System.out.println("请输入要删除的文件名: ");
String toDeleteName = scanner.next();
File rootDir = new File(rootDirPath);
if (!rootDir.isDirectory()) {
System.out.println("输入的扫描路径有误!");
return;
}
// 2. 遍历目录, 把 指定目录 中的所有文件和子目录都遍历一遍, 从而找到要删除的文件
// 通过这个方法来实现递归遍历并删除的操作
scanDir(rootDir, toDeleteName);
}
private static void scanDir(File rootDir, String toDeleteName) {
// 1. 先列出 rootDir 中都有哪些内容
File[] files = rootDir.listFiles();
if (files == null) {
// rootDir 是一个空目录
return;
}
// 2. 遍历当前列出的这些内容. 如果是普通文件, 就检测文件名是否是要删除的文件.
// 如果是目录, 就递归的进行遍历
for (File f : files) {
if (f.isFile()) {
// 普通文件的情况
if (f.getName().contains(toDeleteName)) {
// 不要求名字完全一样, 只要文件名中包含了关键字即可删除
// 就进行删除操作
deleteFile(f);
}
} else if (f.isDirectory()) {
// 目录就递归的进行遍历
scanDir(f, toDeleteName);
}
}
}
private static void deleteFile(File f) {
try {
System.out.println(f.getCanonicalPath() + " 确认要删除吗? (Y/n)");
Scanner scanner = new Scanner(System.in);
String choice = scanner.next();
if (choice.equals("Y") || choice.equals("y")) {
f.delete();
System.out.println("文件删除成功!");
} else {
System.out.println("文件取消删除!");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.2 复制文件
public class Demo13 {
public static void main(String[] args) {
// 1. 输入两个路径
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要拷贝的源路径: ");
String src = scanner.next();
System.out.println("请输入要拷贝的目标路径: ");
String dest = scanner.next();
File srcFile = new File(src);
if (!srcFile.isFile()) {
System.out.println("输入的源路径不正确!");
return;
}
// 此处不太需要检查目标文件是否存在. OutputStream 写文件的时候能够自动创建不存在的文件.
// 2. 读取源文件, 拷贝到目标文件中
try (InputStream inputStream = new FileInputStream(src)) {
try (OutputStream outputStream = new FileOutputStream(dest)) {
// 把 inputStream 中的数据读出来, 写入到 outputStream 中
byte[] buffer = new byte[1024];
while (true) {
int len = inputStream.read(buffer);
if (len == -1) {
// 读取完毕
break;
}
// 写入的时候, 不能把整个 buffer 都写进去. 毕竟 buffer 可能是只有一部分才是有效数据.
outputStream.write(buffer, 0, len);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.3 查找文件
public class Demo14 {
public static void main(String[] args) throws IOException {
// 1. 输入要扫描的文件路径
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要扫描的路径: ");
String rootDirPath = scanner.next();
System.out.println("请输入要查询的关键词: ");
String word = scanner.next();
File rootDir = new File(rootDirPath);
if (!rootDir.isDirectory()) {
System.out.println("输入的路径非法!");
return;
}
// 2. 递归的进行遍历
scanDir(rootDir, word);
}
private static void scanDir(File rootDir, String word) throws IOException {
// 1. 先列出 rootDir 中都有哪些内容
File[] files = rootDir.listFiles();
if (files == null) {
return;
}
// 2. 遍历每个元素, 针对普通文件和目录分别进行处理.
for (File f : files) {
if (f.isFile()) {
// 针对文件进行内容查找
if (containsWord(f, word)) {
System.out.println(f.getCanonicalPath());
}
} else if (f.isDirectory()) {
// 针对目录进行递归
scanDir(f, word);
}
}
}
private static boolean containsWord(File f, String word) {
// 写代码, 慎重使用缩写!!! 缩写的可读性会比较差. (一些业界常见缩写, 可以用, 不要随便滥用)
StringBuilder stringBuilder = new StringBuilder();
// 把 f 中的内容都读出来, 放到一个 StringBuilder 中
try (Reader reader = new FileReader(f)) {
char[] buffer = new char[1024];
while (true) {
int len = reader.read(buffer);
if (len == -1) {
break;
}
// 把这一段读到的结果, 放到 StringBuilder 中
stringBuilder.append(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
// indexOf 返回的是子串的下标. 如果 word 在 stringBuilder 中不存在, 则返回下标为 -1
return stringBuilder.indexOf(word) != -1;
}
}