【问题标题】:Java resource closingJava 资源关闭
【发布时间】:2011-03-03 22:30:09
【问题描述】:

我正在编写一个连接到网站并从中读取一行的应用程序。我是这样做的:

try{
        URLConnection connection = new URL("www.example.com").openConnection();
        BufferedReader rd = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        String response = rd.readLine();
        rd.close();
    }catch (Exception e) {
        //exception handling
    }

好吃吗?我的意思是,我在最后一行关闭了 BufferedReader,但我没有关闭 InputStreamReader。我是否应该从 connection.getInputStream 创建一个独立的 InputStreamReader,从独立的 InputStreamReader 创建一个 BufferedReader,而不是关闭所有两个阅读器? 我认为像这样将关闭方法放在 finally 块中会更好:

InputStreamReader isr = null;
BufferedReader br = null;
try{
    URLConnection connection = new URL("www.example.com").openConnection();
    isr = new InputStreamReader(connection.getInputStream());
    br = new BufferedReader(isr);
    String response = br.readLine();
}catch (Exception e) {
    //exception handling
}finally{
    br.close();
    isr.close();
}

但是很丑,因为关闭方法可以抛出异常,所以我必须处理或抛出它。

哪种解决方案更好?或者最好的解决方案是什么?

【问题讨论】:

  • 您的代码在 finally 子句中实际上有一个小问题。 isr 和 br 在 finally 子句中可能仍然是 null,因为 InputStreamReader 和 BufferedReader 构造函数可能会抛出异常。您应该将 finally 子句更改为:finally { if (br != null) br.close(); if (isr != null) isr.close(); }。这仍然不正确,因为br.close() 可能会引发异常,因此isr 在这种情况下根本不会关闭。安德烈亚斯的回答似乎是遵循恕我直言的方式。
  • 除非你知道原因,否则永远不要赶上Exception

标签: java url exception-handling inputstream


【解决方案1】:

Java中资源获取和释放的一般习语是:

final Resource resource = acquire();
try {
    use(resource);
} finally {
    resource.release();
}

注意:

  • try 应立即跟随获取。这意味着您不能将其包装在装饰器中并保持安全(并且删除空格或将东西放在一行上无济于事:)。
  • 每个finally 一个版本,否则不会是异常安全的。
  • 避免使用null,使用final。否则你的代码会很乱,并且有 NPE 的潜力。
  • 通常不需要关闭装饰器,除非它有与之关联的其他资源。但是,您通常需要刷新输出,但在异常情况下应避免这样做。
  • 应该将异常传递给调用者,或者从周围的try 块中捕获(Java 会导致您误入歧途)。

你可以用Execute Around idiom抽象出这些废话,所以你不必重复自己(只需写很多样板)。

【讨论】:

  • 如何实现这样的获取方法来返回,例如FileInputStream,而不抛出IOException?有必要将上面的代码 sn-p 包装在另一个 try { // your code snippet } catch (IOException ioe) { ... } 块中,这同样混乱。此外,当您必须关闭 finally 块中的多个资源时,这种模式也不起作用,因为第一个可能会引发异常,而下一个根本不会关闭。
  • 如果您有多个资源,则需要多个 try-finally 语句。是的,异常应该在外部处理,可能在方法之外。
【解决方案2】:

关闭 BufferedReader 就足够了 - 这也会关闭底层阅读器。

Yishai posted a nice pattern 用于关闭流(关闭可能会引发另一个异常)。

【讨论】:

    【解决方案3】:

    好吃吗?我的意思是,我在最后一行关闭了 BufferedReader,但我没有关闭 InputStreamReader。

    除了应该在finally 中完成之外(这样可以确保关闭,即使在出现异常的情况下),这很好。 Java IO 类使用装饰器模式。关闭将委托给底层流。

    但是很丑,因为关闭方法会抛出异常,所以我得自己处理或者抛出。

    当关闭抛出异常时,往往只是表示对方已经关闭或删除,完全不受你的控制。您可以最高记录或忽略它。在一个简单的应用程序中,我会忽略它。在一个关键任务应用程序中,我会记录它,只是为了确定。

    简而言之,您的代码可以重写为:

    BufferedReader br = null;
    try {
        URLConnection connection = new URL("www.example.com").openConnection();
        br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        String response = br.readLine();
    }catch (Exception e) {
        //exception handling
    }finally{
        if (br != null) try { br.close(); } catch (IOException ignore) {}
    }
    

    在 Java 7 中会有自动资源处理,这将使您的代码简洁:

    try (BufferedReader br = new InputStreamReader(new URL("www.example.com").openStream())) {
        String response = br.readLine();
    } catch (Exception e) {
        //exception handling
    }
    

    另见:

    【讨论】:

      【解决方案4】:
      BufferedReader br = null;
      

      您声明了一个变量但没有分配它(null 不算数 - 在这种情况下这是一个无用的分配)。这是 Java 中的“气味”代码(参考 Effective Java;Code Complete 了解有关变量声明的更多信息)。

      }finally{
          br.close();
          isr.close();
      }
      

      首先,您只需要关闭最顶层的流装饰器(br 将关闭 isr)。其次,如果br.close() 抛出异常,isr.close() 将不会被调用,所以这不是健全的代码。在某些异常情况下,您的代码将使用 NullPointerException 隐藏原始异常。

      isr = new InputStreamReader(connection.getInputStream());
      

      如果InputStreamReader 构造函数引发任何类型的运行时异常(当然不太可能)事件,则不会关闭来自连接的流。

      利用Closeable接口减少冗余。

      我会这样写你的代码:

      URLConnection connection = new URL("www.example.com").openConnection();
      InputStream in = connection.getInputStream();
      Closeable resource = in;
      try {
        InputStreamReader isr = new InputStreamReader(in);
        resource = isr;
        BufferedReader br = new BufferedReader(isr);
        resource = br;
        String response = br.readLine();
      } finally {
        resource.close();
      }
      

      注意:

      • 无论抛出何种异常(运行时或检查)或在何处,代码都不会泄漏流资源
      • 没有捕获块;异常应该被传递到代码可以对错误处理做出明智决定的地方;如果这个方法是正确的地方,你会用 try/catch 包围上述所有内容

      前段时间,我花了一些时间思考如何avoid leaking resources/data when things go wrong。

      【讨论】:

      • @Bob - 与我知道的问题正交,但你不知道谁在复制这些东西 - 此代码中没有适当的字符处理 - 即检查返回的内容类型/编码数据。
      【解决方案5】:

      我认为最好放置 finally 块中的关闭方法

      是的,总是。因为可能会发生异常并且资源没有正确释放/关闭。

      您只需要关闭最外层的阅读器,因为它将负责关闭任何封闭的阅读器。

      是的,它很丑……暂时。我认为有计划在 Java 中使用 automatic resource management。

      【讨论】:

        【解决方案6】:

        正如其他人所建议的那样,我会为此使用 apache commons IO,主要是 IOUtils.toString(InputStream) 和 IOUtils.closeQuietly(InputStream):

        public String readFromUrl(final String url) {
        
            InputStream stream = null; // keep this for finally block
        
            try {
                stream = new URL(url).openConnection().getInputStream();  // don't keep unused locals
                return IOUtils.toString(stream);
            } catch (final IOException e) {
                // handle IO errors here (probably not like this)
                throw new IllegalStateException("Can't read URL " + url, e);
            } finally {
                // close the stream here, if it's null, it will be ignored
                IOUtils.closeQuietly(stream);
            }
        
        }
        

        【讨论】:

          【解决方案7】:

          对于 java.io 中的任何嵌套流和读取器,您不需要多个 close 语句。很少需要在一个 finally 中关闭多个事物 - 大多数构造函数都会抛出异常,因此您将尝试关闭尚未创建的事物。

          如果你想不管读取成功与否都关闭流,那么你需要放入一个finally。

          不要将null赋给变量,然后再比较它们看是否之前发生过什么事情;而是构建您的程序,以便只有在未引发异常时才能到达关闭流的路径。除了用于在 for 循环中迭代的变量之外,变量不需要更改值 - 我倾向于将所有内容标记为 final,除非有其他要求。在程序周围设置标志来告诉您如何到达当前正在执行的代码,然后根据这些标志更改行为,这是一种非常程序化(甚至不是结构化)的编程风格。

          如何嵌套 try/catch/finally 块取决于您是否要以不同方式处理不同阶段抛出的异常。

          private static final String questionUrl = "http://stackoverflow.com/questions/3044510/";
          
          public static void main ( String...args )
          {
              try {
                  final URLConnection connection = new URL ( args.length > 0 ? args[0] : questionUrl ).openConnection();
          
                  final BufferedReader br = new BufferedReader ( new InputStreamReader (
                              connection.getInputStream(), getEncoding ( connection ) ) );
          
                  try {
                      final String response = br.readLine();
          
                      System.out.println ( response );
                  } catch ( IOException e ) {
                      // exception handling for reading from reader
                  } finally {
                      // br is final and cannot be null. no need to check
                      br.close();
                  }
              } catch ( UnsupportedEncodingException  uee ) {
                  // exception handling for unsupported character encoding
              } catch ( IOException e ) {
                  // exception handling for connecting and opening reader
                  // or for closing reader
              }
          }
          

          getEncoding需要检查连接的getContentEncoding()和getContentType()的结果来判断网页的编码;您的代码只是使用了平台的默认编码,这很可能是错误的。

          您的示例在结构化方面并不常见,因为它非常程序化;通常你会在更大的系统中分离打印和检索,并允许客户端代码处理任何异常(或者有时捕获并创建自定义异常):

          public static void main ( String...args )
          {
              final GetOneLine getOneLine = new GetOneLine();
          
              try {
                  final String value = getOneLine.retrieve ( new URL ( args.length > 0 ? args[0] : questionUrl ) );
                  System.out.println ( value );
              } catch ( IOException e ) {
                  // exception handling for retrieving one line of text
              }
          }
          
          public String retrieve ( URL url ) throws IOException
          {
              final URLConnection connection = url.openConnection();
              final InputStream in = connection.getInputStream();
          
              try {
                  final BufferedReader br = new BufferedReader ( new InputStreamReader (
                              in, getEncoding ( connection ) ) );
          
                  try {
                      return br.readLine();
                  } finally {
                      br.close();
                  }
              } finally {
                  in.close();
              }
          }
          

          正如 McDowell 指出的那样,如果 new InputStreamReader 抛出,您可能需要关闭输入流。

          【讨论】:

            【解决方案8】:

            在 Java 8 的范围内,我会使用类似的:

            try(Resource resource = acquire()) {
                use(resource);
                reuse(resource);
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2023-03-31
              • 2010-10-12
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多