2008-10-11 8 views
14

방금 ​​Java 파일 시스템 API를 사용하여 이진 파일을 복사하는 데 사용되는 다음 기능을 사용했습니다. 원래 소스는 웹에서 왔지만 try/catch/finally 절을 추가하여 기능이 끝나기 전에 버퍼 스트림이 닫히고 (따라서 OS 리 소스가 해제 됨) 문제가 발생하면이를 해제해야합니다.RAII in Java ... 자원 처분은 항상 너무 못생긴가요?

public static void copyFile(FileOutputStream oDStream, FileInputStream oSStream) throw etc... 
{ 
    BufferedInputStream oSBuffer = new BufferedInputStream(oSStream, 4096); 
    BufferedOutputStream oDBuffer = new BufferedOutputStream(oDStream, 4096); 

    try 
    { 
     try 
     { 
     int c; 

     while((c = oSBuffer.read()) != -1) // could throw a IOException 
     { 
      oDBuffer.write(c); // could throw a IOException 
     } 
     } 
     finally 
     { 
     oDBuffer.close(); // could throw a IOException 
     } 
    } 
    finally 
    { 
     oSBuffer.close(); // could throw a IOException 
    } 
} 
지금까지 내가 그것을 이해

, 나는 다음의 첫 번째 close() 잘 던질 수 있기 때문에 마지막 절에서 두 close()을 넣어 수 없습니다

나는 패턴을 표시하는 기능을 손질 두 번째는 실행되지 않습니다.

저는 C#이 을 가지고 있음을 알고 있습니다. using 키워드로이 패턴을 처리했던 패턴을 처리했습니다.

void copyFile(FileOutputStream & oDStream, FileInputStream & oSStream) 
{ 
    BufferedInputStream oSBuffer(oSStream, 4096); 
    BufferedOutputStream oDBuffer(oDStream, 4096); 

    int c; 

    while((c = oSBuffer.read()) != -1) // could throw a IOException 
    { 
     oDBuffer.write(c); // could throw a IOException 
    } 

    // I don't care about resources, as RAII handle them for me 
} 

내가 모르는 뭔가가, 나 정말 자바 추하고 비 대한 코드를 생성해야합니까 오전 :

은 난 (자바와 같은 API를 사용하여) 같은 더 나은 C++ 코드를했을 뭔가를 알고 단지 버퍼 된 스트림의 close() 메서드에서 예외를 처리하기 위해?

편집을 (... 어디 선가 틀렸다 알려주세요) : 그것은 나인가, 또는이 페이지를 업데이트 할 때, 나는 모두 질문을보고 모든 해답은 하나의 점으로 감소 몇 분 동안? 익명을 유지하면서 누군가 자신을 너무 즐기고 있습니까?

는 편집 2 : http://illegalargumentexception.blogspot.com/2008/10/java-how-not-to-make-mess-of-stream.html

편집 : 3 : 맥도웰의 링크를, 내가 유사한 패턴의 자바 7에 대한 제안에 하락 맥도웰 내가 여기에 언급했다 느꼈다 매우 흥미로운 링크를 제공 C#을 사용하여 패턴 : http://tech.puredanger.com/java7/#resourceblock. 내 문제가 명시 적으로 설명됩니다. 분명히 Java 7 do을 사용해도 문제가 남아 있습니다.

+0

나의 잘못된 답변입니다. Java에서 RAII를 수행하는 방법을 실제로 파악하고 있는지 또는 단순히 Java에서 입력 스트림의 데이터를 출력 스트림으로 복사하는 방법을 알지 못했습니다. – Alexander

+0

문제 없음 ... 사실, 나는 깨끗하고 효율적인 복사본을 만드는 방법을 모르고있다 ... :-p ... – paercebal

+0

첫 번째 목록에는 8 줄, 두 번째 줄에는 2 줄의 코드를 저장할 수있다. Java 코드 규칙을 사용할지 여부를 나열합니다. 이 경우이 코드는 그렇게 추한 것은 아닙니다. – msangel

답변

16

try/finally 패턴은 대부분 Java 6 이하의 스트림을 처리하는 올바른 방법입니다.

일부 사용자는 자동으로 닫는 스트림을 옹호합니다. 주의 이러한 이유로이 일을 : Java: how not to make a mess of stream handling


자바 7을 소개합니다 시도 -과 - 자원 :

/** transcodes text file from one encoding to another */ 
public static void transcode(File source, Charset srcEncoding, 
          File target, Charset tgtEncoding) 
                  throws IOException { 
    try (InputStream in = new FileInputStream(source); 
     Reader reader = new InputStreamReader(in, srcEncoding); 
     OutputStream out = new FileOutputStream(target); 
     Writer writer = new OutputStreamWriter(out, tgtEncoding)) { 
     char[] buffer = new char[1024]; 
     int r; 
     while ((r = reader.read(buffer)) != -1) { 
      writer.write(buffer, 0, r); 
     } 
    } 
} 

AutoCloseable 유형이 닫혀 자동으로됩니다있다

public class Foo { 
    public static void main(String[] args) { 
    class CloseTest implements AutoCloseable { 
     public void close() { 
     System.out.println("Close"); 
     } 
    } 
    try (CloseTest closeable = new CloseTest()) {} 
    } 
} 
+0

흥미로운 링크. 고맙습니다. +1. – paercebal

+0

대부분의 경우에, 그러나이 경우에는 충분히 재미 있지 않습니다. :) –

+0

@ 톰 - 예, 이것은 좋은 스트림 복사 메커니즘이 아닙니다 + 나는 당신과 함께 갈 것입니다. – McDowell

3

불행히도이 유형의 코드는 Java에서 약간 비대 해지는 경향이 있습니다.

그런데 oSBuffer.read 또는 oDBuffer.write에 대한 호출 중 하나가 예외를 throw하면 예외가 호출 계층 구조 위로 스며 들게 할 수 있습니다.

finally 절에서 close()를 호출하지 않으면 원 래 예외가 close() 호출에 의해 생성 된 예외로 바뀝니다. 즉, 실패한 close() - 메서드는 read() 또는 write()에 의해 생성 된 원래 예외를 숨길 수 있습니다. 그래서, 나는 당신이 예외 (close)에 의해 던지기를 원한다고 생각한다. 그리고 다른 메소드가 던지지 않은 경우에만.

난 보통 내 시도 내에서, 명시 적으로 근접 전화를 포함하여이 문제를 해결 :

 
    try { 
    while (...) { 
     read... 
     write... 
    } 
    oSBuffer.close(); // exception NOT ignored here 
    oDBuffer.close(); // exception NOT ignored here 
    } finally { 
    silentClose(oSBuffer); // exception ignored here 
    silentClose(oDBuffer); // exception ignored here 
    } 
 
    static void silentClose(Closeable c) { 
    try { 
     c.close(); 
    } catch (IOException ie) { 
     // Ignored; caller must have this intention 
    } 
    } 

마지막으로, 성능, 코드가 아마 읽기 당 (버퍼 여러 바이트를 작동해야/쓰기). 숫자로 그 것을 되돌릴 수는 없지만, 위에 버퍼링 된 스트림을 추가하는 것보다 적은 수의 호출이 더 효율적이어야합니다.

+0

자동으로이 방법을 닫으면, close가 예외를 throw하면 코드에서 오류 처리를하지 않습니다. BufferedOutputStream과 같은 많은 스트림은 닫을 때 데이터를 씁니다. – McDowell

+0

McDowell : 예, close()가 예외를 throw하면 예외를 잡아야합니다. close() - 호출은 try 블록 내부에 먼저 만들어집니다. finally 블록은 어떤 메소드가 예외를 던지면 정리를 보장하는 것입니다. 권리? (첫 번째 게시 버전에서 버퍼의 닫음을 잊어 버렸습니다.) – volley

+0

BufferedOutputStream은 약간의 버그입니다. 명시 적 플러시 (비 예외적 인 경우)를 선호하지만이를 기억해야합니다. IIRC에서는 Java SE 1.6 이전에 예외 처리가 중단되었습니다. –

4

문제는 아니지만 웹에 거짓말 한 코드는 실제로 가난합니다.

버퍼 스트림을 닫으면 스트림이 닫힙니다. 당신은 정말로 그것을하고 싶지 않습니다. 출력 스트림을 플러시하면됩니다. 또한 파일을위한 기본 스트림 지정에 대한 요점은 없습니다. 한 번에 한 바이트 씩 복사하기 때문에 성능이 떨어집니다 (실제로 java.io를 사용하는 경우 transferTo/transferFrom을 조금 더 빨리 사용할 수 있습니다). 우리가 그것에 관해서 이야기하는 동안, 변수 이름은 빠릅니다. 그래서 :

public static void copy(
    InputStream in, OutputStream out 
) throw IOException { 
    byte[] buff = new byte[8192]; 
    for (;;) { 
     int len = in.read(buff); 
     if (len == -1) { 
      break; 
     } 
     out.write(buff, 0, len); 
    } 
} 

당신이 시도 - 마지막으로 많이 사용하는 자신을 발견한다면, 당신은 "주위에 실행"관용구로를 인수 분해 할 수 있습니다.

제 의견으로는 Java는 범위 끝에서 리소스를 닫아야합니다. 단호한 접미사 연산자로 private을 추가하여 둘러싸는 블록의 끝에서 닫는 것이 좋습니다.

+0

더 나은 코드를 보내 주셔서 감사합니다, 당신의 대답을 다시 upvote 것입니다. 현재 개인 프로젝트의 경우 그다지 중요하지 않지만 앞으로 대체 할 코드를 복사/붙여 넣기 할 수 있습니다. +1. – paercebal

+0

파일을 복사하고 있다면 스트림을 끝내기를 원할 것입니다. 복사가 완료되었으므로 스트림을 열어 두지 않아도됩니다. 이 경우 중첩 된 try-finally 블록 + close() 호출이 적절합니다. –

+0

파일 스트림을 여는 방법으로 수행해야합니다. –

3

네, 그게 자바가 작동하는 방법입니다. 제어 반전이 있습니다 - 개체의 사용자는 개체 자체를 정리하는 대신 개체를 정리하는 방법을 알아야합니다. 이 불행히도 귀하의 자바 코드를 통해 흩어져 정리 코드를 많이 이어집니다.

C#에는 개체가 범위를 벗어날 때 Dispose를 자동으로 호출하는 "using"키워드가 있습니다. Java에는 그러한 것이 없습니다.

+1

특수 구문의 유무에 관계없이 클라이언트 코드는 정리할시기를 리소스에 알려야합니다. 자원이 알 수있는 방법이 없습니다. 물론 콜백으로 실행되는 재미있는 코드로 자원 획득, 설정 및 해제를 추상화 할 수 있습니다. –

+0

그건 사실이 아니야. C++ 및 C# 객체는 호출자가 참여하지 않아도 자신을 잘 처리합니다. 이 언어들에 대해 읽어보십시오. –

+1

나는 그 언어에 익숙하다. C#에서 클라이언트 코드는 using 블록의 끝에 dispose 메서드를 호출합니다. C++에서 클라이언트 코드는 범위 끝에서 소멸자를 호출합니다. –

2

파일 복사와 같은 일반적인 IO 작업의 경우 위에 표시된 것과 같은 코드가 휠을 다시 작성합니다. 아쉽게도 JDK는 상위 수준의 유틸리티를 제공하지 않지만 commons-io는 아파치를 제공합니다.

예를 들어, FileUtils에는 파일 및 디렉토리 작업 (복사 포함)을위한 다양한 유틸리티 메소드가 있습니다. 한편, JDK에서 IO 지원을 실제로 사용해야하는 경우 IOUtils에는 예외를 throw하지 않고 판독기, 작성기, 스트림 등을 닫는 closeQuietly() 메서드 세트가 포함되어 있습니다.