Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8054221

StringJoiner imlementation optimization

XMLWordPrintable

    • Icon: Enhancement Enhancement
    • Resolution: Fixed
    • Icon: P5 P5
    • 9
    • 8
    • core-libs
    • None
    • b28
    • generic
    • generic
    • Verified

        Proposal by Martin Buchholz:

        http://mail.openjdk.java.net/pipermail/core-libs-dev/2014-July/027923.html
        Webrev: http://cr.openjdk.java.net/~martin/webrevs/openjdk9/StringJoiner-optimization/

        --- old/src/share/classes/java/util/StringJoiner.java 2014-07-19 08:53:55.255284000 -0700
        +++ new/src/share/classes/java/util/StringJoiner.java 2014-07-19 08:53:55.124283000 -0700
        @@ -24,6 +24,9 @@
          */
         package java.util;
         
        +import sun.misc.JavaLangAccess;
        +import sun.misc.SharedSecrets;
        +
         /**
          * {@code StringJoiner} is used to construct a sequence of characters separated
          * by a delimiter and optionally starting with a supplied prefix
        @@ -67,22 +70,24 @@
             private final String delimiter;
             private final String suffix;
         
        - /*
        - * StringBuilder value -- at any time, the characters constructed from the
        - * prefix, the added element separated by the delimiter, but without the
        - * suffix, so that we can more easily add elements without having to jigger
        - * the suffix each time.
        - */
        - private StringBuilder value;
        -
        - /*
        - * By default, the string consisting of prefix+suffix, returned by
        - * toString(), or properties of value, when no elements have yet been added,
        - * i.e. when it is empty. This may be overridden by the user to be some
        - * other value including the empty String.
        + /** Contains all the string components added so far. */
        + private String[] elts;
        +
        + /** The number of string components added so far. */
        + private int size;
        +
        + /** Total length in chars so far, excluding prefix and suffix. */
        + private int len;
        +
        + /**
        + * When overriden by the user to be non-null via {@link setEmptyValue}, the
        + * string returned by toString() when no elements have yet been added.
        + * When null, prefix + suffix is used as the empty value.
              */
             private String emptyValue;
         
        + private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
        +
             /**
              * Constructs a {@code StringJoiner} with no characters in it, with no
              * {@code prefix} or {@code suffix}, and a copy of the supplied
        @@ -125,7 +130,6 @@
                 this.prefix = prefix.toString();
                 this.delimiter = delimiter.toString();
                 this.suffix = suffix.toString();
        - this.emptyValue = this.prefix + this.suffix;
             }
         
             /**
        @@ -148,29 +152,39 @@
                 return this;
             }
         
        + private static int getChars(String s, char[] chars, int start) {
        + int len = s.length();
        + s.getChars(0, len, chars, start);
        + return len;
        + }
        +
             /**
              * Returns the current value, consisting of the {@code prefix}, the values
              * added so far separated by the {@code delimiter}, and the {@code suffix},
              * unless no elements have been added in which case, the
        - * {@code prefix + suffix} or the {@code emptyValue} characters are returned
        + * {@code prefix + suffix} or the {@code emptyValue} characters are returned.
              *
              * @return the string representation of this {@code StringJoiner}
              */
             @Override
             public String toString() {
        - if (value == null) {
        + final String[] elts = this.elts;
        + if (elts == null && emptyValue != null) {
                     return emptyValue;
        - } else {
        - if (suffix.equals("")) {
        - return value.toString();
        - } else {
        - int initialLength = value.length();
        - String result = value.append(suffix).toString();
        - // reset value to pre-append initialLength
        - value.setLength(initialLength);
        - return result;
        - }
                 }
        + final int size = this.size;
        + final String delimiter = this.delimiter;
        + final char[] chars = new char[len + prefix.length() + suffix.length()];
        + int k = getChars(prefix, chars, 0);
        + if (size > 0) {
        + k += getChars(elts[0], chars, k);
        + }
        + for (int i = 1; i < size; i++) {
        + k += getChars(delimiter, chars, k);
        + k += getChars(elts[i], chars, k);
        + }
        + k += getChars(suffix, chars, k);
        + return jla.newStringUnsafe(chars);
             }
         
             /**
        @@ -182,7 +196,16 @@
              * @return a reference to this {@code StringJoiner}
              */
             public StringJoiner add(CharSequence newElement) {
        - prepareBuilder().append(newElement);
        + final String elt = String.valueOf(newElement);
        + if (elts == null) {
        + elts = new String[8];
        + } else {
        + if (size == elts.length)
        + elts = Arrays.copyOf(elts, 2 * size);
        + len += delimiter.length();
        + }
        + len += elt.length();
        + elts[size++] = elt;
                 return this;
             }
         
        @@ -207,24 +230,18 @@
              */
             public StringJoiner merge(StringJoiner other) {
                 Objects.requireNonNull(other);
        - if (other.value != null) {
        - final int length = other.value.length();
        - // lock the length so that we can seize the data to be appended
        - // before initiate copying to avoid interference, especially when
        - // merge 'this'
        - StringBuilder builder = prepareBuilder();
        - builder.append(other.value, other.prefix.length(), length);
        + final String[] elts = other.elts;
        + if (elts == null) {
        + return this;
                 }
        - return this;
        - }
        -
        - private StringBuilder prepareBuilder() {
        - if (value != null) {
        - value.append(delimiter);
        - } else {
        - value = new StringBuilder().append(prefix);
        + final int size = other.size;
        + final String delimiter = other.delimiter;
        + final char[] chars = new char[other.len];
        + for (int k = getChars(elts[0], chars, 0), i = 1; i < size; i++) {
        + k += getChars(delimiter, chars, k);
        + k += getChars(elts[i], chars, k);
                 }
        - return value;
        + return add(jla.newStringUnsafe(chars));
             }
         
             /**
        @@ -238,10 +255,7 @@
              * @return the length of the current value of {@code StringJoiner}
              */
             public int length() {
        - // Remember that we never actually append the suffix unless we return
        - // the full (present) value or some sub-string or length of it, so that
        - // we can add on more if we need to.
        - return (value != null ? value.length() + suffix.length() :
        - emptyValue.length());
        + return (size == 0 && emptyValue != null) ? emptyValue.length() :
        + len + prefix.length() + suffix.length();
             }
         }

              igerasim Ivan Gerasimov
              igerasim Ivan Gerasimov
              Votes:
              0 Vote for this issue
              Watchers:
              3 Start watching this issue

                Created:
                Updated:
                Resolved: