認知IO流之 — FileDescriptor

關注公衆號回覆002,有你想要的一切

640?wx_fmt=jpeg

這是 cxuan 的第35篇原創文章

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 來使用

那麼如何創建這三種輸出流呢?在 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;
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 的情況下,會對需要關閉的資源集合循環遍歷進行關閉操作

640?wx_fmt=png
點個好看再走吧
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章