通常说的文件时磁盘上存储的文件,但文件的概念更加广泛,为了操作系统方便管理硬件设备,把一切资源都抽象成文件进行管理「比如操作网卡,显卡,CPU,打印机等设备」
1. File类的使用
1.1 读取文件属性
package file;
import java.io.File;
import java.io.IOException;
public class Demo1 {
public static void main(String[] args) throws IOException {
// 绝对路径
File f = new File(" /Users/cxf/java/4.System/src/file/content/test.txt");
System.out.println(f.getParent());
System.out.println(f.getName());
System.out.println(f.getPath());
System.out.println(f.getAbsolutePath());
System.out.println(f.getCanonicalPath());
System.out.println("**********");
/*
相对路径:一定要找对应的路径作为文件描述的基准
Tomcat:如果打了个 war 包,在 Tomcat 中运行,部署在 webapps 里,基准路径就会是对应 Tomcat 工作的 bin 目录
打了个 jar 包,通过 java -jar [jar包名],基准路径就是运行 java 命令所在的路径
如果通过 IDEA 右上角的绿色三角符号运行,基准路径就是整个项目下的路径「和src统计目录」
*/
File f1 = new File("./test.txt");
System.out.println(f1.getParent());
System.out.println(f1.getName());
System.out.println(f1.getPath());
System.out.println(f1.getAbsolutePath());
System.out.println(f1.getCanonicalPath());
}
}
// 运行结果:
/Users/cxf/java/4.System/src/file/content
test.txt
/Users/cxf/java/4.System/src/file/content/test.txt
/Users/cxf/java/4.System/ /Users/cxf/java/4.System/src/file/content/test.txt
/Users/cxf/java/4.System/ /Users/cxf/java/4.System/src/file/content/test.txt
**********
.
test.txt
./test.txt
/Users/cxf/java/4.System/./test.txt
/Users/cxf/java/4.System/test.txt
1.2 创建文件
package file;
import java.io.File;
import java.io.IOException;
public class Demo2 {
public static void main(String[] args) throws IOException {
File f = new File("./test.txt");
System.out.println(f.exists());
System.out.println(f.isDirectory());
System.out.println(f.isFile());
System.out.println("**********");
// 创建一个文件
System.out.println(f.createNewFile());
System.out.println(f.exists());
System.out.println(f.isDirectory());
System.out.println(f.isFile());
}
}
// 运行结果:
true
false
true
**********
false
true
false
true
1.3 删除文件
「立即删除」
package file;
import java.io.File;
import java.io.IOException;
// 文件删除
public class Demo3 {
public static void main(String[] args) throws IOException {
File f = new File("./deleted.txt");
System.out.println(f.exists());
System.out.println("创建文件之前");
System.out.println("**********");
System.out.println(f.createNewFile());
System.out.println("创建文件之后");
System.out.println(f.exists());
System.out.println("删除文件之前");
f.delete();
System.out.println("删除文件之后");
System.out.println(f.exists());
}
}
// 运行结果:
false
创建文件之前
**********
true
创建文件之后
true
删除文件之前
删除文件之后
false
「删除动作会在JVM运行结束的时候执行」
package file;
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
public class Demo4 {
public static void main(String[] args) throws IOException {
File f = new File("./delete.txt");
f.createNewFile();
f.deleteOnExit();
// 在这里加一个阻塞,方便看到
new Scanner(System.in).nextInt();
}
}
1.4 创建目录
package file;
import java.io.File;
public class Demo5 {
public static void main(String[] args) {
File f=new File("./src/file/content/testDir");
/*
mkdir:创建单个目录
mkdirs:还会创建中间的文件夹目录
*/
f.mkdirs();
}
}
返回值类型 | 函数 | 意义 |
String | getParent | 返回File对象的父目录 |
String | getName | 返回File对象的纯文件名 |
String | getPath | 返回File对象的文件夹路径 |
String | getAbsolutePath | 返回File对象的绝对路径 |
String | getCanonicalPath | 返回File对象修饰过的绝对路径 |
boolean | exists | 判断File对象是否存在 |
boolean | isFile | 判断File对象是否是文件 |
boolean | isDirectory | 判断File对象是否是目录 |
boolean | createNewFile | 根据File对象创建一个新文件,成功返回true |
boolean | delete | 根据File对象删除文件,成功返回 true |
void | deleteOnExit | 根据File对象删除文件,删除动作会在JVM运行结束的时候执行 |
String[ ] | list | 返回File对象下的所有文件名 |
File[ ] | listFiles | 返回File对象下的所有文件,以File对象显示 |
boolean | mkdir | 创建File对象的目录 |
boolean | mkdirs | 创建File对象的目录,回创建中间目录 |
boolean | renameTo(File dest) | 文件改名,相当于剪切,粘贴操作 |
boolean | canRead | 判断用户对文件是否有读权限 |
boolean | canWrite | 判断用户对文件是否有写权限 |
1.5 文件改名
package file;
import java.io.File;
public class Demo6 {
public static void main(String[] args) {
File f = new File("./src/file/content/test.txt");
File f1 = new File("./src/file/content/test.txt");
f.renameTo(f1);
}
}
2. 文件的读写
- 字节流:以字节为单位,针对二进制文件「InputStream:负责读;OutputStream:负责写」
- 字符流:以字符为单位,针对文本「Reader:负责读;Writer:负责写」
2.1 字节流读
package file;
import java.io.*;
// 字节流读取文件
public class Demo7 {
public static void main(String[] args) {
/*
InputStream:是一个抽象类,是总领全局的,表示所有的按二进制读取文件的类
构造方法中参数:
打开的文件名:相对路径/绝对路径
也可以是一个 File 对象
*/
// InputStream inputStream = null;
// try {
// inputStream = new FileInputStream("./src/file/content/test.txt");
// OutputStream outputStream = new FileOutputStream("./src/file/content/write.txt");
// while (true) {
// /*
// 用 int 而不是用 byte 是因为:读出来的数据范围[0, 255]
// -1代表读完了
// byte[-127, 128],一个字节
// */
// int b = inputStream.read();
// if (b == -1) {
// break;
// }
// System.out.println(b);
// }
// } catch (IOException e) {
// e.printStackTrace();
// } finally {
// // 把 close 放在 finally 中保证即使代码出现异常也能关闭,但是代码看的太不方便
// try {
// inputStream.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
// try white resources 把要释放的资源放到 try() 然后就会自动调用到关闭:要求 try() 里的对象能够实现 Closeable 接口。文件流对象都是实现了 Closeable
try (InputStream inputStream = new FileInputStream("./src/file/content/test.txt")) {
while (true) {
int b = inputStream.read();
if (b == -1) {
break;
}
System.out.println(b);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
代码是对上述注释代码的优化
- read:一次读一个字节
- read(byte[] b):一次读若干个字节,尽可能的填满这个字节数组
- 如果文件中剩余的数据比较多,超过了数组的长度,就会直接返回数组长度(把数组填满)
- 如果文件中剩余的数据比较少,不超过参数数组的长度,此时就会直接返回时记得元素个数
- read(byte[] b, int off, int len):加了一个起始和终止
为何选用int而不是byte接受inputStream
byte[-127, 128]
而读取数据遇到中文等特殊字符用的是 Unicode编码,而数据范围已经由之前的 ASCII码进行扩展,最大支持 128,已经无法满足,所以需要 255 来支撑
按照指定字节数读取
package file;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class Demo8 {
public static void main(String[] args) {
// 尝试一次读取 1024 个字节
try (InputStream inputStream = new FileInputStream("./src/file/content/test.txt")) {
byte[] buffer = new byte[1024];
while (true) {
int len = inputStream.read(buffer);
if (len == -1) {
break;
}
// 遍历每个字节
// for (int b:buffer) {
// System.out.print(b);
// }
String s = new String(buffer, 0, len, "UTF-8");
System.out.println(s);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.2 字符流读
package file;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
public class Demo9 {
public static void main(String[] args) {
try(Reader reader=new FileReader("./src/file/content/test.txt")){
while (true){
int c=reader.read();
if (c==-1){
break;
}
System.out.print((char)c);
}
}catch (IOException e){
e.printStackTrace();
}
}
}
读字符,一次读的是一个 char;读字节,一次读的是一个 byte
2.3 字节流写
package file;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class Demo10 {
public static void main(String[] args) {
try (OutputStream outputStream = new FileOutputStream("./src/file/content/test.txt")) {
// 使用 write 会清空原始数据
outputStream.write(65);
outputStream.write(66);
outputStream.write(67);
outputStream.write(68);
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.4 字符流写
OutputStream.write()可传入的参数
package file;
import java.io.*;
public class Demo11 {
public static void main(String[] args) {
try (Writer writer = new FileWriter("./src/file/content/test.txt")) {
writer.write('朱');
writer.write("🐷");
} catch (IOException e) {
e.printStackTrace();
}
}
}
3. 案例
3.1 删除指定文件
package file;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
// 根据指定的路径,堆路径进行遍历,删除符合要求的文件
public class Demo12 {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
// 1.提示用户输入一个带扫描的路径
System.out.print("请输入要扫描的目录路径:");
String rootDirPath = scanner.next();
// 2.约定一下这个路径是否合法
File rootDir = new File(rootDirPath);
if (!rootDir.isDirectory()) {
// 如果给定的这个路径不存在,或者不是目录,就直接出错
System.out.println("您输入的扫描路径非法");
return;
}
// 3.输入要删除的文件
System.out.print("请输入要删除的文件名:");
String deleteFileName = scanner.next();
// 4.遍历当前目录,找到所有文件名和带删除文件名匹配的文件
List<File> result = new ArrayList<>();
// 通过 scanDir 这个方法把所有和 deleteFileName 匹配的文件都给找出来,放到 result 中「这个遍历文件的过程就需要用到递归」
scanDir(rootDir, deleteFileName, result);
// 5.进行删除,把 result 里找到的所有文件,都依次进行删除
for (File f : result) {
System.out.print(f.getCanonicalPath() + "该文件是否确认删除?y/n");
String choice = scanner.next();
if ("y".equals(choice) || "Y".equals(choice)) {
f.delete();
System.out.println(f.getCanonicalPath() + "删除成功");
}
}
}
private static void scanDir(File rootDir, String deleteFileName, List<File> result) throws IOException {
// 1. 首先,先罗列出 rootDir 下都有哪些文件
File[] files = rootDir.listFiles();
for (File f : files) {
System.out.println("扫描到了文件:" + f.getCanonicalPath());
if (f.isFile()) {
// 是普通文件
if (f.getName().equals(deleteFileName)) {
result.add(f);
}
} else if (f.isDirectory()) {
// 是目录,递归调用 scnDir 方法,针对这个目录,在进行进一步的判定
scanDir(f, deleteFileName, result);
}
}
}
}
3.2 文件拷贝
package file;
import java.io.*;
import java.util.Scanner;
// 文件拷贝
public class Demo13 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入复制的源文件:");
String src = scanner.next();
System.out.print("请输入要复制的目标文件");
String dst = scanner.next();
// 判定一下 src 是否存在
File srcFile = new File(src);
if (!srcFile.isFile()) {
System.out.println("当前输入的路径有误,不是一个合法的路径!");
return;
}
// 进行复制操作「打开src,把内容一个字节一个字节写入」
try (InputStream inputStream = new FileInputStream(src); OutputStream outputStream = new FileOutputStream(dst)) {
// 每次尝试读一个 byte[],把这整个数组都给写过去
byte[] buffer = new byte[1024];
while (true) {
int len = inputStream.read(buffer);
if (len == -1) {
break;
}
// 读取成功,写入到 outputStream 中
outputStream.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.3 查找目录下是否包含某个字符串
package file;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
// 在指定目录中查找包含制定字符串的文件「比较低效的代码」
public class Demo14 {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
// 1. 先输入要获取到的目录和内容
System.out.print("请输入要遍历的目录:");
String rootDirPath = scanner.next();
System.out.print("请输入要查找的字符串:");
String content = scanner.next();
File rootDir = new File(rootDirPath);
if (!rootDir.isDirectory()) {
System.out.println("输入的路径存在问题!");
return;
}
// 2.进行递归遍历,找到所有符合要求的文件
List<File> results = new ArrayList<>();
scanDirWithContent(rootDir, content, results);
// 3.打印一下找到的文件结果
for (File f : results) {
System.out.println(f.getCanonicalPath());
}
}
private static void scanDirWithContent(File rootDir, String content, List<File> results) {
// 1.先列出 rootDir 中所有的文件
File[] files = rootDir.listFiles();
if (files == null){
// 空目录
return;
}
// 2.依次遍历每个文件,进行判定,如果是目录就进行递归
for (File f : files) {
if (f.isFile()) {
// 如果是普通文件,就判定一下 content 是否被这个文件包含
if (isContainsExist(f, content)) {
// 如果条件为 true,说明文件 f 包含 content,添加到 results
results.add(f);
}
} else if (f.isDirectory()) {
// 是目录就递归
scanDirWithContent(f, content, results);
}
}
}
private static boolean isContainsExist(File f, String content) {
// 1.读取 f 的内容,把内容放到一个 String 类
StringBuilder stringBuilder = new StringBuilder();
try (Reader reader = new FileReader(f)) {
while (true) {
int c = reader.read();
if (c == -1) {
break;
}
stringBuilder.append((char) c);
}
} catch (IOException e) {
e.printStackTrace();
}
// 2.判定 content 是否是读取的 String 的子串
return stringBuilder.indexOf(content) != -1;
}
}