Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

File类源代码解析及使用

$
0
0

路径名可以是绝对的(相对于文件系统的根目录)或相对于在该程序正在运行的当前目录。

由File类定义的文件在实际中有可能存在也有可能不存在,有可能是一个目录或者其它非普通文件。

File类提供用于获取/设置文件权限、文件类型和最后修改时间等有限功能。


1.构造函数

使用指定路径创建新的文件

path:指定的文件路径

fixSlashes(path): 去掉路径中重复的斜杠和反斜杠,如:‘//’-->'/'

public File(String path) {
        this.path = fixSlashes(path);
    }


构造使用指定的目录和文件名的文件。

dir:已存储的文件的目录,注意是File类型

name:文件名,带后缀

public File(File dir, String name) {
        this(dir == null ? null : dir.getPath(), name);
    }


使用指定的目录和文件名构造新的文件。文件名不能为空,不然会报错。

dirPath: 新的文件的目录/路径

name:文件名

join(dirPath,name):连接dirPath和name,如果dirPath最后一个字母不是斜杠或者dirPath是空的,则会添加斜杠再将dirPath和name连接

public File(String dirPath, String name) {
        if (name == null) {
            throw new NullPointerException();
        }
        if (dirPath == null || dirPath.isEmpty()) {
            this.path = fixSlashes(name);
        } else if (name.isEmpty()) {
            this.path = fixSlashes(dirPath);
        } else {
            this.path = fixSlashes(join(dirPath, name));
        }
    }

使用指定的URI路径创建新的文件。注意这里是URI不是URL!!两者是不同的概念。URI,统一资源标识符,用于标识抽象或物理资源,且唯一的。

uri:不能为空,用于构造该文件的统一资源标识符。备注:uri可以由路径转换。

checkURI(uri):检查uri是否符合规范。

public File(URI uri) {
        // check pre-conditions
        checkURI(uri);
        this.path = fixSlashes(uri.getPath());
    }

2. 其它方法

返回文件系统的所有根目录,在Android和Unix系统中,只有一个根目录。就一个斜杠。

public static File[] listRoots() {
        return new File[] { new File("/") };
    }

测试File是否可使用或者进程是否可执行该File。这个方法不一定可靠,还要以实际操作为主,且使用此方法必须是API1.6以后的,也就是说minSdkVersion的值要大于等于9。

doAccess(X_OK)实际上是调用了Libcore.os.access(path, mode);这个方法是用来判断某个目录/文件是否存在、是否可读、是否可写、是否执行。

 public boolean canExecute() {
        return doAccess(X_OK);
    }

返回当前File和另外一个File的相对路径的排序/字典顺序,排序方式根据操作系统来。路径一样返回0,返回负值则表示小于参数路径,大于0表示大于参数路径。
public int compareTo(File another) {
        return this.getPath().compareTo(another.getPath());
    }


当前环境是否允许读该文件,或者说该文件是否可读
  public boolean canRead() {
        return doAccess(R_OK);
    }

当前环境是否允许写入该文件,或者说该文件是否可写

 public boolean canWrite() {
        return doAccess(W_OK);
    }


删除该文件,如果删除成功,返回true,反之为false。注意:删除失败不会抛出IOException,所以要check返回值。

 public boolean delete() {
        try {
            Libcore.os.remove(path);
            return true;
        } catch (ErrnoException errnoException) {
            return false;
        }
    }

当虚拟机终止时自动删除该目录/文件。注意:在Androd上应用程序的生命周期不包括虚拟机的终止。所以调用此方法不一定会删除文件。为了确保文件/目录被删除,最好是调用delete方法,且在finally代码块里手动调用。或者自己写一套删除File的方法,并且在合适的应用生命周期中调用。
public void deleteOnExit() {
        DeleteOnExit.getInstance().addFile(getAbsolutePath());
    }


Object类和该目录/文件比较,如果一样则返回true。实际上比较的是path。

@Override
    public boolean equals(Object obj) {
        if (!(obj instanceof File)) {
            return false;
        }
        return path.equals(((File) obj).getPath());
    }


在文件系统中该目录/文件是否存在,如果存在则返回true,反之false

public boolean exists() {
        return doAccess(F_OK);
    }


获取该目录/文件的绝对路径,绝对路径起始于文件系统的根目录。在Android操作系统里,只有一个根目录,就是 "/"。

源码中isAbsolute()方法是判断其是判断该目录/文件是否是绝对路径。如果当前File的路径为空,则返回用户当前的工作目录,如果当前目录/文件的路径不为空,则返回用户当前的工作目录和路径的连接。

public String getAbsolutePath() {
        if (isAbsolute()) {
            return path;
        }
        String userDir = System.getProperty("user.dir");//系统属性,获取用户当前的工作目录
        return path.isEmpty() ? userDir : join(userDir, path);
    }

判断该目录/文件是否是绝对路径,如果是则返回true,反之为false。判断标准是根据path是否是斜杠开头。因为Android的根目录就是"/"。所以只要判断其父目录是否是根目录。
public boolean isAbsolute() {
        return path.length() > 0 && path.charAt(0) == separatorChar;
    }

通过当前上下文的绝对路径构造的新的目录/文件。本质上是new File(this.getAbsolutePath())
public File getAbsoluteFile() {
        return new File(getAbsolutePath());
    }

获取当前目录/文件的规范路径。绝对路径是开始于文件系统的根目录,而规范路径是绝对路径和连接符号。例如C:\Program Files\..\test.txt。如果一个路径元素不存在或者不可被搜索,就会有转换成规范化文本的冲突,例如a/../b,如果a不存在或者不可被搜索,则规范路径就变成了“b”。所以还是建议使用绝对路径getAbsolutePath()。一个规范的路径的计算成本比较大,不建议使用。对于规范路径的主要用途是通过比较规范路径来判断两个路径是否指向同一个文件。

 public String getCanonicalPath() throws IOException {
        return realpath(getAbsolutePath());
    }

通过当前目录/文件的规范化路径创建一个新的目录/文件。File another = file.getCanonicalFile();

public File getCanonicalFile() throws IOException {
        return new File(getCanonicalPath());
    }

返回当前目录/文件的名称。先获得path中斜杠最后出现的位置。如果位置的值小于0,说明path中没有斜杠,则可知path就是目录/文件名称。反则,则分割path中最后一个斜杠出现的位置的下一个值到path长度的区间的字符串。substring分割的区间是[a,b)。file.getName()

 public String getName() {
        int separatorIndex = path.lastIndexOf(separator);
        return (separatorIndex < 0) ? path : path.substring(separatorIndex + 1, path.length());
    }

返回该目录/文件的父目录/上一级目录。但不包括根目录,根目录的父目录是null,因为根目录是所有目录的父节点。file.getParent()

public String getParent() {
        int length = path.length(), firstInPath = 0;
        if (separatorChar == '\\' && length > 2 && path.charAt(1) == ':') {//标准路径
            firstInPath = 2;
        }
        int index = path.lastIndexOf(separatorChar);//获取斜杠或者反斜杠在路径中最后出现的位置
        if (index == -1 && firstInPath > 0) {//判断separatorChar是斜杠还是反斜杠。如果是反斜杠,说明是标准化路径。
            index = 2;//separatorChar是斜杠,例如"c:\\",separatorChar="/",该等式成立
        }
        if (index == -1 || path.charAt(length - 1) == separatorChar) {//根目录返回null,因为根目录是最大父节点
            return null;
        }
        if (path.indexOf(separatorChar) == index
                && path.charAt(firstInPath) == separatorChar) {//只有一级子目录
            return path.substring(0, index + 1);
        }
        return path.substring(0, index);//例如:path="/a/b/c.txt";返回"/a/b/c"
    }

从该File的父目录创建一个新的File。注意,这里是从父目录new一个新的File,不是得到父目录的File。file.getParentFile()

 public File getParentFile() {
        String tempParent = getParent();
        if (tempParent == null) {//先判断当前File是否有父目录,因为根目录的父目录是null,new File(null)是错误的
            return null;
        }
        return new File(tempParent);
    }

返回该File的路径。从前面可知,在调用File的构造函数时,实际上就是初始化path的值,这个方法就是path的get方法。file.getPath()

 public String getPath() {
        return path;
    }

获得一个整数哈希码。两个对象如果是equal的,则这两个对象的hashCode值也是相等的。file.hashCode()

 @Override
    public int hashCode() {
        return getPath().hashCode() ^ 1234321;
    }

判断当前File的路径是否是绝对路径。在Android系统中,绝对路径的起始是"/"。因为"/"是根目录!!file.isAbsolute()

public boolean isAbsolute() {
        return path.length() > 0 && path.charAt(0) == separatorChar;
    }

判断File是目录还是文件。如果是目录,返回true,反之返回false。file.isDirectory()

public boolean isDirectory() {
        try {
            return S_ISDIR(Libcore.os.stat(path).st_mode);
        } catch (ErrnoException errnoException) {
            // The RI returns false on error. (Even for errors like EACCES or ELOOP.)
            return false;
        }
    }

判断File是文件还是目录。如果是文件,返回true,反之false。file.isFile();

public boolean isFile() {
        try {
            return S_ISREG(Libcore.os.stat(path).st_mode);
        } catch (ErrnoException errnoException) {
            // The RI returns false on error. (Even for errors like EACCES or ELOOP.)
            return false;
        }
    }

判断该文件是否是隐藏文件。如果是返回true,反之false。file.isHidden();

public boolean isHidden() {
        if (path.isEmpty()) {
            return false;
        }
        return getName().startsWith(".");
    }

返回一个文件最后一件被修改的时间,如果返回0,则文件不存在。返回的数值是一个长整型毫秒数。获取失败会抛ErrnoException异常。file.lastModified();

public long lastModified() {
        try {
            return Libcore.os.stat(path).st_mtime * 1000L;
        } catch (ErrnoException errnoException) {
            // The RI returns 0 on error. (Even for errors like EACCES or ELOOP.)
            return 0;
        }
    }

设置文件最后更新的时间。传入参数是一个长整型毫秒数。需要注意的是,调用失败不会抛异常,所以要通过返回值来判断是否设置成功。返回true为设置成功,反之为false。

如果参数time小于0,会抛出非法参数异常。但是该方法不会抛出ErrnoException。

  public boolean setLastModified(long time) {
        if (time < 0) {
            throw new IllegalArgumentException("time < 0");
        }
        return setLastModifiedImpl(path, time);
    }

设置File属性为只读。

 public boolean setReadOnly() {
        return setWritable(false, false);
    }


设定文件属性为可执行文件或不可执行。实际上执行的是file.setExecutable(true, true);

public boolean setExecutable(boolean executable, boolean ownerOnly) {
        return doChmod(ownerOnly ? S_IXUSR : (S_IXUSR | S_IXGRP | S_IXOTH), executable);
    }


设置执行文件的权限。第一个参数设置文件是否可执行,第二参数设置该执行权利的对象是当前的调用还是对于整个文件系统。如果第二个参数设置为true,则表示当前权限只针对当前用户或当前调用,否则针对每个人或者之后的每次调用。如果底层文件系统不区分所有者,那第二个参数设置就没有意义。当且仅当操作成功时返回true。如果用户对该File没有访问权限,该方法的调用将失败。如果底层文件系统不支持该File的读权限且该File不可读,这个操作也会失败。注意:调用该方法失败不会抛IOException异常,所以设置成功与否要根据返回值确认。

public boolean setExecutable(boolean executable, boolean ownerOnly) {
        return doChmod(ownerOnly ? S_IXUSR : (S_IXUSR | S_IXGRP | S_IXOTH), executable);
    }

实际是调用setReadable(readable, true)方法

public boolean setReadable(boolean readable) {
        return setReadable(readable, true);
    }

设置该File是否为可写。第一个参数设置为true表示允许对该File的写入,反则为不允许。如果用户没有权限修改该File的操作权限,该调用失败。

 public boolean setWritable(boolean writable, boolean ownerOnly) {
        return doChmod(ownerOnly ? S_IWUSR : (S_IWUSR | S_IWGRP | S_IWOTH), writable);
    }

实际是调用setWritable(writable,true)方法。不同的是,设置File是否可写属性只针对当前用户。

 public boolean setWritable(boolean writable) {
        return setWritable(writable, true);
    }



返回此文件中的字节长度。如果文件不存在,返回0 。该方法对于目录没有意义,如果File是目录的话。
public long length() {
        try {
            return Libcore.os.stat(path).st_size;
        } catch (ErrnoException errnoException) {
            // The RI returns 0 on error. (Even for errors like EACCES or ELOOP.)
            return 0;
        }
    }

如果该File是一个目录,则返回当前目录中的所有文件的文件名数组。如果该File是一个文件,该方法无意义。
public String[] list() {
        return listImpl(path);
    }


过滤器功能。先获取该文件表示的目录中的所有文件列表,这个列表再通过FilenameFilter过滤器返回符合条件的文件名数组。如果FilenameFilter是空,则返回整个文件列表的文件名数组,如果该File是文件,不是一个目录,则该方法无意义,返回null;

 public String[] list(FilenameFilter filter) {
        String[] filenames = list();
        if (filter == null || filenames == null) {
            return filenames;
        }
        List<String> result = new ArrayList<String>(filenames.length);
        for (String filename : filenames) {
            if (filter.accept(this, filename)) {
                result.add(filename);
            }
        }
        return result.toArray(new String[result.size()]);
    }

返回file目录下的所有文件数组,如果file是文件则返回null。如果该file是绝对路径,那数组里的File之间是相对路径关系。该方法实际执行filenamesToFiles(list())方法,该方法是私有的。list()获取该File目录里的所有文件名数组,然后通过文件名数组创建new File的数组

public File[] listFiles() {
        return filenamesToFiles(list());
    }

将一个包含文件名的字符串数组转换成文件数组,且文件名不能有斜杠。

 private File[] filenamesToFiles(String[] filenames) {
        if (filenames == null) {
            return null;
        }
        int count = filenames.length;
        File[] result = new File[count];
        for (int i = 0; i < count; ++i) {
            result[i] = new File(this, filenames[i]);
        }
        return result;
    }


通过文件名过滤器filter得到该文件目录下符合条件的所有File,并以数组的形式返回。先调用lis(filter))得到所有符合条件的文件名并以字符串形式返回,再用文件名数组入参filenamesToFiles方法,上面讲到,这个方法就是遍历文件名数组,创建新的File,并将创建的File放在数组里保存并返回。如果参数filter是null或者调用该方法的File不是目录而是文件,则返回的结果均为null。

public File[] listFiles(FilenameFilter filter) {
        return filenamesToFiles(list(filter));
    }

通过文件过滤器filter得到该文件目录下符合条件的所有File。不同的是,该方法先得到该目录下的所有文件名并通过文件名创建新的File。之后再对这些文件进行过滤。新创建后过滤。而上面的方法,listFiles(filter)是先过滤再创建,且是根据文件名过滤,本方法是通过路径过滤。
public File[] listFiles(FileFilter filter) {
        File[] files = listFiles();//得到当前目录下的所有文件
        if (filter == null || files == null) {
            return files;
        }
        List<File> result = new ArrayList<File>(files.length);
        for (File file : files) {
            if (filter.accept(file)) {//当前目录下的文件是否包括在一个路径列表里
                result.add(file);
            }
        }
        return result.toArray(new File[result.size()]);
    }

创建该File目录。如果该File没有父目录,则可以调用mkdirs()创建,当然,根目录是不存在父目录/上级目录的。该方法调用失败不会抛IO异常,所以通过判断返回值来判断是否创建成功。如果该目录是存在的,也会返回false。该方法是调用mkdirErrno方法来实现目录的创建。且调用该方法需要用户授权写入权限。

public boolean mkdir() {
        try {
            mkdirErrno();
            return true;
        } catch (ErrnoException errnoException) {
            return false;
        }
    }

底层操作系统进行文件/目录的创建。

private void mkdirErrno() throws ErrnoException {
        // On Android, we don't want default permissions to allow global access.
        Libcore.os.mkdir(path, S_IRWXU);
    }


创建File的上级目录。由mkdirs(false)方法可知,如果直接创建目录失败,则根据错误类型调用对应方法。如果父目录不存在,会再一次调用getParenFile方法,如果父目录存在,返回false。
public boolean mkdirs() {
        return mkdirs(false);
    }

  private boolean mkdirs(boolean resultIfExists) {
        try {
            // Try to create the directory directly.
            mkdirErrno();
            return true;
        } catch (ErrnoException errnoException) {
            if (errnoException.errno == ENOENT) {
                // If the parent was missing, try to create it and then try again.
                File parent = getParentFile();
                return parent != null && parent.mkdirs(true) && mkdir();
            } else if (errnoException.errno == EEXIST) {
                return resultIfExists;
            }
            return false;
        }
    }


FileDescriptor:一个Unix文件描述符。

根据存储在文件中的路径信息创建一个文件系统上新的空的File。如果创建成功返回true,如果文件已经存在,返回false。注意,即使该文件是一个目录,也会返回false。该方法不常用,一般来说创建临时文件用createTempFile,对于文件的读写操作使用FileOutputStream和FileInputStream或者RandomAccessFile,这些方法都可以用于创建文件。注意:如果文件已经存在,该方法不会抛出IO异常,所以要通过返回值来判断是否创建成功,或者调用isFile来判断是否创建成功。

public boolean createNewFile() throws IOException {
        FileDescriptor fd = null;
        try {
            // On Android, we don't want default permissions to allow global access.
            fd = Libcore.os.open(path, O_RDWR | O_CREAT | O_EXCL, 0600);
            return true;
        } catch (ErrnoException errnoException) {
            if (errnoException.errno == EEXIST) {
                // The file already exists.
                return false;
            }
            throw errnoException.rethrowAsIOException();
        } finally {
            IoUtils.close(fd); // TODO: should we suppress IOExceptions thrown here?
        }
    }


使用给定的perfix前缀和suffix后缀作为文件名的一部分,创建一个临时文件。如果参数suffix后缀是null,则临时文件的后缀为.tmp。

 public static File createTempFile(String prefix, String suffix) throws IOException {
        return createTempFile(prefix, suffix, null);
    }


使用给定的前缀和后缀创建一个临时文件。如果参数suffix是null,则默认后缀为.tmp,如果参数directory是null,则调用createTempFile(prefix,suffix)创建临时文件。注意:使用该方法创建临时文件,不能回调deleteOnExit方法。参数directory是临时文件被写入的路径,如果参数null,则默认路径,由"java.io.tmpdir"的系统属性设置,且该默认路径是可读写的。
public static File createTempFile(String prefix, String suffix, File directory)
            throws IOException {
        // Force a prefix null check first
        if (prefix.length() < 3) {//前缀的长度至少3位
            throw new IllegalArgumentException("prefix must be at least 3 characters");
        }
        if (suffix == null) {//后缀默认是.tmp
            suffix = ".tmp";
        }
        File tmpDirFile = directory;
        if (tmpDirFile == null) {
            String tmpDir = System.getProperty("java.io.tmpdir", ".");
            tmpDirFile = new File(tmpDir);
        }
        File result;
        do {
            result = new File(tmpDirFile, prefix + Math.randomIntInternal() + suffix);//前缀加随机数加后缀命名的临时文件
        } while (!result.createNewFile());
        return result;
    }


将此File重命名为newPath,此方法对目录/文件都有效。有可能造成重命名失败的原因:旧的路径和新的路径都要有写权限;对两个路径的所有父目录都有搜索权限即新旧路径的父目录都要可被搜索;两个路径都要在同一个安装点上,在Android中,最有可能在SD卡和内部存储之间有这点的限制。注意:该方法调用失败不会抛出IO异常,所以调用成功与否要通过返回值判断。
public boolean renameTo(File newPath) {
        try {
            Libcore.os.rename(path, newPath.path);
            return true;
        } catch (ErrnoException errnoException) {
            return false;
        }
    }

返回该File的简洁的,可读的描述符字符串。其实就是输出该File的路径。
 @Override
    public String toString() {
        return path;
    }


每个File都对应一个唯一的Uri值。Uri是依赖于系统的,与操作系统和文件系统不同的表示。

public URI toURI() {
        String name = getAbsoluteName();
        try {
            if (!name.startsWith("/")) {
                // start with sep.
                return new URI("file", null, "/" + name, null, null);
            } else if (name.startsWith("//")) {
                return new URI("file", "", name, null); // UNC path
            }
            return new URI("file", null, name, null, null);
        } catch (URISyntaxException e) {
            // this should never happen
            return null;
        }
    }


返回该File对应的URL。但是该方法不推荐使用。URL同样依赖于系统。如果File转换成URL失败,抛MalformedURLException异常。
@Deprecated
    public URL toURL() throws java.net.MalformedURLException {
        String name = getAbsoluteName();//得到File的绝对路径
        if (!name.startsWith("/")) {
            // start with sep.
            return new URL("file", "", -1, "/" + name, null);
        } else if (name.startsWith("//")) {
            return new URL("file:" + name); // UNC path
        }
        return new URL("file", "", -1, name, null);
    }

转换成绝对路径
 // TODO: is this really necessary, or can it be replaced with getAbsolutePath?
    private String getAbsoluteName() {
        File f = getAbsoluteFile();
        String name = f.getPath();
        if (f.isDirectory() && name.charAt(name.length() - 1) != separatorChar) {
            // Directories must end with a slash
            name = name + "/";
        }
        if (separatorChar != '/') { // Must convert slashes.
            name = name.replace(separatorChar, '/');
        }
        return name;
    }


返回该File所在分区的总字节大小。如果File的路径不存在,返回0。

 public long getTotalSpace() {
        try {
            StructStatVfs sb = Libcore.os.statvfs(path);
            return sb.f_blocks * sb.f_bsize; // total block count * block size in bytes.总字块*字节大小
        } catch (ErrnoException errnoException) {
            return 0;
        }
    }

返回该File所在分区的可用字节大小。如果File的路径不存在,返回0。但是该方法的计算值是乐观估计,实际上要比这个值小,所以不能作为程序可写入的大小。在Android系统以及其他基于Unix系统上,该方法的计算使适用于手机未root用户。

 public long getUsableSpace() {
        try {
            StructStatVfs sb = Libcore.os.statvfs(path);
            return sb.f_bavail * sb.f_bsize; // non-root free block count * block size in bytes.non-root可用字块*字节大小
        } catch (ErrnoException errnoException) {
            return 0;
        }
    }

返回该File所在分区的未使用字节大小。如果File的路径不存在,返回0。这是一个理论上的计算,实际上的可使用值比返回的值要小。该方法适用于不论手机是否root用户。

 public long getFreeSpace() {
        try {
            StructStatVfs sb = Libcore.os.statvfs(path);
            return sb.f_bfree * sb.f_bsize; // free block count * block size in bytes.
        } catch (ErrnoException errnoException) {
            return 0;
        }
    }





作者:qq_27570955 发表于2016/10/2 22:11:08 原文链接
阅读:28 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles