Java 8 - How to build up a string from initial string with only one traversal
I have an url like: String url = "https://.../foo/a/555/data1";
Goal: Transform the url to the string: a555data1
I want to build this result traversing the string only once.
I decided for the following process:
- I want to "stream" the string starting from the back.
- If it is not a backslash insert/append at the front of a deque.
- If it is the third backslash end
I have successfully written a horrible solution below, can it be made pretty using streams?
Deque<String> lifo = new ArrayDeque<>();
int count = 0;
for (int i = testUrl.length() - 1; count < 3 ; --i) {
if (testUrl.codePointAt(i) == ((int) '/') ) {
++count;
continue;
}
result.addFirst(testUrl.substring(i,i+1));
}
String foo = result.stream().collect(Collectors.joining());
assertThat(foo).isEqualTo("a606KAM1");
java java-8 java-stream
add a comment |
I have an url like: String url = "https://.../foo/a/555/data1";
Goal: Transform the url to the string: a555data1
I want to build this result traversing the string only once.
I decided for the following process:
- I want to "stream" the string starting from the back.
- If it is not a backslash insert/append at the front of a deque.
- If it is the third backslash end
I have successfully written a horrible solution below, can it be made pretty using streams?
Deque<String> lifo = new ArrayDeque<>();
int count = 0;
for (int i = testUrl.length() - 1; count < 3 ; --i) {
if (testUrl.codePointAt(i) == ((int) '/') ) {
++count;
continue;
}
result.addFirst(testUrl.substring(i,i+1));
}
String foo = result.stream().collect(Collectors.joining());
assertThat(foo).isEqualTo("a606KAM1");
java java-8 java-stream
add a comment |
I have an url like: String url = "https://.../foo/a/555/data1";
Goal: Transform the url to the string: a555data1
I want to build this result traversing the string only once.
I decided for the following process:
- I want to "stream" the string starting from the back.
- If it is not a backslash insert/append at the front of a deque.
- If it is the third backslash end
I have successfully written a horrible solution below, can it be made pretty using streams?
Deque<String> lifo = new ArrayDeque<>();
int count = 0;
for (int i = testUrl.length() - 1; count < 3 ; --i) {
if (testUrl.codePointAt(i) == ((int) '/') ) {
++count;
continue;
}
result.addFirst(testUrl.substring(i,i+1));
}
String foo = result.stream().collect(Collectors.joining());
assertThat(foo).isEqualTo("a606KAM1");
java java-8 java-stream
I have an url like: String url = "https://.../foo/a/555/data1";
Goal: Transform the url to the string: a555data1
I want to build this result traversing the string only once.
I decided for the following process:
- I want to "stream" the string starting from the back.
- If it is not a backslash insert/append at the front of a deque.
- If it is the third backslash end
I have successfully written a horrible solution below, can it be made pretty using streams?
Deque<String> lifo = new ArrayDeque<>();
int count = 0;
for (int i = testUrl.length() - 1; count < 3 ; --i) {
if (testUrl.codePointAt(i) == ((int) '/') ) {
++count;
continue;
}
result.addFirst(testUrl.substring(i,i+1));
}
String foo = result.stream().collect(Collectors.joining());
assertThat(foo).isEqualTo("a606KAM1");
java java-8 java-stream
java java-8 java-stream
edited Nov 14 '18 at 17:35
Stefan Zobel
2,44031828
2,44031828
asked Nov 14 '18 at 14:10
ShakkaShakka
514
514
add a comment |
add a comment |
6 Answers
6
active
oldest
votes
Another way would be a regex:
String result = url.replaceAll(".+/(.+)/(.+)/(.+)", "$1$2$3");
this is the slowest one, see my answer; this does not matter in tests, however regexps should be used with care in production code
– Alex
Nov 14 '18 at 15:14
@Alex have you ever thought that may be your tests are wrong?
– Eugene
Nov 14 '18 at 15:19
they very well may be, but you are free to look at the code and point any mistakes to me or do your own benchmarking
– Alex
Nov 14 '18 at 15:30
@Alex I am at the same time free not to do that, also.
– Eugene
Nov 14 '18 at 15:31
@Alex when you really want a fast solution, you would use neither of these solutions, as it’s not the chosen framework, but the data movement that matters here. Compare with this answer. However, you should really check your test code against the points of this answer.
– Holger
Nov 14 '18 at 20:41
|
show 5 more comments
You may do it like so,
final String splitStrArr = url.split("/");
String result = Arrays.stream(splitStrArr).skip(splitStrArr.length - 3)
.collect(Collectors.joining(""));
1
"I want to "stream" the string starting from the back." You're not starting from the back
– Michael
Nov 14 '18 at 14:26
3
One more thing. You can omit the empty string inCollectors.joining()
. The result will be the same.
– ETO
Nov 14 '18 at 14:28
@Michael What he wants is to concat last three tokens in the url. For an instance this is what he expects a555data1 given the url https://.../foo/a/555/data1. So the direction of traversal is rather an implementation detail. Some other strategy to solve the problem may mandate you to traverse backward.
– Ravindra Ranwala
Nov 14 '18 at 15:05
@ETO That makes sense. Thanks !
– Ravindra Ranwala
Nov 14 '18 at 15:06
add a comment |
An alternative solution without loops and streams:
String split = url.split("/");
int n = split.length;
return split[n - 3] + split[n - 2] + split[n - 1];
Thats a nice solution if you never need to change amount of slashes you have to pass.
– Worthless
Nov 14 '18 at 14:26
@Worthless personally, I would do it with a regex, but this solution is the easiest one to read.
– Eugene
Nov 14 '18 at 14:36
add a comment |
If you want to do it really fast, you have to reduce the amount of data copying happening with every string construction.
int ix1 = url.lastIndexOf('/'), ix2 = url.lastIndexOf('/', ix1-1),
ix3 = url.lastIndexOf('/', ix2-1);
String result = new StringBuilder(url.length() - ix3 - 3)
.append(url, ix3+1, ix2)
.append(url, ix2+1, ix1)
.append(url, ix1+1, url.length())
.toString();
Even when you expand it to support a configurable number of parts,
int chunks = 3;
int ix = new int[chunks];
int index = url.length();
for(int a = ix.length-1; a >= 0; a--) index = url.lastIndexOf('/', (ix[a] = index)-1);
StringBuilder sb = new StringBuilder(url.length() - index - chunks);
for(int next: ix) sb.append(url, index+1, index = next);
String result = sb.toString();
it’s likely faster than all alternatives.
add a comment |
Initially my thought was that streams should not be used here due to supposed performance overhead, so I created a little performance test for solutions proposed in another answers:
import static java.util.Arrays.stream;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(value = 1)
public class CJMH {
@State(Scope.Thread)
public static class CState {
public String url = "https://.../foo/a/555/data1";
}
@Benchmark
public String fastest(CState state) {
String url = state.url;
int chunks = 3;
int ix = new int[chunks];
int index = url.length();
for(int a = ix.length-1; a >= 0; a--) index = url.lastIndexOf('/', (ix[a] = index)-1);
StringBuilder sb = new StringBuilder(url.length() - index - chunks);
for(int next: ix) sb.append(url, index+1, index = next);
return sb.toString();
}
@Benchmark
public String splitAndStreams(CState state) {
final String splitStrArr = state.url.split("/");
String result = stream(splitStrArr).
skip(splitStrArr.length - 3).
collect(Collectors.joining(""));
return result;
};
@Benchmark
public String splitAndIterate(CState state) {
final String splitStrArr = state.url.split("/");
String result = "";
for (int k=splitStrArr.length - 3; k<splitStrArr.length; k++) {
result += splitStrArr[k];
}
return result;
};
@Benchmark
public String splitAndSum(CState state) {
String split = state.url.split("/");
int n = split.length;
return split[n - 3] + split[n - 2] + split[n - 1];
};
@Benchmark
public String regexp(CState state) {
return state.url.replaceAll(".+/(.+)/(.+)/(.+)", "$1$2$3");
};
}
And the output was:
Benchmark Mode Cnt Score Error Units
CJMH.fastest avgt 5 46.731 ± 0.445 ns/op
CJMH.regexp avgt 5 937.797 ± 11.928 ns/op
CJMH.splitAndIterate avgt 5 194.626 ± 1.880 ns/op
CJMH.splitAndStreams avgt 5 275.640 ± 1.887 ns/op
CJMH.splitAndSum avgt 5 180.257 ± 2.986 ns/op
So surprisingly streams are in no way much slower than iterating over the array. The fastest one is a no-copy algorithm provided by @Holger in this answer. And do not use regexps if you could avoid it!
Note that usingCollectors.joining()
may be faster thanCollectors.joining("")
. Also, thesplitAndSum
performance may depend on the platform; compiling for Java 9 and newer will use the improved string concatenation, hence, may perform even better.
– Holger
Nov 15 '18 at 14:54
@Holger yes,Collectors.joining()
gives about 250 ns/op forsplitAndStreams
(10% faster) on Java 8, and using Java 11 reducessplit*
algorithms times for another 10-15%. Strangely, regexp algorithm performs even worse on Java 11 giving about 1000ns/op.
– Alex
Nov 16 '18 at 8:40
add a comment |
I'd probably simplify your code a bit to:
StringBuilder sb = new StringBuilder();
char c;
for (int i = testUrl.length() - 1, count = 0; count < 3 ; --i) {
if ((c = testUrl.charAt( i )) == '/') {
++count;
continue;
}
sb.append( c );
}
String foo = sb.reverse().toString();
In my opinion there is no real point in using stream here - the url isn't long enough to justify effor spent setting up stream. Also we can use StringBuilder - which will be used during joining anyways.
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53302179%2fjava-8-how-to-build-up-a-string-from-initial-string-with-only-one-traversal%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
6 Answers
6
active
oldest
votes
6 Answers
6
active
oldest
votes
active
oldest
votes
active
oldest
votes
Another way would be a regex:
String result = url.replaceAll(".+/(.+)/(.+)/(.+)", "$1$2$3");
this is the slowest one, see my answer; this does not matter in tests, however regexps should be used with care in production code
– Alex
Nov 14 '18 at 15:14
@Alex have you ever thought that may be your tests are wrong?
– Eugene
Nov 14 '18 at 15:19
they very well may be, but you are free to look at the code and point any mistakes to me or do your own benchmarking
– Alex
Nov 14 '18 at 15:30
@Alex I am at the same time free not to do that, also.
– Eugene
Nov 14 '18 at 15:31
@Alex when you really want a fast solution, you would use neither of these solutions, as it’s not the chosen framework, but the data movement that matters here. Compare with this answer. However, you should really check your test code against the points of this answer.
– Holger
Nov 14 '18 at 20:41
|
show 5 more comments
Another way would be a regex:
String result = url.replaceAll(".+/(.+)/(.+)/(.+)", "$1$2$3");
this is the slowest one, see my answer; this does not matter in tests, however regexps should be used with care in production code
– Alex
Nov 14 '18 at 15:14
@Alex have you ever thought that may be your tests are wrong?
– Eugene
Nov 14 '18 at 15:19
they very well may be, but you are free to look at the code and point any mistakes to me or do your own benchmarking
– Alex
Nov 14 '18 at 15:30
@Alex I am at the same time free not to do that, also.
– Eugene
Nov 14 '18 at 15:31
@Alex when you really want a fast solution, you would use neither of these solutions, as it’s not the chosen framework, but the data movement that matters here. Compare with this answer. However, you should really check your test code against the points of this answer.
– Holger
Nov 14 '18 at 20:41
|
show 5 more comments
Another way would be a regex:
String result = url.replaceAll(".+/(.+)/(.+)/(.+)", "$1$2$3");
Another way would be a regex:
String result = url.replaceAll(".+/(.+)/(.+)/(.+)", "$1$2$3");
answered Nov 14 '18 at 14:34
EugeneEugene
69.3k999164
69.3k999164
this is the slowest one, see my answer; this does not matter in tests, however regexps should be used with care in production code
– Alex
Nov 14 '18 at 15:14
@Alex have you ever thought that may be your tests are wrong?
– Eugene
Nov 14 '18 at 15:19
they very well may be, but you are free to look at the code and point any mistakes to me or do your own benchmarking
– Alex
Nov 14 '18 at 15:30
@Alex I am at the same time free not to do that, also.
– Eugene
Nov 14 '18 at 15:31
@Alex when you really want a fast solution, you would use neither of these solutions, as it’s not the chosen framework, but the data movement that matters here. Compare with this answer. However, you should really check your test code against the points of this answer.
– Holger
Nov 14 '18 at 20:41
|
show 5 more comments
this is the slowest one, see my answer; this does not matter in tests, however regexps should be used with care in production code
– Alex
Nov 14 '18 at 15:14
@Alex have you ever thought that may be your tests are wrong?
– Eugene
Nov 14 '18 at 15:19
they very well may be, but you are free to look at the code and point any mistakes to me or do your own benchmarking
– Alex
Nov 14 '18 at 15:30
@Alex I am at the same time free not to do that, also.
– Eugene
Nov 14 '18 at 15:31
@Alex when you really want a fast solution, you would use neither of these solutions, as it’s not the chosen framework, but the data movement that matters here. Compare with this answer. However, you should really check your test code against the points of this answer.
– Holger
Nov 14 '18 at 20:41
this is the slowest one, see my answer; this does not matter in tests, however regexps should be used with care in production code
– Alex
Nov 14 '18 at 15:14
this is the slowest one, see my answer; this does not matter in tests, however regexps should be used with care in production code
– Alex
Nov 14 '18 at 15:14
@Alex have you ever thought that may be your tests are wrong?
– Eugene
Nov 14 '18 at 15:19
@Alex have you ever thought that may be your tests are wrong?
– Eugene
Nov 14 '18 at 15:19
they very well may be, but you are free to look at the code and point any mistakes to me or do your own benchmarking
– Alex
Nov 14 '18 at 15:30
they very well may be, but you are free to look at the code and point any mistakes to me or do your own benchmarking
– Alex
Nov 14 '18 at 15:30
@Alex I am at the same time free not to do that, also.
– Eugene
Nov 14 '18 at 15:31
@Alex I am at the same time free not to do that, also.
– Eugene
Nov 14 '18 at 15:31
@Alex when you really want a fast solution, you would use neither of these solutions, as it’s not the chosen framework, but the data movement that matters here. Compare with this answer. However, you should really check your test code against the points of this answer.
– Holger
Nov 14 '18 at 20:41
@Alex when you really want a fast solution, you would use neither of these solutions, as it’s not the chosen framework, but the data movement that matters here. Compare with this answer. However, you should really check your test code against the points of this answer.
– Holger
Nov 14 '18 at 20:41
|
show 5 more comments
You may do it like so,
final String splitStrArr = url.split("/");
String result = Arrays.stream(splitStrArr).skip(splitStrArr.length - 3)
.collect(Collectors.joining(""));
1
"I want to "stream" the string starting from the back." You're not starting from the back
– Michael
Nov 14 '18 at 14:26
3
One more thing. You can omit the empty string inCollectors.joining()
. The result will be the same.
– ETO
Nov 14 '18 at 14:28
@Michael What he wants is to concat last three tokens in the url. For an instance this is what he expects a555data1 given the url https://.../foo/a/555/data1. So the direction of traversal is rather an implementation detail. Some other strategy to solve the problem may mandate you to traverse backward.
– Ravindra Ranwala
Nov 14 '18 at 15:05
@ETO That makes sense. Thanks !
– Ravindra Ranwala
Nov 14 '18 at 15:06
add a comment |
You may do it like so,
final String splitStrArr = url.split("/");
String result = Arrays.stream(splitStrArr).skip(splitStrArr.length - 3)
.collect(Collectors.joining(""));
1
"I want to "stream" the string starting from the back." You're not starting from the back
– Michael
Nov 14 '18 at 14:26
3
One more thing. You can omit the empty string inCollectors.joining()
. The result will be the same.
– ETO
Nov 14 '18 at 14:28
@Michael What he wants is to concat last three tokens in the url. For an instance this is what he expects a555data1 given the url https://.../foo/a/555/data1. So the direction of traversal is rather an implementation detail. Some other strategy to solve the problem may mandate you to traverse backward.
– Ravindra Ranwala
Nov 14 '18 at 15:05
@ETO That makes sense. Thanks !
– Ravindra Ranwala
Nov 14 '18 at 15:06
add a comment |
You may do it like so,
final String splitStrArr = url.split("/");
String result = Arrays.stream(splitStrArr).skip(splitStrArr.length - 3)
.collect(Collectors.joining(""));
You may do it like so,
final String splitStrArr = url.split("/");
String result = Arrays.stream(splitStrArr).skip(splitStrArr.length - 3)
.collect(Collectors.joining(""));
answered Nov 14 '18 at 14:17
Ravindra RanwalaRavindra Ranwala
8,83031634
8,83031634
1
"I want to "stream" the string starting from the back." You're not starting from the back
– Michael
Nov 14 '18 at 14:26
3
One more thing. You can omit the empty string inCollectors.joining()
. The result will be the same.
– ETO
Nov 14 '18 at 14:28
@Michael What he wants is to concat last three tokens in the url. For an instance this is what he expects a555data1 given the url https://.../foo/a/555/data1. So the direction of traversal is rather an implementation detail. Some other strategy to solve the problem may mandate you to traverse backward.
– Ravindra Ranwala
Nov 14 '18 at 15:05
@ETO That makes sense. Thanks !
– Ravindra Ranwala
Nov 14 '18 at 15:06
add a comment |
1
"I want to "stream" the string starting from the back." You're not starting from the back
– Michael
Nov 14 '18 at 14:26
3
One more thing. You can omit the empty string inCollectors.joining()
. The result will be the same.
– ETO
Nov 14 '18 at 14:28
@Michael What he wants is to concat last three tokens in the url. For an instance this is what he expects a555data1 given the url https://.../foo/a/555/data1. So the direction of traversal is rather an implementation detail. Some other strategy to solve the problem may mandate you to traverse backward.
– Ravindra Ranwala
Nov 14 '18 at 15:05
@ETO That makes sense. Thanks !
– Ravindra Ranwala
Nov 14 '18 at 15:06
1
1
"I want to "stream" the string starting from the back." You're not starting from the back
– Michael
Nov 14 '18 at 14:26
"I want to "stream" the string starting from the back." You're not starting from the back
– Michael
Nov 14 '18 at 14:26
3
3
One more thing. You can omit the empty string in
Collectors.joining()
. The result will be the same.– ETO
Nov 14 '18 at 14:28
One more thing. You can omit the empty string in
Collectors.joining()
. The result will be the same.– ETO
Nov 14 '18 at 14:28
@Michael What he wants is to concat last three tokens in the url. For an instance this is what he expects a555data1 given the url https://.../foo/a/555/data1. So the direction of traversal is rather an implementation detail. Some other strategy to solve the problem may mandate you to traverse backward.
– Ravindra Ranwala
Nov 14 '18 at 15:05
@Michael What he wants is to concat last three tokens in the url. For an instance this is what he expects a555data1 given the url https://.../foo/a/555/data1. So the direction of traversal is rather an implementation detail. Some other strategy to solve the problem may mandate you to traverse backward.
– Ravindra Ranwala
Nov 14 '18 at 15:05
@ETO That makes sense. Thanks !
– Ravindra Ranwala
Nov 14 '18 at 15:06
@ETO That makes sense. Thanks !
– Ravindra Ranwala
Nov 14 '18 at 15:06
add a comment |
An alternative solution without loops and streams:
String split = url.split("/");
int n = split.length;
return split[n - 3] + split[n - 2] + split[n - 1];
Thats a nice solution if you never need to change amount of slashes you have to pass.
– Worthless
Nov 14 '18 at 14:26
@Worthless personally, I would do it with a regex, but this solution is the easiest one to read.
– Eugene
Nov 14 '18 at 14:36
add a comment |
An alternative solution without loops and streams:
String split = url.split("/");
int n = split.length;
return split[n - 3] + split[n - 2] + split[n - 1];
Thats a nice solution if you never need to change amount of slashes you have to pass.
– Worthless
Nov 14 '18 at 14:26
@Worthless personally, I would do it with a regex, but this solution is the easiest one to read.
– Eugene
Nov 14 '18 at 14:36
add a comment |
An alternative solution without loops and streams:
String split = url.split("/");
int n = split.length;
return split[n - 3] + split[n - 2] + split[n - 1];
An alternative solution without loops and streams:
String split = url.split("/");
int n = split.length;
return split[n - 3] + split[n - 2] + split[n - 1];
answered Nov 14 '18 at 14:23
Schidu LucaSchidu Luca
2,834520
2,834520
Thats a nice solution if you never need to change amount of slashes you have to pass.
– Worthless
Nov 14 '18 at 14:26
@Worthless personally, I would do it with a regex, but this solution is the easiest one to read.
– Eugene
Nov 14 '18 at 14:36
add a comment |
Thats a nice solution if you never need to change amount of slashes you have to pass.
– Worthless
Nov 14 '18 at 14:26
@Worthless personally, I would do it with a regex, but this solution is the easiest one to read.
– Eugene
Nov 14 '18 at 14:36
Thats a nice solution if you never need to change amount of slashes you have to pass.
– Worthless
Nov 14 '18 at 14:26
Thats a nice solution if you never need to change amount of slashes you have to pass.
– Worthless
Nov 14 '18 at 14:26
@Worthless personally, I would do it with a regex, but this solution is the easiest one to read.
– Eugene
Nov 14 '18 at 14:36
@Worthless personally, I would do it with a regex, but this solution is the easiest one to read.
– Eugene
Nov 14 '18 at 14:36
add a comment |
If you want to do it really fast, you have to reduce the amount of data copying happening with every string construction.
int ix1 = url.lastIndexOf('/'), ix2 = url.lastIndexOf('/', ix1-1),
ix3 = url.lastIndexOf('/', ix2-1);
String result = new StringBuilder(url.length() - ix3 - 3)
.append(url, ix3+1, ix2)
.append(url, ix2+1, ix1)
.append(url, ix1+1, url.length())
.toString();
Even when you expand it to support a configurable number of parts,
int chunks = 3;
int ix = new int[chunks];
int index = url.length();
for(int a = ix.length-1; a >= 0; a--) index = url.lastIndexOf('/', (ix[a] = index)-1);
StringBuilder sb = new StringBuilder(url.length() - index - chunks);
for(int next: ix) sb.append(url, index+1, index = next);
String result = sb.toString();
it’s likely faster than all alternatives.
add a comment |
If you want to do it really fast, you have to reduce the amount of data copying happening with every string construction.
int ix1 = url.lastIndexOf('/'), ix2 = url.lastIndexOf('/', ix1-1),
ix3 = url.lastIndexOf('/', ix2-1);
String result = new StringBuilder(url.length() - ix3 - 3)
.append(url, ix3+1, ix2)
.append(url, ix2+1, ix1)
.append(url, ix1+1, url.length())
.toString();
Even when you expand it to support a configurable number of parts,
int chunks = 3;
int ix = new int[chunks];
int index = url.length();
for(int a = ix.length-1; a >= 0; a--) index = url.lastIndexOf('/', (ix[a] = index)-1);
StringBuilder sb = new StringBuilder(url.length() - index - chunks);
for(int next: ix) sb.append(url, index+1, index = next);
String result = sb.toString();
it’s likely faster than all alternatives.
add a comment |
If you want to do it really fast, you have to reduce the amount of data copying happening with every string construction.
int ix1 = url.lastIndexOf('/'), ix2 = url.lastIndexOf('/', ix1-1),
ix3 = url.lastIndexOf('/', ix2-1);
String result = new StringBuilder(url.length() - ix3 - 3)
.append(url, ix3+1, ix2)
.append(url, ix2+1, ix1)
.append(url, ix1+1, url.length())
.toString();
Even when you expand it to support a configurable number of parts,
int chunks = 3;
int ix = new int[chunks];
int index = url.length();
for(int a = ix.length-1; a >= 0; a--) index = url.lastIndexOf('/', (ix[a] = index)-1);
StringBuilder sb = new StringBuilder(url.length() - index - chunks);
for(int next: ix) sb.append(url, index+1, index = next);
String result = sb.toString();
it’s likely faster than all alternatives.
If you want to do it really fast, you have to reduce the amount of data copying happening with every string construction.
int ix1 = url.lastIndexOf('/'), ix2 = url.lastIndexOf('/', ix1-1),
ix3 = url.lastIndexOf('/', ix2-1);
String result = new StringBuilder(url.length() - ix3 - 3)
.append(url, ix3+1, ix2)
.append(url, ix2+1, ix1)
.append(url, ix1+1, url.length())
.toString();
Even when you expand it to support a configurable number of parts,
int chunks = 3;
int ix = new int[chunks];
int index = url.length();
for(int a = ix.length-1; a >= 0; a--) index = url.lastIndexOf('/', (ix[a] = index)-1);
StringBuilder sb = new StringBuilder(url.length() - index - chunks);
for(int next: ix) sb.append(url, index+1, index = next);
String result = sb.toString();
it’s likely faster than all alternatives.
answered Nov 14 '18 at 20:37
HolgerHolger
163k23231438
163k23231438
add a comment |
add a comment |
Initially my thought was that streams should not be used here due to supposed performance overhead, so I created a little performance test for solutions proposed in another answers:
import static java.util.Arrays.stream;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(value = 1)
public class CJMH {
@State(Scope.Thread)
public static class CState {
public String url = "https://.../foo/a/555/data1";
}
@Benchmark
public String fastest(CState state) {
String url = state.url;
int chunks = 3;
int ix = new int[chunks];
int index = url.length();
for(int a = ix.length-1; a >= 0; a--) index = url.lastIndexOf('/', (ix[a] = index)-1);
StringBuilder sb = new StringBuilder(url.length() - index - chunks);
for(int next: ix) sb.append(url, index+1, index = next);
return sb.toString();
}
@Benchmark
public String splitAndStreams(CState state) {
final String splitStrArr = state.url.split("/");
String result = stream(splitStrArr).
skip(splitStrArr.length - 3).
collect(Collectors.joining(""));
return result;
};
@Benchmark
public String splitAndIterate(CState state) {
final String splitStrArr = state.url.split("/");
String result = "";
for (int k=splitStrArr.length - 3; k<splitStrArr.length; k++) {
result += splitStrArr[k];
}
return result;
};
@Benchmark
public String splitAndSum(CState state) {
String split = state.url.split("/");
int n = split.length;
return split[n - 3] + split[n - 2] + split[n - 1];
};
@Benchmark
public String regexp(CState state) {
return state.url.replaceAll(".+/(.+)/(.+)/(.+)", "$1$2$3");
};
}
And the output was:
Benchmark Mode Cnt Score Error Units
CJMH.fastest avgt 5 46.731 ± 0.445 ns/op
CJMH.regexp avgt 5 937.797 ± 11.928 ns/op
CJMH.splitAndIterate avgt 5 194.626 ± 1.880 ns/op
CJMH.splitAndStreams avgt 5 275.640 ± 1.887 ns/op
CJMH.splitAndSum avgt 5 180.257 ± 2.986 ns/op
So surprisingly streams are in no way much slower than iterating over the array. The fastest one is a no-copy algorithm provided by @Holger in this answer. And do not use regexps if you could avoid it!
Note that usingCollectors.joining()
may be faster thanCollectors.joining("")
. Also, thesplitAndSum
performance may depend on the platform; compiling for Java 9 and newer will use the improved string concatenation, hence, may perform even better.
– Holger
Nov 15 '18 at 14:54
@Holger yes,Collectors.joining()
gives about 250 ns/op forsplitAndStreams
(10% faster) on Java 8, and using Java 11 reducessplit*
algorithms times for another 10-15%. Strangely, regexp algorithm performs even worse on Java 11 giving about 1000ns/op.
– Alex
Nov 16 '18 at 8:40
add a comment |
Initially my thought was that streams should not be used here due to supposed performance overhead, so I created a little performance test for solutions proposed in another answers:
import static java.util.Arrays.stream;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(value = 1)
public class CJMH {
@State(Scope.Thread)
public static class CState {
public String url = "https://.../foo/a/555/data1";
}
@Benchmark
public String fastest(CState state) {
String url = state.url;
int chunks = 3;
int ix = new int[chunks];
int index = url.length();
for(int a = ix.length-1; a >= 0; a--) index = url.lastIndexOf('/', (ix[a] = index)-1);
StringBuilder sb = new StringBuilder(url.length() - index - chunks);
for(int next: ix) sb.append(url, index+1, index = next);
return sb.toString();
}
@Benchmark
public String splitAndStreams(CState state) {
final String splitStrArr = state.url.split("/");
String result = stream(splitStrArr).
skip(splitStrArr.length - 3).
collect(Collectors.joining(""));
return result;
};
@Benchmark
public String splitAndIterate(CState state) {
final String splitStrArr = state.url.split("/");
String result = "";
for (int k=splitStrArr.length - 3; k<splitStrArr.length; k++) {
result += splitStrArr[k];
}
return result;
};
@Benchmark
public String splitAndSum(CState state) {
String split = state.url.split("/");
int n = split.length;
return split[n - 3] + split[n - 2] + split[n - 1];
};
@Benchmark
public String regexp(CState state) {
return state.url.replaceAll(".+/(.+)/(.+)/(.+)", "$1$2$3");
};
}
And the output was:
Benchmark Mode Cnt Score Error Units
CJMH.fastest avgt 5 46.731 ± 0.445 ns/op
CJMH.regexp avgt 5 937.797 ± 11.928 ns/op
CJMH.splitAndIterate avgt 5 194.626 ± 1.880 ns/op
CJMH.splitAndStreams avgt 5 275.640 ± 1.887 ns/op
CJMH.splitAndSum avgt 5 180.257 ± 2.986 ns/op
So surprisingly streams are in no way much slower than iterating over the array. The fastest one is a no-copy algorithm provided by @Holger in this answer. And do not use regexps if you could avoid it!
Note that usingCollectors.joining()
may be faster thanCollectors.joining("")
. Also, thesplitAndSum
performance may depend on the platform; compiling for Java 9 and newer will use the improved string concatenation, hence, may perform even better.
– Holger
Nov 15 '18 at 14:54
@Holger yes,Collectors.joining()
gives about 250 ns/op forsplitAndStreams
(10% faster) on Java 8, and using Java 11 reducessplit*
algorithms times for another 10-15%. Strangely, regexp algorithm performs even worse on Java 11 giving about 1000ns/op.
– Alex
Nov 16 '18 at 8:40
add a comment |
Initially my thought was that streams should not be used here due to supposed performance overhead, so I created a little performance test for solutions proposed in another answers:
import static java.util.Arrays.stream;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(value = 1)
public class CJMH {
@State(Scope.Thread)
public static class CState {
public String url = "https://.../foo/a/555/data1";
}
@Benchmark
public String fastest(CState state) {
String url = state.url;
int chunks = 3;
int ix = new int[chunks];
int index = url.length();
for(int a = ix.length-1; a >= 0; a--) index = url.lastIndexOf('/', (ix[a] = index)-1);
StringBuilder sb = new StringBuilder(url.length() - index - chunks);
for(int next: ix) sb.append(url, index+1, index = next);
return sb.toString();
}
@Benchmark
public String splitAndStreams(CState state) {
final String splitStrArr = state.url.split("/");
String result = stream(splitStrArr).
skip(splitStrArr.length - 3).
collect(Collectors.joining(""));
return result;
};
@Benchmark
public String splitAndIterate(CState state) {
final String splitStrArr = state.url.split("/");
String result = "";
for (int k=splitStrArr.length - 3; k<splitStrArr.length; k++) {
result += splitStrArr[k];
}
return result;
};
@Benchmark
public String splitAndSum(CState state) {
String split = state.url.split("/");
int n = split.length;
return split[n - 3] + split[n - 2] + split[n - 1];
};
@Benchmark
public String regexp(CState state) {
return state.url.replaceAll(".+/(.+)/(.+)/(.+)", "$1$2$3");
};
}
And the output was:
Benchmark Mode Cnt Score Error Units
CJMH.fastest avgt 5 46.731 ± 0.445 ns/op
CJMH.regexp avgt 5 937.797 ± 11.928 ns/op
CJMH.splitAndIterate avgt 5 194.626 ± 1.880 ns/op
CJMH.splitAndStreams avgt 5 275.640 ± 1.887 ns/op
CJMH.splitAndSum avgt 5 180.257 ± 2.986 ns/op
So surprisingly streams are in no way much slower than iterating over the array. The fastest one is a no-copy algorithm provided by @Holger in this answer. And do not use regexps if you could avoid it!
Initially my thought was that streams should not be used here due to supposed performance overhead, so I created a little performance test for solutions proposed in another answers:
import static java.util.Arrays.stream;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(value = 1)
public class CJMH {
@State(Scope.Thread)
public static class CState {
public String url = "https://.../foo/a/555/data1";
}
@Benchmark
public String fastest(CState state) {
String url = state.url;
int chunks = 3;
int ix = new int[chunks];
int index = url.length();
for(int a = ix.length-1; a >= 0; a--) index = url.lastIndexOf('/', (ix[a] = index)-1);
StringBuilder sb = new StringBuilder(url.length() - index - chunks);
for(int next: ix) sb.append(url, index+1, index = next);
return sb.toString();
}
@Benchmark
public String splitAndStreams(CState state) {
final String splitStrArr = state.url.split("/");
String result = stream(splitStrArr).
skip(splitStrArr.length - 3).
collect(Collectors.joining(""));
return result;
};
@Benchmark
public String splitAndIterate(CState state) {
final String splitStrArr = state.url.split("/");
String result = "";
for (int k=splitStrArr.length - 3; k<splitStrArr.length; k++) {
result += splitStrArr[k];
}
return result;
};
@Benchmark
public String splitAndSum(CState state) {
String split = state.url.split("/");
int n = split.length;
return split[n - 3] + split[n - 2] + split[n - 1];
};
@Benchmark
public String regexp(CState state) {
return state.url.replaceAll(".+/(.+)/(.+)/(.+)", "$1$2$3");
};
}
And the output was:
Benchmark Mode Cnt Score Error Units
CJMH.fastest avgt 5 46.731 ± 0.445 ns/op
CJMH.regexp avgt 5 937.797 ± 11.928 ns/op
CJMH.splitAndIterate avgt 5 194.626 ± 1.880 ns/op
CJMH.splitAndStreams avgt 5 275.640 ± 1.887 ns/op
CJMH.splitAndSum avgt 5 180.257 ± 2.986 ns/op
So surprisingly streams are in no way much slower than iterating over the array. The fastest one is a no-copy algorithm provided by @Holger in this answer. And do not use regexps if you could avoid it!
edited Nov 15 '18 at 12:32
answered Nov 14 '18 at 15:06
AlexAlex
546210
546210
Note that usingCollectors.joining()
may be faster thanCollectors.joining("")
. Also, thesplitAndSum
performance may depend on the platform; compiling for Java 9 and newer will use the improved string concatenation, hence, may perform even better.
– Holger
Nov 15 '18 at 14:54
@Holger yes,Collectors.joining()
gives about 250 ns/op forsplitAndStreams
(10% faster) on Java 8, and using Java 11 reducessplit*
algorithms times for another 10-15%. Strangely, regexp algorithm performs even worse on Java 11 giving about 1000ns/op.
– Alex
Nov 16 '18 at 8:40
add a comment |
Note that usingCollectors.joining()
may be faster thanCollectors.joining("")
. Also, thesplitAndSum
performance may depend on the platform; compiling for Java 9 and newer will use the improved string concatenation, hence, may perform even better.
– Holger
Nov 15 '18 at 14:54
@Holger yes,Collectors.joining()
gives about 250 ns/op forsplitAndStreams
(10% faster) on Java 8, and using Java 11 reducessplit*
algorithms times for another 10-15%. Strangely, regexp algorithm performs even worse on Java 11 giving about 1000ns/op.
– Alex
Nov 16 '18 at 8:40
Note that using
Collectors.joining()
may be faster than Collectors.joining("")
. Also, the splitAndSum
performance may depend on the platform; compiling for Java 9 and newer will use the improved string concatenation, hence, may perform even better.– Holger
Nov 15 '18 at 14:54
Note that using
Collectors.joining()
may be faster than Collectors.joining("")
. Also, the splitAndSum
performance may depend on the platform; compiling for Java 9 and newer will use the improved string concatenation, hence, may perform even better.– Holger
Nov 15 '18 at 14:54
@Holger yes,
Collectors.joining()
gives about 250 ns/op for splitAndStreams
(10% faster) on Java 8, and using Java 11 reduces split*
algorithms times for another 10-15%. Strangely, regexp algorithm performs even worse on Java 11 giving about 1000ns/op.– Alex
Nov 16 '18 at 8:40
@Holger yes,
Collectors.joining()
gives about 250 ns/op for splitAndStreams
(10% faster) on Java 8, and using Java 11 reduces split*
algorithms times for another 10-15%. Strangely, regexp algorithm performs even worse on Java 11 giving about 1000ns/op.– Alex
Nov 16 '18 at 8:40
add a comment |
I'd probably simplify your code a bit to:
StringBuilder sb = new StringBuilder();
char c;
for (int i = testUrl.length() - 1, count = 0; count < 3 ; --i) {
if ((c = testUrl.charAt( i )) == '/') {
++count;
continue;
}
sb.append( c );
}
String foo = sb.reverse().toString();
In my opinion there is no real point in using stream here - the url isn't long enough to justify effor spent setting up stream. Also we can use StringBuilder - which will be used during joining anyways.
add a comment |
I'd probably simplify your code a bit to:
StringBuilder sb = new StringBuilder();
char c;
for (int i = testUrl.length() - 1, count = 0; count < 3 ; --i) {
if ((c = testUrl.charAt( i )) == '/') {
++count;
continue;
}
sb.append( c );
}
String foo = sb.reverse().toString();
In my opinion there is no real point in using stream here - the url isn't long enough to justify effor spent setting up stream. Also we can use StringBuilder - which will be used during joining anyways.
add a comment |
I'd probably simplify your code a bit to:
StringBuilder sb = new StringBuilder();
char c;
for (int i = testUrl.length() - 1, count = 0; count < 3 ; --i) {
if ((c = testUrl.charAt( i )) == '/') {
++count;
continue;
}
sb.append( c );
}
String foo = sb.reverse().toString();
In my opinion there is no real point in using stream here - the url isn't long enough to justify effor spent setting up stream. Also we can use StringBuilder - which will be used during joining anyways.
I'd probably simplify your code a bit to:
StringBuilder sb = new StringBuilder();
char c;
for (int i = testUrl.length() - 1, count = 0; count < 3 ; --i) {
if ((c = testUrl.charAt( i )) == '/') {
++count;
continue;
}
sb.append( c );
}
String foo = sb.reverse().toString();
In my opinion there is no real point in using stream here - the url isn't long enough to justify effor spent setting up stream. Also we can use StringBuilder - which will be used during joining anyways.
answered Nov 14 '18 at 14:25
WorthlessWorthless
26716
26716
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53302179%2fjava-8-how-to-build-up-a-string-from-initial-string-with-only-one-traversal%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown