“朋友,十小屋!”

“我很高興學習Java,船長!”

“放心,阿米戈。今天我們有一個超級有趣的話題。我們將討論Java程序如何與外部資源交互,我們將研究一個非常有趣的Java語句。最好不要摀住耳朵。”

“我洗耳恭聽。”

“當 Java 程序運行時,有時它會與 Java 機器外部的實體進行交互。例如,與磁盤上的文件進行交互。這些實體通常稱為外部資源。”

“那什麼才算是內部資源?”

“內部資源是在 Java 機器內部創建的對象。通常,交互遵循以下方案:

Try-with-resources 語句

“操作系統嚴格跟踪可用資源,並控制不同程序對它們的共享訪問。例如,如果一個程序更改文件,則另一個程序不能更改(或刪除)該文件。這個原則不是僅限於文件,但它們提供了最容易理解的示例。

“操作系統具有允許程序獲取和/或釋放資源的功能(API)。如果資源繁忙,則只有獲取它的程序才能使用它。如果資源空閒,則任何程序都可以獲取它。

“想像一下,一個辦公室共用咖啡杯。如果有人拿了一個杯子,其他人就不能再拿了。但是一旦杯子用完、洗乾淨並放回原處,那麼任何人都可以再拿一次。”

“知道了,就像地鐵或者其他公共交通工具的座位一樣,如果有空位,誰都可以坐,如果有人坐,就由坐的人控制。”

“是的。現在我們來談談獲取外部資源的問題。每次你的 Java 程序開始使用磁盤上的文件時,Java 機器都會向操作系統請求獨占訪問它。如果資源是空閒的,那麼 Java 機器就會獲取它。

“但是當你處理完這個文件之後,這個資源(文件)必須被釋放,即你需要通知操作系統你不再需要它。如果你不這樣做,那麼這個資源將繼續被釋放由你的程序持有。”

“聽起來很公平。”

“為了保持這種狀態,操作系統維護了一個每個正在運行的程序佔用的資源列表。如果你的程序超過了分配的資源限制,那麼操作系統將不再給你新的資源。

“這就像可以吃掉所有內存的程序......”

“類似的東西。好消息是,如果您的程序終止,所有資源都會自動釋放(操作系統本身會這樣做)。”

“如果這是好消息,是否意味著有壞消息?”

“正是這樣。壞消息是,如果您正在編寫服務器應用程序……”

“但是我會寫這樣的應用程序嗎?”

“很多服務器應用程序都是用 Java 編寫的,因此您編寫它們很可能是為了工作。正如我所說,如果您正在編寫服務器應用程序,那麼您的服務器需要不間斷地運行數天、數週、數月, ETC。”

“換句話說,程序不會終止,這意味著內存不會自動釋放。”

“完全正確。如果你每天打開 100 個文件並且不關閉它們,那麼幾週後你的應用程序將達到其資源限制並崩潰。”

“這還差幾個月的穩定工作呢!有什麼辦法?”

“使用外部資源的類有一種釋放它們的特殊方法:close().

“這是一個程序示例,它向文件寫入內容,然後在完成後關閉文件,即釋放操作系統的資源。它大致如下所示:

代碼 筆記
String path = "c:\\projects\\log.txt";
FileOutputStream output = new FileOutputStream(path);
output.write(1);
output.close();
文件的路徑。
獲取文件對象:獲取資源。
寫入文件
關閉文件 - 釋放資源

“啊……所以,在處理文件(或其他外部資源)後,我必須調用鏈接close()到外部資源的對像上的方法。”

“是的,這一切看起來很簡單。但是程序運行時可能會出現異常,外部資源不會被釋放。”

“那很糟糕。怎麼辦?”

“為了確保close()始終調用該方法,我們需要將我們的代碼包裝在一個--try塊中並將該方法添加到該塊中。它看起來像這樣:catchfinallyclose()finally

try
{
   FileOutputStream output = new FileOutputStream(path);
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

“嗯……這裡有什麼不對嗎?”

“對。這段代碼不會編譯,因為output變量是在try{}塊內聲明的,因此在finally塊中是不可見的。

讓我們修復它:

FileOutputStream output = new FileOutputStream(path);

try
{
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

“現在一切都好嗎?”

“沒關係,但如果我們在創建對象時發生錯誤,它就不會起作用FileOutputStream,而且很容易發生這種情況。

讓我們修復它:

FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

“現在一切正常了嗎?”

“還有一些批評。首先,如果在創建對象時發生錯誤FileOutputStream,則output變量將為空。必須在塊中考慮這種可能性finally

”其次,該close()方法總是在finally塊中調用,這意味著它在塊中沒有必要try。最終代碼將如下所示:

FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   if (output!=null)
      output.close();
}

“即使我們不考慮catch塊,可以省略,那麼我們的3行代碼就變成了10行。但我們基本上只是打開文件並寫入1。”

“呼……總算把事情搞定了,比較好理解,但也有些乏味,不是嗎?”

“就是這樣。這就是為什麼 Java 的創建者通過添加一些語法糖來幫助我們。現在讓我們繼續討論程序的亮點,或者更確切地說,本課:

try-有資源

“從第 7 版開始,Java 有一個新的try-with-resources 聲明。

“它的創建正是為了解決強制調用方法的問題close()。”

“聽起來很有希望!”

“一般情況看起來很簡單:

try (ClassName name = new ClassName())
{
   Code that works with the name variable
}

“所以這是try 聲明的另一種變體?”

“是的。你需要在關鍵字後面加上括號try,然後在括號內創建帶有外部資源的對象。對於括號中的每個對象,編譯器都會添加一個finally段和對方法的調用close()

“下面是兩個等價的例子:

長碼 使用 try-with-resources 編寫代碼
FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
}
finally
{
   if (output!=null)
   output.close();
}
try(FileOutputStream output = new FileOutputStream(path))
{
   output.write(1);
}

“太棒了!使用 -with-resources 的代碼try更短且更易於閱讀。我們擁有的代碼越少,出現拼寫錯誤或其他錯誤的可能性就越小。”

“我很高興你喜歡它。順便說一下,我們可以在 -with-resources 聲明中添加catchfinally阻止try。或者如果不需要它們,你可以不添加它們。

多個變量同時出現

“你可能經常會遇到需要同時打開多個文件的情況。假設你正在復制一個文件,那麼你需要兩個對象:你從中復制數據的文件和你要復制數據的文件.

“在這種情況下,try-with-resources 語句允許您在其中創建一個但多個對象。創建對象的代碼必須用分號分隔。這是此語句的一般外觀:

try (ClassName name = new ClassName(); ClassName2 name2 = new ClassName2())
{
   Code that works with the name and name2 variables
}

複製文件示例:

短代碼 長碼
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileInputStream input = new FileInputStream(src);

FileOutputStream output = new FileOutputStream(dest))
{
   byte[] buffer = input.readAllBytes();
   output.write(buffer);
}
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

FileInputStream input = null;
FileOutputStream output = null;

try
{
   input = new FileInputStream(src);
   output = new FileOutputStream(dest);

   byte[] buffer = input.readAllBytes();
   output.write(buffer);
}
finally
{
   if (input!=null)
      input.close();
   if (output!=null)
      output.close();
}

“好吧,我們能在這裡說什麼?-try有資源是一件很棒的事情!”

“我們能說的是我們應該使用它。”