Я просто играл с 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# есть шаблон Утилизировать, который справился бы с этим с помощью ключевого слова 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() буферизованного потока?
(Скажите, пожалуйста, где-то я ошибаюсь ...)
Обновлено: Это я, или при обновлении этой страницы я увидел, что и вопрос, и все ответы уменьшились на один пункт за пару минут? Кто-то слишком развлекается, оставаясь анонимным?
Обновлено еще раз: Макдауэлл предложил очень интересную ссылку, которую, как мне казалось, я должен был упомянуть здесь: http://illegalargumentexception.blogspot.com/2008/10/java-how-not-to-make-mess-of-stream.html
РЕДАКТИРОВАТЬ 3: Следуя ссылке Макдауэлла, я наткнулся на предложение для Java 7 шаблона, аналогичного C# с использованием шаблона: http://tech.puredanger.com/java7/#resourceblock. Моя проблема подробно описана. Судя по всему, даже с Java 7 do проблемы остаются.
Без проблем ... Дело в том, что я тоже не знал, как сделать чистую и эффективную копию ... :-p ...
Вы можете сохранить 8 строк кода в первом листинге и 2 строки во втором листинге, если будете использовать соглашение о Java-коде. В таком случае этот код будет не таким уродливым.
@msangel: You can save 8 lines of code in first listing and 2 lines in second listing if you will use the java code convention. In that case this code will be not so ugly.: Вы, ТАК, упустили суть этого поста ... :-D
Размещение oDBuffer действительно должно происходить внутри внешнего оператора try. Это показывает, насколько неудобен такой способ ведения дел.
@Nicola Musatti: Вы правы ... o.O ... И учитывая, что они ждали 2012 года, чтобы реализовать try-with-resources (C# 1.0 реализовал ключевое слово using в ~ 2001 году) ...




К сожалению, этот тип кода имеет тенденцию быть немного раздутым в Java.
Между прочим, если один из вызовов oSBuffer.read или oDBuffer.write вызывает исключение, вы, вероятно, захотите позволить этому исключению проникнуть вверх по иерархии вызовов.
Наличие незащищенного вызова close () внутри предложения 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
}
}
Наконец, для повышения производительности код, вероятно, должен работать с буферами (несколько байтов на чтение / запись). Не могу подтвердить это числами, но меньшее количество вызовов должно быть более эффективным, чем добавление буферизованных потоков поверх.
Если вы молча закроете таким образом, ваш код не будет обрабатывать ошибки, если close выдает исключение. Многие потоки (например, BufferedOutputStream) записывают данные при закрытии.
Макдауэлл: Да, он должен перехватывать исключения, если close () выдает исключение. Обратите внимание, что вызовы close () сначала выполняются ВНУТРИ блока try! Блок finally должен обеспечить очистку, если какие-либо методы вызывают исключение. Верно? (Обратите внимание, я забыл о закрытии одного из буферов в первой версии сообщения.)
BufferedOutputStream немного глючит. Я предпочитаю явный сброс (в неисключительном случае), но вы должны это запомнить. IIRC, закрытие прервало обработку исключений до Java SE 1.6.
Я знаю, что исключения из close () жизненно важны. Но если вызовы close () выполняются сразу после последнего вызова write (), разве это не должно гарантировать надлежащее отслеживание исключений? Макдауэлл, пожалуйста, подтвердите, есть ли здесь изъян; Затем я сам отзову код, но я действительно хочу знать. :)
Некоторые из криптографии / сжатия немного неприятны. У вас есть не только базовый ресурс, с которым нужно иметь дело, но и реализация может также иметь некоторые ресурсы памяти C, не относящиеся к Java.
«Другими словами, неудачный метод close () может скрыть исходное исключение, созданное read () или write ()». Не может, воля. Это гарантировано стандартом. Новое исключение приведет к тому, что предыдущее исключение будет отброшено.
@Derek абсолютно, исправление (плохое отображение разума в текст). @McDowell, спасибо.
Шаблон try / finally - правильный способ обработки потоков в большинстве случаев для Java 6 и ниже.
Некоторые выступают за молчаливое закрытие потоков. Будьте осторожны, делая это по следующим причинам: Java: как не запутать обработку потоков
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()) {}
}
}
В большинстве случаев, но, как ни странно, не в этом случае. :)
@Tom - да, это не очень хороший механизм потокового копирования + я бы пошел с вашим.
Моя точка зрения была больше о RAII, чем о точной реализации кода с использованием BufferOutputStream. Ваша ссылка - правильный ответ на мой вопрос о RAII. В качестве забавного примечания, у меня была возможность поработать с новым проектом на Java, но я отклонил приглашение и вместо этого выбрал другой проект на .NET (почти) только из-за деструкторов и финализаторов C# using и C++ / CLI ...
@McDowell: Спасибо за обновление примера кода "попробуйте с ресурсами". Я бы поддержал ваш ответ еще раз, если бы я мог сделать это более одного раза ... :-) ...
Есть проблемы, но код, о котором вы нашли в сети, действительно плохой.
Закрытие буферных потоков закрывает нижний поток. Вы действительно не хотите этого делать. Все, что вам нужно сделать, это очистить выходной поток. Также нет смысла указывать, что базовые потоки предназначены для файлов. Производительность отстой, потому что вы копируете по одному байту за раз (на самом деле, если вы используете 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 в качестве унарного постфиксного оператора для закрытия в конце включающего блока.
Спасибо за лучший код. Для моего текущего личного проекта это не очень важно, но я копирую / вставляю ваш код прямо сейчас в качестве будущей замены. +1.
Если он копирует файл, то он, вероятно, делает захочет закрыть потоки, когда закончит. Копия завершена, поэтому нет смысла оставлять потоки открытыми. В этом случае подходят его вложенные блоки try-finally + вызовы close ().
Дерек Парк прав. Хотя ваш код меня заинтересовал, он все еще упускает из виду суть вопроса, то есть утилизацию ресурсов. Скажем, у меня есть метод copyFile(String in, String out), создающий экземпляры FileOutputStream и FileInputStream и вызывающий этот метод copy(InputStream in, OutputStream out), как следует написать copyFile, чтобы правильно обрабатывать удаление ресурсов?
Да, именно так работает Java. Есть инверсия управления - пользователь объекта должен знать, как очистить объект, а не сам объект, убирающий за собой. К сожалению, это приводит к тому, что код очистки разбросан по всему Java-коду.
В C# есть ключевое слово using для автоматического вызова Dispose, когда объект выходит за пределы области видимости. В Java такого нет.
Независимо от того, существует ли для этого специальный синтаксис или нет, клиентский код должен сообщать ресурсу, когда следует выполнять очистку. Ресурс никак не может сказать об этом. Конечно, вы можете абстрагироваться от приобретения, настройки и выпуска ресурсов с помощью интересного кода, выполняемого как обратный вызов.
Это не правда. Объекты C++ и C# прекрасно избавляются от себя без какого-либо участия вызывающей стороны. Читайте об этих языках.
Я немного знаком с этими языками. В C# клиентский код вызывает метод dispose в конце блока using. В C++ клиентский код вызывает деструктор в конце области видимости.
Точно. Вызывающая сторона C++ и C# не обязана участвовать в разрушении, а вызывающая сторона не должна знать внутреннюю работу разрушаемого объекта. Только в Java есть такая инверсия управления, при которой вызывающий должен знать не только, как объект должен быть разрушен, но и его побочные эффекты.
Смущенный. В C++ деструктор вызывается клиентским кодом в точке, выбранной клиентским кодом. Как это не участие. В C# есть немного синтаксического сахара для вызова метода точно так же, как и try-finally.
Как в C++, так и в C# вызывающей стороне через соответствующий синтаксис нужно только сказать «Я хочу автоматическое уничтожение» (C# через «using» и C++ через инициализацию на основе стека), а сам объект позаботится о деталях. Однако в Java вызывающий должен сам выполнить уничтожение (вызывая close () или что-то еще).
@Tom Hawtin - tackline: Дейл прав. В C# ключевое слово using указывает, что финализатор должен запускаться в момент выхода кода из области видимости. В C++ деструктор будет вызываться в момент выхода кода из области, в которой объявлен объект. В Java вы должны завершить код и обработать кровавые детали.
@Tom Hawtin: C++ неявно вызывает деструктор, когда объект выходит из области видимости, если, конечно, вы явно не создаете и не удаляете объекты, и в этом случае любая автоматическая очистка C++ должна быть явно указана код.
Для обычных задач ввода-вывода, таких как копирование файла, код, подобный показанному выше, изобретает колесо. К сожалению, JDK не предоставляет никаких утилит более высокого уровня, в отличие от apache commons-io.
Например, FileUtils содержит различные служебные методы для работы с файлами и каталогами (включая копирование). С другой стороны, если вам действительно нужно использовать поддержку ввода-вывода в JDK, IOUtils содержит набор методов closeQuietly (), которые закрывают Readers, Writers, Streams и т. д. Без создания исключений.
Прошу прощения за мой ошибочный ответ. Я не был уверен, действительно ли вы пытались придумать способ использования RAII в Java, или просто не знали, как обычно копируются данные из входного потока в выходной поток в Java.