常用的文件读写操作

1. 摘要

在Java I/O体系中,File类是唯一代表磁盘文件本身的对象。

File类定义了一些与平台无关的方法来操作文件,包括检查一个文件是否存在、创建、删除、重命名、判断文件的读写权限、设置和查询文件的最近修改时间等操作。

值得注意的地方是,Java中通常的File并不代表一个真是存在的文件对象,当你通过指定的一个路径扫描时,它就会返回一个代表这个路径相关联的一个虚拟对象,这个对象可能是一个真是存在的文件或者是一个包含多个文件的目录。

2. File 类介绍

File类没有无参构造方法,最常用的是使用下面的构造方法来生成File对象

1
2
3
4
5
6
7
// 指定一个完整路径,获取文件对象
File file = new File(FILE_PATH);
System.out.println(file.getName()); //1.txt

//定义个一个父文件路径和子文件名称,获取文件对象
File file1 = new File("/Users/xiaoyuge/Desktop", "1.txt");
System.out.println(file1.getName()); //1.txt

File类中定义类很对关于File 对象的一些操作方法,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/**
* @author xiaoyuge
*/
public class FileDemo {
public static final String FILE_PATH = "/Users/xiaoyuge/Desktop/txt/1.txt";

public static void main(String[] args) throws IOException {
// 指定一个完整路径,获取文件对象
File file = new File(FILE_PATH);
//判断文件是否存在
if (file.exists()) {
System.out.println("文件存在");
} else {
file.createNewFile();
System.out.println("文件不存在,创建一个文件");
}

//获取文件父节点目录对象
File parentFile = file.getParentFile();
if (parentFile.exists()) {
System.out.println("文件目录存在");
} else {
parentFile.mkdirs();
System.out.println("文件目录不存在,创建");
}
//判断指定的父节点路径是否是一个目录
if (parentFile.isDirectory()) {
System.out.println("父节点是一个目录");
}
//获取目录下的所哟吴恩见/文件夹(仅该层路径下)
File[] files = parentFile.listFiles();
for (File f : files) {
System.out.println(f + ";");
}
File file2 = files[0];
//获取文件名,文件夹名
System.out.println("文件名:" + file2.getName());
//获取文件、文件夹路径
System.out.println("路径:" + file2.getPath());
//获取绝对路径
System.out.println("绝对路径:" + file2.getAbsolutePath());
//获取文件父目录路径
System.out.println("父文件夹名:" + file2.getParent());
//判断文件、文件夹是否存在
System.out.println(file2.exists() ? "存在" : "不存在");
//判断文件是否可写
System.out.println(file2.canWrite() ? "可写" : "不可写");
//判断文件是否可读
System.out.println(file2.canRead() ? "可读" : "不可读");
//判断文件是否可执行
System.out.println(file2.canExecute() ? "可执行" : "不可执行");
//判断文件、文件夹是否是目录
System.out.println(file2.isDirectory() ? "目录" : "不是目录");
//判断文件、文件夹是否是标准文件
System.out.println(file2.isFile() ? "是文件" : "不是文件");
//判断路径名是否是绝对路径
System.out.println(file2.isAbsolute() ? "是绝对路径" : "不是绝对路径");
//获取文件、文件夹上一次修改时间
System.out.println("最后一次修改时间:" + file2.lastModified());
//获取文件自结束,如果是文件夹则为0
System.out.println("文件大小:" + file2.length());


//对文件重命名
File newFile = new File(file2.getParentFile(),"2.txt");
file2.renameTo(newFile);

//删除指定文件、文件夹
file2.delete();

//当虚拟机终止时,删除指定文件,文件夹
file2.deleteOnExit();
}
}

需要注意的地方:

  • 分隔符问题:不同的操作系统,路径分隔符不一致,可以通过File.separator实现跨平台的编程

    1
    2
    File file3 = new File(File.separator+"Users"+File.separator+"xiaoyuge"+File.separator+"Desktop"+File.separator+"txt"+File.separator+"22.txt");
    System.out.println(file3.getPath());
  • 删除的如果是一个文件夹的话,如果文件夹有文件/文件夹,是无法删除成功

3. 文件的读写操作

对文件的读写,可以通过字节流或者字符流接口来完成,不管那种方式,大致分为一下几个步骤:

  1. 获取文件file对象

  2. 通过file对象,获取一个字节流或者字符流接口对象,进行读写操作

  3. 关闭文件流

IO流继承体系

3.1 通过字节流写入

字节流接口的文件写入,可以通过OutputStream下的子类FileOutputStream来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class FileWriteDemo {
public static final String FILE_PATH = "/Users/xiaoyuge/Desktop/txt/22.txt";

public static void main(String[] args) throws IOException {
File file = new File(FILE_PATH);
if (!file.exists()) {
file.createNewFile();
}
//向文件中写如数据(这种方式会覆盖原始数据),该文件中原来包含asdfasdfasdf
String content = "通过字节流写入";
OutputStream outputStream = new FileOutputStream(file);
outputStream.write(content.getBytes(StandardCharsets.UTF_8));
//关闭流
outputStream.close();
}
}

上面的操作方式会覆盖原始的数据,如果想在已有的文件里面追加数据,可以使用下面的方式

1
2
3
4
5
6
7
8
//向文件中写如数据(这种方式会覆盖原始数据),该文件中原来包含asdfasdfasdf
String content = "-----这是追加的部分-----";
OutputStream outputStream = new FileOutputStream(file, true);
outputStream.write(content.getBytes(StandardCharsets.UTF_8));
//关闭流
outputStream.close();

//文件内容为:通过字节流写入-----这是追加的部分-----

3.2 通过字节流读取

字节流读取文件,可以通过InputStream下的子类FileInputStream来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class FileReadDemo {

public static final String FILE_PATH = "/Users/xiaoyuge/Desktop/txt/22.txt";

public static void main(String[] args) throws IOException {
File file = new File(FILE_PATH);
if (file.exists()){
//获取文件流
InputStream is = new FileInputStream(file);
//临时区
byte[] buffer = new byte[1024];
//粉刺读取数据,每次最多读取1024个字节,将数据读取到临时区中,同时返回读取的字节个数,如果遇到文件末尾,会返回-1
int len;
while ((len = is.read(buffer)) > -1){
//字节转化为字符
String msg = new String(buffer, 0, len, StandardCharsets.UTF_8);
System.out.println(msg);
}
//关闭流
is.close();
}
}
}

3.3 通过字符流写入

JDK 提供了WriterReader字符流接口,字符流写入可以通过Writer下的子类FileWriter来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class FileWriteDemo {
public static final String FILE_PATH = "/Users/xiaoyuge/Desktop/txt/22.txt";

public static void main(String[] args) throws IOException {
File file = new File(FILE_PATH);
if (!file.exists()){
file.createNewFile();
}
//实例话writer类对象
Writer writer = new FileWriter(file);
writer.write("hello xiaoyuge");
writer.write("\n");
writer.append("this is a demo");
//关闭流
writer.close();
}
}

3.4 通过字符流读取

字符流读取可以通过Reader下的子类FileReader来实现文件的数据读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class FileReadDemo {

public static final String FILE_PATH = "/Users/xiaoyuge/Desktop/txt/22.txt";

public static void main(String[] args) throws IOException {
File file = new File(FILE_PATH);
if (file.exists()){
//实例化输入流
Reader reader = new FileReader(file);
//临时区
char[] buffer = new char[1024];
// 分次读取数据,每次最多读取1024个字符,将数据读取到临时区之中,同时返回读取的字节个数,如果遇到文件末尾,会返回-1
int len;
while ((len = reader.read(buffer)) > -1){
String msg = new String(buffer, 0 , len);
System.out.println(msg);
}
reader.close();
}
}
}

3.5 文件拷贝

在实际的软件开发过程中,避免不了文件拷贝,通过以上的接口方法,可以很容易的写出一个文件复制的方法,以字节流操作为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[]args){
// 1. 创建一个字节数组作为数据读取的临时区
byte[] buffer = new byte[1024];
// 2. 创建一个 FileInputStream 对象用于读取文件
InputStream input = new FileInputStream(new File("input.txt"));
// 3. 创建一个 FileOutputStream 对象用于写入文件
OutputStream output = new FileOutputStream(new File("output.txt"));
// 4. 循环读取文件内容到临时区,并将临时区中的数据写入到输出文件中
int length;
while ((length = input.read(buffer)) != -1) {
output.write(buffer, 0, length);
}
// 5. 关闭输入流
input.close();
// 6. 关闭输出流
output.close();
}

除此之外,JDK也支持采用缓冲流读写技术来实现数据的高效读写

之所以高效,是因为字节缓冲流内部维护了一个缓冲区,读写是先将数据存入缓冲区中,当缓冲区满时,再将数据一次性读出/写入;这样可以减少与磁盘实际的I/O操作次数,可以显著提升读写效率

比如以字节流缓冲流为例,包装类分别是BufferedInputStream(字节流缓冲输入流)BufferedOutputStream(字节流缓冲输出流)

采用缓冲流拷贝文件,具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) throws IOException {
//创建一个自结束组作为数据读取的临时区
byte[] buffer = new byte[1024];
//创建一个BufferedInputStream 读取文件
InputStream bis = new BufferedInputStream(new FileInputStream("/Users/xiaoyuge/Desktop/txt/input.txt"));
//创建一个BufferedOutputStream 写入文件
OutputStream bos = new BufferedOutputStream(new FileOutputStream("/Users/xiaoyuge/Desktop/txt/output.txt"));

//循环读取文件内容到临时区,并将缓冲区的数据写入到输出文件中
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
//关闭输入
bis.close();
//关闭输出
bos.close();
}

在大文件的拷贝中,使用缓冲流比不适用缓冲流技术至少快10倍。

4. 字节流和字符流互转

4.1 字节流转字符流

字节流转字符流的操作,主要体现在数据的读取阶段,转化过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void main(String[] args) throws IOException {
File file = new File(FILE_PATH);
if (file.exists()){
InputStream is = new FileInputStream(file);
//转字符流输入流,指定UTF-8编码规则,读取数据
Reader reader = new InputStreamReader(is,StandardCharsets.UTF_8);
//缓冲区
char[] buffer = new char[1024];
// 分次读取数据,每次最多读取1024个字符,将数据读取到缓冲区之中,同时返回读取的字节个数
int len;
while ((len = reader.read(buffer)) > -1) {
// 字符转为字符串
String msg = new String(buffer, 0, len);
System.out.println(msg);
}

// 关闭输入流
reader.close();
is.close();
}
}

当读取数据的时候,先通过字节流读取,在转成字符流读取。

字节流转字符流需要指定编码规则,如果没有指定,会区当前系统的默认规则。

4.2 字符流转字节流

字符流转字节流的操作,主要体现在数据的写入阶段,转化过程如下图所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[]args){
// 创建一个 newReadWriteDemo.txt 文件
File file = new File("readWriteDemo.txt");
if(!file.exists()){
file.createNewFile();
}

// 获取字节输出流
OutputStream outputStream = new FileOutputStream(file);
// 转字符流输出流,指定 UTF_8 编码规则,写入数据
Writer out = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
// 输出字符串
out.write("Hello");
// 输出换行
out.write("\n");
// 追加信息,append 方法底层本质调用的是 write 方法
out.append("我们一起来学习Java");

// 关闭输出流
out.close();
outputStream.close();
}

同样的,当写入数据的时候,先通过字符流写入,再转成字节流输出。

字符流转字节流,也需要指定编码规则,如果没有指定,会取当前系统默认的编码规则。