Java 8 - How to build up a string from initial string with only one traversal












3















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:




  1. I want to "stream" the string starting from the back.

  2. If it is not a backslash insert/append at the front of a deque.

  3. 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");









share|improve this question





























    3















    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:




    1. I want to "stream" the string starting from the back.

    2. If it is not a backslash insert/append at the front of a deque.

    3. 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");









    share|improve this question



























      3












      3








      3


      1






      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:




      1. I want to "stream" the string starting from the back.

      2. If it is not a backslash insert/append at the front of a deque.

      3. 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");









      share|improve this question
















      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:




      1. I want to "stream" the string starting from the back.

      2. If it is not a backslash insert/append at the front of a deque.

      3. 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






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Nov 14 '18 at 17:35









      Stefan Zobel

      2,44031828




      2,44031828










      asked Nov 14 '18 at 14:10









      ShakkaShakka

      514




      514
























          6 Answers
          6






          active

          oldest

          votes


















          4














          Another way would be a regex:



          String result = url.replaceAll(".+/(.+)/(.+)/(.+)", "$1$2$3");





          share|improve this answer
























          • 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





















          3














          You may do it like so,



          final String splitStrArr = url.split("/");
          String result = Arrays.stream(splitStrArr).skip(splitStrArr.length - 3)
          .collect(Collectors.joining(""));





          share|improve this answer



















          • 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 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











          • @ETO That makes sense. Thanks !

            – Ravindra Ranwala
            Nov 14 '18 at 15:06



















          3














          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];





          share|improve this answer
























          • 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





















          3














          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.






          share|improve this answer































            1














            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!






            share|improve this answer


























            • 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



















            0














            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.






            share|improve this answer























              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
              });


              }
              });














              draft saved

              draft discarded


















              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









              4














              Another way would be a regex:



              String result = url.replaceAll(".+/(.+)/(.+)/(.+)", "$1$2$3");





              share|improve this answer
























              • 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


















              4














              Another way would be a regex:



              String result = url.replaceAll(".+/(.+)/(.+)/(.+)", "$1$2$3");





              share|improve this answer
























              • 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
















              4












              4








              4







              Another way would be a regex:



              String result = url.replaceAll(".+/(.+)/(.+)/(.+)", "$1$2$3");





              share|improve this answer













              Another way would be a regex:



              String result = url.replaceAll(".+/(.+)/(.+)/(.+)", "$1$2$3");






              share|improve this answer












              share|improve this answer



              share|improve this answer










              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





















              • 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















              3














              You may do it like so,



              final String splitStrArr = url.split("/");
              String result = Arrays.stream(splitStrArr).skip(splitStrArr.length - 3)
              .collect(Collectors.joining(""));





              share|improve this answer



















              • 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 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











              • @ETO That makes sense. Thanks !

                – Ravindra Ranwala
                Nov 14 '18 at 15:06
















              3














              You may do it like so,



              final String splitStrArr = url.split("/");
              String result = Arrays.stream(splitStrArr).skip(splitStrArr.length - 3)
              .collect(Collectors.joining(""));





              share|improve this answer



















              • 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 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











              • @ETO That makes sense. Thanks !

                – Ravindra Ranwala
                Nov 14 '18 at 15:06














              3












              3








              3







              You may do it like so,



              final String splitStrArr = url.split("/");
              String result = Arrays.stream(splitStrArr).skip(splitStrArr.length - 3)
              .collect(Collectors.joining(""));





              share|improve this answer













              You may do it like so,



              final String splitStrArr = url.split("/");
              String result = Arrays.stream(splitStrArr).skip(splitStrArr.length - 3)
              .collect(Collectors.joining(""));






              share|improve this answer












              share|improve this answer



              share|improve this answer










              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 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











              • @ETO That makes sense. Thanks !

                – Ravindra Ranwala
                Nov 14 '18 at 15:06














              • 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 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











              • @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











              3














              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];





              share|improve this answer
























              • 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


















              3














              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];





              share|improve this answer
























              • 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
















              3












              3








              3







              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];





              share|improve this answer













              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];






              share|improve this answer












              share|improve this answer



              share|improve this answer










              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





















              • 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













              3














              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.






              share|improve this answer




























                3














                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.






                share|improve this answer


























                  3












                  3








                  3







                  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.






                  share|improve this answer













                  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.







                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered Nov 14 '18 at 20:37









                  HolgerHolger

                  163k23231438




                  163k23231438























                      1














                      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!






                      share|improve this answer


























                      • 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
















                      1














                      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!






                      share|improve this answer


























                      • 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














                      1












                      1








                      1







                      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!






                      share|improve this answer















                      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!







                      share|improve this answer














                      share|improve this answer



                      share|improve this answer








                      edited Nov 15 '18 at 12:32

























                      answered Nov 14 '18 at 15:06









                      AlexAlex

                      546210




                      546210













                      • 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



















                      • 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

















                      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











                      0














                      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.






                      share|improve this answer




























                        0














                        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.






                        share|improve this answer


























                          0












                          0








                          0







                          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.






                          share|improve this answer













                          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.







                          share|improve this answer












                          share|improve this answer



                          share|improve this answer










                          answered Nov 14 '18 at 14:25









                          WorthlessWorthless

                          26716




                          26716






























                              draft saved

                              draft discarded




















































                              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.




                              draft saved


                              draft discarded














                              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





















































                              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







                              這個網誌中的熱門文章

                              Xamarin.form Move up view when keyboard appear

                              Post-Redirect-Get with Spring WebFlux and Thymeleaf

                              Anylogic : not able to use stopDelay()