应用程序经常需要访问文件和目录,读取文件信息或写入信息到文件,即从外界输入数据或者向外界传输数据,这些数据可以保存在磁盘文件、内存或其他程序中。在Java中,对这些数据的操作是通过I/O技术来实现的。所谓I/O技术,就是数据的输入(Input)、输出(Output)技术。本章将对Java的 I/O系统进行讲解,包括I/O的体系结构、流的概念、字节流、处理字节流的基本类InputStream和OutputStream、字符流、处理字符流的基本类Reader和Writer、文件管理、序列化和反序列化等。
12.1 I/O流概述
Java将数据的输入/输出操作当作“流”来处理,“流”是一组从源头到目的地的有序的字节序列。在Java程序中,从某个数据源读取数据到程序的流称为输入流,通过程序使用数据流将数据写入到目的地的称为输出流。输入流和输出的读取和写入流程如图12.1所示。
(a)输入流 (b)输出流
图12.1 输入/输出流示意图
当程序需要从某个数据源读入数据的时候,就会开启一个输入流,数据源可以是文件、内存或网络等。相反,需要写出数据到某个数据源目的地的时候,也会开启一个输出流,这个数据源目的地也能够是文件、内存或网络等。I/O流有很多种,按操作数据单位不同可分为字节流和字符流,按数据流的方向不同分为输入流和输出流,如表12.1所示。
表12.1 流的分类
输入/输出 |
字节流 |
字符流 |
输入流 |
InputStream |
Reader |
输出流 |
OutputStream |
Writer |
输入流和输出流的区别是以程序为中心来进行判断,从外部设备读取数据到程序是输入流,从程序写入数据到外部设备是输出流。字节流的单位是一个字节,即8bit;字符流的单位是两个字节,即16bit。表12.1是I/O流的简单分类,实际开发中需要使用的的I/O流共涉及40多个类,都是从这4个抽象基类派生的。接下来,我们先学习输入/输出流的体系结构。
Java.io包中的最重要的部分是由5个类和一个接口组成。5个类是指File、RandomAccessFile、InputStream、OutputStream、Writer、Reader,一个接口指的是Serializable。掌握了这些I/O的核心操作,那么对于Java中的I/O体系也就有了一个初步的认识了。总体上看,Java I/O主要包括如下3个部分:
• 流式部分:I/O的主体部分。
• 非流式部分:主要包含一些辅助流式部分的类,如File类、RandomAccessFile类和FileDescriptor类等。
• 其他类:主要是文件读取部分的与安全相关的类(如SerializablePermission类),以及与本地操作系统相关的文件系统的类,如(FileSystem类、Win32FileSystem类和WinNTFileSystem类)。
这里,将Java I/O中主要的类简单介绍如下:
• File类(文件特征与管理类):用于文件或者目录的描述信息等(An abstract representation of file and directory pathnames),如生成新目录、修改文件名、删除文件、判断文件所在路径等。
• InputStream类(二进制格式操作类):基于字节输入操作的抽象类,是所有输入流的父类,定义了所有输入流都具有的共同特征。
• OutputStream类(二进制格式操作类):基于字节输出操作的抽象类,是所有输出流的父类,定义了所有输出流都具有的共同特征。
• Reader类(文件格式操作类):抽象类,基于字符的输入操作。
• Writer类(文件格式操作类):抽象类,基于字符的输出操作。
• RandomAccessFile类(随机文件操作类):它的功能丰富,可以从文件的任意位置进行存取(输入输出)操作。
综上所述,Java中I/O流的体系结构如图12.2所示。
图12.2 I/O流体系结构图
12.2 File类
File类可以用于处理文件目录。在对一个文件进行输入/输出,必须先获取有关该文件的基本信息,如文件是否可以读取、能否被写入、路径是什么等。java.io.File类不属于Java流系统,但它是文件流进行文件操作的辅助类,提供了获取文件基本信息以及操作文件的一些方法,通过调用File类提供的相应方法,能够完成创建文件、删除文件以及对目录的一些操作。
12.2.1 File类的常用方法
File类的对象是一个“文件或目录”的抽象,它并不打开文件或目录,而是指定要操作的文件或目录。File类的对象一旦创建,就不能再修改。要创建一个新的File对象,需要使用它的构造方法,如表12.2所示。
表12.2 File类构造方法
构造方法 |
功能描述 |
public File(String filename) |
创建File对象,filename表示文件或目录的路径 |
public File(String parent,String child) |
创建File对象,parent表示上级目录,child表示指定的子目录或文件名 |
public File(File obj,String child) |
设置File对象,obj表示File对象,child表示指定的子目录或文件名 |
使用表12.2所列的哪种构造方法要由其他被访问的文件来决定。例如,当在应用程序中只用到一个文件时,使用第1种构造方法最合适;如果使用了一个公共目录下的几个文件,那么使用第2种或第3种构造方法会更方便。
创建File类的对象后,就可以使用File的相关方法来获取文件信息。接下来,先了解一下File类的常用方法,如表12.3所示。
表12.3 File类常用方法
常用方法 |
功能描述 |
备注 |
String getName() |
获取相关文件名 |
与文件名相关的方法 |
String getPath() |
获取文件路径 |
|
String getAbsolutePath() |
获取文件绝对路径 |
|
String getParent() |
获取文件上级目录名称 |
|
boolean renameTo(File newName) |
更改文件名,成功则返回true,否则返回false |
|
boolean exists() |
检测文件对象是否存在 |
文件测定相关方法 |
boolean canWrite() |
检测文件对象是否可写 |
|
boolean canRead() |
检测文件对象是否可读 |
|
boolean isFile() |
检测文件对象是否是文件 |
|
boolean isDirectory() |
检测文件对象是否是目录 |
|
boolean isAbsolute() |
检测文件对象是否是绝对路径 |
|
long lastModified() |
返回此File对象表示的文件或目录最后一次被修改的时间 |
常用文件信息和方法 |
long length() |
返回此File对象表示的文件或目录的长度 |
|
boolean delete() |
删除文件或目录。如果File对象为目录,则该目录为空,方可删除。删除成功,返回true,否则返回false |
|
boolean mkdir() |
创建File对象指定目录。如果创建成功,则返回true,否则返回false |
目录相关类工具 |
boolean mkdirs() |
创建File对象指定的目录,如果此目录的父级不存在,则还会创建父目录。如创建成功,则返回true,否则返回false |
|
String []list() |
返回此File对象表示的目录中的文件和目录的名称所组成字符串数组 |
接下来,通过一个案例来演示File类常用方法的基本使用,先在当前目录创建一个1201.txt文件,在里面输入“AAA软件教育欢迎您!”,然后编写代码,如例12-1所示。
例12-1 Demo1201.java
1 package com.aaa.p120201;
2 import java.io.*;
3 import java.util.*;
4 import java.text.SimpleDateFormat;
5
6 public class Demo1201 {
7 public static void main(String[] args) {
8 File file = new File("src/1201.txt");
9 System.out.println("文件是否存在-->" + file.exists());
10 System.out.println("文件是否可写-->" + file.canWrite());
11 System.out.println("文件是否可读-->" + file.canRead());
12 System.out.println("文件是否是文件-->" + file.isFile());
13 System.out.println("文件是否是目录-->" + file.isDirectory());
14 System.out.println("文件是否是绝对路径-->" + file.isAbsolute());
15 System.out.println("文件名是-->" + file.getName());
16 System.out.println("文件的路径是-->" + file.getPath());
17 System.out.println("文件的绝对路径是-->" + file.getAbsolutePath());
18 System.out.println("文件的上级路径是-->" + file.getParent());
19 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
20 System.out.print("最后修改时间-->");
21 System.out.println(sdf.format(new Date(file.lastModified())));
22 System.out.println("文件长度是-->" + file.length());
23 }
24 }
程序的运行结果如下:
文件是否存在-->true
文件是否可写-->true
文件是否可读-->true
文件是否是文件-->true
文件是否是目录-->false
文件是否是绝对路径-->false
文件名是-->1201.txt
文件的路径是-->src\1201.txt
文件的绝对路径是-->D:\work\AAA课程研发\教材编写\javaIO\src\1201.txt
文件的上级路径是-->src
最后修改时间-->2021-06-15
文件长度是-->25
例12-1在程序中构造了File类的对象,运用File类的各个方法得到文件的各种相关属性。在第19~21行代码中,通过格式化时间信息,获取文件最后修改时间,最后打印文件1201.txt相关属性的信息。
12.2.2 遍历目录下的文件
File类用来操作文件和获得文件的信息,但是不提供对文件读取的方法,这些方法由文件流提供。File类中提供了list()方法和listFiles()方法,用来遍历目录下所有文件。两者不同之处是list()方法只返回文件名,没有路径信息;而listFiles()方法不但返回文件名称,还包含有路径信息。
接下来,通过案例来演示list()方法与listFiles()方法的使用,如例12-2所示。
例12-2 Demo1202.java
1 package com.aaa.p120202;
2 import java.io.*;
3
1 public class Demo1202 {
2 public static void main(String[] args) {
3 System.out.printf("***********list()方法***********");
4 File file = new File("D:\\javaCode"); // 创建File对象
5 if (file.isDirectory()) { // 判断file目录是否存在
6 String[] list = file.list();
7 for (String fileName : list) {
8 System.out.println(fileName); // 打印文件名
9 }
10 }
11 System.out.printf("***********listFiles()方法***********");
12 files(file);
13 }
14 public static void files(File file) {
15 File[] listFile = file.listFiles(); // 遍历目录下所有文件
16 for (File f : listFile) {
17 if (f.isDirectory()) { // 判断是否是目录
18 files(f); // 递归调用
19 }
20 System.out.println(f.getAbsolutePath());
21 }
22 }
23 }
程序的运行结果如下:
***********list()方法***********
chapter02
test.txt
***********listFiles()方法***********
D:\javaCode\chapter02\.idea\.gitignore
D:\javaCode\chapter02\.idea\misc.xml
D:\javaCode\chapter02\.idea\modules.xml
D:\javaCode\chapter02\.idea\uiDesigner.xml
D:\javaCode\chapter02\.idea\workspace.xml
D:\javaCode\chapter02\.idea
D:\javaCode\chapter02\chapter02.iml
D:\javaCode\chapter02\out\production\chapter02\Demo02.class
D:\javaCode\chapter02\out\production\chapter02\Demo0201.class
例12-2中,首先创建File对象,指定File对象的目录。第5~10行代码先判断file目录是否存在,若存在,则调用list()方法,第6行代码以String数组的形式得到所有文件名,最后循环遍历数组内容并打印。如果目录下仍然有子目录则不能遍历到,此时就需要用到File类的listFiles()方法,遍历目录下所有文件之后,循环判断遍历到的是否是目录,如果是目录,则再次递归调用file(file)方法本身。第14~22行代码是自定义的静态方法,直到遍历完到文件。通过程序运行结果可以看到,listFiles()方法输出的信息比list()方法输出的信息更加详细,而且listFiles()方法返回值是File类型,可以直接使用该文件。
注意:在Windows系统中,目录的分隔符是反斜杠(\)。但是,在Java语言中,使用反斜杠表示转义字符,所以如果需要在Windows系统的路径下包括反斜杠,则应该使用两条反斜线,如D:\\javaC