JavaIO流复习与巩固--FileDescriptor与File

#FileDescriptor

FileDescriptor,顾名思义:“文件描述符”,用来表示开放文件、开放套接字。这个概念来自于*nux文件系统,Linux继承Unix “一切皆文件” 的概念:

文件类型标记符
普通文件-
目录文件(directory)d
字符设备文件(character)c
块设备文件(block)b
套接字文件(socket)s
管道文件(pipe)p
链接文件(link)l

POSIX标准中打开文件的函数如下:

1
2
// 这里返回的int值就是FileDescriptor
int open(const char *path, int oflag, .../*,mode_t mode */);

这里返回的int值就是FileDescriptor,它被用来标识一个文件,你可以把它理解为一个“文件句柄”。

但是只有FileDescriptor,我们是无法读写文件的,我们还需要FileInputStream、FileOutputStream或RandomAccessFile等类来实现文件的读写。这几个类的使用下面会依次讲到。

而操作系统中有三个标准的I/O流:

FileDescriptor名称POSIX常量标识(unistd.h)标准IO常量标识(stdio.h)
0标准输入流STDIN_FILENOstdin
1标准输出流STDOUT_FILENOstdout
2标准错误流STDERR_FILENOstderr

标准IO流

FileDescriptor类中也定义了这三个常量:

1
2
3
4
5
public static final FileDescriptor in = standardStream(0);

public static final FileDescriptor out = standardStream(1);

public static final FileDescriptor err = standardStream(2);

但是通常我们都是通过System中的三个常量来使用标准I/O流,因为它已经帮我们封装好了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
   public final static InputStream in = null;
public final static PrintStream out = null;
public final static PrintStream err = null;
public static void setIn(InputStream in) {
checkIO();
setIn0(in);
}
public static void setOut(PrintStream out) {
checkIO();
setOut0(out);
}
public static void setErr(PrintStream err) {
checkIO();
setErr0(err);
}
private static native void setIn0(InputStream in);
private static native void setOut0(PrintStream out);
private static native void setErr0(PrintStream err);
private static void initializeSystemClass() {
...
FileInputStream fdIn = new FileInputStream(FileDescriptor.in);
FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err);
setIn0(new BufferedInputStream(fdIn));
setOut0(newPrintStream(fdOut, props.getProperty("sun.stdout.encoding")));
setErr0(newPrintStream(fdErr, props.getProperty("sun.stderr.encoding")));
...
}

有关标准I/O的内容可以参考《JavaIO总结与巩固》

#File

FileDescriptor为文件的读写功能提供了基础,而File则是提供文件的增删查功能。

在Java中File更像是一个文件路径,只是Java在File类中还提供了文件的增删查的操作。所以File类是个“大杂烩”,功能相当的丰富。也许正是因为File类功能太多,在Java1.7的NIO2中把它的功能划分成了PathFiles两个类。

有关NIO的内容会在后续的文章中讲到

Java File与FileSystem

File的功能大部分是由FileSystem类来实现的,而FileSystem的实现类根据平台分为两种:WinNTFileSystem代表微软的Windows系列的操作系统,UnixFileSystem代表了BSD,Linux,MaxOS为首遵循POSIX规范的类Unix系统,但FileSystem及其实现类我们无法直接使用它(因为是package访问级别)。

注意这里的FileSystem不是java.nio.file包中的FileSystem。

File类有四个公有的构造方法:

  • public File(String pathname)

    路径名可以是相对路径,也可以使绝对路径,pathname不能为null,如果pathname为空字符串,将会构造空的抽象路径

  • public File(String parent, String child)

  • public File(File parent, String child)

    两个参数的构造方法中,当父路径为空,构造器不会用当前路径去解析子路径,而是将FileSystem.getDefaultParent()方法的返回值作为父路径,在类Unix系统中这个默认父路径为/根目录,而在微软的Windows系统中默认父路径为\\,也就是当前磁盘根目录。

    Windows将磁盘分为多个分区,每个分区使用一个磁盘标签。如C:\代表C盘根目录,D:\代表D盘根目录…

    类Unix系统将磁盘分区后,将分区挂载在/根目录下。如/home可以作为一个分区挂载在/根目录下。

  • public File(URI uri) @since 1.4

    通过解析以file:开头的URI来作为文件路径。-

以上的构造方法中都调用了FileSystem.normalize方法来对文件路径进行解析。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void test() throws URISyntaxException {
File file1 = new File("a.txt");
System.out.println(file1);
File file2 = new File("");
System.out.println(file2);
File file3 = new File("D:/dir/a.txt");
System.out.println(file3);
File file4 = new File("D:/dir", "a.txt");
System.out.println(file4);
File file5 = new File("", "a.txt");
System.out.println(file5);
File file6 = new File(new File("D:/dir"), "a.txt");
System.out.println(file6);
File file7 = new File(new File(""), "a.txt");
System.out.println(file7);
File file8 = new File(new URI("file:/D:/dir/a.txt"));
System.out.println(file8);
File file9 = new File(new URI("file:/a.txt"));
System.out.println(file9);
}

输出结果:

1
2
3
4
5
6
7
8
9
a.txt

D:\dir\a.txt
D:\dir\a.txt
\a.txt
D:\dir\a.txt
\a.txt
D:\dir\a.txt
\a.txt

File类会帮我们自动把/转成Windows上的\

  • 在类Unix系统中目录分隔符为/,在Windows上目录分隔符为\\(前一个\用于转义)。

    类Unix:/home/user/file

    Windows:D:\dir\file

  • 在类Unix系统中路径分隔符为:,在Windows上路径分隔符为;

    类Unix:export PATH = $PATH:/user/local/Xxx/bin$PATH是取出PATH变量的值

    Windows:set PATH = %PATH%;D:\Program Files\Xxx\bin%PATH%是取出PATH变量的值

#File相关方法

File类的方法可以分为以下几类:

#1. 路径操作

Path-component accessorsPeriodExplain
String getName()@since JDK1.0获取文件名
String getPath()@since JDK1.0获取抽象路径
String getParent()@since JDK1.0获取父目录路径
File getParentFile()@since 1.2获取父目录File对象
Path operationsPeriodExplain
boolean isAbsolute()@since JDK1.0路径是否为绝对路径
String getAbsolutePath()@since JDK1.0获取绝对路径
File getAbsoluteFile()@since 1.2获取绝对路径的File对象
String getCanonicalPath()@since JDK1.1获取权威绝对路径
File getCanonicalFile()@since 1.2获取权威绝对路径的File对象
URL toURL()@since 1.2将路径转成URL,被弃用(可使用URI.toURL方法转换)
URI toURI()@since 1.4将路径转成URI

getPathgetAbsolutePathgetCanonicalPath的区别可用以下概念区别(StackOverflow的一位大神一语道破):

  • C:\temp\file.txt - 是Path, 是AbsolutePath,也是CanonicalPath.
  • .\file.txt - 是Path,但既不是AbsolutePath,也不是CanonicalPath.
  • C:\temp\myapp\bin\..\\..\file.txt - 是Path,也是AbsolutePath,但不是CanonicalPath.

因为File的路径可能是多个路径字符串拼凑而成,所以绝对路径就有了AbsolutePath和CanonicalPath的分别

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package cn.hff.io;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;

import org.junit.Before;
import org.junit.Test;

public class FileTest {

private List<File> files = new ArrayList<File>();

@Before
public void setup() throws URISyntaxException {
files.add(new File("a.txt"));
files.add(new File(""));
files.add(new File("D:/dir/a.txt"));
files.add(new File("D:/dir", "a.txt"));
files.add(new File("D:/dir/innerdir/", "../a.txt"));
files.add(new File("", "a.txt"));
files.add(new File(new File("D:/dir"), "a.txt"));
files.add(new File(new File(""), "a.txt"));
files.add(new File(new URI("file:/D:/dir/a.txt")));
files.add(new File(new URI("file:/a.txt")));
}

@Test
public void testPath() throws IOException {
for (File file : files) {
pathOperation(file);
}
}

public void pathOperation(File file) throws IOException {
System.out.println("----------" + file + "----------");
System.out.println("Name:" + file.getName());
System.out.println("Path:" + file.getPath());
System.out.println("Parent:" + file.getParent());
System.out.println("IsAbsolute:" + file.isAbsolute());
System.out.println("AbsolutePath:" + file.getAbsolutePath());
System.out.println("CanonicalPath:" + file.getCanonicalPath());
System.out.println("ParentFile:" + file.getParentFile());
System.out.println("AbsoluteFile:" + file.getAbsoluteFile());
System.out.println("CanonicalFile:" + file.getCanonicalFile());
System.out.println("URI:" + file.toURI());
}
}

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
----------a.txt----------
Name:a.txt
Path:a.txt
Parent:null
IsAbsolute:false
AbsolutePath:D:\java\Apache Commons\commons-io2.5\a.txt
CanonicalPath:D:\java\Apache Commons\commons-io2.5\a.txt
ParentFile:null
AbsoluteFile:D:\java\Apache Commons\commons-io2.5\a.txt
CanonicalFile:D:\java\Apache Commons\commons-io2.5\a.txt
URI:file:/D:/java/Apache%20Commons/commons-io2.5/a.txt
--------------------
Name:
Path:
Parent:null
IsAbsolute:false
AbsolutePath:D:\java\Apache Commons\commons-io2.5
CanonicalPath:D:\java\Apache Commons\commons-io2.5
ParentFile:null
AbsoluteFile:D:\java\Apache Commons\commons-io2.5
CanonicalFile:D:\java\Apache Commons\commons-io2.5
URI:file:/D:/java/Apache%20Commons/commons-io2.5/
----------D:\dir\a.txt----------
Name:a.txt
Path:D:\dir\a.txt
Parent:D:\dir
IsAbsolute:true
AbsolutePath:D:\dir\a.txt
CanonicalPath:D:\dir\a.txt
ParentFile:D:\dir
AbsoluteFile:D:\dir\a.txt
CanonicalFile:D:\dir\a.txt
URI:file:/D:/dir/a.txt
----------D:\dir\a.txt----------
Name:a.txt
Path:D:\dir\a.txt
Parent:D:\dir
IsAbsolute:true
AbsolutePath:D:\dir\a.txt
CanonicalPath:D:\dir\a.txt
ParentFile:D:\dir
AbsoluteFile:D:\dir\a.txt
CanonicalFile:D:\dir\a.txt
URI:file:/D:/dir/a.txt
----------D:\dir\innerdir\..\a.txt----------
Name:a.txt
Path:D:\dir\innerdir\..\a.txt
Parent:D:\dir\innerdir\..
IsAbsolute:true
AbsolutePath:D:\dir\innerdir\..\a.txt
CanonicalPath:D:\dir\a.txt
ParentFile:D:\dir\innerdir\..
AbsoluteFile:D:\dir\innerdir\..\a.txt
CanonicalFile:D:\dir\a.txt
URI:file:/D:/dir/innerdir/../a.txt
----------\a.txt----------
Name:a.txt
Path:\a.txt
Parent:\
IsAbsolute:false
AbsolutePath:D:\a.txt
CanonicalPath:D:\a.txt
ParentFile:\
AbsoluteFile:D:\a.txt
CanonicalFile:D:\a.txt
URI:file:/D:/a.txt
----------D:\dir\a.txt----------
Name:a.txt
Path:D:\dir\a.txt
Parent:D:\dir
IsAbsolute:true
AbsolutePath:D:\dir\a.txt
CanonicalPath:D:\dir\a.txt
ParentFile:D:\dir
AbsoluteFile:D:\dir\a.txt
CanonicalFile:D:\dir\a.txt
URI:file:/D:/dir/a.txt
----------\a.txt----------
Name:a.txt
Path:\a.txt
Parent:\
IsAbsolute:false
AbsolutePath:D:\a.txt
CanonicalPath:D:\a.txt
ParentFile:\
AbsoluteFile:D:\a.txt
CanonicalFile:D:\a.txt
URI:file:/D:/a.txt
----------D:\dir\a.txt----------
Name:a.txt
Path:D:\dir\a.txt
Parent:D:\dir
IsAbsolute:true
AbsolutePath:D:\dir\a.txt
CanonicalPath:D:\dir\a.txt
ParentFile:D:\dir
AbsoluteFile:D:\dir\a.txt
CanonicalFile:D:\dir\a.txt
URI:file:/D:/dir/a.txt
----------\a.txt----------
Name:a.txt
Path:\a.txt
Parent:\
IsAbsolute:false
AbsolutePath:D:\a.txt
CanonicalPath:D:\a.txt
ParentFile:\
AbsoluteFile:D:\a.txt
CanonicalFile:D:\a.txt
URI:file:/D:/a.txt

Demo输出有点长,区别很细微,最好是自己敲一遍

#2. 访问属性

Attribute accessorsPeriodExplain
boolean canRead()@since JDK1.0文件是否可读
boolean canWrite()@since JDK1.0文件是否可写
boolean canExecute()@since 1.6文件是否可执行
boolean exists()@since JDK1.0文件是否存在
boolean isDirectory()@since JDK1.0是否为目录文件
boolean isFile()@since JDK1.0是否为普通文件
boolean isHidden()@since 1.2是否为隐藏文件
long lastModified()@since JDK1.0上次修改的时间戳
long length()@since JDK1.0文件长度

上面方法可能在Windows体现不是很明显,Linux上对于读、写、执行的权限管理非常严格。

文件属性

#3. 文件操作

File operationsPeriodExplain
boolean createNewFile()@since 1.2创建新文件,类似于touch命令
boolean delete()@since JDK1.0删除文件,类似于rm命令
void deleteOnExit()@since 1.2程序退出时删除文件(通过添加程序退出是的钩子实现)
String[] list()@since JDK1.0列出目录下的文件名,类似于ls命令
String[] list(FilenameFilter filter)@since JDK1.0根据文件名过滤器滤出文件名,类似于`ls
File[] listFiles()@since 1.2列出目录下的文件
File[] listFiles(FilenameFilter filter)@since 1.2根据文件名过滤器滤出文件
File[] listFiles(FileFilter filter)@since 1.2根据文件过滤器滤出文件
boolean mkdir()@since JDK1.0创建目录(如果父目录不存在,返回false),类似于mkdir命令
boolean mkdirs()@since JDK1.0创建目录(如果父目录不存在,递归创建父目录),类似于mkdir -p命令
boolean renameTo(File dest)@since JDK1.0重命名(可用作文件移动),类似于mv命令
boolean setLastModified(long time)@since 1.2设置文件的最后修改时间,类似于touch -m命令
boolean setReadOnly()@since 1.2设置文件为只读模式
boolean setWritable(boolean writable, boolean ownerOnly)@since 1.6设置文件可写属性(ownerOnly用于设置只针对文件所有者)
boolean setWritable(boolean writable)@since 1.6setWritable(writable, true);
boolean setReadable(boolean readable, boolean ownerOnly)@since 1.6设置文件可读属性(ownerOnly用于设置只针对文件所有者)
boolean setReadable(boolean readable)@since 1.6setReadable(readable, true);
boolean setExecutable(boolean executable, boolean ownerOnly)@since 1.6设置文件可执行属性(ownerOnly用于设置只针对文件所有者)
boolean setExecutable(boolean executable)@since 1.6setExecutable(executable, true);

Linux中可使用chmod对文件的读、写、执行权限进行修改。

这里有五个目录相关的方法——列出目录下的所有文件(名)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 目录下的所有文件名
public String[] list() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(path);
}
if (isInvalid()) {
return null;
}
// 调用相应平台的FileSystem对象的list方法
return fs.list(this);
}
// 根据文件名过滤器指定的规则获取目录下的所有文件
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()]);
}
// 获取目录下所有文件的File对象
public File[] listFiles() {
String[] ss = list();// 先获取目录下所有的文件名
if (ss == null) return null;
int n = ss.length;
File[] fs = new File[n];
for (int i = 0; i < n; i++) {
// 为每个文件名创建File对象
fs[i] = new File(ss[i], this);
}
return fs;
}
// 根据文件名过滤器指定的规则获取目录下的所有文件对应的File对象
public File[] listFiles(FilenameFilter filter) {
String ss[] = list();
if (ss == null) return null;
ArrayList<File> files = new ArrayList<>();
for (String s : ss)
// 根据文件名规则过滤文件名
if ((filter == null) || filter.accept(this, s))
// 符合要求的文件名创建File对象
files.add(new File(s, this));
return files.toArray(new File[files.size()]);
}
// 根据文件过滤器指定的规则获取目录下所有文件对应的File对象
public File[] listFiles(FileFilter filter) {
String ss[] = list();// 先获取目录下所有的文件名
if (ss == null) return null;
ArrayList<File> files = new ArrayList<>();
for (String s : ss) {
File f = new File(s, this);
// 根据文件规则过滤文件
if ((filter == null) || filter.accept(f))
files.add(f);
}
return files.toArray(new File[files.size()]);
}

Apache的commons-io库中有各种过滤器的实现,我画了张图概括commons-io中这些接口与类之间的继承关系(暗红色为已弃用的类):

FileFilter

#4. 磁盘空间

Disk usagePeriodExplain
long getTotalSpace()@since 1.6获取文件所在磁盘的所有空间
long getFreeSpace()@since 1.6获取文件所在磁盘的可用空间
long getUsableSpace()@since 1.6获取文件所在磁盘的不可用空间

上面三个方法的文件需要存在才能获取空间值,否则会返回0。

Linux可使用df命令查看文件系统的使用情况,du命令可查看文件大小

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class FileTest {
@Test
public void testDisk() {
diskUsage(new File("D:\\"));
}

public void diskUsage(File file) {
System.out.println("----------" + file + "----------");
System.out.println(file.getTotalSpace());
System.out.println(file.getFreeSpace());
System.out.println(file.getUsableSpace());
}
}

运行结果:

1
2
3
4
----------D:\----------
155342336000
129711624192
129711624192

注意:返回结果是字节数

#5. 静态方法

  1. 根路径
Filesystem interfacePeriodExplain
static File[] listRoots()@since 1.2列出根路径下的文件

示例:

1
2
3
4
5
6
7
@Test
public void testStatic() {
File[] roots = File.listRoots();
for (int i = 0; i < roots.length; i++) {
System.out.println(roots[i]);
}
}

执行结果:

1
2
3
4
C:\
D:\
E:\
F:\
  1. 临时文件
Temporary filesPeriodExplain
static File createTempFile(String prefix, String suffix, File directory)@since 1.2创建临时文件
static File createTempFile(String prefix, String suffix)@since 1.2createTempFile(prefix, suffix, null);

prefix标识文件名前缀,suffix标识文件名后缀,directory标识临时文件目录。

来看一下createTempFile的部分源码:

1
2
3
4
5
6
7
8
9
10
11
12
// 前缀长度不能小于3
if (prefix.length() < 3)
throw new IllegalArgumentException("Prefix string too short");
// 后缀为空,默认设为.tmp
if (suffix == null)
suffix = ".tmp";

// 临时文件目录为空时,使用默认的临时目录
// 默认临时目录可通过java.io.tmpdir系统参数设置
// 如果未设置,则使用系统环境变量TEMP中指定的目录
File tmpdir = (directory != null) ? directory
: TempDirectory.location();

参考链接:

本作品采用 知识共享署名 4.0 国际许可协议 进行许可。

转载时请注明原文链接:https://blog.hufeifei.cn/2017/07/Java/JavaIO%E6%B5%81%E5%A4%8D%E4%B9%A0%E4%B8%8E%E5%B7%A9%E5%9B%BA--File%E6%96%87%E4%BB%B6%E6%93%8D%E4%BD%9C/

鼓励一下
支付宝微信