计算机程序的思惟逻辑 (59) - 文件和目录操做

本系列文章经补充和完善,已修订整理成书《Java编程的逻辑》(马俊昌著),由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买:京东自营连接 html

前面两节咱们介绍了如何经过流的方式读写文件内容,本节咱们介绍文件元数据和目录的一些操做。java

文件和目录操做最终是与操做系统和文件系统相关的,不一样系统的实现是不同的,但Java中的java.io.File类提供了统一的接口,底层它会经过本地方法调用操做系统和文件系统的具体实现,本节,咱们就来介绍File类。编程

File类中的操做大概能够分为三类:数组

  • 文件元数据
  • 文件操做
  • 目录操做

在介绍这些操做以前,咱们先来看下File的构造方法。安全

构造方法

File既能够表示文件,也能够表示目录,它的主要构造方法有:bash

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

能够是一个参数pathname,表示完整路径,该路径能够是相对路径,也能够是绝对路径。还能够是两个参数,表示父目录的parent和表示孩子的child。微信

File中的路径能够是已经存在的,也能够是不存在的。ide

经过new新建一个File对象,不会实际建立一个文件,只是建立一个表示文件或目录的对象,new以后,File对象中的路径是不可变的。ui

文件元数据

文件名与文件路径

有了File对象后,就能够获取它的文件名和路径信息,相关方法有:spa

public String getName() public boolean isAbsolute() public String getPath() public String getAbsolutePath() public String getCanonicalPath() throws IOException public String getParent() public File getParentFile() public File getAbsoluteFile() public File getCanonicalFile() throws IOException 复制代码

getName()返回的就是文件或目录名称,不含路径名。isAbsolute()判断File中的路径是不是绝对路径。

getPath()返回构造File对象时的完整路径名,包括路径和文件名称。getAbsolutePath()返回完整的绝对路径名。getCanonicalPath()返回标准的完整路径名,它会去掉路径中的冗余名称如".","..",跟踪软链接(Unix系统概念)等。这三个路径容易混淆,咱们看一个例子来讲明:

File f = new File("../io/test/students.txt");
System.out.println(System.getProperty("user.dir"));
System.out.println("path: " + f.getPath());
System.out.println("absolutePath: " + f.getAbsolutePath());
System.out.println("canonicalPath: " + f.getCanonicalPath());
复制代码

这里,使用相对路径来构造File对象,..表示上一级目录,输出为:

/Users/majunchang/io
path: ../io/test/students.txt
absolutePath: /Users/majunchang/io/../io/test/students.txt
canonicalPath: /Users/majunchang/io/test/students.txt
复制代码

当前目录为/Users/majunchang/io,getPath()返回的就是构造File对象时使用的相对路径,而getAbsolutePath()返回的是完整路径,可是包含冗余路径"../io/",而getCanonicalPath()则去除了该冗余路径。

getParent()返回父目录路径,getParentFile()返回父目录的File对象,须要注意的是,若是File对象是相对路径,则这些方法可能得不到父目录,好比:

File f = new File("students.txt");
System.out.println(System.getProperty("user.dir"));
System.out.println("parent: " + f.getParent());
System.out.println("parentFile: " + f.getParentFile());
复制代码

输出为:

/Users/majunchang/io
parent: null
parentFile: null
复制代码

即便是有父目录的,getParent()的返回值也是null。那如何解决这个问题呢?能够先使用getAbsoluteFile()或getCanonicalFile()方法,它们都返回一个新的File对象,新的File对象分别使用getAbsolutePath()和getCanonicalPath()的返回值做为参数构造。好比,修改上面的代码为:

File f = new File("students.txt");
System.out.println(System.getProperty("user.dir"));
System.out.println("parent: " + f.getCanonicalFile().getParent());
System.out.println("parentFile: " + f.getCanonicalFile().getParentFile());
复制代码

此次,就能获得父目录了,输出为:

/Users/majunchang/io
parent: /Users/majunchang/io
parentFile: /Users/majunchang/io
复制代码

File类中有四个静态变量,表示路径分隔符,它们是:

public static final String separator
public static final char separatorChar
public static final String pathSeparator
public static final char pathSeparatorChar
复制代码

separator和separatorChar表示文件路径分隔符,在Windows系统中,通常为"",Linux系统中通常为"/"。

pathSeparator和pathSeparatorChar表示多个文件路径中的分隔符,好比环境变量PATH中的分隔符,Java类路径变量classpath中的分隔符,在执行命令时,操做系统会从PATH指定的目录中寻找命令,Java运行时加载class文件时,会从classpath指定的路径中寻找类文件。在Windows系统中,这个分隔符通常为';',在Linux系统中,这个分隔符通常为':'。

文件基本信息

除了文件名和路径,File对象还有以下方法,以获取文件或目录的基本信息:

//文件或目录是否存在
public boolean exists() //是否为目录 public boolean isDirectory() //是否为文件 public boolean isFile() //文件长度,字节数 public long length() //最后修改时间,从纪元时开始的毫秒数 public long lastModified() //设置最后修改时间,设置成功返回true,不然返回false public boolean setLastModified(long time) 复制代码

对于目录,length()方法的返回值是没有意义的。

须要说明的是,File对象没有返回建立时间的方法,由于建立时间不是一个公共概念,Linux/Unix就没有建立时间的概念。

安全和权限信息

File类中与安全和权限相关的方法有:

//是否为隐藏文件
public boolean isHidden() //是否可执行 public boolean canExecute() //是否可读 public boolean canRead() //是否可写 public boolean canWrite() //设置文件为只读文件 public boolean setReadOnly() //修改文件读权限 public boolean setReadable(boolean readable, boolean ownerOnly) public boolean setReadable(boolean readable) //修改文件写权限 public boolean setWritable(boolean writable, boolean ownerOnly) public boolean setWritable(boolean writable) //修改文件可执行权限 public boolean setExecutable(boolean executable, boolean ownerOnly) public boolean setExecutable(boolean executable) 复制代码

在修改方法中,若是修改为功,返回true,不然返回false。在设置权限方法中,ownerOnly为true表示只针对owner,为false表示针对全部用户,没有指定ownerOnly的方法中,ownerOnly至关因而true。

文件操做

文件操做主要有建立、删除、重命名。

建立

新建一个File对象不会实际建立文件,但以下方法能够:

public boolean createNewFile() throws IOException 复制代码

建立成功返回true,不然返回false,新建立的文件内容为空。若是文件已存在,不会建立。

File对象还有两个静态方法,能够建立临时文件:

public static File createTempFile(String prefix, String suffix) throws IOException public static File createTempFile(String prefix, String suffix, File directory) throws IOException 复制代码

临时文件的完整路径名是系统指定的、惟一的,但能够经过参数指定前缀(prefix)、后缀(suffix)和目录(directory),prefix是必须的,且至少要三个字符,suffix若是为null,则默认为".tmp", directory若是不指定或指定为null,则使用系统默认目录。咱们看个例子:

File file = File.createTempFile("upload_", ".jpg");
System.out.println(file.getAbsolutePath());
复制代码

在个人电脑上的一些运行的输出为:

/var/folders/fs/8s4jdbj51jvcm7vc6lm_144r0000gn/T/upload_8850973909847443784.jpg
复制代码

删除

File类以下删除方法:

public boolean delete() public void deleteOnExit() 复制代码

delete删除文件或目录,删除成功返回true,不然返回false。若是File是目录且不为空,则delete不会成功,返回false,换句话说,要删除目录,先要删除目录下的全部子目录和文件。

deleteOnExit将File对象加入到待删列表,在Java虚拟机正常退出的时候进行实际删除。

重命名

方法为:

public boolean renameTo(File dest) 复制代码

参数dest表明重命名后的文件,重命名可否成功与系统有关,若是成功返回true,不然返回false。

目录操做

当File对象表明目录时,能够执行目录相关的操做,如建立、遍历。

建立

有两个方法用于建立目录:

public boolean mkdir() public boolean mkdirs() 复制代码

它们都是建立目录,建立成功返回true,失败返回false。须要注意的是,若是目录已存在,返回值是false。这两个方法的区别在于,若是某一个中间父目录不存在,则mkdir会失败,返回false,而mkdirs则会建立必需的中间父目录。

遍历

有以下方法访问一个目录下的子目录和文件:

public String[] list()
public String[] list(FilenameFilter filter)
public File[] listFiles()
public File[] listFiles(FileFilter filter)
public File[] listFiles(FilenameFilter filter)
复制代码

它们返回的都是直接子目录或文件,不会返回子目录下的文件。list返回的是文件名数组,而listFiles返回的是File对象数组。FilenameFilter和FileFilter都是接口,用于过滤,FileFilter的定义为:

public interface FileFilter {
    boolean accept(File pathname);
}
复制代码

FilenameFilter的定义为:

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

在遍历子目录和文件时,针对每一个文件,会调用FilenameFilter或FileFilter的accept方法,只有accept方法返回true时,才将该子目录或文件包含到返回结果中。

FilenameFilter和FileFilter的区别在于,FileFilter的accept方法参数只有一个File对象,而FilenameFilter的accept方法参数有两个,dir表示父目录,name表示子目录或文件名。

咱们来看个例子,列出当前目录下的全部后缀为.txt的文件,代码能够为:

File f = new File(".");
File[] files = f.listFiles(new FilenameFilter(){
    @Override
    public boolean accept(File dir, String name) {
        if(name.endsWith(".txt")){
            return true;
        }
        return false;
    }
});
for(File file : files){
    System.out.println(file.getCanonicalPath());
}
复制代码

咱们建立了个FilenameFilter的匿名内部类对象并传递给了listFiles。

使用遍历方法,咱们能够方便的进行递归遍历,完成一些更为高级的功能。

好比,计算一个目录下的全部文件的大小(包括子目录),代码能够为:

public static long sizeOfDirectory(final File directory) {
    long size = 0;
    if (directory.isFile()) {
        return directory.length();
    } else {
        for (File file : directory.listFiles()) {
            if (file.isFile()) {
                size += file.length();
            } else {
                size += sizeOfDirectory(file);
            }
        }
    }
    return size;
}
复制代码

再好比,在一个目录下,查找全部给定文件名的文件,代码能够为:

public static Collection<File> findFile(final File directory, final String fileName) {
    List<File> files = new ArrayList<>();
    for (File f : directory.listFiles()) {
        if (f.isFile() && f.getName().equals(fileName)) {
            files.add(f);
        } else if (f.isDirectory()) {
            files.addAll(findFile(f, fileName));
        }
    }
    return files;
}
复制代码

前面介绍了File类的delete方法,咱们提到,若是要删除目录而目录不为空,须要先清空目录,利用遍历方法,咱们能够写一个删除非空目录的方法,代码能够为:

public static void deleteRecursively(final File file) throws IOException {
    if (file.isFile()) {
        if (!file.delete()) {
            throw new IOException("Failed to delete "
                    + file.getCanonicalPath());
        }
    } else if (file.isDirectory()) {
        for (File child : file.listFiles()) {
            deleteRecursively(child);
        }
        if (!file.delete()) {
            throw new IOException("Failed to delete "
                    + file.getCanonicalPath());
        }
    }
}
复制代码

小结

本节介绍了如何在Java中利用File类进行文件和目录操做,File类封装了操做系统和文件系统的差别,提供了统一的API。

理解了这些操做,咱们回过头来,再看下文件内容的操做,前面咱们介绍的都是流,除了流,还有其余操做方式,如随机访问和内存映射文件,为何还须要这些方式?它们有什么特色?适用于什么场合?让咱们接下来继续探索。


未完待续,查看最新文章,敬请关注微信公众号“老马说编程”(扫描下方二维码),深刻浅出,老马和你一块儿探索Java编程及计算机技术的本质。用心原创,保留全部版权。

相关文章
相关标签/搜索