4

Assume each T object can be instantiated as

T tobj = new T(//int value);

So to create an array of T[ ] from integers in a file seperated by space I do the following:

 BufferedReader br;
 FileReader fr;
 int[] arr;
 try{               
        fr = new FileReader(fo); // assume "fo" file name
        br = new BufferedReader(fr);
        arr = Arrays.stream(br.readLine().split("\\s")).mapToInt(Integer::parseInt).toArray();

 }catch(SomeException e){//something else}

T[] tobjarr = new T[arr.length];
    for(int i=0; i<arr.length; ++i)){
        tobjarr[i] = new T(arr[i]);
    }

1.Is the above method efficient in terms of time and space usage?

2.Is there any other way? if so how does it compare to above method?

magpie
  • 653
  • 1
  • 7
  • 12
  • 1
    How many integers are contained in the file? Regarding space usage, this wouldn't be efficient since the entire file is loaded into memory (assuming it's a single line). – Jacob G. Dec 13 '17 at 19:04
  • A large number. Say > 10^6 – magpie Dec 13 '17 at 19:06
  • 1
    I wouldn't say that's large in today's standards. Your file would only be a few MB. Unless you're working with billions, I wouldn't worry about performance that much. – Jacob G. Dec 13 '17 at 19:11
  • 2
    Instead of `.toArray()`, do `.mapToObj(T::new).toArray(T[]::new)` and you can skip the temporary array and extra loop. – shmosel Dec 13 '17 at 19:18
  • So if I were using billions >10^9 . What would I do then? – magpie Dec 13 '17 at 19:25
  • 1
    You would then need to process the data partially. Only in chunks as large that you can handle them with your limited memory. – Zabuzard Dec 13 '17 at 19:56

2 Answers2

5

In general your approach is fine. However, you can do that with a single stream cascade. Compared to your original approach this saves you one iteration.

Also note that nowadays we read files using Javas new I/O API called NIO. One big advantage is that it offers Stream methods. For example the Files#lines method that returns a stream over all lines of the file, perfectly suited for your approach.

So all in all, here is the complete code:

String file = ...
Pattern separator = Pattern.compile("\\s");

try (Stream<String> lines = Files.lines(Paths.get(file))) {
    T[] values = lines                      // Stream<String> lines
        .flatMap(separator::splitAsStream)  // Stream<String> words
        .mapToInt(Integer::parseInt)        // IntStream values
        .mapToObj(T::new)                   // Stream<T> valuesAsT
        .toArray(T[]::new);
} catch (IOException e) {
    System.out.println("Something went wrong.");
}

Note that this code is slightly different to yours, as yours will only process one line and mine all lines. You may change that if you don't want it:

List<T[]> valuesPerLine = Files.lines(Paths.get(file))  // Stream<String> lines
    .map(separator::splitAsStream)  // Stream<Stream<String>> wordsPerLine
    .map(lineStream -> {
        return lineStream                 // Stream<String> words
            .mapToInt(Integer::parseInt)  // IntStream values
            .mapToObj(T::new)             // Stream<T> valuesAsT
            .toArray(T[]::new);
    })                              // Stream<T[]> valuesPerLine
    .collect(Collectors.toList());

The main difference to your original approach is that we can easily transform an IntStream into a Stream<T> by using mapToObj(T::new) (or just map if it's a regular stream and not IntStream) which passes the elements to the constructor. After that we collect Stream<T> into an array by using toArray(T[]::new).

Zabuzard
  • 25,064
  • 8
  • 58
  • 82
  • 1
    Mh, it seems that, though many methods of **NIO** throw `java.io.UncheckedIOException` instead of `java.io.IOException`, the `Files#lines` method forces you to catch the exception too, it throws an `java.io.IOException` according to its documentation and source code. I've updated the answer and added `try-catch`. – Zabuzard Dec 13 '17 at 19:48
  • What if my data source is not non-interfering? – magpie Dec 13 '17 at 20:00
  • 2
    Better to use [try with resources](https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html) for this: – fps Dec 13 '17 at 20:01
  • 2
    @FedericoPeraltaSchaffner Thanks, I thought `Files#lines` does this by itself. But apparently it does not: [Why is Files.lines (and similar Streams) not automatically closed?](https://stackoverflow.com/questions/34072035/why-is-files-lines-and-similar-streams-not-automatically-closed) – Zabuzard Dec 13 '17 at 20:05
  • @magpie Sorry, I didn't get you, can you explain the problem in more detail? – Zabuzard Dec 13 '17 at 20:13
  • what if my stream source is modified periodically? Will this behave like Buffered Reader ? – magpie Dec 13 '17 at 20:26
  • 1
    @magpie `Files#lines` internally uses a `BufferedReader`, so it will be as efficient. See the [source code](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/java/nio/file/Files.java#Files.lines%28java.nio.file.Path%29). The advantage is that it automatically closes the `BufferedReader` in error case and attaches any additional exception that occurs while closing, it simply provides a very **compact** solution for a very frequent need, reading files. – Zabuzard Dec 13 '17 at 20:29
2
T[] array = Arrays.stream(br.readLine().split("\\s"))
    .map(s -> new T(Integer.parseInt(s)))
    .toArray(T[]::new)

EDIT: noticed that you're using a different delimiter

Zabuzard
  • 25,064
  • 8
  • 58
  • 82
Alex Savitsky
  • 2,306
  • 5
  • 24
  • 30