RAII на Яве & # 8230; утилизация ресурсов всегда так безобразна?

StackOverflow https://stackoverflow.com/questions/194261

Вопрос

Я только что поиграл с API файловой системы Java и нашел следующую функцию, используемую для копирования бинарных файлов. Первоначальный источник был взят из Интернета, но я добавил предложения try / catch / finally, чтобы быть уверенным, что в случае чего-то неправильного потоки буфера будут закрыты (и, следовательно, моя ОС освободит ресурсы) перед выходом из функции.

Я урезал функцию, чтобы показать шаблон:

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 () в предложение finally, потому что первый close () вполне может выбросить, а затем второй не будет выполнен.

Я знаю, что в C # есть шаблон Dispose , который бы обрабатывал это с помощью ключевого слова using .

Я даже лучше знаю, что код C ++ был бы чем-то вроде (с использованием 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 () буферизованного потока?

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

РЕДАКТИРОВАТЬ: Это я или при обновлении этой страницы я видел, как вопрос и все ответы уменьшились на одну точку за пару минут? Кто-то слишком наслаждается, оставаясь анонимным?

РЕДАКТИРОВАТЬ 2: МакДауэлл предложил очень интересную ссылку, которую, я чувствовал, я должен упомянуть здесь http: //illegalargumentexception.blogspot .com / 2008/10 / Java-как-не-к-макияж беспорядок-оф-stream.html

РЕДАКТИРОВАТЬ 3: Перейдя по ссылке Макдауэлла, я наткнулся на предложение для Java 7 шаблона, похожего на шаблон C # с использованием шаблона: http://tech.puredanger.com/java7/#resourceblock . Моя проблема подробно описана. Очевидно, что даже с Java 7 do проблемы остаются.

Это было полезно?

Решение

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

Некоторые выступают за тихое закрытие потоков. Делайте это осторожно по следующим причинам: Java: как не создавать путаницу при обработке потоков

<Ч>

Java 7 представляет try-with-resources :

/** 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()) {}
  }
}

Другие советы

Есть проблемы, но код, который вы нашли в Интернете, действительно плохой.

Закрытие потоков буфера закрывает поток внизу. Вы действительно не хотите этого делать. Все, что вы хотите сделать, это очистить поток вывода. Также нет смысла указывать базовые потоки для файлов. Производительность отстой, потому что вы копируете один байт за раз (на самом деле, если вы используете 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-finally, вы можете выделить его с помощью команды " выполнить вокруг " идиомы.

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

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

C # имеет "использование" ключевое слово для автоматического вызова Dispose, когда объект выходит из области видимости. У Java такого нет.

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

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

Наличие неохраняемого вызова close () внутри предложения finally приведет к замене исходного исключения на исключение, вызванное вызовом close (). Другими словами, ошибочный метод close () может скрыть исходное исключение, созданное read () или write (). Итак, я думаю, что вы хотите игнорировать исключения, генерируемые close (), если и только если другие методы не генерировали.

Обычно я решаю эту проблему путем включения явного вызова 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
    }
  }

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top