表示一个文件的 File 类型

从本篇文章开始,咱们将开启对 Java IO 系统的学习,本质上就是对文件的读写操做,听上去简单,其实并不容易。Java 的 IO 系统一直在完善和改进,设计了大量的类,也只有理解了这些类型被设计出来的意义以及各自的应用场景,才能提高文件 IO 的理解。java

那么,第一步就是要解决如何表示一个文件的问题,Java 世界中「万物皆对象」,如何将一个实际磁盘文件或目录对应到一个 Java 对象则是咱们首要的问题。git

Java 中使用 File 来抽象一个文件,不管是普通文件或是目录,均可对应于一个 File 对象。我以为你们对于 File 这个类型的定位必定要准确:它只是抽象的表明了磁盘上的某个文件或目录,内部其实是依赖一个平台无关的本地文件系统类,而且 File 没法对其所表示文件内容进行任何读写操做(那是流作的事情)github

构建一个 File 实例

在实际介绍 File 实例构造方法以前,咱们得先看看它的几种重要的属性成员。bash

private static final FileSystem fs = DefaultFileSystem.getFileSystem();
复制代码

这是 File 类中最核心的成员,它表示为当前系统的文件系统 API,全部向磁盘发出的操做都是基于这个属性的。微信

private final String path;
复制代码

path 表明了当前实例的完整路径名称,若是当前的 File 实例表示的是目录的话,那么 path 的值就是这个完整的目录名称,若是表示的是纯文件的话,那么这个 path 的值等于该文件的完整路径 + 文件名称。ide

public static final char separatorChar = fs.getSeparator();
public static final char pathSeparatorChar = fs.getPathSeparator();
复制代码

separatorChar 表示的是目录间的分隔符,pathSeparatorChar 表示的是不一样路径下的分隔符,这两个值在不一样的系统平台下不尽相同。例如 Windows 下这二者的值分别为:「\」 和 「;」,其中封号用于分隔多个不一样路径。学习

File 类提供了四种不一样的构造器用于实例化一个 File 对象,但较为经常使用的只有三个,咱们也着重学习前三个构造器。ui

public File(String pathname)
复制代码

这是最广泛的实例化一个 File 对象的方法,pathname 的值能够是一个目录,也能够是一个纯文件的名称。例如:this

File file = new File("C:\\Users\\yanga\\Desktop");

File file1 = new File("C:\\Users\\yanga\\Desktop\\a.txt");

File file2 = new File("a.txt");
复制代码

固然也能够显式指定一个父路径:spa

public File(String parent, String child)
复制代码

在构造器的内部,程序会为咱们拼接出一个完整的文件路径,例如:

File file = new File("C:\\Users\\yanga\\Desktop","a.txt");

File file1 = new File("C:\\Users\\yanga\\Desktop","java");
复制代码

第三种构造器其实本质上和第二种是同样的,只不过增长了一个父类 File 实例的封装过程:

public File(File parent, String child)
复制代码

相似的状况,再也不举例说明了。咱们这里并无深究这些构造器的内部具体实现状况,并非说它简单,而是 File 过分依赖本地文件系统,不少方法的实现状况都不得直接看到,因此对于 File 的学习,定位为熟练掌握便可,具体实现暂时无法深刻学习。

文件名称或路径相关信息获取

getName 方法能够用于获取文件名称:

public String getName() {
    int index = path.lastIndexOf(separatorChar);
    if (index < prefixLength) return path.substring(prefixLength);
    return path.substring(index + 1);
}
复制代码

还记得咱们的 separatorChar 表示的是什么了吗?

它表示为路径分隔符,Windows 中为符号「\」,path 属性存储的当前 File 实例的完整路径名称,因此最后一次出现的位置后面全部的字符必然是咱们的文件名称。

固然你必定发现了,对于纯文件来讲,该方法可以返回文件的简单名称,而对于一个目录而言,返回值将会是最近的目录名。例如:

File file = new File("C:\\Users\\yanga\\Desktop\\a.txt");
System.out.println(file.getName());

File file1 = new File("C:\\Users\\yanga\\Desktop");
System.out.println(file1.getName());
复制代码

输出结果不会出乎你的意料:

a.txt
Desktop
复制代码

getParent 方法用于返回当前文件的父级目录,不管你是纯文件或是目录,你终有你的父目录(固然,虚拟机生成的临时文件天然不是)。

public String getParent() {
    int index = path.lastIndexOf(separatorChar);
    if (index < prefixLength) {
        if ((prefixLength > 0) && (path.length() > prefixLength))
            return path.substring(0, prefixLength);
        return null;
    }
    return path.substring(0, index);
}
复制代码

方法的实现很简单,再也不赘述。

getPath 方法能够返回当前 File 实例的完整文件名称:

public String getPath() {
    return path;
}
复制代码

如下是一些有关目录的相关操做,实现比较简单,此处简单罗列了:

  • public boolean isAbsolute():是否为绝对路径
  • public String getAbsolutePath():获取当前 File 实例的绝对路径
  • public String getCanonicalPath():返回当前 File 实例的标准路径

这里咱们须要对 getCanonicalPath 作一点解释,什么叫标准路径,和绝对路径有区别吗?

通常而言,「../」表示源文件所在目录的上一级目录,「../../」表示源文件所在目录的上上级目录,并以此类推。getAbsolutePath 方法不会作这种转换的操做,而 getCanonicalPath 方法则会将这些特殊字符进行识别并取合适的语义。

例如:

File file = new File("..\\a.txt");
System.out.println(file.getAbsolutePath());
System.out.println(file.getCanonicalPath());
复制代码

输出结果:

C:\Users\yanga\Desktop\Java\workspace2017\TestFile\..\a.txt
C:\Users\yanga\Desktop\Java\workspace2017\a.txt
复制代码

前者会将「..\a.txt」做为文件路径名称的一部分,然后者却可以识别「..\a.txt」表示的是「a.txt」位于当前目录的上级目录中。这就是二者最大的不一样之处,适合不一样的情境。

文件的属性信息获取

这部分的文件操做其实很简单,无非是一些文件权限的问题,是否可读,是否可写,是否为隐藏文件等。下面咱们具体看看这些方法:

  • public boolean canRead():该抽象的 File 实例对应的文件是否可读
  • public boolean canWrite():该抽象的 File 实例对应的文件是否可写
  • public boolean exists():该抽象的 File 实例对应的文件是否实际存在
  • public boolean isDirectory():该抽象的 File 实例对应的文件是不是一个目录
  • public boolean isFile():该抽象的 File 实例对应的文件是不是一个纯文件
  • public boolean isHidden():该抽象的 File 实例对应的文件是不是一个隐藏文件
  • public long length():文件内容所占的字节数

须要说明一点的是,length 方法对于纯文件来讲,能够正确返回该文件的字节总数,可是对于一个目录而言,返回值将会是一个「unspecified」的数值,既不是目录下全部文件的总字节数,也不是零,只是一个未被说明的数值,没有意义。

文件的操做

文件的操做无外乎「增删改查」,下面咱们一块儿来看看。

  • public boolean createNewFile():根据抽象的 File 对象建立一个实际存在的磁盘文件
  • public boolean delete():删除该 File 对象对应的磁盘文件,删除失败会返回 false

固然,处理上述两个简单的新建和删除操做,File 类还提供了所谓「查询」操做,这个咱们要好好学习一下。例如:

public String[] list() {
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkRead(path);
    }
    if (isInvalid()) {
        return null;
    }
    return fs.list(this);
}
复制代码

这个方法会检索出当前实例所表明的目录下全部的「纯文件」和「目录」简单名称集合。例如:

File file = new File("C:\\Users\\yanga\\Desktop");
String[] list = file.list();
for (String str : list){
    System.out.println(str);
}
复制代码

程序的输出结果会打印我电脑桌面目录下全部的文件的简单名称,就不给你们看了。

须要注意一点,若是咱们的 File 实例对应的不是一个目录,而是一个纯文件,那么 list 将返回 null。

接着,咱们再看一个检索目录文件的方法:

public String[] list(FilenameFilter filter) {
    String names[] = list();
    if ((names == null) || (filter == null)) {
        return names;
    }
    List<String> v = new ArrayList<>();
    for (int i = 0 ; i < names.length ; i++) {
        if (filter.accept(this, names[i])) {
            v.add(names[i]);
        }
    }
    return v.toArray(new String[v.size()]);
}
复制代码

这个方法实际上是 list 的重载版本,它容许传入一个过滤器用于检索目录时只筛选咱们须要的文件及目录。

而这个 FilenameFilter 接口的定义倒是如此简单:

public interface FilenameFilter {
    boolean accept(File dir, String name);
}
复制代码

只须要重写这个 accept 方法便可,list 的 for 循环每获取一个文件或目录就会尝试着先调用这个过滤方法,若是经过筛选,才会将当前文件的简单名称添加进返回集合中。

因此这个 accept 方法的重写就决定着哪些文件可以经过筛选,哪些则不能。咱们看个例子:

个人桌面上 test 文件夹下文件状况以下:

image

File file = new File("C:\\Users\\yanga\\Desktop\\test");
    String[] list = file.list(
        new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                // dir 表明的当前 File 对象
                //name 是当前遍历的文件项的简单名称
                if (!name.endsWith(".txt"))
                    return false;
                else
                    return true;
            }
        }
    );
    for (String str : list){
        System.out.println(str);
    }
复制代码

这里呢,咱们使用匿名内部类建立一个 FilenameFilter 的子类实例,而后实现了它的 accept 方法,具体的实现很简单,过滤掉全部的目录并取出全部纯文件的简单名称。

最后输出结果以下:

3.txt
4.txt
复制代码

固然,File 类中还提供了两个「变种」list 方法,例如:

  • public File[] listFiles()
  • public File[] listFiles(FilenameFilter filter)

它们再也不返回目标目录下的「纯文件」和「目录」的简单名称,而返回它们所对应的 File 对象,其实也没什么,目标目录 + 简单名称 便可构建出这些 File 实例了。

因此,本质上说,list 方法并不会遍历出目标目录下的全部文件,即目标目录的子目录中的文件并不会被访问遍历。

因此你应当思考如何完成目标目录下全部文件的遍历,包含一级子目录下的深层次文件的遍历。文末将给出答案。

接下来的两个方法和文件夹的建立有关:

  • public boolean mkdir()
  • public boolean mkdirs()

二者都是依据的当前 File 实例建立文件夹,关于它们的不一样点,咱们先看一段代码:

File file = new File("C:\\Users\\yanga\\Desktop\\test2");
System.out.println(file.mkdir());

File file2 = new File("C:\\Users\\yanga\\Desktop\\test3\\hello");
System.out.println(file2.mkdir());
复制代码

其中,test2 和 test3 在程序执行以前都不存在。

输出结果以下:

true
false
复制代码

为何后者建立失败了?

这源于 mkdir 方法一次只能建立一个文件夹,假若给定的目录的父级或更上层目录存在未被建立的目录,那么将致使建立失败。

而 mkdirs 方法就是用于解决这种情境的,它会建立目标路径上全部未建立的目录,看代码:

File file3 = new File("C:\\Users\\yanga\\Desktop\\test3\\hello\\231");
System.out.println(file3.mkdirs());
复制代码

即使咱们 test3 文件夹就不存在,程序运行以后,test三、hello、231 这三个文件夹都会被建立出来。

除此以外,File 还有一类建立临时文件的方法,所谓临时文件即:运行期存在,虚拟机关闭时销毁。你们能够自行研究,使用上仍是比较简单的,这里再也不赘述了。

至此,有关 File 这个文件类型,咱们大体学习了一下,想必你们都会或多或少的感受到将纯文件和目录使用同一个类型进行表示的设计彷佛有些混乱不合理。知道 jdk1.7 sun 推出了 Files 和 Path 分离了文件和目录,咱们后续文章会详细学习一下。


文章中的全部代码、图片、文件都云存储在个人 GitHub 上:

(https://github.com/SingleYam/overview_java)

欢迎关注微信公众号:扑在代码上的高尔基,全部文章都将同步在公众号上。

image
相关文章
相关标签/搜索