认知IO流之 — FileDescriptor

简介: FileDescriptor 顾名思义是文件描述符,FileDescriptor 可以被用来表示开放文件、开放套接字等。比如用 FileDescriptor 表示文件来说: 当 FileDescriptor 表示文件时,我们可以通俗的将 FileDescriptor 看成是该文件。但是,我们不能直接通过 FileDescriptor 对该文件进行操作。

FileDescriptor 是什么

FileDescriptor 顾名思义是文件描述符,FileDescriptor 可以被用来表示开放文件、开放套接字等。比如用 FileDescriptor 表示文件来说: 当 FileDescriptor 表示文件时,我们可以通俗的将 FileDescriptor 看成是该文件。但是,我们不能直接通过 FileDescriptor 对该文件进行操作。

若需要通过 FileDescriptor 对该文件进行操作,则需要新创建 FileDescriptor 对应的 FileOutputStream或者是 FileInputStream,再对文件进行操作,应用程序不应该创建他们自己的文件描述符

下面让我们用两个例子来演示一下 FileDescriptor 分别与 FileInputStream 和 FileOutputStream 的使用

publicclass 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,分别代表

publicstaticfinal FileDescriptor in = new FileDescriptor(0);

一个标准输入流的句柄。通常情况下,这个文件描述符不会直接使用,而是通过称为System.in的输入流。

publicstaticfinal FileDescriptor out = new FileDescriptor(1);

一个标准的输出流句柄。通过 System.out 来使用

publicstaticfinal FileDescriptor err = new FileDescriptor(2);

一个标准的错误流句柄。通过 System.err 来使用

那么如何创建这三种输出流呢?在 FileInputStreamFileOutputStream 中使用其对应的构造方法来创建。

public FileInputStream(FileDescriptor fdObj) {...}

文件描述符的创建

我们可以通过如下的方式来创建文件描述符

FileInputStream fileInputStream = new FileInputStream(FileDescriptor.in);

fileInputStream.read();
fileInputStream.close();

这段代码创建了一个标准输入流,让你可以从控制台输入信息,它的作用等同于System.in

类似的,outerror 都是文件输出流,你可以按照下面这种方式创建

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 接口

  • AutoCloseable 接口 : 实现了此接口的类能够持有资源直到被关闭的时候。其中的 close() 方法是自动关闭的,离开try-with-resources(jdk1.7 的新特性,不清楚请移步至 你会使用try-with-resources吗)中的 try 语句块的时候。这种方式确保了能够及时释放资源,避免资源的枯竭和可能出现的错误。
  • Closeable 接口:Closeable 表示一个资源或者数据能够被关闭,close 方法被调用用来释放对象持有的资源,如果资源已经关闭了,那么调用 close 方法不会再产生作用。

然后回到 FileDescriptor 的描述中来,FileDescriptor 有三个属性

private Closeable parent;
private List<Closeable> otherParents;
privateboolean closed;

有一个 Closeable 对象的 parent,表示用来关闭单个资源,List<Closeable> otherParents,需要关闭对象的集合,下面源码中会用到, closed用来判断资源是否已经关闭。

attach 源码:

synchronized void attach(Closeable c) {
if (parent == null) {
parent = c;
} elseif (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 的情况下,会对需要关闭的资源集合循环遍历进行关闭操作

            </div>
目录
相关文章
|
Android开发
【Android App】蓝牙的设备配对、音频传输、点对点通信的讲解及实战(附源码和演示 超详细)
【Android App】蓝牙的设备配对、音频传输、点对点通信的讲解及实战(附源码和演示 超详细)
3194 1
|
11月前
|
存储 Java 数据库连接
时序数据库TDengine 3.3.5.0 发布:高并发支持与增量备份功能引领新升级
TDengine 3.3.5.0 版本正式发布,带来多项更新与优化。新特性包括提升 MQTT 稳定性和高并发性能、新增 taosX 增量备份与恢复、支持 JDBC 和 Rust 连接器 STMT2 接口、灵活配置 Grafana Dashboard 等。性能优化涵盖查询内存管控、多级存储迁移、强密码策略等,全面提升时序数据管理的效率和可靠性。欢迎下载体验并提出宝贵意见。
332 5
|
人工智能 算法 搜索推荐
从调研到交付,双钻设计模型如何贯穿整个设计流程?
2分钟带你了解双钻设计模型及其实操
563 2
从调研到交付,双钻设计模型如何贯穿整个设计流程?
|
移动开发 前端开发 JavaScript
JS配合canvas实现贪吃蛇小游戏_升级_丝滑版本_支持PC端和移动端
本文介绍了一个使用JavaScript和HTML5 Canvas API实现的贪吃蛇游戏的升级版本,该版本支持PC端和移动端,提供了丝滑的转向效果,并允许玩家通过键盘或触摸屏控制蛇的移动。代码中包含了详细的注释,解释了游戏逻辑、食物生成、得分机制以及如何响应不同的输入设备。
559 1
JS配合canvas实现贪吃蛇小游戏_升级_丝滑版本_支持PC端和移动端
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android多线程编程的重要性及其实现方法,涵盖了基本概念、常见线程类型(如主线程、工作线程)以及多种多线程实现方式(如`Thread`、`HandlerThread`、`Executors`、Kotlin协程等)。通过合理的多线程管理,可大幅提升应用性能和用户体验。
521 15
一个Android App最少有几个线程?实现多线程的方式有哪些?
|
10月前
|
SQL 消息中间件 Kafka
Flink+Paimon+Hologres,面向未来的一体化实时湖仓平台架构设计
本文介绍了阿里云实时数仓Hologres负责人姜伟华在Flink Forward Asia 2024上的分享,涵盖实时数仓的发展历程、从实时数仓到实时湖仓的演进,以及总结。文章通过三代实时数仓架构的演变,详细解析了Lambda架构、Kafka实时数仓分层+OLAP、Hologres实时数仓分层复用等方案,并探讨了未来从实时数仓到实时湖仓的演进方向。最后,结合实际案例和Demo展示了Hologres + Flink + Paimon在实时湖仓中的应用,帮助用户根据业务需求选择合适的方案。
1489 20
Flink+Paimon+Hologres,面向未来的一体化实时湖仓平台架构设计
|
存储 应用服务中间件 开发工具
docker镜像上传至Harbor及从Harbor下载
docker镜像上传至Harbor及从Harbor下载
|
存储 机器学习/深度学习 缓存
【数据挖掘】XGBoost面试题:与GBDT的区别?为什么使用泰勒二阶展开?为什么可以并行训练?为什么快?防止过拟合的方法?如何处理缺失值?
XGBoost与GBDT的区别、XGBoost使用泰勒二阶展开的原因、并行训练的原理、速度优势、防止过拟合的策略以及处理缺失值的方法,突出了XGBoost在提升模型性能和训练效率方面的一系列优化。
927 1
|
程序员
VSCode变量命名转换插件(Var-Conv)
VSCode变量命名转换插件(Var-Conv)
2513 0
VSCode变量命名转换插件(Var-Conv)
|
缓存 负载均衡 应用服务中间件
【揭秘】nginx代理配置全攻略:从零到精通,一文带你玩转高效网络代理的秘密武器!
【8月更文挑战第22天】nginx是一款高性能的HTTP与反向代理服务器,支持代理服务、负载均衡及缓存等功能,有助于提升网站响应速度和安全性。首先需确保已安装nginx,可通过包管理器进行安装。安装后启动并确认nginx运行状态。接着编辑配置文件(通常位于`/etc/nginx/nginx.conf`),设置代理转发规则,例如指定目标服务器地址和请求头信息。配置完成后测试有效性并重新加载nginx以应用更改。可以通过部署简易HTTP服务器验证代理功能是否正常工作。此外,还可以通过扩展配置文件实现更复杂的代理需求,如基于路径的代理和SSL加密等。
2627 2