1

So I am using Java.net.URL to get a connection to a URL that contains some xml (.atom?) data for parsing for writing to a local file.

This is teaching me a lot about reading and writing. Here is my current process:

  1. create URL object
  2. get a "Stream"
  3. Create a InputStreamReader
  4. Wrap the ISR in Buffered Reader
  5. Create FileWriter
  6. Wrap FW in Bufferedwriter
  7. line by line, read then write
  8. close the BufferedWriter.

It works! I learned the difference between reader, BufferedReader, writer and BfferedWriter. That's great. I'm a little unclear on streams, but that's ok for now.

However, I was wondering if there is a way to create a BufferedWriter directly from an InputStream to save all those middle steps.

  1. Create URL object
  2. get a "Stream"
  3. skip straight to BufferedWriter()
  4. write to local file.
  5. close the BufferedWriter

I see the BufferedWriter constuctor takes a writer, so I'm assuming not, but I can't help but wonder if you could cut down some steps.

rocksNwaves
  • 5,331
  • 4
  • 38
  • 77
  • 1
    You seem to only be concerned with downloading data into a local file. If that's the case then you don't need to use a `Reader` or a `Writer`, as you're not trying to decode the binary data into characters or vice versa. Transferring the data (i.e. the raw bytes) from the `InputStream` to an `OutputStream` is sufficient (though you may want to use buffers/buffered-streams). Is there more to your application that requires using a reader and/or a writer? – Slaw Feb 09 '20 at 19:35
  • @Slaw Yeah, that's the heart of my question. I just want to save data from the URL straight to file. I don't need to do any intermediate manipulations with the data. – rocksNwaves Feb 10 '20 at 03:21

3 Answers3

3

Can you create a FileWriter or BufferedWriter directly from an InputStream?

No. As you've noted, BufferedWriter wraps another instance of Writer. This is Java's quintessential example of the Decorator pattern.

One of the cons of the Decorator pattern is that it tends to produce a lot of little objects that need to be composed.

On the bright side, in addition to teaching you a lot about reading and writing, this exercise has now taught you about design patterns as well!

jaco0646
  • 15,303
  • 7
  • 59
  • 83
  • Thanks, that's good stuff to know. This, plus another answer shows me that I can just use an input and output stream to write the raw data without the readers and writers. – rocksNwaves Feb 10 '20 at 03:29
1

Something like the following?

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;

public class Main {
    public static void main(String[] args) {
        try (InputStream inputStream = URI.create("https://www.example.com/").toURL().openStream()) {
            try (FileOutputStream outputStream = new FileOutputStream(new File("output.txt"))) {
                int read;
                byte[] bytes = new byte[1024];
                while ((read = inputStream.read(bytes)) != -1) {
                    outputStream.write(bytes, 0, read);
                }
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
1

To answer your main question: No, you cannot go directly from an InputStream / OutputStream to a BufferedReader / BufferedWriter. The problem is you need a class which can translate from an input/output stream, which deals directly with bytes, to a reader/writer, which deals with characters (i.e. by decoding bytes into characters and vice versa). As pointed out by @jaco0646's answer, this is one of the cons of the decorator pattern. Of course, you could make a utility method:

public static BufferedReader toBufferedReader(InputStream stream, Charset charset) {
  return new BufferedReader(new InputStreamReader(stream, charset));
}

To make your other code shorter. Though I'm sure there's already a library out there that provides this (e.g. maybe Commons IO or Guava).

That all being said, you don't appear to need a reader or a writer. Your question indicates, and you later confirm in a comment, that you're simply downloading data directly into a file. This means you have no need to decode the bytes into characters only to immediately encode the characters back into bytes. You just need to transfer the bytes from the InputStream to the OutputStream directly. Here's some examples (staying within the JDK):

Manually copying the bytes using a buffer

public static void copyUrlToFile(URL url, File file) throws IOException {
  try (InputStream input = url.openStream();
      OutputStream output = new FileOutputStream(file)) {

    byte[] buffer = new byte[1024 * 8]; // 8 KiB buffer
    int read;
    while ((read = input.read(buffer)) != -1) {
      output.write(buffer, 0, read);
    }
  }
}

Could also use Path and Files#newOutputStream(Path,OpenOption...) instead of File and FileOutputStream.

Using InputStream#transferTo(OutputStream) (Java 9+)

public static void copyUrlToFile(URL url, File file) throws IOException {
  try (InputStream input = url.openStream();
      OutputStream output = new FileOutputStream(file)) {
    input.transferTo(output);
  }
}

Using Files#copy(InputStream,Path,CopyOption...)

public static void copyUrlToFile(URL url, Path file) throws IOException {
  try (InputStream input = url.openStream()) {
    Files.copy(input, file, StandardCopyOption.REPLACE_EXISTING);
  }
}

Note that, when using NIO, you can open buffered readers and writers to files directly using Files#newBufferedReader(Path) and Files#newBufferedWriter(Path,OpenOption...), respectively.

Slaw
  • 37,820
  • 8
  • 53
  • 80