關注公衆號回覆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
接口
AutoCloseable 接口 : 實現了此接口的類能夠持有資源直到被關閉的時候。其中的 close() 方法是自動關閉的,離開
try-with-resources
(jdk1.7 的新特性,不清楚請移步至 你會使用try-with-resources嗎)中的 try 語句塊的時候。這種方式確保了能夠及時釋放資源,避免資源的枯竭和可能出現的錯誤。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 的情況下,會對需要關閉的資源集合循環遍歷進行關閉操作