文章目录
- 一、文件
- 1.1 文件的概念
- 1.2 文件的组织结构
- 1.3 绝对路径和相对路径
- 二、文件操作File类
- 2.1 属性和常用方法
- 2.2 使用案例
- 三、字节流和字符流
- 3.1 InputStream 和 FileInputStream
- 3.2 使用 Scanner 读取字符
- 3.2 OutputStream 和 FileOutputStream
- 3.3 Reader 和 FileReader
- 3.4 Writer 和 FileWriter
- 四、文件操作案例
- 4.1 删除文件
- 4.2 复制文件
一、文件
1.1 文件的概念
在计算机中,文件是用于存储数据的基本单位。它可以包含文本、图像、音频、视频或其他类型的数据。文件可以用于保存程序代码、用户数据、操作系统配置等内容。每个文件都有一个唯一的名称,通过这个名称可以在计算机系统中找到和访问文件。
文件除了有数据内容之外,还有一部分信息,例如文件名、文件类型、文件大小等并不作为文件的数据而存在,我们把这部分信息可以视为文件的元信息。
1.2 文件的组织结构
文件系统是计算机中用于组织和管理文件的一种结构。文件系统将文件组织成层次化的目录(或文件夹)结构,类似于树状结构。在大多数现代操作系统中,文件系统的顶层是根目录,下面可以有多个子目录,而每个子目录下又可以包含更多的文件或子目录。
文件系统的组织结构可以让用户更方便地管理和访问文件,通过目录结构可以更快速地定位到目标文件。
1.3 绝对路径和相对路径
1. 绝对路径
绝对路径是文件或目录在文件系统中的完整路径。它从根目录开始描述文件的路径,一直延伸到目标文件所在的目录。绝对路径的写法因操作系统而异。
- 例如在Windows系统中,一个文件的绝对路径可能是类似这样的:
"C:\Users\Username\Documents\file.txt"
; - 而在类Unix系统中,绝对路径可能是类似这样的:
"/home/username/documents/file.txt"
。
2. 相对路径
相对路径是相当于当前工作目录的路径,当前工作目录是指用户当前所在的目录位置。相对路径不需要从根目录开始,它只需要描述从当前目录到目标文件的相对位置。相对路径的写法也因操作系统而异,但是相对路径在不同操作系统中通常更加简洁。例如,在当前目录下的一个文件,其相对路径可以是:"file.txt"
。
另外值得注意的是:
./
表示当前目录;../
表示上级目录。
二、文件操作File类
File
类是在 Java 中用于处理文件和目录的标准类。它提供了许多的属性和方法,用于管理文件以及目录的创建、文件的读取、写入等操作。
2.1 属性和常用方法
1. 属性
File
类提供了以下几个基本属性:
-
路径名(Path):文件或目录在文件系统中的路径。可以通过
getPath()
方法获取路径名。 -
绝对路径(Absolute Path):文件或目录在文件系统中的完整路径。可以通过
getAbsolutePath()
方法获取绝对路径。 -
父目录(Parent):文件或目录所在的上级目录。可以通过
getParent()
方法获取父目录的路径。 -
文件名(Name):文件或目录的名称,不包含路径信息。可以通过
getName()
方法获取文件名。 -
是否存在(Exists):判断文件或目录是否存在。可以通过
exists()
方法来检查文件或目录是否存在。
这些属性可以帮助我们更好地了解文件或目录的位置和状态。例如,可以使用绝对路径来明确指定文件的位置,使用文件名来对文件进行标识,使用 exists()
方法来判断文件是否存在等等。
2. 构造方法
File
类构造方法以及对应的描述如下:
构造方法 | 描述 |
---|---|
File(String pathName) | 使用给定的路径名创建一个新的 File 实例。 |
File(String parent, String child) | 使用给定的父目录和子文件/目录名创建一个新的 File 实例。 |
File(File parent, String child) | 使用给定的父目录抽象路径和子文件/目录名创建一个新的 File 实例。 |
3. 基本方法
File
类的一些基本操作方法如下:
方法 | 描述 |
---|---|
boolean createNewFile() | 在文件系统中创建新文件。 |
boolean mkdir() | 创建此抽象路径名指定的目录。 |
boolean mkdirs() | 创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。 |
boolean delete() | 删除文件或目录。 |
boolean isFile() | 测试此抽象路径名表示的是否为文件。 |
boolean isDirectory() | 测试此抽象路径名表示的是否为目录。 |
String[] list() | 返回目录中的文件和子目录的名称数组。 |
File[] listFiles() | 返回目录中的文件和子目录的 File 对象数组。 |
long length() | 返回文件的长度(字节数)。 |
long lastModified() | 返回文件最后修改的时间戳。 |
这些方法可以用于创建、删除、判断文件/目录类型、获取文件信息等基本的文件操作。
2.2 使用案例
1. 案例一:观察 get
系列方法的特点和差异
public static void main(String[] args) throws IOException {
File file = new File("test.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());
}
运行结果:
null
test.txt
test.txt
D:\JavaCode\learn-code\code20230729\test.txt
D:\JavaCode\learn-code\code20230729\test.txt
2. 案例二:目录的创建
public static void main(String[] args) {
File file = new File("test");
System.out.println(file.exists());
System.out.println(file.mkdirs());
System.out.println(file.exists());
}
运行结果:
false
true
true
此时发现在当前工作目录下就多了一个test
目录:
三、字节流和字符流
Java 中的 I/O 操作可以分为字节流和字符流。字节流主要用于处理二进制数据,而字符流用于处理文本数据。下面介绍四种常用的流类:
3.1 InputStream 和 FileInputStream
1. InputStream
类
InputStream
是 Java 中用于读取字节数据的抽象类,它是所有字节输入流的基类。下面是一些常用的 InputStream
方法及其操作:
方法 | 描述 |
---|---|
int read() | 从输入流中读取下一个字节的数据,返回读取的字节(0 到 255),如果已达到文件末尾,则返回 -1。 |
int read(byte[] b) | 从输入流中读取最多 b.length 个字节的数据到字节数组 b ,返回实际读取的字节数,如果已达到文件末尾,则返回 -1。 |
int read(byte[] b, int off, int len) | 从输入流中读取最多 len 个字节的数据到字节数组 b 的指定偏移量 off 处,返回实际读取的字节数,如果已达到文件末尾,则返回 -1。 |
long skip(long n) | 跳过输入流中的 n 个字节的数据,返回实际跳过的字节数。 |
int available() | 返回可以从输入流中读取的字节数。 |
void close() | 关闭输入流并释放与其关联的所有资源。 |
2. FileInputStream
类
InputStream
只是一个抽象类,要使用还需要具体的实现类。关于 InputStream
的实现类有很多,基本可以认为不同的输入设备都可以对应一个 InputStream
类,此次需要从文件中读取,所以使用就会FileInputStream
类。
构造方法:
构造方法 | 描述 |
---|---|
FileInputStream(File file) | 利用 File 构造文件输入流 |
FileInputStream(String name) | 利用文件路径构造文件输入流 |
读取文件案例:
首先在工作目录下创建一个test.txt
的文本文件,其中的内容有hello world
,然后使用read
方法进行读取:
public static void main(String[] args) throws IOException {
InputStream inputStream = new FileInputStream("test.txt");
/*
while (true){
int b = inputStream.read();
if(b==-1){
break;
}
System.out.printf("%x \n", (int)b);
}*/
while (true){
byte[] buffer = new byte[1024];
int len = inputStream.read(buffer);
System.out.println("len: " + len);
if(-1 == len){
break;
}
for (int i = 0; i < len; i++) {
System.out.printf("%x ", buffer[i]);
}
System.out.println();
}
inputStream.close();
}
执行结果:
len: 11
68 65 6c 6c 6f 20 77 6f 72 6c 64
len: -1
将读取到的结果,对照ASCII表就可以发现,其内容就是hello world
。
3.2 使用 Scanner 读取字符
上述例子中,我们看到了对字符类型直接使用 InputStream 进行读取是非常麻烦且困难的,所以,我们使用一种我们之前比较熟悉的类来完成该工作,就是 Scanner
类。
Scanner的构造方法有一个就是使用InputStream
对象来构造的:
构造方法 | 说明 |
---|---|
Scanner(InputStream is, String charset) | 使用 charset 字符集进行 is 的扫描读取 |
例如:使用InputStream
对象来构造Scanner
对象:
public static void main(String[] args) throws IOException {
try (InputStream inputStream = new FileInputStream("test.txt");
Scanner scanner = new Scanner(inputStream, "utf8")) {
while (scanner.hasNext()) {
String s = scanner.next();
System.out.println(s);
}
}
}
3.2 OutputStream 和 FileOutputStream
1. OutputStream
类
OutputStream
是 Java 中所有字节输出流的抽象基类。它用于向目标写入字节数据,是处理二进制数据的常用流类。OutputStream
提供了一系列方法来写入不同类型的数据到输出流中,并且可以通过其子类来实现具体的输出目标,如文件、网络连接等。
下面是 OutputStream
的几个基本方法以及对应的描述:
方法 | 描述 |
---|---|
void write(int b) | 将指定的字节写入输出流。 |
void write(byte[] b) | 将字节数组中的所有字节写入输出流。 |
void write(byte[] b, int off, int len) | 将字节数组中的一部分字节写入输出流。 |
void flush() | 刷新输出流,将缓冲区的内容强制写入目标设备。 |
void close() | 关闭输出流并释放相关的资源。 |
在使用 OutputStream
时,通常会使用其子类 FileOutputStream
,并可能结合使用 BufferedOutputStream
进行缓冲输出,以提高写入性能。
2. FileOutputStream
类
OutputStream
同样只是一个抽象类,要使用还需要具体的实现类。在文件操作中,需要使用 FileOutputStream
类,其构造方法如下:
构造方法 | 描述 |
---|---|
FileOutputStream(File file) | 创建一个向指定 File 对象表示的文件中写入数据的输出流。 |
FileOutputStream(File file, boolean append) | 创建一个向指定 File 对象表示的文件中写入数据的输出流,并指定是否以追加方式写入数据。 |
FileOutputStream(String name) | 创建一个向指定文件名表示的文件中写入数据的输出流。 |
FileOutputStream(String name, boolean append) | 创建一个向指定文件名表示的文件中写入数据的输出流,并指定是否以追加方式写入数据。 |
- 这些构造方法用于创建
FileOutputStream
的实例,可以用于向指定的文件中写入数据。构造方法中的参数可以是一个File
对象或一个表示文件名的字符串。 - 其中,如果指定的文件不存在,将会创建新的文件;
- 如果指定的文件已经存在,可以通过
append
参数来控制是否以追加方式写入数据。 - 若
append
参数为true
,则数据会追加到文件的末尾;若为false
,则会覆盖原有文件的内容。
示例:使用OutputStream
将Hello, OutputStream!
写入文件test.txt
中:
import java.io.*;
public class OutputStreamExample {
public static void main(String[] args) {
String data = "Hello, OutputStream!";
try (OutputStream outputStream = new FileOutputStream("test.txt")) {
byte[] bytes = data.getBytes();
outputStream.write(bytes);
System.out.println("数据已经写入文件.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.3 Reader 和 FileReader
1. Reader
类
Reader
是 Java 中所有字符输入流的抽象基类,它用于从输入源(如文件、网络连接等)读取字符数据,适合处理文本数据。Reader
类提供了用于读取字符数据的方法,并可以通过其子类实现具体的输入源,如文件、字符串等。
基本方法如下:
方法签名 | 描述 |
---|---|
int read() | 读取单个字符并返回字符的 Unicode 值。 |
int read(char[] cbuf) | 将字符读入数组 cbuf,并返回读取的字符数。 |
int read(char[] cbuf, int off, int len) | 将字符读入数组 cbuf 的指定位置,并返回读取的字符数。 |
int read(CharBuffer target) | 将字符读入 CharBuffer,并返回读取的字符数。 |
void close() | 关闭输入流并释放相关的资源。 |
需要注意的是,在使用 Reader
读取字符数据时,要考虑字符编码的设置,以免出现乱码问题。同时,为了提高读取性能,通常会结合使用缓冲流(如 BufferedReader
)对 Reader
进行包装,从而减少实际的读取操作。
Reader 的子类:
Java 中提供了多个 Reader 的子类,其中常用的包括:
FileReader
:用于从文件中读取字符数据。StringReader
:用于从字符串中读取字符数据。BufferedReader
:用于缓冲读取字符数据,提高读取性能。
2. FileReader
类
构造方法:
构造方法 | 描述 |
---|---|
FileReader(File file) | 创建一个从指定 File 对象表示的文件中读取数据的 FileReader 。 |
FileReader(String fileName) | 创建一个从指定文件名表示的文件中读取数据的 FileReader 。 |
示例代码:使用 FileReader
读取文件:
import java.io.*;
public class ReaderExample {
public static void main(String[] args) {
try (Reader reader = new FileReader("test.txt")) {
char[] buffer = new char[1024];
int length;
while ((length = reader.read(buffer)) != -1) {
System.out.print(new String(buffer, 0, length));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.4 Writer 和 FileWriter
1. Writer
类
Writer
是 Java 中所有字符输出流的抽象基类,它用于向目标写入字符数据,适合处理文本数据。Writer
类提供了用于写入字符数据的方法,并可以通过其子类实现具体的输出目标,如文件、字符串等。
基本方法如下:
方法签名 | 描述 |
---|---|
void write(int c) | 写入单个字符。 |
void write(char[] cbuf) | 将字符数组中的所有字符写入输出流。 |
void write(char[] cbuf, int off, int len) | 将字符数组中的一部分字符写入输出流。 |
void write(String str) | 写入字符串。 |
void write(String str, int off, int len) | 写入字符串的一部分。 |
void flush() | 刷新输出流,将缓冲区的内容强制写入目标设备。 |
void close() | 关闭输出流并释放相关的资源。 |
Writer 的子类:
Java 中提供了多个 Writer 的子类,其中常用的包括:
FileWriter
:用于向文件中写入字符数据。StringWriter
:用于向字符串中写入字符数据。BufferedWriter
:用于缓冲写入字符数据,提高写入性能。
2. FileWriter
类
构造方法:
构造方法 | 描述 |
---|---|
FileWriter(File file) | 创建一个向指定 File 对象表示的文件中写入数据的字符输出流。 |
FileWriter(File file, boolean append) | 创建一个向指定 File 对象表示的文件中写入数据的字符输出流,并指定是否以追加方式写入数据。 |
FileWriter(String fileName) | 创建一个向指定文件名表示的文件中写入数据的字符输出流。 |
FileWriter(String fileName, boolean append) | 创建一个向指定文件名表示的文件中写入数据的字符输出流,并指定是否以追加方式写入数据。 |
- 这些构造方法用于创建
FileWriter
的实例,可以用于向指定的文件中写入字符数据。构造方法中的参数可以是一个 File 对象或一个表示文件名的字符串。 - 其中,如果指定的文件不存在,将会创建新的文件;
- 如果指定的文件已经存在,可以通过
append
参数来控制是否以追加方式写入数据。 - 若
append
参数为true
,则数据会追加到文件的末尾;若为false
,则会覆盖原有文件的内容。
示例代码:使用 FileWriter
写入文件:
import java.io.*;
public class WriterExample {
public static void main(String[] args) {
try (Writer writer = new FileWriter("test.txt")) {
String data = "Hello, Writer!";
writer.write(data);
System.out.println("数据写入文件成功.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
四、文件操作案例
4.1 删除文件
扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件。
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
public class IODemo {
private static final Scanner scanner = new Scanner(System.in);
public static void main(String[] args) throws IOException {
// 让用户指定一个搜索的目录
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) throws IOException {
System.out.println("[scanDir]:" + root.getCanonicalPath());
// 列出当前目录下的文件
File[] files = root.listFiles();
if (files == null) {
return;
}
for (File file : files) {
if (file.isDirectory()) {
// 如果是目录就继续递归
scanDir(file, nameToDelete);
} else {
// 如果是普通文件, 判断是否是要删除的文件
if (file.getName().equals(nameToDelete)) {
System.out.println("确认是否要删除文件(true/false):" + file.getCanonicalPath());
boolean flag = Boolean.parseBoolean(scanner.next());
if (flag) {
boolean delete = file.delete();
System.out.println("删除成功!");
} else {
System.out.println("取消删除!");
}
}
}
}
}
}
上述代码实现了一个简单的文件搜索和删除功能,以下是代码的功能描述和简要解释:
-
首先,程序会要求用户输入一个要搜索的目录路径。
-
接着,程序会验证用户输入的路径是否为一个目录,如果不是目录,则提示输入错误并结束程序。
-
然后,程序会要求用户输入一个要删除的文件名。
-
接下来,程序会递归地扫描指定路径下的所有文件和子目录,查找指定要删除的文件名。
-
如果找到了与要删除的文件名匹配的文件,程序会询问用户是否确认删除该文件。用户可以输入 “true” 确认删除,或输入 “false” 取消删除。
-
如果用户确认删除,程序将执行文件删除操作,并输出 “删除成功!”,否则输出 “取消删除!”。
-
程序使用递归的方式对目录进行扫描,以找到指定的文件并进行删除。
4.2 复制文件
import java.io.*;
import java.util.Scanner;
// 文件的复制
public class IODemo {
public static void main(String[] args) throws IOException {
// 输入两个路径:源和目标
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;
}
destFile.createNewFile();
// 进行拷贝操作
try(InputStream inputStream = new FileInputStream(srcFile);
OutputStream outputStream = new FileOutputStream(destFile)){
while (true){
int read = inputStream.read();
if(read == -1){
System.out.println("复制完成");
break;
}
outputStream.write(read);
}
}
}
}
上述代码实现了一个简单的文件复制功能。以下是代码的功能描述和简要解释:
-
程序要求用户输入两个路径:源文件路径和目标文件路径。
-
然后,程序会验证用户输入的源文件路径是否为一个已存在的文件,如果不是文件,则提示输入错误并结束程序。
-
接着,程序会验证用户输入的目标文件路径是否为一个不存在的文件,如果是文件,则提示输入错误并结束程序。如果目标文件不存在,会创建一个新的目标文件。
-
然后,程序使用
FileInputStream
和FileOutputStream
分别创建输入流和输出流,用于读取源文件内容和写入目标文件内容。 -
程序通过一个循环逐字节地读取源文件内容,并将读取到的字节写入目标文件。循环终止条件是读取到源文件的结尾(即
read()
方法返回 -1)。 -
当循环结束后,表示文件复制完成,程序输出 “复制完成”。