Java FileDescriptor原理用法源码分析
原理
FileDescriptor
是 Java 中用于表示文件或文件描述符的抽象类。它提供了对底层操作系统文件句柄的访问和控制。
原理实现:
FileDescriptor
类是一个封装了底层文件句柄的对象,用于在 Java 程序中进行文件和 I/O 操作。- 它通过整数值来表示底层操作系统的文件描述符(file descriptor)。
- 在 Java 中,每个打开的文件都会被赋予一个唯一的文件描述符,它作为文件在操作系统中的标识符。
用法示例
FileDescriptor
主要用于以下情况:
- 调用本地方法或使用低级别 I/O API 进行文件和 I/O 操作时,需要将底层文件描述符传递给相应的方法。
- 可以使用
FileDescriptor
来创建自定义的输入流或输出流,并在这些流中直接操作底层文件描述符。
以下是一个使用FileDescriptor
的简单示例,演示如何使用它来创建自定义的输入流和输出流,并在这些流中直接操作底层文件描述符:
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
// 自定义的输出流,基于底层文件描述符进行操作
class MyCustomOutputStream extends FileOutputStream {
public MyCustomOutputStream(FileDescriptor fd) {
super(fd);
}
// 可以在这里添加自定义的写入方法或重写父类的写入方法
// ...
}
// 自定义的输入流,基于底层文件描述符进行操作
class MyCustomInputStream extends FileInputStream {
public MyCustomInputStream(FileDescriptor fd) {
super(fd);
}
// 可以在这里添加自定义的读取方法或重写父类的读取方法
// ...
}
try {
// 创建文件输出流并获取其底层文件描述符
FileOutputStream fos = new FileOutputStream("example.txt");
FileDescriptor fdOut = fos.getFD();
// 使用底层文件描述符创建自定义的输出流
MyCustomOutputStream customOutputStream = new MyCustomOutputStream(fdOut);
// 写入数据到文件
String data = "Hello, World!";
byte[] bytes = data.getBytes();
customOutputStream.write(bytes);
customOutputStream.close();
// 创建文件输入流并获取其底层文件描述符
FileInputStream fis = new FileInputStream("example.txt");
FileDescriptor fdIn = fis.getFD();
// 使用底层文件描述符创建自定义的输入流
MyCustomInputStream customInputStream = new MyCustomInputStream(fdIn);
// 读取文件内容
int dataByte;
while ((dataByte = customInputStream.read()) != -1) {
System.out.print((char) dataByte);
}
customInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
在这个示例中,我们首先创建了一个FileOutputStream
来打开文件并获取其底层文件描述符。然后,我们使用该文件描述符创建了自定义的输出流MyCustomOutputStream
,在其中可以添加自定义的写入方法或重写父类的写入方法。我们将数据写入到文件中,并关闭输出流。
接下来,我们创建了一个FileInputStream
来打开同一个文件,并获取其底层文件描述符。然后,我们使用该文件描述符创建了自定义的输入流MyCustomInputStream
,在其中可以添加自定义的读取方法或重写父类的读取方法。我们读取文件内容,并在控制台上打印出来,最后关闭输入流。
请注意,在实际使用时,您需要根据具体需求和文件操作,添加适当的异常处理、缓冲区管理等。
方法总结
FileDescriptor
是一个文件描述符类,它作为对底层机器特定结构的不透明句柄,表示一个打开的文件、打开的套接字或其他字节源或接收器。主要实际用途是创建 FileInputStream
或 FileOutputStream
。
应用程序不应该自己创建文件描述符。
该类具有以下主要成员和方法:
int fd
:文件描述符的整数值。long handle
:文件描述符的长整型值。Closeable parent
:关联的Closeable
对象,用于跟踪引用此文件描述符的其他对象。List<Closeable> otherParents
:与此文件描述符共享的其他Closeable
对象列表。boolean closed
:标记文件描述符是否已关闭。
构造函数:
FileDescriptor()
:构造一个(无效的)FileDescriptor
对象。
静态成员:
FileDescriptor in
:标准输入流的文件描述符。FileDescriptor out
:标准输出流的文件描述符。FileDescriptor err
:标准错误流的文件描述符。
方法:
boolean valid()
:测试此文件描述符对象是否有效。void sync()
:强制系统缓冲区与底层设备同步。void attach(Closeable c)
:将一个Closeable
对象附加到此文件描述符以进行跟踪。void closeAll(Closeable releaser)
:循环遍历所有共享此文件描述符的Closeable
对象,并调用它们的close()
方法。
FileDescriptor
类的具体用法可以参考 Java API 文档中的说明。
中文源码
/**
* FileDescriptor 类的实例用作底层机器特定结构(如打开的文件、打开的套接字等)的不透明句柄。
* 主要实际用途是创建 FileInputStream 或 FileOutputStream 对象来包含它。
*
* <p>应用程序不应创建自己的文件描述符。
*
* @author Pavani Diwanji
* @since JDK1.0
*/
public final class FileDescriptor {
private int fd; // 文件描述符的整数值
private long handle; // 文件描述符的长整型值
private Closeable parent;
private List<Closeable> otherParents;
private boolean closed;
/**
* 构造一个(无效的)FileDescriptor对象。
*/
public /**/ FileDescriptor() {
fd = -1;
handle = -1;
}
static {
initIDs();
}
// 在SharedSecrets中设置JavaIOFileDescriptorAccess
static {
sun.misc.SharedSecrets.setJavaIOFileDescriptorAccess(
new sun.misc.JavaIOFileDescriptorAccess() {
public void set(FileDescriptor obj, int fd) {
obj.fd = fd;
}
public int get(FileDescriptor obj) {
return obj.fd;
}
public void setHandle(FileDescriptor obj, long handle) {
obj.handle = handle;
}
public long getHandle(FileDescriptor obj) {
return obj.handle;
}
}
);
}
/**
* 标准输入流的文件描述符。
*
* @see java.lang.System#in
*/
public static final FileDescriptor in = standardStream(0);
/**
* 标准输出流的文件描述符。
*
* @see java.lang.System#out
*/
public static final FileDescriptor out = standardStream(1);
/**
* 标准错误流的文件描述符。
*
* @see java.lang.System#err
*/
public static final FileDescriptor err = standardStream(2);
/**
* 测试此文件描述符对象是否有效。
*
* @return 如果文件描述符对象表示一个有效的、打开的文件、套接字或其他活动 I/O 连接,则返回 true;否则返回 false。
*/
public boolean valid() {
return ((handle != -1) || (fd != -1));
}
/**
* 强制所有系统缓冲区与底层设备同步。
* 在所有修改的数据和属性都已写入相关设备后,此方法返回。
* 特别地,如果此FileDescriptor引用物理存储介质(如文件系统中的文件),则sync将一直阻塞,直到所有内存中已修改的缓冲区的副本都已写入物理介质。
*
* sync用于需要物理存储(如文件)处于已知状态的代码。
* 例如,提供简单事务功能的类可以使用sync来确保由给定事务导致的对文件的所有更改都记录在存储介质上。
*
* sync仅影响此FileDescriptor之下的缓冲区。如果应用程序正在执行任何内存中的缓冲(例如,通过BufferedOutputStream对象),那些缓冲区必须在受sync影响之前刷新到FileDescriptor(例如,通过调用OutputStream.flush)。
*
* @exception SyncFailedException
* 当无法刷新缓冲区时抛出,或者因为系统无法保证所有缓冲区已与物理介质同步。
* @since JDK1.1
*/
public native void sync() throws SyncFailedException;
/* 此例程初始化类的JNI字段偏移量 */
private static native void initIDs();
private static native long set(int d);
private static FileDescriptor standardStream(int fd) {
FileDescriptor desc = new FileDescriptor();
desc.handle = set(fd);
return desc;
}
/*
* 包私有方法以跟踪引用。
* 如果多个流指向相同的FileDescriptor,我们会循环遍历所有引用,并调用close()。
*/
/**
* 附加一个Closeable对象到此FD以进行跟踪。
* 当需要时,将parent引用添加到otherParents中,以使closeAll更简单。
*/
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);
}
}
/**
* 循环遍历所有共享此FD的Closeable对象,并对每个对象调用close()。
* 调用closeable的对象可以调用close0()。
*/
@SuppressWarnings("try")
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) {
/*
* 如果releaser的close()抛出IOException,
* 将其他异常作为被抑制异常添加。
*/
if (ioe != null)
ex.addSuppressed(ioe);
ioe = ex;
} finally {
if (ioe != null)
throw ioe;
}
}
}
}