Reputation: 4220
I just wanted to see if there was a better way I should be handling this. My understanding of streams is that as long as you close a stream, any streams encapsulated within it will be closed which is why I only close TarArchiveOutputStream in finally. If I get FileNotFound on the rawDir or archiveDir I want to log it, otherwise anything else I want to throw.
public static void createTarGzOfDirectory(File rawDir, File archiveFile) throws IOException {
FileOutputStream fOut = null;
BufferedOutputStream bOut = null;
GzipCompressorOutputStream gzOut = null;
TarArchiveOutputStream tOut = null;
try {
fOut = new FileOutputStream(archiveFile);
bOut = new BufferedOutputStream(fOut);
gzOut = new GzipCompressorOutputStream(bOut);
tOut = new TarArchiveOutputStream(gzOut);
addFileToTarGz(tOut, rawDir, "");
} catch (FileNotFoundException e) {
log.error("File not found: " + e);
} finally {
if(tOut != null) {
tOut.finish();
tOut.close();
}
}
Any other considerations or thoughts on improving things?
Upvotes: 2
Views: 2611
Reputation: 718788
My understanding of streams is that as long as you close a stream, any streams encapsulated within it will be closed ...
That is correct.
However, your code is (effectively) assuming that if tOut
is null
, then none of the other streams in the chain have been created. That's a somewhat dodgy assumption. Consider this sequence:
FileOutputStream
is created and is assigned to fOut
.BufferedOutputStream
is created and is assigned to bOut
.GzipCompressorOutputStream
constructor throws an exception or error. (Maybe the heap is full ...).catch
is skipped ... wrong exception.finally
checks tOut
, finds it is null
, and does nothing.Net result: we've leaked the file descriptor / channel held by the FileOUtputStream
.
The key to getting this example (absolutely) right is to understand which of those stream objects holds the critical resources, and ensuring that THAT stream gets closed. The other streams that don't hold resources don't have to be closed.
} finally {
if (fOut != null) {
fOut.close();
}
}
The other point is that you need to move the tOut.finish()
call into the try
block after the addFileToTarGz
call.
If the addFileToTarGz
call throws an exception, or if you don't get that far, the finish
call is a waste of time.
The finish
call will attempt to write the index to the archive, and THAT could throw an IOException. If this happens in the finally
block, then any following code in the finally block to close the stream chain won't get executed ... and a file descriptor will be leaked.
Upvotes: 3
Reputation: 47608
Although it would look ugly and is,maybe, unlikely to be the case, you should close them all in cascade. Yes, if you close the TarArchiveOutputStream, it is supposed to close the underlyning streams. But, depending on the implementation, it may not always be the case. Moreover, and probably mainly, if one of the intermediate constructors throw an exception, tOut will be null, but the other ones may not be. Meaning that your streams are opened but your did not close any.
Upvotes: 1
Reputation: 4157
You could chain all your constructors together like so:
tOut = new TarArchiveOutputStream(new GzipCompressorOutputStream(new BufferedOutputStream(new FileOutputStream(archiveFile))));
And save yourself 6 lines of initialization and 3 local variables for debugging. Not everyone likes chaining things that way - I personally find it more readable but the rest of your team may prefer it your way.
As far as closing the stream, it looks correct to me.
Upvotes: 0