diff --git a/src/java.base/share/classes/java/lang/String.java b/src/java.base/share/classes/java/lang/String.java --- a/src/java.base/share/classes/java/lang/String.java +++ b/src/java.base/share/classes/java/lang/String.java @@ -3699,7 +3699,11 @@ return value; } - boolean isLatin1() { + /** + * Returns true in case all symbols belong to Latin1 + * @return whether String has only ASCII symbols + */ + public boolean isLatin1() { return COMPACT_STRINGS && coder == LATIN1; } diff --git a/src/java.base/share/classes/java/util/StringJoiner.java b/src/java.base/share/classes/java/util/StringJoiner.java --- a/src/java.base/share/classes/java/util/StringJoiner.java +++ b/src/java.base/share/classes/java/util/StringJoiner.java @@ -83,6 +83,8 @@ */ private String emptyValue; + private boolean allLatin1; + /** * Constructs a {@code StringJoiner} with no characters in it, with no * {@code prefix} or {@code suffix}, and a copy of the supplied @@ -125,6 +127,7 @@ this.prefix = prefix.toString(); this.delimiter = delimiter.toString(); this.suffix = suffix.toString(); + this.allLatin1 = this.prefix.isLatin1() && this.delimiter.isLatin1() && this.suffix.isLatin1(); } /** @@ -144,6 +147,7 @@ public StringJoiner setEmptyValue(CharSequence emptyValue) { this.emptyValue = Objects.requireNonNull(emptyValue, "The empty value must not be null").toString(); + this.allLatin1 &= this.emptyValue.isLatin1(); return this; } @@ -153,6 +157,13 @@ return len; } + @SuppressWarnings("deprecation") + private static int getBytes(String s, byte[] bytes, int start) { + int len = s.length(); + s.getBytes(0, len, bytes, 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}, @@ -173,6 +184,13 @@ compactElts(); return size == 0 ? "" : elts[0]; } + if (allLatin1) { + return bytesToString(elts, size, addLen); + } + return charsToString(elts, size, addLen); + } + + private String charsToString(String[] elts, int size, int addLen) { final String delimiter = this.delimiter; final char[] chars = new char[len + addLen]; int k = getChars(prefix, chars, 0); @@ -183,10 +201,25 @@ k += getChars(elts[i], chars, k); } } - k += getChars(suffix, chars, k); + getChars(suffix, chars, k); return new String(chars); } + private String bytesToString(String[] elts, int size, int addLen) { + final String delimiter = this.delimiter; + final byte[] bytes = new byte[len + addLen]; + int k = getBytes(prefix, bytes, 0); + if (size > 0) { + k += getBytes(elts[0], bytes, k); + for (int i = 1; i < size; i++) { + k += getBytes(delimiter, bytes, k); + k += getBytes(elts[i], bytes, k); + } + } + getBytes(suffix, bytes, k); + return new String(bytes); + } + /** * Adds a copy of the given {@code CharSequence} value as the next * element of the {@code StringJoiner} value. If {@code newElement} is @@ -206,6 +239,7 @@ } len += elt.length(); elts[size++] = elt; + allLatin1 &= elt.isLatin1(); return this; } @@ -239,18 +273,38 @@ private void compactElts() { if (size > 1) { - final char[] chars = new char[len]; - int i = 1, k = getChars(elts[0], chars, 0); - do { - k += getChars(delimiter, chars, k); - k += getChars(elts[i], chars, k); - elts[i] = null; - } while (++i < size); - size = 1; - elts[0] = new String(chars); + if (allLatin1) { + compactBytes(); + } else { + compactChars(); + } } } + private void compactChars() { + final char[] chars = new char[len]; + int i = 1, k = getChars(elts[0], chars, 0); + do { + k += getChars(delimiter, chars, k); + k += getChars(elts[i], chars, k); + elts[i] = null; + } while (++i < size); + size = 1; + elts[0] = new String(chars); + } + + private void compactBytes() { + final byte[] bytes = new byte[len]; + int i = 1, k = getBytes(elts[0], bytes, 0); + do { + k += getBytes(delimiter, bytes, k); + k += getBytes(elts[i], bytes, k); + elts[i] = null; + } while (++i < size); + size = 1; + elts[0] = new String(bytes); + } + /** * Returns the length of the {@code String} representation * of this {@code StringJoiner}. Note that if