2008-10-11 2 views
14

Я просто играл с API-интерфейсом файловой системы Java и пришел со следующей функцией, используемой для копирования двоичных файлов. Исходный источник пришел из Интернета, но я добавил предложения try/catch/finally, чтобы убедиться, что, если что-то не так, буферизованные потоки будут закрыты (и, следовательно, освобождены мои операционные системы), прежде чем выйти из функции.RAII на 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# есть Dispose шаблон, который бы обработал это с помощью ключевого слова using.

Я даже знаю, лучше С ++ код был бы что-то вроде (с использованием Java-подобный API):

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 
} 

Я что-то не хватает, или я действительно должен производить уродливые и раздутый код в Java просто для обработки исключений в методе буферизованного потока close()?

(Пожалуйста, скажите мне, что я неправильно где-то ...)

EDIT: Это я, или при обновлении этой страницы, я увидел, как этот вопрос и все ответы снизился на один пункт в пара минут? Кто-то слишком любит себя, оставаясь анонимным?

EDIT 2: Макдауэлл предложил очень интересную ссылку, я чувствовал, что я должен был упомянуть здесь: http://illegalargumentexception.blogspot.com/2008/10/java-how-not-to-make-mess-of-stream.html

EDIT 3: После ссылки Макдауэлл, я упал на предложение о Java 7 узора аналогичной C# с использованием шаблона: http://tech.puredanger.com/java7/#resourceblock. Моя проблема явно описана. По-видимому, даже с Java 7 do проблемы остаются.

+0

Извинения за мой ошибочный ответ. Я не был уверен, действительно ли вы пытаетесь понять, как сделать RAII на Java, или просто не знал, как обычно копировать данные из входного потока в выходной поток в Java. – Alexander 2008-10-11 16:58:52

+0

Нет проблем ... Дело в том, что я тоже не знал, как сделать чистую и эффективную копию ... :-p ... – paercebal 2008-10-12 17:47:12

+0

Вы можете сохранить 8 строк кода в первом листинге и 2 строки в секунду если вы будете использовать соглашение Java-кода. В этом случае этот код будет не таким уродливым. – msangel 2013-03-04 02:56:52

ответ

16

Шаблон try/finally - это правильный способ обработки потоков в большинстве случаев для Java 6 и ниже.

Некоторые из них выступают за молчание, закрывая потоки. Будьте осторожны, делая это по следующим причинам: Java: how not to make a mess of stream handling


Java 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()) {} 
    } 
} 
3

К сожалению, этот тип кода имеет тенденцию немного раздуваться в Java.

Кстати, если один из вызовов oSBuffer.read или oDBuffer.write генерирует исключение, то вы, вероятно, захотите, чтобы это исключение пронизывало иерархию вызовов.

Наличие неохраняемого вызова для закрытия() внутри предложения finally приведет к тому, что исходное исключение будет заменено на одно, вызванное вызовом 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 
    } 
    } 

Наконец, для выполнения, код, вероятно, следует работать с буферами (несколько байт на операции чтения/записи). Не могу поддержать это по номерам, но меньшее количество вызовов должно быть более эффективным, чем добавление буферизованных потоков сверху.

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

Если вы нашли себя с помощью Try-наконец много, то вы можете фактор ее с «выполнить вокруг» идиома.

На мой взгляд: Java должен иметь возможность закрытия ресурсов в конце области. Я предлагаю добавить private в качестве унарного оператора постфикса, чтобы закрыть его в конце закрывающего блока.

3

Да, так работает Java. Существует инверсия управления - пользователь объекта должен знать, как очистить объект, а не сам объект, очищающий сам по себе. Это, к сожалению, приводит к большому количеству кода очистки, разбросанного по всему вашему Java-коду.

C# имеет ключевое слово «using» для автоматического вызова Dispose, когда объект выходит из области видимости. Java не имеет такой вещи.

2

Для обычных задач ввода-вывода, таких как копирование файла, код, такой как показанный выше, изобретает колесо. К сожалению, JDK не предоставляет никаких утилит более высокого уровня, но apache commons-io делает.

Например, FileUtils содержит различные утилиты для работы с файлами и каталогами (включая копирование). С другой стороны, если вам действительно нужно использовать поддержку IO в JDK, то IOUtils содержит набор методов closeQuietly(), которые закрывают Readers, Writers, Streams и т. Д. Без исключений.

Смежные вопросы