M.Dietz
M.Dietz

Reputation: 1000

Read out of multiple files with one BufferedReader?

So i´m able to read out of an existing file like this:

    File file = new File("C:\\Something/test.txt");
    FileInputStream fis = new FileInputStream(file);
    BufferedReader bfr = new BufferedReader(new InputStreamReader(fis));

Now i´m able to use bfr.readLine() and read line by line. If i wanted to read out of another file, do i have to create a new BufferedReader or is there any way to read out of multiple files with one BufferedReader?

Upvotes: 2

Views: 1378

Answers (1)

Roman Puchkovskiy
Roman Puchkovskiy

Reputation: 11835

You could use SequenceInputStream to read files sequentially.

File file1 = new File("C:\\Something/test1.txt");
File file2 = new File("C:\\Something/test2.txt");
FileInputStream fis1 = new FileInputStream(file1);
FileInputStream fis2 = new FileInputStream(file2);
SequenceInputStream is = new SequenceInputStream(fis1, fis2);
BufferedReader bfr = new BufferedReader(new InputStreamReader(is));

Closing streams correctly

To close these two streams (and reader), just use try-with-resources:

    try (
            FileInputStream fis1 = new FileInputStream(file1);
            FileInputStream fis2 = new FileInputStream(file2);
            SequenceInputStream is = new SequenceInputStream(fis1, fis2);
            BufferedReader bfr = new BufferedReader(new InputStreamReader(is))
    ) {
        System.out.println(bfr.readLine());
        System.out.println(bfr.readLine());
    }

Using more than two streams

If there are more than two streams, we'd have to use the constructor that accepts Enumeration of InputStreams. The tricky part here, as mentioned by @Boris the Spider, is closing all the streams correctly. We could create an Autoclosable that would contain all the InputStreams to close, but then, before we construct that container, but after some of the streams were already constructed, an exception could occur, so some streams would remain unclosed.

A clean way is to include all the streams explicitly in try-with-resources statement:

    try (
            FileInputStream fis1 = new FileInputStream(file1);
            FileInputStream fis2 = new FileInputStream(file2);
            FileInputStream fis3 = new FileInputStream(file3);
            ...
            SequenceInputStream is = new SequenceInputStream(new IteratorEnumeration<>(Arrays.asList(fis1, fis2, fis3, ...).iterator()));
            BufferedReader bfr = new BufferedReader(new InputStreamReader(is))
    ) {
        // .. read from bfr
    }

IteratorEnumeration is the one suggested in https://stackoverflow.com/a/7086010/7637120

Another option is to manually track a list of input streams that were already opened successfully and close them if construction of next stream fails.

public class InputStreams implements AutoCloseable {
    private final List<InputStream> streams = new ArrayList<>();

    public List<InputStream> getStreams() {
        return streams;
    }

    public void add(InputStream is) {
        streams.add(is);
    }

    @Override
    public void close() throws IOException {
        IOException firstException = null;
        for (InputStream stream : streams) {
            try {
                stream.close();
            } catch (IOException e) {
                if (firstException == null) {
                    firstException = e;
                } else {
                    firstException.addSuppressed(e);
                }
            }
        }
        if (firstException != null) {
            throw firstException;
        }
    }
}

InputStreams streams = new InputStreams();
while (moreStreams()) {
    InputStream nextStream = null;
    try {
        nextStream = getNextStream();
        streams.add(nextStream);
    } catch (IOException e) {
        // the following will close nextStream and all the earlier streams
        try (InputStreams streamsToClose = streams) {
            if (nextStream != null) {
                nextStream.close();
            }
        } finally {
            throw e;
        }
    }
}

try (
    InputStreams streamsToClose = streams;
    SequenceInputStream is = new SequenceInputStream(new IteratorEnumeration<>(streams.getStreams().iterator()));
    BufferedReader bfr = new BufferedReader(new InputStreamReader(is))
) {
    // work with bfr...
}

Upvotes: 3

Related Questions