认知IO流之 — FileDescriptor
关注公众号回复002,有你想要的一切
FileDescriptor 是什么
FileDescriptor 顾名思义是文件描述符
,FileDescriptor 可以被用来表示开放文件、开放套接字等。比如用 FileDescriptor 表示文件来说: 当 FileDescriptor 表示文件时,我们可以通俗的将 FileDescriptor 看成是该文件。但是,我们不能直接通过 FileDescriptor 对该文件进行操作。
若需要通过 FileDescriptor 对该文件进行操作,则需要新创建 FileDescriptor 对应的 FileOutputStream
或者是 FileInputStream
,再对文件进行操作,应用程序不应该创建他们自己的文件描述符
下面让我们用两个例子来演示一下 FileDescriptor 分别与 FileInputStream 和 FileOutputStream 的使用
-
public class FileDescriptorExample {
-
-
public static void main(String[] args) {
-
try (FileInputStream fileInputStream = new FileInputStream("/Users/mr.l/Desktop/test")) {
-
-
// 返回 FileDescriptor 对象代表着文件系统中的真实文件的链接。
-
FileDescriptor fd = fileInputStream.getFD();
-
-
System.out.println("File descriptor of the file /Users/mr.l/Desktop/test.txt : "
-
+ fd.hashCode());
-
-
} catch (FileNotFoundException e) {
-
e.printStackTrace();
-
} catch (IOException e) {
-
e.printStackTrace();
-
}
-
-
try (FileOutputStream fileOutputStream = new FileOutputStream("/Users/mr.l/Desktop/test2")) {
-
-
FileDescriptor fd = fileOutputStream.getFD();
-
System.out.println("File descriptor of the file /Users/mr.l/Desktop/test2.txt : " + fd.hashCode());
-
-
} catch (FileNotFoundException e) {
-
e.printStackTrace();
-
} catch (IOException e) {
-
e.printStackTrace();
-
}
-
-
}
-
-
}
FileDescriptor 结构
FileDescriptor 有三种输出方式:in、out、error
,分别代表
public static final FileDescriptor in = new FileDescriptor(0);
一个标准输入流的句柄。通常情况下,这个文件描述符不会直接使用,而是通过称为System.in
的输入流。
public static final FileDescriptor out = new FileDescriptor(1);
一个标准的输出流句柄。通过 System.out
来使用
public static final FileDescriptor err = new FileDescriptor(2);
一个标准的错误流句柄。通过 System.err
来使用
那么如何创建这三种输出流呢?在 FileInputStream
和 FileOutputStream
中使用其对应的构造方法来创建。
public FileInputStream(FileDescriptor fdObj) {...}
文件描述符的创建
我们可以通过如下的方式来创建文件描述符
-
FileInputStream fileInputStream = new FileInputStream(FileDescriptor.in);
-
fileInputStream.read();
-
fileInputStream.close();
这段代码创建了一个标准输入流,让你可以从控制台输入信息,它的作用等同于System.in
类似的,out
和 error
都是文件输出流,你可以按照下面这种方式创建
-
FileOutputStream out = new FileOutputStream(FileDescriptor.out);
-
out.write('A');
-
out.close();
它们用于向控制台输出消息,out 的作用等于 System.out,err 的作用等于 System.err。
因此,我们可以等价的将上面的程序转换为如下代码:System.out.print('A'); System.err.print('A');
FileDescriptor 方法与使用
FileDescriptor 的方法比较少,下面就一起看一下 FileDescriptor 都包含了哪些方法
sync
public native void sync() throws SyncFailedException;
此方法是一个 native 方法,由 C 语言实现,它的主要用处是说:强制所有系统缓冲区与基础设备同步。该方法在此 FileDescriptor 的所有修改数据和属性都写入相关设备后返回。如果此 FileDescriptor 引用物理存储介质,比如文件系统中的文件,则一直要等到将与此 FileDesecriptor 有关的缓冲区的所有内存中修改副本写入物理介质中,sync 方法才会返回。sync 方法由要求物理存储(比例文件)处于某种已知状态下的代码使用。例如,提供简单事务处理的类可以使用 sync 来确保某个文件所有由给定事务造成的更改都记录在存储介质上。sync 只影响此 FileDescriptor 的缓冲区下游。如果正通过应用程序(例如,通过一个 BufferedOutputStream 对象)实现内存缓冲,那么必须在数据受 sync 影响之前将这些缓冲区刷新,并转到 FileDescriptor 中(例如,通过调用 OutputStream.flush)。
它的一般用法是
-
public static void main(String[] args) throws IOException {
-
-
FileDescriptor descriptor = null;
-
FileOutputStream outputStream = null;
-
byte[] buffer = {71,69,69,75,83};
-
-
try {
-
outputStream = new FileOutputStream("/Users/mr.l/Desktop/test3");
-
descriptor = outputStream.getFD();
-
outputStream.write(buffer);
-
-
descriptor.sync();
-
-
}catch(Exception excpt) {
-
excpt.printStackTrace();
-
}
-
finally{
-
if(outputStream != null)
-
outputStream.close();
-
}
-
}
valid
测试此文件描述符对象是否有效。如果文件描述符对象代表着 有效的开放文件,套接字或者其他有效的 I/O 连接 则返回true ,其他返回 false。它的用法如下
-
FileDescriptor descriptor = null;
-
try(FileInputStream inputStream = new FileInputStream("/Users/mr.l/Desktop/test3")) {
-
boolean check = false;
-
-
descriptor = inputStream.getFD();
-
check = descriptor.valid();
-
-
System.out.println("check = " + check);
-
-
}catch (Exception e){
-
e.printStackTrace();
-
}
打印输出 check = true, 因为此文件目前处于开放状态。如果把 valid() 方法放在 inputStream.close()
方法后呢,就如下所表示的
-
FileInputStream inputStream = null;
-
FileDescriptor descriptor = null;
-
boolean check = false;
-
try {
-
inputStream = new FileInputStream("/Users/mr.l/Desktop/test3");
-
-
descriptor = inputStream.getFD();
-
check = descriptor.valid();
-
-
System.out.println("check = " + check);
-
-
}catch (Exception e){
-
e.printStackTrace();
-
} finally {
-
inputStream.close();
-
check = descriptor.valid();
-
System.out.println("check = " + check);
-
}
会输出两条消息
check = true check = false
因为在流关闭之后,文件不在有效,故返回false。
attach
解析 attach 方法前首先来看一下两个接口 Closeable
接口和 AutoCloseable
接口
Closeable 接口:Closeable 表示一个资源或者数据能够被关闭,close 方法被调用用来释放对象持有的资源,如果资源已经关闭了,那么调用 close 方法不会再产生作用。
然后回到 FileDescriptor 的描述中来,FileDescriptor 有三个属性
-
private Closeable parent;
-
private List<Closeable> otherParents;
-
private boolean closed;
有一个 Closeable 对象的 parent
,表示用来关闭单个资源,List<Closeable> otherParents
,需要关闭对象的集合,下面源码中会用到, closed
用来判断资源是否已经关闭。
attach 源码:
-
synchronized void attach(Closeable c) {
-
if (parent == null) {
-
parent = c;
-
} else if (otherParents == null) {
-
otherParents = new ArrayList<>();
-
otherParents.add(parent);
-
otherParents.add(c);
-
} else {
-
otherParents.add(c);
-
}
-
}
此方法用于追踪需要关闭的对象,如果只有单个需要关闭的对象,那么直接调用后面的 closeAll() 方法即可,如果多个流指向同一个相同的描述符,FileDescriptor 会把需要关闭的资源放在 otherParents 的集合中,我们会循环list 中的每个引用,并且把它们添加到 parent 后面,这个 parent 顾名思义相当于就是 第一个需要被关闭的资源,这个方法主要为下面的 closeAll() 方法做铺垫。
closeAll
-
synchronized void closeAll(Closeable releaser) throws IOException {
-
if (!closed) {
-
closed = true;
-
IOException ioe = null;
-
try (Closeable c = releaser) {
-
if (otherParents != null) {
-
for (Closeable referent : otherParents) {
-
try {
-
referent.close();
-
} catch(IOException x) {
-
if (ioe == null) {
-
ioe = x;
-
} else {
-
ioe.addSuppressed(x);
-
}
-
}
-
}
-
}
-
} catch(IOException ex) {
-
if (ioe != null)
-
ex.addSuppressed(ioe);
-
ioe = ex;
-
} finally {
-
if (ioe != null)
-
throw ioe;
-
}
-
}
-
}
在资源没有被关闭的时候,在需要关闭的资源为 null 的情况下,会对需要关闭的资源集合循环遍历进行关闭操作
文章来源: cxuan.blog.csdn.net,作者:程序员cxuan,版权归原作者所有,如需转载,请联系作者。
原文链接:cxuan.blog.csdn.net/article/details/102634183
- 点赞
- 收藏
- 关注作者
评论(0)