2010-06-15 42 views
8

Tôi đang viết một ứng dụng kết nối với một trang web và đọc một dòng từ đó. Tôi làm như sau:Đóng tài nguyên Java

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 
    } 

Có tốt không? Ý tôi là, tôi đóng BufferedReader ở dòng cuối cùng, nhưng tôi không đóng InputStreamReader. Tôi có nên tạo một InputStreamReader độc lập từ connection.getInputStream và một BufferedReader từ InputStreamReader độc lập, hơn là đóng tất cả hai trình đọc không? Tôi nghĩ rằng nó sẽ được tốt hơn để đặt các phương pháp đóng cửa trong khối finally như thế này:

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

Nhưng nó là xấu xí, bởi vì các phương pháp đóng cửa có thể ném ngoại lệ, vì vậy tôi phải xử lý hoặc vứt nó.

Giải pháp nào tốt hơn? Hoặc giải pháp tốt nhất là gì?

+0

Mã của bạn đã thực sự có một chút vấn đề trong mệnh đề cuối cùng. Có thể cho 'isr' và' br' vẫn là 'null' trong mệnh đề cuối cùng là các hàm tạo' InputStreamReader' và 'BufferedReader' có thể ném các ngoại lệ. Bạn nên thay đổi mệnh đề cuối cùng thành: 'finally {if (br! = Null) br.close(); if (isr! = null) isr.close(); } '. Điều này vẫn không đúng bởi vì 'br.close() 'có thể ném một ngoại lệ và do đó' isr' sẽ không bị đóng trong trường hợp đó. Câu trả lời của Andreas dường như là cách để theo dõi IMHO. – Behrang

+0

Không bao giờ bắt được 'Ngoại lệ 'trừ khi bạn biết tại sao –

Trả lời

6

Các thành ngữ chung của mua lại tài nguyên và phát hành trong Java là:

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

Lưu ý:

  • try ngay lập tức nên làm theo các đạt được. Điều này có nghĩa là bạn không thể bọc nó trong trang trí và duy trì an toàn (và loại bỏ không gian hoặc đặt mọi thứ trên một dòng không giúp :).
  • Một bản phát hành cho mỗi finally, nếu không sẽ không ngoại lệ an toàn.
  • Tránh null, sử dụng final. Nếu không, bạn sẽ có mã lộn xộn và tiềm năng cho NPE.
  • Nói chung không cần phải đóng trang trí trừ khi nó có thêm tài nguyên liên kết với nó. Tuy nhiên, bạn thường sẽ cần phải xóa các đầu ra, nhưng tránh điều đó trong trường hợp ngoại lệ.
  • Ngoại lệ phải được chuyển đến người gọi hoặc bị chặn từ khối try xung quanh (Java dẫn bạn đi lạc lối tại đây).

ou có thể trừu tượng điều này vô nghĩa với Execute Around idiom, vì vậy bạn không phải lặp lại chính mình (chỉ cần viết nhiều bản mẫu).

+0

Làm thế nào bạn có thể thực hiện phương thức thu được như vậy để trả về, ví dụ' FileInputStream', mà không có nó ném một 'IOException'? Nó là cần thiết để quấn đoạn mã trên bên trong một 'try {// đoạn mã của bạn} catch (IOException ioe) {...}' khối và điều này chỉ là lộn xộn. Ngoài ra mô hình này sẽ không hoạt động khi bạn phải đóng nhiều tài nguyên trong khối cuối cùng như là một trong những đầu tiên có thể ném một ngoại lệ và tiếp theo sẽ không nhận được đóng cửa ở tất cả. – Behrang

+0

Nếu bạn có nhiều tài nguyên, bạn cần nhiều câu lệnh 'try'-'finally'. Và có ngoại lệ nên được xử lý bên ngoài, có thể bên ngoài phương pháp. –

2

Đóng BufferedReader là đủ - điều này cũng đóng trình đọc cơ bản.

Yishai posted a nice pattern để đóng các luồng (đóng cửa có thể ném một ngoại lệ khác).

1

Tôi nghĩ rằng nó sẽ được tốt hơn để đặt phương pháp đóng cửa trong khối finally

Có, luôn luôn. Bởi vì một ngoại lệ có thể xảy ra và tài nguyên không được phát hành/đóng đúng cách.

Bạn chỉ cần đóng trình đọc ngoài nhiều nhất vì nó sẽ chịu trách nhiệm đóng mọi trình đọc kèm theo.

Vâng, thật xấu xí ... hiện tại. Tôi nghĩ rằng có một kế hoạch cho một automatic resource management trong Java.

2

Có tốt không?Ý tôi là, tôi đóng BufferedReader ở dòng cuối cùng, nhưng tôi không đóng InputStreamReader.

Ngoài thực tế là nó phải được thực hiện trong finally (để đóng được đảm bảo, ngay cả trong trường hợp ngoại lệ), nó vẫn ổn. Các lớp Java IO sử dụng mẫu trang trí. Việc đóng sẽ được ủy quyền cho các luồng cơ bản.

Nhưng điều này là xấu, bởi vì các phương pháp đóng có thể ném ngoại lệ, vì vậy tôi phải xử lý hoặc ném nó.

Khi đóng gần một ngoại lệ, điều này thường có nghĩa là phía bên kia đã bị đóng hoặc xóa hoàn toàn nằm ngoài tầm kiểm soát của bạn. Bạn có thể đăng nhập cao nhất hoặc bỏ qua nó. Trong một ứng dụng đơn giản, tôi sẽ bỏ qua nó. Trong một ứng dụng quan trọng nhiệm vụ tôi sẽ đăng nhập nó, chỉ để chắc chắn.

Trong một hạt, mã của bạn có thể được viết lại như sau:

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) {} 
} 

Trong Java 7 sẽ có xử lý tài nguyên tự động mà sẽ làm cho mã của bạn càng ngắn gọn càng:

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

Xem thêm :

2
BufferedReader br = null; 

Bạn đang tuyên bố một biến mà không gán nó (null không tính - đó là một nhiệm vụ vô dụng trong trường hợp này). Đây là một mã "mùi" trong Java (ref Effective Java; Code Complete để biết thêm về khai báo biến).

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

Trước tiên, bạn chỉ cần đóng-nhất trên dòng trang trí (br sẽ đóng isr). Thứ hai, nếu br.close() ném một ngoại lệ, isr.close() sẽ không được gọi, vì vậy đây không phải là mã âm thanh. Trong một số điều kiện ngoại lệ nhất định, mã của bạn sẽ ẩn ngoại lệ gốc với NullPointerException.

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

Nếu (phải thừa nhận là khó xảy ra) sự kiện rằng các nhà xây dựng InputStreamReader ném bất kỳ loại ngoại lệ thời gian chạy, dòng từ kết nối sẽ không được đóng lại.

Sử dụng giao diện Closeable để giảm sự thừa.

Dưới đây là làm thế nào tôi sẽ viết mã của bạn:

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

Lưu ý rằng:

  • không có vấn đề gì loại ngoại lệ được ném (runtime hoặc kiểm tra) hoặc trong trường hợp, mã không bị rò rỉ tài nguyên luồng
  • không có khối bắt; các trường hợp ngoại lệ phải được chuyển đến nơi mã có thể đưa ra quyết định hợp lý về xử lý lỗi; nếu phương pháp này là đúng nơi, bạn muốn bao quanh tất cả những điều trên với try/catch

Một khi trở lại, tôi đã dành một chút thời gian suy nghĩ về làm thế nào để avoid leaking resources/data when things go wrong.

+0

@Bob - Trực giao cho câu hỏi tôi biết, nhưng bạn không biết ai đang sao chép nội dung này - không có xử lý ký tự thích hợp trong mã này - tức là kiểm tra loại nội dung/mã hóa dữ liệu trả về. – McDowell

0

Bạn không cần nhiều câu lệnh đóng cho bất kỳ luồng và trình đọc lồng nhau nào trong java.io. Rất hiếm khi cần phải đóng nhiều thứ trong một lần cuối cùng - hầu hết các nhà xây dựng có thể ném một ngoại lệ, vì vậy bạn sẽ cố gắng đóng những thứ bạn chưa tạo ra.

Nếu bạn muốn đóng luồng cho dù thành công có đọc hay không, thì bạn cần phải đưa vào cuối cùng.

Không chỉ định null cho biến và sau đó so sánh chúng để xem liệu có điều gì xảy ra trước đó hay không; thay vì cấu trúc chương trình của bạn để đường dẫn nơi bạn đóng luồng chỉ có thể đạt được nếu ngoại lệ không được ném. Ngoài các biến được sử dụng để lặp lại trong vòng lặp, các biến không cần phải thay đổi giá trị - tôi có xu hướng đánh dấu mọi thứ cuối cùng trừ khi có một yêu cầu để làm khác. Có cờ xung quanh chương trình của bạn để cho bạn biết làm thế nào bạn đã nhận được mã hiện đang được thực hiện, và sau đó thay đổi hành vi dựa trên những lá cờ, là rất nhiều một thủ tục (thậm chí không cấu trúc) phong cách lập trình.

Cách bạn lồng khối try/catch/finally tùy thuộc vào việc bạn có muốn xử lý các ngoại lệ được ném theo các giai đoạn khác nhau hay không.

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 nhu cầu để kiểm tra kết quả của việc kết nối của getContentEncoding()getContentType() để xác định mã hóa của trang web; mã của bạn chỉ sử dụng mã hóa mặc định của nền tảng, điều này cũng có thể sai.

Ví dụ của bạn mặc dù không bình thường về mặt cấu trúc, vì nó rất thủ tục; thông thường bạn sẽ tách in ấn và nhặt đồ trong một hệ thống lớn hơn, và cho phép mã khách hàng để xử lý bất kỳ ngoại lệ (hoặc đôi khi bắt và tạo ra một ngoại lệ tùy chỉnh):

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

Như McDowell chỉ ra, bạn có thể cần phải đóng luồng đầu vào nếu new InputStreamReader ném.

1

Tôi muốn sử dụng commons apache IO cho điều này, như những người khác đã gợi ý, chủ yếu là 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); 
    } 

} 
Các vấn đề liên quan