“朋友,十小屋!”

“我很高兴学习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有资源是一件很棒的事情!”

“我们能说的是我们应该使用它。”