Java文件操作
什么是文件?
这里的文件是指硬盘中的文件。比如:aaa.txt文件就是一个在硬盘中存储的文件
什么是路径?
-
绝对路径:从根节点到目标文件
-
相对路径:从任意节点到目标文件
注意: \ 在Windows中适用 ,在代码中需要进行转义及“\\”
在 liunx 上可能存在,一个文件,有两个不同的路径能找到它。
Windows 上可以认为,路径 和 文件 是一一对应的。
路径就相当于一个文件的 “身份标识”
文件类型:
-
文本文件:ASCII字符组成(表示范围:0-127)(补充:java中默认的字符编码是Unicode)
ASCII码很多:具体链接:http://c.biancheng.net/c/ascii/
-
二进制文件:没有任何字符集的限制
Txt 文本文件, .java / .c 文本文件
.class 二进制文件,.exe 二进制文件 ,jpg MP3 也是二进制文件
硬盘和内存的区别:
-
速度:内存比硬盘快的多
-
空间:内存空间比硬盘小
-
成本:内存比硬盘贵(现在很便宜了)
-
持久化:内存掉点后数据丢失,硬盘还存在
-
注意:JavaSE + 数据结构 都是对内存的处理
IO文件操作
I:input
O:output
文件系统操作
创建文件
签名 | 说明 |
File(File parent, String child) | 根据父目录 + 孩子文件路径,创建一个新的 File 实例 |
File(String pathname) | 根据文件路径创建一个新的 File 实例,路径可以是绝对路径或者相对路径 |
File(String parent, String child) | 根据父目录 + 孩子文件路径,创建一个新的 File 实例,父目录用路径表示 |
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());
}
}
修饰符及返回值类型 | 方法签名 | 说明 |
String | getParent() | 返回 File 对象的父目录文件路径 |
String | getName() | 返回 FIle 对象的纯文件名称 |
String | getPath() | 返回 File 对象的文件路径 |
String | getAbsolutePath() | 返回 File 对象的绝对路径 |
String | getCanonicalPath() | 返回 File 对象的修饰过的绝对路径 |
boolean | exists() | 判断 File 对象描述的文件是否真实存在 |
boolean | isDirectory() | 判断 File 对象代表的文件是否是一个目录 |
boolean | isFile() | 判断 File 对象代表的文件是否是一个普通文件 |
boolean | createNewFile() | 根据 File 对象,自动创建一个空文件。成功创建后返回 true |
String[] | list() | 返回 File 对象代表的目录下的所有文件名 |
File[] | listFiles() | 返回 File 对象代表的目录下的所有文件,以 File 对象表示 |
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());
}
}
删除文件
修饰符及返回值类型 | 方法签名 | 说明 |
boolean | delete() | 根据 File 对象,删除该文件。成功删除后返回 true |
void | deleteOnExit() | 根据 File 对象,标注文件将被删除,删除动作会到 JVM 运行结束时才会进行 |
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());
}
}
重命名文件
修饰符及返回值类型 | 方法签名 | 说明 |
boolean | renameTo(File dest) | 进行文件改名,也可以视为我们平时的剪切、粘贴操作 |
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());
}
}
创建目录
修饰符及返回值类型 | 方法签名 | 说明 |
boolean | mkdir() | 创建 File 对象代表的目录 |
boolean | mkdirs() | 创建 File 对象代表的目录,如果必要,会创建中间目录 |
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());
}
}
判断文件权限
修饰符及返回值类型 | 方法签名 | 说明 |
boolean | canRead() | 判断用户是否对文件有可读权限 |
boolean | canWrite() | 判断用户是否对文件有可写权限 |
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.canRead());
System.out.println(file.canWrite());
System.out.println(file.exists());
}
文件内容操作--数据流
针对文本内容,Java中提供了一组类-->"字符流" :Reader,Writer
1.
Reader ---> FileReader
Writer---> FileWriter
通过read方法读文件,一次读取一个char(字符)或者char[](字符数组的长度)
通过write方法写文件,一次写一个 char(字符)或者char[](字符数组的长度)
2. 通过Scanner 读取字符
构造方法 | 说明 |
Scanner(InputStream is, String charset) | 使用 charset 字符集进行 is 的扫描读取 |
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);
}
}
}
}
}
-
针对字节内容,Java中提供了一组类-->"字节流" :InputStream,OutputStream
读操作:对内容进行读操作(InputStream,Reader)
修饰符及返回值类型 | 方法签名 | 说明 |
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 类,从文件中读取,使用FileInputStream
FileInputStream 概述
签名 | 说明 |
FileInputStream(File file) | 利用 File 构造文件输入流 |
FileInputStream(String name) | 利用文件路径构造文件输入流 |
重点注意:close()
在使用完后一定要将流关闭,否则会有其他操作是的文件中的数据发生改变,并且不断的吃文件资源,使得系统奔溃。(通常这种错误,不会立即显示,但是当文件描述符吃完后就会发生错误,就像一个定时炸弹)
什么是文件描述符?
进程,是使用PCB这样的结构来表示:
-
PID
-
内存指针
-
文件描述符表:
-
记载了当前进程都打开那些文件,每次打开一个文件,都会在文件描述符表申请到一个位置,这个表可以理解成一个数组,而描述符就是这个数组的下标。数组的元素为这个文件在内核中的结构体(C语言)的表示。
-
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;
}
for (int i = 0; i < len; i++) {
System.out.printf("%c", buf[i]);
}
}
}
}
}
注意:读取的内容为中文的话,且字符集为UTF-8,每个汉字三个字节。Unicode和utf8,是两种不同的编码表,即使是一个汉字字符,得到的数值也是不同的。
写操作:对内容进行写操作(OutputStream,Writer)
修饰符及返回值类型 | 方法签名 | 说明 |
void | write(int b) | 写入要给字节的数据 |
void | write(byte[] b) | 将 b 这个字符数组中的数据全部写入 os 中 |
int | write(byte[] b, int off, int len) | 将 b 这个字符数组中从 off 开始的数据写入 os 中,一共写 len 个 |
void | flush() | 调用 flush(刷新)操作,将数据刷到设备中 |
重要:
flush():
I/O 的速度是很慢的,所以,大多的 OutputStream 为 了减少设备操作的次数,在写数据的时候都会将数据先暂时写入内存的一个指定区域里,直到该区域满了或者其他指定条件时才真正将数据写入设备中,这个区域一般称为缓冲区。但造成一个结果,就是我们写的数据,很可能会遗留一部分在缓冲区中。需要在最后或者合适的位置,调用 flush(刷新)操作,将数据刷到设备中。
跟 InputStream 一样,OutputStream 同样只是一个抽象类,要使用还需要具体的实现类。现在还是写入文件中, 所以使用 FileOutputStream
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();
}
}
}
同样的对对于字符的写入:
利用 OutputStreamWriter 进行字符写入,需要搭配PrintWriter进行使用
-
PrintWriter 类中提供了我们熟悉的 print/println/printf 方法
OutputStreamWriter osWriter = new OutputStreamWriter(os, "utf-8"); // 告诉它,我们的字符集编码是 utf-8 的
PrintWriter writer = new PrintWriter(osWriter);
// 接下来我们就可以方便的使用 writer 提供的各种方法了
writer.print("Hello");
writer.println("你好");
writer.printf("%d: %s\n", 1, "没什么");
// 不要忘记 flush
writer.flush();
例子:扫描指定目录
并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件
public class Main {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入要扫描的根目录(绝对路径 OR 相对路径): ");
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<>();
// 因为文件系统是树形结构,所以我们使用深度优先遍历(递归)完成遍历
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 || files.length == 0) {
return;
}
for (File file : files) {
if (file.isDirectory()) {
scanDir(file, token, result);
} else {
if (file.getName().contains(token)) {
result.add(file.getAbsoluteFile());
}
}
}
}
}
例子:进行普通文件的复制
public class Main {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入要复制的文件(绝对路径 OR 相对路径): ");
String sourcePath = scanner.next();
File sourceFile = new File(sourcePath);
if (!sourceFile.exists()) {
System.out.println("文件不存在,请确认路径是否正确");
return;
}
if (!sourceFile.isFile()) {
System.out.println("文件不是普通文件,请确认路径是否正确");
return;
}
System.out.print("请输入要复制到的目标路径(绝对路径 OR 相对路径): ");
String destPath = scanner.next();
File destFile = new File(destPath);
if (destFile.exists()) {
if (destFile.isDirectory()) {
System.out.println("目标路径已经存在,并且是一个目录,请确认路径是否正确");
return;
}
if (destFile.isFile()) {
System.out.println("目录路径已经存在,是否要进行覆盖?y/n");
String ans = scanner.next();
if (!ans.toLowerCase().equals("y")) {
System.out.println("停止复制");
return;
}
}
}
try (InputStream is = new FileInputStream(sourceFile)) {
try (OutputStream os = new FileOutputStream(destFile)) {
byte[] buf = new byte[1024];
int len;
while (true) {
len = is.read(buf);
if (len == -1) {
break;
}
os.write(buf, 0, len);
}
os.flush();
}
}
System.out.println("复制已完成");
}
}
示例:找到名称或者内容中包含指定字符的所有普通文件
扫描指定目录,并找到名称或者内容中包含指定字符的所有普通文件(不包含目录)
public class Main {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入要扫描的根目录(绝对路径 OR 相对路径): ");
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() + " 个,它们分别是");
for (File file : result) {
System.out.println(file.getCanonicalPath());
}
}
private static void scanDirWithContent(File rootDir, String token,
List<File> result) throws IOException {
File[] files = rootDir.listFiles();
if (files == null || files.length == 0) {
return;
}
for (File file : files) {
if (file.isDirectory()) {
scanDirWithContent(file, token, result);
} else {
if (isContentContains(file, token)) {
result.add(file.getAbsoluteFile());
}
}
}
}
// 我们全部按照utf-8的字符文件来处理
private static boolean isContentContains(File file, String token) throws
IOException {
StringBuilder sb = new StringBuilder();
try (InputStream is = new FileInputStream(file)) {
try (Scanner scanner = new Scanner(is, "UTF-8")) {
while (scanner.hasNextLine()) {
sb.append(scanner.nextLine());
sb.append("\r\n");
}
}
}
return sb.indexOf(token) != -1;
}
}
注意:
这里有一个细节需要注意,以上的案例都是在文件大小较小的情况的实行的。诺是出现一个文件大小大于内存的大小,如何解决?
比如内存只有 1G 而文件有 4G ,理论上一次可以读取 1G 的内容,可是,诺是有些字符在第一个G 和 第二个 G的分界点,此时就会导致一个问题,字符被夹断了。所以我们在读取时就不能一次读取 1G 的内容,而是读取小于 1G 内容,比如:第一次读取 1G的内容,第二次 从1G-1K的位置开始读取,每次读取都会重复 (每次-1)*1k 的内容