1. 外部资源

当 Java 程序运行时,有时它会与 Java 机器外部的实体进行交互。例如,磁盘上的文件。这些实体通常称为外部资源。内部资源是在 Java 机器内部创建的对象。

通常,交互遵循以下方案:

Try-with-resources 语句

追踪资源

操作系统严格跟踪可用资源,并控制不同程序对它们的共享访问。例如,如果一个程序更改了一个文件,那么另一个程序就不能更改(或删除)该文件。这个原则并不局限于文件,但它们提供了最容易理解的例子。

操作系统具有允许程序获取和/或释放资源的函数 (API)。如果资源繁忙,则只有获取它的程序才能使用它。如果资源是免费的,那么任何程序都可以获取它。

想象一下,您的办公室共用咖啡杯。如果有人拿了一个杯子,那么其他人就不能再拿了。但是一旦杯子被使用、清洗并放回原位,任何人都可以再次使用它。公共汽车或地铁上的座位情况是一样的。如果有空位,那么任何人都可以坐。如果一个座位被占用,则它由占用它的人控制。

获取外部资源

每次您的 Java 程序开始使用磁盘上的文件时,Java 机器都会请求操作系统对其进行独占访问。如果资源空闲,则 Java 机器会获取它。

但是当你使用完这个文件之后,这个资源(文件)必须被释放,也就是说你需要通知操作系统你不再需要它了。如果您不这样做,那么该资源将继续由您的程序持有。

操作系统维护着每个正在运行的程序占用的资源列表。如果你的程序超过了分配的资源限制,那么操作系统将不再给你新的资源。

好消息是,如果您的程序终止,所有资源都会自动释放(操作系统本身会这样做)。

坏消息是,如果您正在编写服务器应用程序(并且很多服务器应用程序是用 Java 编写的),您的服务器需要能够连续运行数天、数周和数月而不停止。如果您每天打开 100 个文件并且不关闭它们,那么几周后您的应用程序将达到其资源限制并崩溃。这远远低于数月的稳定工作。


2.close()方法

使用外部资源的类有一种释放它们的特殊方法:close().

下面我们提供了一个程序示例,该程序将某些内容写入文件,然后在完成后关闭文件,即释放操作系统的资源。它看起来像这样:

代码 笔记
String path = "c:\\projects\\log.txt";
FileOutputStream output = new FileOutputStream(path);
output.write(1);
output.close();
文件的路径。
获取文件对象:获取资源。
写入文件
关闭文件 - 释放资源

使用文件(或其他外部资源)后,您必须close()在链接到外部资源的对象上调用该方法。

例外情况

一切看起来都很简单。但是程序运行时可能会出现异常,外部资源不会被释放。那是非常糟糕的。

为了确保该close()方法始终被调用,我们需要将我们的代码包装在一个try--块中,并将该方法添加到该块catch中。它看起来像这样:finallyclose()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。有点麻烦,你不觉得吗?


3.-try资源

在这里,Java 的创造者决定给我们撒上一些语法糖。从第 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 语句中添加catchfinallytry。或者,如果不需要,您可以不添加它们。



4.同时有几个变量

顺便说一句,您可能经常会遇到需要同时打开多个文件的情况。假设您正在复制一个文件,那么您需要两个对象:要从中复制数据的文件和要将数据复制到的文件。

在这种情况下,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";

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();
}
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);
}

那么,我们能在这里说什么呢?try-with-resources 是一件很棒的事情!


5.AutoCloseable界面

但这还不是全部。细心的读者会立即开始寻找限制该陈述应用方式的陷阱。

try但是,如果类没有方法,-with-resources 语句如何工作close()?好吧,假设什么都不会被调用。没有方法,没有问题。

try但是如果类有多个close()方法,-with-resources 语句如何工作呢?他们需要将参数传递给他们吗?而且这个类没有没有close()参数的方法?

我希望你真的问过自己这些问题,也许还有其他问题。

为了避免此类问题,Java 的创建者提出了一个名为 的特殊接口AutoCloseable,它只有一个方法 — close(),没有参数。

他们还添加了限制,即只有实现类的对象AutoCloseable才能在try-with-resources 语句中声明为资源。因此,此类对象将始终具有close()不带参数的方法。

顺便说一下,您认为try-with-resources 语句可以将一个对象声明为资源,该对象的类有自己的close()不带参数的方法但没有实现AutoCloseable

坏消息:正确答案是否定的——类必须实现接口AutoCloseable

好消息: Java 有很多实现此接口的类,因此很可能一切都会按预期工作。