表示一个文件的 File 类型
从本篇文章开始,我们将开启对 Java IO 系统的学习,本质上就是对文件的读写操作,听上去简单,其实并不容易。Java 的 IO 系统一直在完善和改进,设计了大量的类,也只有理解了这些类型被设计出来的意义以及各自的应用场景,才能提升文件 IO 的理解。
那么,第一步就是要解决如何表示一个文件的问题,Java 世界中「万物皆对象」,如何将一个实际磁盘文件或目录对应到一个 Java 对象则是我们首要的问题。
Java 中使用 File 来抽象一个文件,无论是普通文件或是目录,都可对应于一个 File 对象。我觉得大家对于 File 这个类型的定位一定要准确:它只是抽象的代表了磁盘上的某个文件或目录,内部实际上是依赖一个平台无关的本地文件系统类,并且 File 无法对其所表示文件内容进行任何读写操作(那是流做的事情)。
构建一个 File 实例
在实际介绍 File 实例构造方法之前,我们得先看看它的几种重要的属性成员。
private static final FileSystem fs = DefaultFileSystem.getFileSystem();
这是 File 类中最核心的成员,它表示为当前系统的文件系统 API,所有向磁盘发出的操作都是基于这个属性的。
private final String path;
path 代表了当前实例的完整路径名称,如果当前的 File 实例表示的是目录的话,那么 path 的值就是这个完整的目录名称,如果表示的是纯文件的话,那么这个 path 的值等于该文件的完整路径 + 文件名称。
public static final char separatorChar = fs.getSeparator();
public static final char pathSeparatorChar = fs.getPathSeparator();
separatorChar 表示的是目录间的分隔符,pathSeparatorChar 表示的是不同路径下的分隔符,这两个值在不同的系统平台下不尽相同。例如 Windows 下这两者的值分别为:「\」 和 「;」,其中封号用于分隔多个不同路径。
File 类提供了四种不同的构造器用于实例化一个 File 对象,但较为常用的只有三个,我们也着重学习前三个构造器。
public File(String pathname)
这是最普遍的实例化一个 File 对象的方法,pathname 的值可以是一个目录,也可以是一个纯文件的名称。例如:
File file = new File("C:\\Users\\yanga\\Desktop");
File file1 = new File("C:\\Users\\yanga\\Desktop\\a.txt");
File file2 = new File("a.txt");
当然也可以显式指定一个父路径:
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 文件夹下文件情况如下:
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 文件夹就不存在,程序运行之后,test3、hello、231 这三个文件夹都会被创建出来。
除此之外,File 还有一类创建临时文件的方法,所谓临时文件即:运行期存在,虚拟机关闭时销毁。大家可以自行研究,使用上还是比较简单的,这里不再赘述了。
至此,有关 File 这个文件类型,我们大致学习了一下,想必大家都会或多或少的感觉到将纯文件和目录使用同一个类型进行表示的设计似乎有些混乱不合理。知道 jdk1.7 sun 推出了 Files 和 Path 分离了文件和目录,我们后续文章会详细学习一下。
文章中的所有代码、图片、文件都云存储在我的 GitHub 上:
(https://github.com/SingleYam/overview_java)
欢迎关注微信公众号:扑在代码上的高尔基,所有文章都将同步在公众号上。
表示一个文件的 File 类型的更多相关文章
- [Loadrunner参数化]一个文件输两列参数的取值
关于LoadRunner参数化的内容,在脚本开发中属于非常重要的一个知识点.关于这部分知识,在书上和网上到处都能找到,本篇只讲一种特殊情况:一个参数化文件为File类型,有多列值,如何进行参数化取值. ...
- 将String类型的二维数组中的元素用FileOutputStream的write方法生成一个文件
将String类型的二维数组中的元素用FileOutputStream的write方法生成一个文件import java.io.File;import java.io.FileOutputStre ...
- 重新想象 Windows 8 Store Apps (33) - 关联启动: 使用外部程序打开一个文件或uri, 关联指定的文件类型或协议
原文:重新想象 Windows 8 Store Apps (33) - 关联启动: 使用外部程序打开一个文件或uri, 关联指定的文件类型或协议 [源码下载] 重新想象 Windows 8 Store ...
- 如何检测或判断一个文件或字节流(无BOM)是什么编码类型
前言: 昨天,在文章:终于等到你:CYQ.Data V5系列 (ORM数据层,支持.NET Core)最新版本开源了 中, 不小心看到一条留言: 然后就去该地址看了一下,这一看,顺带折腾了一天. 今天 ...
- 关于ACtion类利用file类型取得上传文件的内容,名字和内容类型
上面的Action提供了两个属性:uploadFileName和uploadContentType,这个两个属性分别用于封转上传文件的文件名.上传文件的文件类型.Action类直接通过File类型属性 ...
- 键盘录入一个文件夹路径,统计该文件夹(包含子文件夹)中每种类型的文件及个数,注意:用文件类型(后缀名,不包含.(点),如:"java","txt")作为key, 用个数作为value,放入到map集合中,遍历map集合
package cn.it.zuoye5; import java.io.File;import java.util.HashMap;import java.util.Iterator;import ...
- 与众不同 windows phone (38) - 8.0 关联启动: 使用外部程序打开一个文件或URI, 关联指定的文件类型或协议
[源码下载] 与众不同 windows phone (38) - 8.0 关联启动: 使用外部程序打开一个文件或URI, 关联指定的文件类型或协议 作者:webabcd 介绍与众不同 windows ...
- file类型允许的文件格式设置问题,“选择文件”打开缓慢
1,file类型的input对于打开的选择框的属性是由以下两个属性控制的: ①multiple="multiple" :一次可以选择多个文件 ②accept="image ...
- 在C#中如何确定一个文件是不是文本文件,以及如何确定一个文件的类型
博客搬到了fresky.github.io - Dawei XU,请各位看官挪步.最新的一篇是:在C#中如何确定一个文件是不是文本文件,以及如何确定一个文件的类型.
随机推荐
- 最简单的基于FFMPEG的图像编码器(YUV编码为JPEG)
伴随着毕业论文的完成,这两天终于腾出了空闲,又有时间搞搞FFMPEG的研究了.想着之前一直搞的都是FFMPEG解码方面的工作,很少涉及到FFMPEG编码方面的东西,于是打算研究一下FFMPEG的编码. ...
- Linux多线程实践(10) --使用 C++11 编写 Linux 多线程程序
在这个多核时代,如何充分利用每个 CPU 内核是一个绕不开的话题,从需要为成千上万的用户同时提供服务的服务端应用程序,到需要同时打开十几个页面,每个页面都有几十上百个链接的 web 浏览器应用程序,从 ...
- ZooKeeper 实现分布式队列
使用场景 在传统的单进程编程中,我们使用队列来存储数据结构,用来在多线程之间共享或者传递数据.在分布式环境下,同样需要一个类似单进程的组件, 用来实现跨进程.跨主机.跨网络的数据共享和数据传递.这就 ...
- Property属性, KVC键值编码OC…
1.属性:帮你自动生成setter 和 getter 方法 属性的声明:(写在.h中) 格式: @property 数据类型 属性名 属性的实现:(写在.m中) ...
- QGIS编译
一.准备工作 1.下载QGIS源码 最新版本的QGIS源码需要从git上下载.最新的发布版是2.0,下载地址见下.https://github.com/qgis/QGIS/tree/release-2 ...
- 最新的App上架教程Object-C
准备 开发者账号 完工的项目 上架步骤 一.创建App ID 二.创建证书请求文件 (CSR文件) 三.创建发布证书 (CER) 四.创建Provisioning Profiles配置文件 (PP文件 ...
- 跨平台移动APP开发进阶(二)HTML5+、mui开发移动app教程
前端开发APP,从HBuilder开始~ 序 通过 HTML5 开发移动App 时,会发现HTML5 很多能力不具备.为弥补HTML5 能力的不足,在W3C 中国的指导下成立了www.HTML5Plu ...
- Pixelmetrix :OTT Media Grinder (OTT TV 质量评价设备)
有关OTT TV 质量评价方法方面的研究少之又少.国内貌似还几乎没有相关的研究.不过在国外已经找到相关的产品了,翻译了一下产品手册的部分内容,很有参考价值,尤其是其提出的8个指标. 概述 OTT Me ...
- 《高效能程序员的修炼》读后感 By Yong Zhang
想不到我工作中经常GOOGLE搜寻技术问题的stack overflow网站的创办人竟然是<高效能程序员的修炼>一书的作者!看了一遍全书,果然名不虚传. 本书更多的从人文角度而非技术角度去 ...
- rabbitMQ之AMQP协议
1.什么是AMQP协议 即高级消息队列协议,规范客户端与消息中间件服务器之间的通信,并能相互操作. 2.AMQP协议的作用 降低应用程序之间的耦合度,这样不同应用之间的集成的难度将变得更小,并开发出更 ...