Arrays.stream/String.split
Since String.split returns an array String[], I always recommend Arrays.stream as the canonical idiom for streaming over an array.
String input = "dog,cat,bird";
Stream<String> stream = Arrays.stream(input.split( "," ));
stream.forEach(System.out::println);
Stream.of/String.split
Stream.of is a varargs method which just happens to accept an array, due to the fact that varargs methods are implemented via arrays and there were compatibility concerns when varargs were introduced to Java and existing methods retrofitted to accept variable arguments.
Stream<String> stream = Stream.of(input.split(",")); // works, but is non-idiomatic
Stream<String> stream = Stream.of("dog", "cat", "bird"); // intended use case
Pattern.splitAsStream
Pattern.compile(",").splitAsStream(string) has the advantage of streaming directly rather than creating an intermediate array. So for a large number of sub-strings, this can have a performance benefit. On the other hand, if the delimiter is trivial, i.e. a single literal character, the String.split implementation will go through a fast path instead of using the regex engine. So in this case, the answer is not trivial.
Stream<String> stream = Pattern.compile(",").splitAsStream(input);
If the streaming happens inside another stream, e.g. .flatMap(Pattern.compile(pattern) ::splitAsStream) there is the advantage that the pattern has to be analyzed only once, rather than for every string of the outer stream.
Stream<String> stream = Stream.of("a,b", "c,d,e", "f", "g,h,i,j")
.flatMap(Pattern.compile(",")::splitAsStream);
This is a property of method references of the form expression::name, which will evaluate the expression and capture the result when creating the instance of the functional interface, as explained in What is the equivalent lambda expression for System.out::println and java.lang.NullPointerException is thrown using a method-reference but not a lambda expression
Arrays.stream/String.split
Since String.split returns an array String[], I always recommend Arrays.stream as the canonical idiom for streaming over an array.
String input = "dog,cat,bird";
Stream<String> stream = Arrays.stream(input.split( "," ));
stream.forEach(System.out::println);
Stream.of/String.split
Stream.of is a varargs method which just happens to accept an array, due to the fact that varargs methods are implemented via arrays and there were compatibility concerns when varargs were introduced to Java and existing methods retrofitted to accept variable arguments.
Stream<String> stream = Stream.of(input.split(",")); // works, but is non-idiomatic
Stream<String> stream = Stream.of("dog", "cat", "bird"); // intended use case
Pattern.splitAsStream
Pattern.compile(",").splitAsStream(string) has the advantage of streaming directly rather than creating an intermediate array. So for a large number of sub-strings, this can have a performance benefit. On the other hand, if the delimiter is trivial, i.e. a single literal character, the String.split implementation will go through a fast path instead of using the regex engine. So in this case, the answer is not trivial.
Stream<String> stream = Pattern.compile(",").splitAsStream(input);
If the streaming happens inside another stream, e.g. .flatMap(Pattern.compile(pattern) ::splitAsStream) there is the advantage that the pattern has to be analyzed only once, rather than for every string of the outer stream.
Stream<String> stream = Stream.of("a,b", "c,d,e", "f", "g,h,i,j")
.flatMap(Pattern.compile(",")::splitAsStream);
This is a property of method references of the form expression::name, which will evaluate the expression and capture the result when creating the instance of the functional interface, as explained in What is the equivalent lambda expression for System.out::println and java.lang.NullPointerException is thrown using a method-reference but not a lambda expression
Robustness
I can see no difference in the robustness of the three approaches.
Readability
I am not aware of any credible scientific studies on code readability involving experienced Java programmers, so readability is a matter of opinion. Even then, you never know if someone giving their opinion is making an objective distinction between actual readability, what they have been taught about readability, and their own personal taste.
So I will leave it to you to make your own judgements on readability ... noting that you do consider this to be a high priority.
FWIW, the only people whose opinions on this matter are you and your team.
Performance
I think that the answer to that is to carefully benchmark the three alternatives. Holger provides an analysis based on his study of some versions of Java. But:
- He was not able to come to a definite conclusion on which was fastest.
- Strictly speaking, his analysis only applies to the versions of Java he looked at. (Some aspects of his analysis could be different on (say) Android Java, or some future Oracle / OpenJDK version.)
- The relative performance is likely depend on the length of the string being split, the number of fields, and the complexity of the separator regex.
- In a real application, the relative performance may also depend what you do with the
Streamobject, what garbage collector you have selected (since the different versions apparently generate different amounts of garbage), and other issues.
So if you (or anyone else) are really concerned with the performance, you should write a micro-benchmark and run it on your production platform(s). Then do some application specific benchmarking. And you should consider looking at solutions that don't involve streams.
To make the code clearer, you could use simple foreach loop:
Stream<String> lines = File.lines(Paths.get("path-to-file");
lines.forEach(s -> s.split(" "));
/**
* Takes a stream, splits group by first part of the string:
*/
public Map<Integer, List<String>> split(Stream<String> a) {
Map<Integer, List<String>> result = new HashMap<>();
a.forEach(s -> {
String[] pair = s.split(" ");
Integer key = Integer.valueOf(pair[0]);
String value = pair[1];
// as 4castle suggested - to avoid unnecessary computation
result.computeIfAbsent(key, key -> new ArrayList<>());
result.get(key).add(value);
});
return result;
}
Or you can map your input directly in the stream processing:
a.map(s -> s.split(" "))
.forEach(pair -> {
Integer key = Integer.valueOf(pair[0]);
String value = pair[1];
result.putIfAbsent(key, new ArrayList<>());
result.get(key).add(value);
});
Use Collectors.groupingBy with a downstream collector which gets the second part of the split line before collecting to a list.
Map<Integer, List<String> table =
Files.lines(Paths.get("path-to-file"))
.map(line -> line.split(" ", 2))
.collect(Collectors.groupingBy(
parts -> Integer.valueOf(parts[0]),
Collectors.mapping(parts -> parts[1], Collectors.toList())
));
Instead of applying replaceAll on a line, do it on words of the line as follows:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
String str = "Harry is a good cricketer. Tanya is an intelligent student. Bravo!";
List<String> words = Arrays.stream(str.split("\\s+")).map(s -> s.replaceAll("[^A-Za-z0-9 ]", ""))
.collect(Collectors.toList());
System.out.println(words);
}
}
Output:
[Harry, is, a, good, cricketer, Tanya, is, an, intelligent, student, Bravo]
Note: The regex, \\s+ splits a string on space(s).
try this:
String fileName = "file.txt";
try {
Map<String, Long> wordCount = Files.lines(Path.of(fileName))
.flatMap(line -> Arrays.stream(line.split("\\s+")))
.filter(w->w.matches("[a-zA-Z]+"))
.sorted(Comparator.comparing(String::length)
.thenComparing(String.CASE_INSENSITIVE_ORDER))
.collect(Collectors.groupingBy(w -> w,
LinkedHashMap::new, Collectors.counting()));
wordCount.entrySet().forEach(System.out::println);
}catch (Exception e) {
e.printStackTrace();
}
This is relatively simple. It just splits on white space and counts the words by putting them in a map where the Key is the word and the Value is a long containing the count.
I included a filter to only capture words of nothing but letters.
The way this works is that the Lines put into a stream. Each line is then split into words using String.split. Since this creates an array, the flatMap converts all these individual streams of words into a single stream where they are processed.
The work horse of this is the Collectors.groupingBy which will group the values in a particular way for each key. In this case, I specified the Collectors.counting() method to increase the count each time the key (i.e. word) appeared.
As an option, I sorted the words first on length and then alphabetically, ignoring case.
First, the split() operation is not necessary. While you could split all the pieces and then join them together like that, it is far simpler to use a replace or replaceAll call instead.
Secondly, the reduce operation will not be very efficient since it is creating lots of intermediary Strings and StringBuilders. Instead, you should use the String joining Collector, which is more efficient:
String temp = list.stream()
.map(x -> x.name.replace(" ", "-"))
.collect(Collectors.joining("&&"));
The second map operation you write keep the first word.
Instead, here what you can do :
Replace the spaces (
\\sas regex) by a-Optional<String> temp = list.stream() .map(x -> x.name) .map(x -> x.replaceAll("\\s", "-")) .reduce((str1, str2) -> str1 + "&&" + str2);Split on space, and then join with the
-Optional<String> temp = list.stream() .map(x -> x.name) .map(x -> String.join("-", x.split("\\s"))) .reduce((str1, str2) -> str1 + "&&" + str2);
Using flatMap is not the right tool for the job. What you apparently want to do is
strings.map(string -> string.split(" ", 2))
.map(array -> new MyClass(array[0], array[1]))
You may process the stream further by using .collect(Collectors.toList()) to get a List<MyClass> or .findAny() to get a single MyClass instance (if any).
Generally, streaming an array is only useful if you want to treat all elements uniformly, i.e. not if their position has a special meaning which has to be preserved for subsequent operations.
And if you really want to create a flat stream of words or tokens, you shouldn’t use the combination of String.split and Arrays.stream as that will create and fill an unnecessary intermediate array. In this case use
strings.flatMap(Pattern.compile(" ")::splitAsStream)
see Pattern.splitAsStream
It was an interesting challenge even if streams should not be used for such simple tasks. Here's the complete code:
public class Example {
public static void main(String[] args) {
String[] strings = {"randomText1 randomText2"};
MyClass myClass = Arrays.stream(strings)
.map(string -> string.split(" "))
.flatMap(Arrays::stream)
.collect(new MyCollector());
System.out.println("myClass = " + myClass.toString());
}
}
class MyCollector implements Collector<String, List<String>, MyClass> {
@Override
public BiConsumer<List<String>, String> accumulator() {
return List::add;
}
@Override
public Supplier<List<String>> supplier() {
return ArrayList::new;
}
@Override
public BinaryOperator<List<String>> combiner() {
return (strings, strings2) -> {
strings.addAll(strings2);
return strings;
};
}
@Override
public Function<List<String>, MyClass> finisher() {
return strings -> new MyClass(strings.get(0), strings.get(1));
}
@Override
public Set<Characteristics> characteristics() {
return Collections.emptySet();
}
}
class MyClass {
String s1;
String s2;
public MyClass(String s1, String s2) {
this.s1 = s1;
this.s2 = s2;
}
@Override
public String toString() {
return "MyClass{" +
"s1='" + s1 + '\'' +
", s2='" + s2 + '\'' +
'}';
}
}
Firstly, I'd suggest avoiding the use of raw types and instead, use List<String[]> as the receiver type.
List<String[]> numbers = reader.lines()
.map(s -> s.split(delimiter)) // substitute with your deilimeter
.collect(Collectors.toList());
You mentioned that you wanted a LinkedList implementation. you should almost always favour ArrayList which toList returns by default currently although it is not guaranteed to persist thus, you can specify the list implementation explcitly with toCollection:
List<String[]> numbers = reader.lines()
.map(s -> s.split(delimiter)) // substitute with your deilimeter
.collect(Collectors.toCollection(ArrayList::new));
and likewise for LinkedList:
List<String[]> numbers = reader.lines()
.map(s -> s.split(delimiter)) // substitute with your deilimeter
.collect(Collectors.toCollection(LinkedList::new));
You may do it like so,
Path path = Paths.get("src/main/resources", "data.txt");
try (Stream<String> lines = Files.lines(path)) {
List<String> strings = lines.flatMap(l -> Arrays.stream(l.split(","))).map(String::trim)
.collect(Collectors.toCollection(LinkedList::new));
}
Read each line of the file, then split it using the delimiter. After that trim it to eliminate any remaining white space characters. Finally collect it to a result container.