Writing HashMap contents to the file

13,397

Solution 1

The simplest, non-copying, most “streamish” solution is

Files.write(mOutputPath, () -> mHashMap.entrySet().stream()
    .<CharSequence>map(e -> e.getKey() + DATA_SEPARATOR + e.getValue())
    .iterator());

While a Stream does not implement Iterable, a lambda expression performing a Stream operation that ends with calling iterator() on the stream, can be. It will fulfill the contract as the lambda expression will, unlike a Stream, produce a new Iterator on each invocation.

Note that I removed the explicit UTF-8 character set specifier as java.nio.Files will use UTF-8 when no charset is specified (unlike the old io classes).

The neat thing about the above solution is that the I/O operation wraps the Stream processing, so inside the Stream, we don’t have to deal with checked exceptions. In contrast, the Writer+forEach solution needs to handle IOExceptions as a BiConsumer is not allowed to throw checked exceptions. As a result, a working solution using forEach would look like:

try(Writer writer = Files.newBufferedWriter(mOutputPath)) {
    mHashMap.forEach((key, value) -> {
        try { writer.write(key + DATA_SEPARATOR + value + System.lineSeparator()); }
        catch (IOException ex) { throw new UncheckedIOException(ex); }
    });
} catch(UncheckedIOException ex) { throw ex.getCause(); }

Solution 2

You can simply avoid using a List<String> by directly writing out the lines to disk using e.g. a Writer:

    Writer writer = new BufferedWriter(new OutputStreamWriter(
            new FileOutputStream(new File(mOutputPath)), StandardCharsets.UTF_8));
    mHashMap.forEach((key, value) -> writer.write(key + DATA_SEPARATOR + value + System.lineSeparator()));
    writer.flush();
    writer.close();

Solution 3

You could map the entries of the map to a string and write them to a FileChannel. The additional methods simply do the exception handling so the stream operations become more readable.

final Charset charset =  Charset.forName("UTF-8");
try(FileChannel fc = FileChannel.open(mOutputPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW)) {
    mHashMap.entrySet().stream().map(e -> e.getKey() + ":::" + e.getValue() + "\n")
            .map(s -> encode(charset, s))
            .forEach(bb -> write(fc, bb));
}

void write(FileChannel fc, ByteBuffer bb){
    try {
        fc.write(bb);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

ByteBuffer encode( Charset charset, String string){
    try {
        return charset.newEncoder().encode(CharBuffer.wrap(string));
    } catch (CharacterCodingException e) {
        throw new RuntimeException(e);
    }
}
Share:
13,397
Helisia
Author by

Helisia

Верните мне мной ХэшКод

Updated on July 28, 2022

Comments

  • Helisia
    Helisia almost 2 years

    I have a HashMap<Integer, Integer>. I write its content to the file, so each line of it contains hashmapKey:::hashmapValue. This is how I do it now:

    List<String> mLines = new ArrayList<String>();
    mHashMap.forEach((key, value) -> mLines.add(key + DATA_SEPARATOR + value));
    Files.write(mOutputPath, mLines, StandardCharsets.UTF_8);
    

    I very doubt that I need to copy entire HashMap to the list of strings, I am sure it will give me performance issues when working with big amounts of data. My question is: how can I write HashMap contents to the file using Java 8 avoiding copying values in another list?

  • Holger
    Holger about 8 years
    You should use try-with-resource.
  • Holger
    Holger about 8 years
    Besides that, this solution doesn’t work as IOExceptions potentially thrown by Writer.write must be catched (which will dramatically complicate the lambda expression)…
  • Tagir Valeev
    Tagir Valeev about 8 years
    Note that (while it's sane assumption) it's not specified that Files.write traverses the Iterable only once.
  • Robert
    Robert about 8 years
    I omitted a try-catch or finally environment intentional as it is just a code snippet showing how to do it an not a full-featured program.
  • Holger
    Holger about 8 years
    @Tagir Valeev: It’s also within the specification if the JRE reuses the lambda instance (assuming that it captures the same Map instance) on multiple executions of this code. That’s another reason to ensure correctly producing a new iterator on each invocation.
  • Holger
    Holger about 8 years
    @Robert: you should really read the linked article. A try-with-resource statement doesn’t need a finally clause and is, in fact, shorter than your code.
  • Robert
    Robert about 8 years
    @Holger I know try-with-resource. As it is equal to a try -finally block I did not mention it separately.
  • Holger
    Holger about 8 years
    If you know it, you should know that it allows to omit the explicit close() invocation (the flush() is obsolete anyway), so again, it would make your code simpler, so there is no reason not to use it in an answer. Your code is more complicated than necessary while failing to ensure safe closing. Besides that, it is not equal to closing in a finally block, there is a fundamental semantic difference.
  • Jagesh Maharjan
    Jagesh Maharjan almost 6 years
    Is there a way to append a file with stream.
  • Robert
    Robert almost 6 years
    @Jagesh Maharjan That is independent of the stream API. The FileOutputStream has a constructor which takes a boolean as second argument. If you set this to true it will append the data to an existing file.
  • Jagesh Maharjan
    Jagesh Maharjan almost 6 years
    @Robert, I was supposed to Comment on the previous Answer but commented here. Sorry for the ambiguity. Thanks.