From 27174f45ba46f3f4b9055419110125f75edb159f Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Tue, 12 Sep 2023 07:59:08 +0800 Subject: [PATCH 01/23] rebase & resolve DecimalDigits delete and add --- .../share/classes/java/lang/StringLatin1.java | 46 ++----------- .../share/classes/java/lang/StringUTF16.java | 3 +- .../share/classes/java/util/FormatItem.java | 3 + .../share/classes/java/util/UUID.java | 1 + .../internal}/util/DecimalDigits.java | 64 +++++++++++++------ .../{java => jdk/internal}/util/Digits.java | 4 +- .../internal}/util/HexDigits.java | 10 +-- .../internal}/util/OctalDigits.java | 6 +- 8 files changed, 67 insertions(+), 70 deletions(-) rename src/java.base/share/classes/{java => jdk/internal}/util/DecimalDigits.java (61%) rename src/java.base/share/classes/{java => jdk/internal}/util/Digits.java (95%) rename src/java.base/share/classes/{java => jdk/internal}/util/HexDigits.java (94%) rename src/java.base/share/classes/{java => jdk/internal}/util/OctalDigits.java (95%) diff --git a/src/java.base/share/classes/java/lang/StringLatin1.java b/src/java.base/share/classes/java/lang/StringLatin1.java index f6e2acd2fe6c6..b39b8aa726200 100644 --- a/src/java.base/share/classes/java/lang/StringLatin1.java +++ b/src/java.base/share/classes/java/lang/StringLatin1.java @@ -33,6 +33,7 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; import jdk.internal.util.ArraysSupport; +import jdk.internal.util.DecimalDigits; import jdk.internal.util.ByteArrayLittleEndian; import jdk.internal.vm.annotation.IntrinsicCandidate; import jdk.internal.vm.annotation.Stable; @@ -43,41 +44,6 @@ import static java.lang.String.checkOffset; final class StringLatin1 { - - /** - * Each element of the array represents the packaging of two ascii characters based on little endian:

- *

-     *      00 -> '0' | ('0' << 8) -> 0x3030
-     *      01 -> '1' | ('0' << 8) -> 0x3130
-     *      02 -> '2' | ('0' << 8) -> 0x3230
-     *
-     *     ...
-     *
-     *      10 -> '0' | ('1' << 8) -> 0x3031
-     *      11 -> '1' | ('1' << 8) -> 0x3131
-     *      12 -> '2' | ('1' << 8) -> 0x3231
-     *
-     *     ...
-     *
-     *      97 -> '7' | ('9' << 8) -> 0x3739
-     *      98 -> '8' | ('9' << 8) -> 0x3839
-     *      99 -> '9' | ('9' << 8) -> 0x3939
-     * 
- */ - @Stable - static final short[] PACKED_DIGITS = new short[] { - 0x3030, 0x3130, 0x3230, 0x3330, 0x3430, 0x3530, 0x3630, 0x3730, 0x3830, 0x3930, - 0x3031, 0x3131, 0x3231, 0x3331, 0x3431, 0x3531, 0x3631, 0x3731, 0x3831, 0x3931, - 0x3032, 0x3132, 0x3232, 0x3332, 0x3432, 0x3532, 0x3632, 0x3732, 0x3832, 0x3932, - 0x3033, 0x3133, 0x3233, 0x3333, 0x3433, 0x3533, 0x3633, 0x3733, 0x3833, 0x3933, - 0x3034, 0x3134, 0x3234, 0x3334, 0x3434, 0x3534, 0x3634, 0x3734, 0x3834, 0x3934, - 0x3035, 0x3135, 0x3235, 0x3335, 0x3435, 0x3535, 0x3635, 0x3735, 0x3835, 0x3935, - 0x3036, 0x3136, 0x3236, 0x3336, 0x3436, 0x3536, 0x3636, 0x3736, 0x3836, 0x3936, - 0x3037, 0x3137, 0x3237, 0x3337, 0x3437, 0x3537, 0x3637, 0x3737, 0x3837, 0x3937, - 0x3038, 0x3138, 0x3238, 0x3338, 0x3438, 0x3538, 0x3638, 0x3738, 0x3838, 0x3938, - 0x3039, 0x3139, 0x3239, 0x3339, 0x3439, 0x3539, 0x3639, 0x3739, 0x3839, 0x3939 - }; - public static char charAt(byte[] value, int index) { checkIndex(index, value.length); return (char)(value[index] & 0xff); @@ -148,13 +114,13 @@ static int getChars(int i, int index, byte[] buf) { r = (q * 100) - i; i = q; charPos -= 2; - ByteArrayLittleEndian.setShort(buf, charPos, PACKED_DIGITS[r]); + ByteArrayLittleEndian.setShort(buf, charPos, DecimalDigits.digitPair(r)); } // We know there are at most two digits left at this point. if (i < -9) { charPos -= 2; - ByteArrayLittleEndian.setShort(buf, charPos, PACKED_DIGITS[-i]); + ByteArrayLittleEndian.setShort(buf, charPos, DecimalDigits.digitPair(-i)); } else { buf[--charPos] = (byte)('0' - i); } @@ -196,7 +162,7 @@ static int getChars(long i, int index, byte[] buf) { while (i <= Integer.MIN_VALUE) { q = i / 100; charPos -= 2; - ByteArrayLittleEndian.setShort(buf, charPos, PACKED_DIGITS[(int)((q * 100) - i)]); + ByteArrayLittleEndian.setShort(buf, charPos, DecimalDigits.digitPair((int)((q * 100) - i))); i = q; } @@ -206,14 +172,14 @@ static int getChars(long i, int index, byte[] buf) { while (i2 <= -100) { q2 = i2 / 100; charPos -= 2; - ByteArrayLittleEndian.setShort(buf, charPos, PACKED_DIGITS[(q2 * 100) - i2]); + ByteArrayLittleEndian.setShort(buf, charPos, DecimalDigits.digitPair((q2 * 100) - i2)); i2 = q2; } // We know there are at most two digits left at this point. if (i2 < -9) { charPos -= 2; - ByteArrayLittleEndian.setShort(buf, charPos, PACKED_DIGITS[-i2]); + ByteArrayLittleEndian.setShort(buf, charPos, DecimalDigits.digitPair(-i2)); } else { buf[--charPos] = (byte)('0' - i2); } diff --git a/src/java.base/share/classes/java/lang/StringUTF16.java b/src/java.base/share/classes/java/lang/StringUTF16.java index 0a7e317940185..eca2a9b00cb3c 100644 --- a/src/java.base/share/classes/java/lang/StringUTF16.java +++ b/src/java.base/share/classes/java/lang/StringUTF16.java @@ -33,6 +33,7 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; import jdk.internal.util.ArraysSupport; +import jdk.internal.util.DecimalDigits; import jdk.internal.vm.annotation.DontInline; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.IntrinsicCandidate; @@ -1612,7 +1613,7 @@ static int getChars(long i, int index, byte[] buf) { } private static void putPair(byte[] buf, int charPos, int v) { - int packed = (int) StringLatin1.PACKED_DIGITS[v]; + int packed = (int) DecimalDigits.digitPair(v); putChar(buf, charPos, packed & 0xFF); putChar(buf, charPos + 1, packed >> 8); } diff --git a/src/java.base/share/classes/java/util/FormatItem.java b/src/java.base/share/classes/java/util/FormatItem.java index 4acef8ad9092d..728d4aee3b7fc 100644 --- a/src/java.base/share/classes/java/util/FormatItem.java +++ b/src/java.base/share/classes/java/util/FormatItem.java @@ -36,6 +36,9 @@ import jdk.internal.access.JavaLangAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.util.FormatConcatItem; +import jdk.internal.util.DecimalDigits; +import jdk.internal.util.HexDigits; +import jdk.internal.util.OctalDigits; import static java.lang.invoke.MethodType.methodType; diff --git a/src/java.base/share/classes/java/util/UUID.java b/src/java.base/share/classes/java/util/UUID.java index 78846ba48c23a..5c7b85fb568f8 100644 --- a/src/java.base/share/classes/java/util/UUID.java +++ b/src/java.base/share/classes/java/util/UUID.java @@ -32,6 +32,7 @@ import jdk.internal.access.JavaLangAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.util.ByteArray; +import jdk.internal.util.HexDigits; /** * A class that represents an immutable universally unique identifier (UUID). diff --git a/src/java.base/share/classes/java/util/DecimalDigits.java b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java similarity index 61% rename from src/java.base/share/classes/java/util/DecimalDigits.java rename to src/java.base/share/classes/jdk/internal/util/DecimalDigits.java index 24975c419e196..6f8fb4125d520 100644 --- a/src/java.base/share/classes/java/util/DecimalDigits.java +++ b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java @@ -23,7 +23,7 @@ * questions. */ -package java.util; +package jdk.internal.util; import java.lang.invoke.MethodHandle; @@ -34,29 +34,46 @@ * * @since 21 */ -final class DecimalDigits implements Digits { +public final class DecimalDigits implements Digits { + + /** + * Each element of the array represents the packaging of two ascii characters based on little endian:

+ *

+     *      00 -> '0' | ('0' << 8) -> 0x3030
+     *      01 -> '1' | ('0' << 8) -> 0x3130
+     *      02 -> '2' | ('0' << 8) -> 0x3230
+     *
+     *     ...
+     *
+     *      10 -> '0' | ('1' << 8) -> 0x3031
+     *      11 -> '1' | ('1' << 8) -> 0x3131
+     *      12 -> '2' | ('1' << 8) -> 0x3231
+     *
+     *     ...
+     *
+     *      97 -> '7' | ('9' << 8) -> 0x3739
+     *      98 -> '8' | ('9' << 8) -> 0x3839
+     *      99 -> '9' | ('9' << 8) -> 0x3939
+     * 
+ */ @Stable - private static final short[] DIGITS; + private static final short[] DIGITS = new short[] { + 0x3030, 0x3130, 0x3230, 0x3330, 0x3430, 0x3530, 0x3630, 0x3730, 0x3830, 0x3930, + 0x3031, 0x3131, 0x3231, 0x3331, 0x3431, 0x3531, 0x3631, 0x3731, 0x3831, 0x3931, + 0x3032, 0x3132, 0x3232, 0x3332, 0x3432, 0x3532, 0x3632, 0x3732, 0x3832, 0x3932, + 0x3033, 0x3133, 0x3233, 0x3333, 0x3433, 0x3533, 0x3633, 0x3733, 0x3833, 0x3933, + 0x3034, 0x3134, 0x3234, 0x3334, 0x3434, 0x3534, 0x3634, 0x3734, 0x3834, 0x3934, + 0x3035, 0x3135, 0x3235, 0x3335, 0x3435, 0x3535, 0x3635, 0x3735, 0x3835, 0x3935, + 0x3036, 0x3136, 0x3236, 0x3336, 0x3436, 0x3536, 0x3636, 0x3736, 0x3836, 0x3936, + 0x3037, 0x3137, 0x3237, 0x3337, 0x3437, 0x3537, 0x3637, 0x3737, 0x3837, 0x3937, + 0x3038, 0x3138, 0x3238, 0x3338, 0x3438, 0x3538, 0x3638, 0x3738, 0x3838, 0x3938, + 0x3039, 0x3139, 0x3239, 0x3339, 0x3439, 0x3539, 0x3639, 0x3739, 0x3839, 0x3939 + }; /** * Singleton instance of DecimalDigits. */ - static final Digits INSTANCE = new DecimalDigits(); - - static { - short[] digits = new short[10 * 10]; - - for (int i = 0; i < 10; i++) { - short hi = (short) ((i + '0') << 8); - - for (int j = 0; j < 10; j++) { - short lo = (short) (j + '0'); - digits[i * 10 + j] = (short) (hi | lo); - } - } - - DIGITS = digits; - } + public static final Digits INSTANCE = new DecimalDigits(); /** * Constructor. @@ -131,4 +148,13 @@ public int size(long value) { return 19 + sign; } + + /** + * For values from 0 to 99 return a short encoding a pair of ASCII-encoded digit characters in little-endian + * @param i value to convert + * @return a short encoding a pair of ASCII-encoded digit characters + */ + public static short digitPair(int i) { + return DIGITS[i]; + } } diff --git a/src/java.base/share/classes/java/util/Digits.java b/src/java.base/share/classes/jdk/internal/util/Digits.java similarity index 95% rename from src/java.base/share/classes/java/util/Digits.java rename to src/java.base/share/classes/jdk/internal/util/Digits.java index 37b715c931992..e301688ff4ea9 100644 --- a/src/java.base/share/classes/java/util/Digits.java +++ b/src/java.base/share/classes/jdk/internal/util/Digits.java @@ -23,7 +23,7 @@ * questions. */ -package java.util; +package jdk.internal.util; import java.lang.invoke.MethodHandle; @@ -33,7 +33,7 @@ * * @since 21 */ -sealed interface Digits permits DecimalDigits, HexDigits, OctalDigits { +public sealed interface Digits permits DecimalDigits, HexDigits, OctalDigits { /** * Insert digits for long value in buffer from high index to low index. * diff --git a/src/java.base/share/classes/java/util/HexDigits.java b/src/java.base/share/classes/jdk/internal/util/HexDigits.java similarity index 94% rename from src/java.base/share/classes/java/util/HexDigits.java rename to src/java.base/share/classes/jdk/internal/util/HexDigits.java index 7cae4b731f8a8..d04d8719e8347 100644 --- a/src/java.base/share/classes/java/util/HexDigits.java +++ b/src/java.base/share/classes/jdk/internal/util/HexDigits.java @@ -23,7 +23,7 @@ * questions. */ -package java.util; +package jdk.internal.util; import java.lang.invoke.MethodHandle; @@ -34,7 +34,7 @@ * * @since 21 */ -final class HexDigits implements Digits { +public final class HexDigits implements Digits { /** * Each element of the array represents the ascii encoded * hex relative to its index, for example:

@@ -79,7 +79,7 @@ final class HexDigits implements Digits { /** * Singleton instance of HexDigits. */ - static final Digits INSTANCE = new HexDigits(); + public static final Digits INSTANCE = new HexDigits(); static { short[] digits = new short[16 * 16]; @@ -105,14 +105,14 @@ private HexDigits() { /** * Combine two hex shorts into one int based on big endian */ - static int digit(int b0, int b1) { + public static int digit(int b0, int b1) { return (DIGITS[b0 & 0xff] << 16) | DIGITS[b1 & 0xff]; } /** * Combine four hex shorts into one long based on big endian */ - static long digit(int b0, int b1, int b2, int b3) { + public static long digit(int b0, int b1, int b2, int b3) { return (((long) DIGITS[b0 & 0xff]) << 48) | (((long) DIGITS[b1 & 0xff]) << 32) | (DIGITS[b2 & 0xff] << 16) diff --git a/src/java.base/share/classes/java/util/OctalDigits.java b/src/java.base/share/classes/jdk/internal/util/OctalDigits.java similarity index 95% rename from src/java.base/share/classes/java/util/OctalDigits.java rename to src/java.base/share/classes/jdk/internal/util/OctalDigits.java index f1c4ce0f5264e..f7af6703c18cf 100644 --- a/src/java.base/share/classes/java/util/OctalDigits.java +++ b/src/java.base/share/classes/jdk/internal/util/OctalDigits.java @@ -23,7 +23,7 @@ * questions. */ -package java.util; +package jdk.internal.util; import java.lang.invoke.MethodHandle; @@ -34,14 +34,14 @@ * * @since 21 */ -final class OctalDigits implements Digits { +public final class OctalDigits implements Digits { @Stable private static final short[] DIGITS; /** * Singleton instance of OctalDigits. */ - static final Digits INSTANCE = new OctalDigits(); + public static final Digits INSTANCE = new OctalDigits(); static { short[] digits = new short[8 * 8]; From 93ed81da079a45e1c05cf2713edf4f0443cbd976 Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Tue, 12 Sep 2023 08:53:32 +0800 Subject: [PATCH 02/23] little-endian --- .../share/classes/jdk/internal/util/DecimalDigits.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java index 6f8fb4125d520..f55452868a610 100644 --- a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java +++ b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java @@ -97,8 +97,8 @@ public int digits(long value, byte[] buffer, int index, value = q; int digits = DIGITS[r]; - putCharMH.invokeExact(buffer, --index, digits & 0xFF); putCharMH.invokeExact(buffer, --index, digits >> 8); + putCharMH.invokeExact(buffer, --index, digits & 0xFF); } int iq, ivalue = (int)value; @@ -107,8 +107,8 @@ public int digits(long value, byte[] buffer, int index, r = (iq * 100) - ivalue; ivalue = iq; int digits = DIGITS[r]; - putCharMH.invokeExact(buffer, --index, digits & 0xFF); putCharMH.invokeExact(buffer, --index, digits >> 8); + putCharMH.invokeExact(buffer, --index, digits & 0xFF); } if (ivalue < 0) { @@ -116,10 +116,10 @@ public int digits(long value, byte[] buffer, int index, } int digits = DIGITS[ivalue]; - putCharMH.invokeExact(buffer, --index, digits & 0xFF); + putCharMH.invokeExact(buffer, --index, digits >> 8); if (9 < ivalue) { - putCharMH.invokeExact(buffer, --index, digits >> 8); + putCharMH.invokeExact(buffer, --index, digits & 0xFF); } if (negative) { From f4190a8a526610f29ca1a797b4cd8ae8862a55c5 Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Sun, 10 Sep 2023 04:07:28 +0800 Subject: [PATCH 03/23] improve date toString performance, includes: java.util.Date.toString java.util.Date.toGMTString java.time.Instant.toString java.time.LocalDate.toString java.time.LocalDateTime.toString java.time.LocalTime.toString --- .../share/classes/java/lang/System.java | 20 +++ .../share/classes/java/time/Instant.java | 30 +++- .../share/classes/java/time/LocalDate.java | 74 ++++++--- .../classes/java/time/LocalDateTime.java | 22 ++- .../share/classes/java/time/LocalTime.java | 127 +++++++++++++--- .../classes/java/time/OffsetDateTime.java | 26 +++- .../share/classes/java/time/OffsetTime.java | 23 ++- .../share/classes/java/time/ZoneOffset.java | 51 +++++-- .../classes/java/time/ZonedDateTime.java | 45 +++++- .../share/classes/java/util/Date.java | 135 +++++++++++++---- .../jdk/internal/access/JavaLangAccess.java | 50 ++++++ .../bench/java/time/ToStringBench.java | 142 ++++++++++++++++++ 12 files changed, 656 insertions(+), 89 deletions(-) create mode 100644 test/micro/org/openjdk/bench/java/time/ToStringBench.java diff --git a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java index e50858b26f1a7..7be3734c6ed53 100644 --- a/src/java.base/share/classes/java/lang/System.java +++ b/src/java.base/share/classes/java/lang/System.java @@ -2483,6 +2483,26 @@ public byte[] getBytesNoRepl(String s, Charset cs) throws CharacterCodingExcepti return String.getBytesNoRepl(s, cs); } + public int stringSize(int i) { + return Integer.stringSize(i); + } + + public int stringSize(long i) { + return Long.stringSize(i); + } + + public int getChars(int i, int index, byte[] buf) { + return StringLatin1.getChars(i, index, buf); + } + + public int getChars(long i, int index, byte[] buf) { + return StringLatin1.getChars(i, index, buf); + } + + public short digitPair(int i) { + return StringLatin1.PACKED_DIGITS[i]; + } + public String newStringUTF8NoRepl(byte[] bytes, int off, int len) { return String.newStringUTF8NoRepl(bytes, off, len, true); } diff --git a/src/java.base/share/classes/java/time/Instant.java b/src/java.base/share/classes/java/time/Instant.java index 37dd9f8d2ace0..3019e8bdd5f17 100644 --- a/src/java.base/share/classes/java/time/Instant.java +++ b/src/java.base/share/classes/java/time/Instant.java @@ -80,6 +80,8 @@ import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.Serializable; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.StandardCharsets; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.time.temporal.ChronoField; @@ -96,6 +98,9 @@ import java.time.temporal.ValueRange; import java.util.Objects; +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.SharedSecrets; + /** * An instantaneous point on the time-line. *

@@ -210,6 +215,8 @@ public final class Instant implements Temporal, TemporalAdjuster, Comparable, Serializable { + private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); + /** * Constant for the 1970-01-01T00:00:00Z epoch instant. */ @@ -1352,7 +1359,28 @@ public int hashCode() { */ @Override public String toString() { - return DateTimeFormatter.ISO_INSTANT.format(this); + LocalDate date = LocalDate.ofEpochDay( + Math.floorDiv(seconds, SECONDS_PER_DAY)); + LocalTime time = LocalTime.ofSecondOfDay( + Math.floorMod(seconds, SECONDS_PER_DAY)); + + int yearSize = LocalDate.yearSize(date.getYear()); + int nanoSize = LocalTime.nanoSize(nanos); + + byte[] buf = new byte[yearSize + 16 + nanoSize]; + + int off = date.getChars(buf, 0); + buf[off] = 'T'; + + off = time.getChars(buf, off + 1); + LocalTime.getNanoChars(buf, off, nanos); + buf[off + nanoSize] = 'Z'; + + try { + return jla.newStringNoRepl(buf, StandardCharsets.ISO_8859_1); + } catch (CharacterCodingException cce) { + throw new AssertionError(cce); + } } // ----------------------------------------------------------------------- diff --git a/src/java.base/share/classes/java/time/LocalDate.java b/src/java.base/share/classes/java/time/LocalDate.java index 2649c098a5b43..6c78e5a82cb1e 100644 --- a/src/java.base/share/classes/java/time/LocalDate.java +++ b/src/java.base/share/classes/java/time/LocalDate.java @@ -80,6 +80,8 @@ import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.Serializable; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.StandardCharsets; import java.time.chrono.ChronoLocalDate; import java.time.chrono.IsoEra; import java.time.chrono.IsoChronology; @@ -103,6 +105,10 @@ import java.util.stream.LongStream; import java.util.stream.Stream; +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.util.ByteArrayLittleEndian; + /** * A date without a time-zone in the ISO-8601 calendar system, * such as {@code 2007-12-03}. @@ -140,6 +146,8 @@ public final class LocalDate implements Temporal, TemporalAdjuster, ChronoLocalDate, Serializable { + private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); + /** * The minimum supported {@code LocalDate}, '-999999999-01-01'. * This could be used by an application as a "far past" date. @@ -2147,28 +2155,58 @@ public int hashCode() { */ @Override public String toString() { - int yearValue = year; - int monthValue = month; - int dayValue = day; - int absYear = Math.abs(yearValue); - StringBuilder buf = new StringBuilder(10); - if (absYear < 1000) { - if (yearValue < 0) { - buf.append(yearValue - 10000).deleteCharAt(1); - } else { - buf.append(yearValue + 10000).deleteCharAt(0); + byte[] buf = new byte[yearSize(year) + 6]; + getChars(buf, 0); + + try { + return jla.newStringNoRepl(buf, StandardCharsets.ISO_8859_1); + } catch (CharacterCodingException cce) { + throw new AssertionError(cce); + } + } + + static int yearSize(int year) { + if (Math.abs(year) < 1000) { + return year < 0 ? 5 : 4; + } + return jla.stringSize(year) + (year > 9999 ? 1 : 0); + } + + int getChars(byte[] buf, int off) { + int year = this.year; + int yearSize = yearSize(year); + int yearAbs = Math.abs(year); + if (yearAbs < 1000) { + if (year < 0) { + buf[off] = '-'; } + int y01 = yearAbs / 100; + int y23 = yearAbs - y01 * 100; + + ByteArrayLittleEndian.setInt( + buf, + year < 0 ? 1 : 0, + (jla.digitPair(y23) << 16) | jla.digitPair(y01)); } else { - if (yearValue > 9999) { - buf.append('+'); + if (year > 9999) { + buf[off] = '+'; } - buf.append(yearValue); + jla.getChars(year, off + yearSize, buf); } - return buf.append(monthValue < 10 ? "-0" : "-") - .append(monthValue) - .append(dayValue < 10 ? "-0" : "-") - .append(dayValue) - .toString(); + + off += yearSize; + buf[off] = '-'; + ByteArrayLittleEndian.setShort( + buf, + off + 1, + jla.digitPair(month)); // mm + buf[off + 3] = '-'; + ByteArrayLittleEndian.setShort( + buf, + off + 4, + jla.digitPair(day)); // dd + + return off + 6; } //----------------------------------------------------------------------- diff --git a/src/java.base/share/classes/java/time/LocalDateTime.java b/src/java.base/share/classes/java/time/LocalDateTime.java index 024aa1dacbeda..9dcef4ac99923 100644 --- a/src/java.base/share/classes/java/time/LocalDateTime.java +++ b/src/java.base/share/classes/java/time/LocalDateTime.java @@ -78,6 +78,8 @@ import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.Serializable; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.StandardCharsets; import java.time.chrono.ChronoLocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; @@ -96,6 +98,8 @@ import java.time.zone.ZoneRules; import java.util.Objects; +import jdk.internal.access.SharedSecrets; + /** * A date-time without a time-zone in the ISO-8601 calendar system, * such as {@code 2007-12-03T10:15:30}. @@ -1965,7 +1969,23 @@ public int hashCode() { */ @Override public String toString() { - return date.toString() + 'T' + time.toString(); + int yearSize = LocalDate.yearSize(date.getYear()); + int nano = time.getNano(); + int nanoSize = LocalTime.nanoSize(nano); + + byte[] buf = new byte[yearSize + 15 + nanoSize]; + + int off = date.getChars(buf, 0); + buf[off] = 'T'; + off = time.getChars(buf, off + 1); + LocalTime.getNanoChars(buf, off, nano); + + try { + return SharedSecrets.getJavaLangAccess() + .newStringNoRepl(buf, StandardCharsets.ISO_8859_1); + } catch (CharacterCodingException cce) { + throw new AssertionError(cce); + } } //----------------------------------------------------------------------- diff --git a/src/java.base/share/classes/java/time/LocalTime.java b/src/java.base/share/classes/java/time/LocalTime.java index 499dca627e286..e44640f0d5bde 100644 --- a/src/java.base/share/classes/java/time/LocalTime.java +++ b/src/java.base/share/classes/java/time/LocalTime.java @@ -76,6 +76,8 @@ import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.Serializable; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.StandardCharsets; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.time.temporal.ChronoField; @@ -92,6 +94,11 @@ import java.time.temporal.ValueRange; import java.util.Objects; +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.util.ByteArrayLittleEndian; +import jdk.internal.vm.annotation.Stable; + /** * A time without a time-zone in the ISO-8601 calendar system, * such as {@code 10:15:30}. @@ -126,6 +133,11 @@ public final class LocalTime implements Temporal, TemporalAdjuster, Comparable, Serializable { + private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); + + @Stable + static final int[] DIGITS_K = new int[1000]; + /** * The minimum supported {@code LocalTime}, '00:00'. * This is the time of midnight at the start of the day. @@ -156,6 +168,14 @@ public final class LocalTime NOON = HOURS[12]; MIN = HOURS[0]; MAX = new LocalTime(23, 59, 59, 999_999_999); + + for (int i = 0; i < 1000; i++) { + int c0 = i < 10 ? 2 : i < 100 ? 1 : 0; + int c1 = (i / 100) + '0'; + int c2 = ((i / 10) % 10) + '0'; + int c3 = i % 10 + '0'; + DIGITS_K[i] = c0 + (c1 << 8) + (c2 << 16) + (c3 << 24); + } } /** @@ -1629,27 +1649,96 @@ public int hashCode() { */ @Override public String toString() { - StringBuilder buf = new StringBuilder(18); - int hourValue = hour; - int minuteValue = minute; - int secondValue = second; - int nanoValue = nano; - buf.append(hourValue < 10 ? "0" : "").append(hourValue) - .append(minuteValue < 10 ? ":0" : ":").append(minuteValue); - if (secondValue > 0 || nanoValue > 0) { - buf.append(secondValue < 10 ? ":0" : ":").append(secondValue); - if (nanoValue > 0) { - buf.append('.'); - if (nanoValue % 1000_000 == 0) { - buf.append(Integer.toString((nanoValue / 1000_000) + 1000).substring(1)); - } else if (nanoValue % 1000 == 0) { - buf.append(Integer.toString((nanoValue / 1000) + 1000_000).substring(1)); - } else { - buf.append(Integer.toString((nanoValue) + 1000_000_000).substring(1)); - } + int nano = this.nano; + int nanoSize = LocalTime.nanoSize(nano); + + byte[] buf = new byte[8 + nanoSize]; + getChars(buf, 0); + LocalTime.getNanoChars(buf, 8, nano); + + try { + return jla.newStringNoRepl(buf, StandardCharsets.ISO_8859_1); + } catch (CharacterCodingException cce) { + throw new AssertionError(cce); + } + } + + static int nanoSize(int nano) { + if (nano == 0) { + return 0; + } + + int div = nano / 1000; + int div2 = div / 1000; + + if (nano - div * 1000 != 0) { + return 10; + } + + return (div - div2 * 1000 == 0) ? 4 : 7; + } + + int getChars(byte[] buf, int off) { + ByteArrayLittleEndian.setShort( + buf, + off, + jla.digitPair(hour)); // hh + buf[off + 2] = ':'; + ByteArrayLittleEndian.setShort( + buf, + off + 3, + jla.digitPair(minute)); // minute + buf[off + 5] = ':'; + ByteArrayLittleEndian.setShort( + buf, + off + 6, + jla.digitPair(second)); // second + return off + 8; + } + + static void getNanoChars(byte[] buf, int off, int nano) { + if (nano == 0) { + return; + } + + int div = nano / 1000; + int div2 = div / 1000; + ByteArrayLittleEndian.setInt( + buf, + off, + DIGITS_K[div2] & 0xffffff00 | '.' + ); + off += 4; + + int rem1 = nano - div * 1000; + int v; + if (rem1 == 0) { + int rem2 = div - div2 * 1000; + if (rem2 == 0) { + return; } + + v = DIGITS_K[rem2]; + } else { + v = DIGITS_K[div - div2 * 1000]; + } + + ByteArrayLittleEndian.setShort( + buf, + off, + (short) (v >> 8) + ); + off += 2; + + if (rem1 == 0) { + buf[off] = (byte) (v >> 24); + } else { + ByteArrayLittleEndian.setInt( + buf, + off, + DIGITS_K[rem1] & 0xffffff00 | (v >> 24) + ); } - return buf.toString(); } //----------------------------------------------------------------------- diff --git a/src/java.base/share/classes/java/time/OffsetDateTime.java b/src/java.base/share/classes/java/time/OffsetDateTime.java index bd75fffb24e55..7e700257aee8e 100644 --- a/src/java.base/share/classes/java/time/OffsetDateTime.java +++ b/src/java.base/share/classes/java/time/OffsetDateTime.java @@ -74,6 +74,8 @@ import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.Serializable; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.StandardCharsets; import java.time.chrono.IsoChronology; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; @@ -93,6 +95,8 @@ import java.util.Comparator; import java.util.Objects; +import jdk.internal.access.SharedSecrets; + /** * A date-time with an offset from UTC/Greenwich in the ISO-8601 calendar system, * such as {@code 2007-12-03T10:15:30+01:00}. @@ -1922,8 +1926,28 @@ public int hashCode() { * @return a string representation of this date-time, not null */ @Override + @SuppressWarnings("deprecation") public String toString() { - return dateTime.toString() + offset.toString(); + int yearSize = LocalDate.yearSize(dateTime.getYear()); + int nano = dateTime.getNano(); + int nanoSize = LocalTime.nanoSize(nano); + + String offSetId = offset.getId(); + + byte[] buf = new byte[yearSize + 15 + nanoSize + offSetId.length()]; + + int off = toLocalDate().getChars(buf, 0); + buf[off] = 'T'; + off = toLocalTime().getChars(buf, off + 1); + LocalTime.getNanoChars(buf, off, nano); + offSetId.getBytes(0, offSetId.length(), buf, off + nanoSize); + + try { + return SharedSecrets.getJavaLangAccess() + .newStringNoRepl(buf, StandardCharsets.ISO_8859_1); + } catch (CharacterCodingException cce) { + throw new AssertionError(cce); + } } //----------------------------------------------------------------------- diff --git a/src/java.base/share/classes/java/time/OffsetTime.java b/src/java.base/share/classes/java/time/OffsetTime.java index b9fe5e533d80e..5cd3e95b748dc 100644 --- a/src/java.base/share/classes/java/time/OffsetTime.java +++ b/src/java.base/share/classes/java/time/OffsetTime.java @@ -75,6 +75,8 @@ import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.Serializable; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.StandardCharsets; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.time.temporal.ChronoField; @@ -92,6 +94,8 @@ import java.time.zone.ZoneRules; import java.util.Objects; +import jdk.internal.access.SharedSecrets; + /** * A time with an offset from UTC/Greenwich in the ISO-8601 calendar system, * such as {@code 10:15:30+01:00}. @@ -118,7 +122,6 @@ @jdk.internal.ValueBased public final class OffsetTime implements Temporal, TemporalAdjuster, Comparable, Serializable { - /** * The minimum supported {@code OffsetTime}, '00:00:00+18:00'. * This is the time of midnight at the start of the day in the maximum offset @@ -1397,8 +1400,24 @@ public int hashCode() { * @return a string representation of this time, not null */ @Override + @SuppressWarnings("deprecation") public String toString() { - return time.toString() + offset.toString(); + int nano = time.getNano(); + int nanoSize = LocalTime.nanoSize(nano); + + String offSetId = offset.getId(); + byte[] buf = new byte[8 + nanoSize + offSetId.length()]; + time.getChars(buf, 0); + + LocalTime.getNanoChars(buf, 8, nano); + offSetId.getBytes(0, offSetId.length(), buf, 8 + nanoSize); + + try { + return SharedSecrets.getJavaLangAccess() + .newStringNoRepl(buf, StandardCharsets.ISO_8859_1); + } catch (CharacterCodingException cce) { + throw new AssertionError(cce); + } } //----------------------------------------------------------------------- diff --git a/src/java.base/share/classes/java/time/ZoneOffset.java b/src/java.base/share/classes/java/time/ZoneOffset.java index 14ac5fcfb6ba1..ee1dc0fcaf823 100644 --- a/src/java.base/share/classes/java/time/ZoneOffset.java +++ b/src/java.base/share/classes/java/time/ZoneOffset.java @@ -72,6 +72,8 @@ import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.Serializable; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.StandardCharsets; import java.time.temporal.ChronoField; import java.time.temporal.Temporal; import java.time.temporal.TemporalAccessor; @@ -86,6 +88,9 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.util.ByteArrayLittleEndian; import jdk.internal.vm.annotation.Stable; /** @@ -134,6 +139,8 @@ public final class ZoneOffset extends ZoneId implements TemporalAccessor, TemporalAdjuster, Comparable, Serializable { + private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); + /** Cache of time-zone offset by offset in seconds. */ private static final ConcurrentMap SECONDS_CACHE = new ConcurrentHashMap<>(16, 0.75f, 4); /** Cache of time-zone offset by ID. */ @@ -448,19 +455,37 @@ private ZoneOffset(int totalSeconds) { private static String buildId(int totalSeconds) { if (totalSeconds == 0) { return "Z"; - } else { - int absTotalSeconds = Math.abs(totalSeconds); - StringBuilder buf = new StringBuilder(); - int absHours = absTotalSeconds / SECONDS_PER_HOUR; - int absMinutes = (absTotalSeconds / SECONDS_PER_MINUTE) % MINUTES_PER_HOUR; - buf.append(totalSeconds < 0 ? "-" : "+") - .append(absHours < 10 ? "0" : "").append(absHours) - .append(absMinutes < 10 ? ":0" : ":").append(absMinutes); - int absSeconds = absTotalSeconds % SECONDS_PER_MINUTE; - if (absSeconds != 0) { - buf.append(absSeconds < 10 ? ":0" : ":").append(absSeconds); - } - return buf.toString(); + } + + int absTotalSeconds = Math.abs(totalSeconds); + int absHours = absTotalSeconds / SECONDS_PER_HOUR; + int minuteSeconds = absTotalSeconds - absHours * SECONDS_PER_HOUR; + int absMinutes = minuteSeconds / SECONDS_PER_MINUTE; + int absSeconds = minuteSeconds - absMinutes * SECONDS_PER_MINUTE; + + byte[] buf = new byte[6 + (absSeconds != 0 ? 3 : 0)]; + buf[0] = (byte) (totalSeconds < 0 ? '-' : '+'); + ByteArrayLittleEndian.setShort( + buf, + 1, + jla.digitPair(absHours)); + buf[3] = ':'; + ByteArrayLittleEndian.setShort( + buf, + 4, + jla.digitPair(absMinutes)); + if (absSeconds != 0) { + buf[6] = ':'; + ByteArrayLittleEndian.setShort( + buf, + 7, + jla.digitPair(absSeconds)); + } + + try { + return jla.newStringNoRepl(buf, StandardCharsets.ISO_8859_1); + } catch (CharacterCodingException cce) { + throw new AssertionError(cce); } } diff --git a/src/java.base/share/classes/java/time/ZonedDateTime.java b/src/java.base/share/classes/java/time/ZonedDateTime.java index 555392cf513ce..6ad5607e638ea 100644 --- a/src/java.base/share/classes/java/time/ZonedDateTime.java +++ b/src/java.base/share/classes/java/time/ZonedDateTime.java @@ -71,6 +71,8 @@ import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.Serializable; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.StandardCharsets; import java.time.chrono.ChronoZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; @@ -91,6 +93,8 @@ import java.util.List; import java.util.Objects; +import jdk.internal.access.SharedSecrets; + /** * A date-time with a time-zone in the ISO-8601 calendar system, * such as {@code 2007-12-03T10:15:30+01:00 Europe/Paris}. @@ -2213,12 +2217,47 @@ public int hashCode() { * @return a string representation of this date-time, not null */ @Override // override for Javadoc + @SuppressWarnings("deprecation") public String toString() { - String str = dateTime.toString() + offset.toString(); + int yearSize = LocalDate.yearSize(dateTime.getYear()); + int nano = dateTime.getNano(); + int nanoSize = LocalTime.nanoSize(nano); + + String offSetId = offset.getId(); + int offsetIdLenth = offSetId.length(); + + String zoneStr = null; + int zoneLength = 0; + + int offsetLength = offsetIdLenth; + if (offset != zone) { + zoneStr = zone.toString(); + zoneLength = zoneStr.length(); + offsetLength += zoneLength + 2; + } + + byte[] buf = new byte[yearSize + 15 + nanoSize + offsetLength]; + + int off = toLocalDate().getChars(buf, 0); + buf[off] = 'T'; + off = toLocalTime().getChars(buf, off + 1); + LocalTime.getNanoChars(buf, off, nano); + off += nanoSize; + offSetId.getBytes(0, offsetIdLenth, buf, off); + if (offset != zone) { - str += '[' + zone.toString() + ']'; + off += offsetIdLenth; + buf[off] = '['; + zoneStr.getBytes(0, zoneLength, buf, off + 1); + buf[off + zoneLength + 1] = ']'; + } + + try { + return SharedSecrets.getJavaLangAccess() + .newStringNoRepl(buf, StandardCharsets.ISO_8859_1); + } catch (CharacterCodingException cce) { + throw new AssertionError(cce); } - return str; } //----------------------------------------------------------------------- diff --git a/src/java.base/share/classes/java/util/Date.java b/src/java.base/share/classes/java/util/Date.java index 1850564d8b290..a0751e1d03b77 100644 --- a/src/java.base/share/classes/java/util/Date.java +++ b/src/java.base/share/classes/java/util/Date.java @@ -29,7 +29,12 @@ import java.io.IOException; import java.io.ObjectOutputStream; import java.io.ObjectInputStream; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.StandardCharsets; import java.time.Instant; +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.util.ByteArrayLittleEndian; import sun.util.calendar.BaseCalendar; import sun.util.calendar.CalendarSystem; import sun.util.calendar.CalendarUtils; @@ -128,6 +133,8 @@ public class Date implements java.io.Serializable, Cloneable, Comparable { + private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); + private static final BaseCalendar gcal = CalendarSystem.getGregorianCalendar(); private static BaseCalendar jcal; @@ -1025,40 +1032,68 @@ public int hashCode() { * @see java.util.Date#toLocaleString() * @see java.util.Date#toGMTString() */ + @SuppressWarnings("deprecation") public String toString() { // "EEE MMM dd HH:mm:ss zzz yyyy"; BaseCalendar.Date date = normalize(); - StringBuilder sb = new StringBuilder(28); + + int year = date.getYear(); + int yearSize = year >= 1000 && year <= 9999 ? 4 : jla.stringSize(year); + + TimeZone zi = date.getZone(); + String shortName = zi != null ? zi.getDisplayName(date.isDaylightTime(), TimeZone.SHORT, Locale.US) : "GMT"; + int shortNameLength = shortName.length(); + + byte[] buf = new byte[21 + yearSize + shortNameLength]; + int index = date.getDayOfWeek(); if (index == BaseCalendar.SUNDAY) { index = 8; } - convertToAbbr(sb, wtb[index]).append(' '); // EEE - convertToAbbr(sb, wtb[date.getMonth() - 1 + 2 + 7]).append(' '); // MMM - CalendarUtils.sprintf0d(sb, date.getDayOfMonth(), 2).append(' '); // dd + convertToAbbr(buf, 0, wtb[index]); // EEE + buf[3] = ' '; + convertToAbbr(buf, 4, wtb[date.getMonth() - 1 + 2 + 7]); // MMM + buf[7] = ' '; + ByteArrayLittleEndian.setShort( + buf, + 8, + jla.digitPair(date.getDayOfMonth())); // dd + buf[10] = ' '; + ByteArrayLittleEndian.setShort( + buf, + 11, + jla.digitPair(date.getHours())); // HH + buf[13] = ':'; + ByteArrayLittleEndian.setShort( + buf, + 14, + jla.digitPair(date.getMinutes())); // mm + buf[16] = ':'; + ByteArrayLittleEndian.setShort( + buf, + 17, + jla.digitPair(date.getSeconds())); // ss + buf[19] = ' '; - CalendarUtils.sprintf0d(sb, date.getHours(), 2).append(':'); // HH - CalendarUtils.sprintf0d(sb, date.getMinutes(), 2).append(':'); // mm - CalendarUtils.sprintf0d(sb, date.getSeconds(), 2).append(' '); // ss - TimeZone zi = date.getZone(); - if (zi != null) { - sb.append(zi.getDisplayName(date.isDaylightTime(), TimeZone.SHORT, Locale.US)); // zzz - } else { - sb.append("GMT"); + shortName.getBytes(0, shortNameLength, buf, 20); + buf[20 + shortNameLength] = ' '; + + jla.getChars(year, buf.length, buf); + try { + return jla.newStringNoRepl(buf, StandardCharsets.ISO_8859_1); + } catch (CharacterCodingException cce) { + throw new AssertionError(cce); } - sb.append(' ').append(date.getYear()); // yyyy - return sb.toString(); } /** * Converts the given name to its 3-letter abbreviation (e.g., - * "monday" -> "Mon") and stored the abbreviation in the given - * {@code StringBuilder}. + * "monday" -> "Mon") and stored the abbreviation in the given buf */ - private static final StringBuilder convertToAbbr(StringBuilder sb, String name) { - sb.append(Character.toUpperCase(name.charAt(0))); - sb.append(name.charAt(1)).append(name.charAt(2)); - return sb; + private static final void convertToAbbr(byte[] buf, int off, String name) { + buf[off] = (byte) (name.charAt(0) - 32); + buf[off + 1] = (byte) name.charAt(1); + buf[off + 2] = (byte) name.charAt(2); } /** @@ -1118,18 +1153,56 @@ public String toLocaleString() { public String toGMTString() { // d MMM yyyy HH:mm:ss 'GMT' long t = getTime(); - BaseCalendar cal = getCalendarSystem(t); BaseCalendar.Date date = - (BaseCalendar.Date) cal.getCalendarDate(getTime(), (TimeZone)null); - StringBuilder sb = new StringBuilder(32); - CalendarUtils.sprintf0d(sb, date.getDayOfMonth(), 1).append(' '); // d - convertToAbbr(sb, wtb[date.getMonth() - 1 + 2 + 7]).append(' '); // MMM - sb.append(date.getYear()).append(' '); // yyyy - CalendarUtils.sprintf0d(sb, date.getHours(), 2).append(':'); // HH - CalendarUtils.sprintf0d(sb, date.getMinutes(), 2).append(':'); // mm - CalendarUtils.sprintf0d(sb, date.getSeconds(), 2); // ss - sb.append(" GMT"); // ' GMT' - return sb.toString(); + (BaseCalendar.Date) getCalendarSystem(t) + .getCalendarDate(getTime(), (TimeZone)null); + + int year = date.getYear(); + int yearSize = year >= 1000 && year <= 9999 ? 4 : jla.stringSize(year); + int dayOfMonth = date.getDayOfMonth(); + + byte[] buf = new byte[(dayOfMonth < 10 ? 19 : 20) + yearSize]; + int off; + if (dayOfMonth < 10) { + buf[0] = (byte) ('0' + dayOfMonth); + off = 1; + } else { + ByteArrayLittleEndian.setShort( + buf, + 0, + jla.digitPair(dayOfMonth)); // dd + off = 2; + } + buf[off++] = ' '; + convertToAbbr(buf, off, wtb[date.getMonth() + 8]); // EEE + buf[off + 3] = ' '; + jla.getChars(year, off + yearSize + 4, buf); + off += yearSize + 4; + buf[off++] = ' '; + ByteArrayLittleEndian.setShort( + buf, + off, + jla.digitPair(date.getHours())); // HH + buf[off + 2] = ':'; + ByteArrayLittleEndian.setShort( + buf, + off + 3, + jla.digitPair(date.getMinutes())); // mm + buf[off + 5] = ':'; + ByteArrayLittleEndian.setShort( + buf, + off + 6, + jla.digitPair(date.getSeconds())); // mm + buf[off + 8] = ' '; + buf[off + 9] = 'G'; + buf[off + 10] = 'M'; + buf[off + 11] = 'T'; + + try { + return jla.newStringNoRepl(buf, StandardCharsets.ISO_8859_1); + } catch (CharacterCodingException cce) { + throw new AssertionError(cce); + } } /** diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java index 6ea7abf6b959c..d6b86674dafb3 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java @@ -333,6 +333,56 @@ public interface JavaLangAccess { */ byte[] getBytesNoRepl(String s, Charset cs) throws CharacterCodingException; + /** + * Returns the string representation size for a given int value. + * + * @param x int value + * @return string size + */ + int stringSize(int x); + + /** + * Returns the string representation size for a given long value. + * + * @param x long value + * @return string size + */ + int stringSize(long i); + + /** + * For values from 0 to 99 return a short encoding a pair of ASCII-encoded digit characters in little-endian, + * e.g. 0 -> ('0' << 8 | '0'). Used for formatting + */ + short digitPair(int i); + + /** + * Places characters representing the integer i into the + * character array buf. The characters are placed into + * the buffer backwards starting with the least significant + * digit at the specified index (exclusive), and working + * backwards from there. + * + * @param i value to convert + * @param index next index, after the least significant digit + * @param buf target buffer, Latin1-encoded + * @return index of the most significant digit or minus sign, if present + */ + int getChars(int i, int index, byte[] buf); + + /** + * Places characters representing the long i into the + * character array buf. The characters are placed into + * the buffer backwards starting with the least significant + * digit at the specified index (exclusive), and working + * backwards from there. + * + * @param i value to convert + * @param index next index, after the least significant digit + * @param buf target buffer, Latin1-encoded + * @return index of the most significant digit or minus sign, if present + */ + int getChars(long i, int index, byte[] buf); + /** * Returns a new string by decoding from the given utf8 bytes array. * diff --git a/test/micro/org/openjdk/bench/java/time/ToStringBench.java b/test/micro/org/openjdk/bench/java/time/ToStringBench.java new file mode 100644 index 0000000000000..4b8b8e26a5ba0 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/time/ToStringBench.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.java.time; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZonedDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; + +import java.util.Locale; +import java.util.Random; +import java.util.concurrent.TimeUnit; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@Fork(3) +@State(Scope.Thread) +public class ToStringBench { + private static final Instant[] INSTANTS; + private static final ZonedDateTime[] ZONED_DATE_TIMES; + private static final LocalDateTime[] LOCAL_DATE_TIMES; + private static final LocalDate[] LOCAL_DATES; + private static final LocalTime[] LOCAL_TIMES; + + static { + Instant loInstant = Instant.EPOCH.plus(Duration.ofDays(365*50)); // 2020-01-01 + Instant hiInstant = loInstant.plus(Duration.ofDays(1)); + long maxOffsetNanos = Duration.between(loInstant, hiInstant).toNanos(); + Random random = new Random(0); + INSTANTS = IntStream + .range(0, 1_000) + .mapToObj(ignored -> { + final long offsetNanos = (long) Math.floor(random.nextDouble() * maxOffsetNanos); + return loInstant.plus(offsetNanos, ChronoUnit.NANOS); + }) + .toArray(Instant[]::new); + + ZONED_DATE_TIMES = Stream.of(INSTANTS) + .map(instant -> ZonedDateTime.ofInstant(instant, ZoneOffset.UTC)) + .toArray(ZonedDateTime[]::new); + + LOCAL_DATE_TIMES = Stream.of(ZONED_DATE_TIMES) + .map(zdt -> zdt.toLocalDateTime()) + .toArray(LocalDateTime[]::new); + + LOCAL_DATES = Stream.of(LOCAL_DATE_TIMES) + .map(ldt -> ldt.toLocalDate()) + .toArray(LocalDate[]::new); + + LOCAL_TIMES = Stream.of(LOCAL_DATE_TIMES) + .map(ldt -> ldt.toLocalTime()) + .toArray(LocalTime[]::new); + } + + @Benchmark + public void testZonedDateTimeToString(Blackhole bh) { + for (final ZonedDateTime zonedDateTime : ZONED_DATE_TIMES) { + bh.consume(zonedDateTime.toString()); + } + } + + @Benchmark + public void testLocalDateTimeToString(Blackhole bh) { + for (LocalDateTime localDateTime : LOCAL_DATE_TIMES) { + bh.consume(localDateTime.toString()); + } + } + + @Benchmark + public void testLocalDateToString(Blackhole bh) { + for (LocalDate localDate : LOCAL_DATES) { + bh.consume(localDate.toString()); + } + } + + @Benchmark + public void testLocalTimeToString(Blackhole bh) { + for (LocalTime localTime : LOCAL_TIMES) { + bh.consume(localTime.toString()); + } + } + + @Benchmark + public void testInstantToString(Blackhole bh) { + for (Instant instant : INSTANTS) { + bh.consume(instant.toString()); + } + } + + @Benchmark + public void testZoneOffsetOffHours(Blackhole bh) { + for (int hour = 0; hour < 12; hour++) { + for (int minute = 0; minute < 60; minute += 15) { + for (int second = 0; second < 60; second += 15) { + bh.consume(ZoneOffset.ofHoursMinutesSeconds(hour, minute, second)); + } + } + } + } +} From 9bb6231fd1fed6e794fdd3531794c9db3c214e9e Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Mon, 11 Sep 2023 22:37:01 +0800 Subject: [PATCH 04/23] fix LocalDate.getChars offset --- src/java.base/share/classes/java/time/LocalDate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/time/LocalDate.java b/src/java.base/share/classes/java/time/LocalDate.java index 6c78e5a82cb1e..1748422ed412f 100644 --- a/src/java.base/share/classes/java/time/LocalDate.java +++ b/src/java.base/share/classes/java/time/LocalDate.java @@ -2185,7 +2185,7 @@ int getChars(byte[] buf, int off) { ByteArrayLittleEndian.setInt( buf, - year < 0 ? 1 : 0, + off + (year < 0 ? 1 : 0), (jla.digitPair(y23) << 16) | jla.digitPair(y01)); } else { if (year > 9999) { From c6ab251ac156b4558381a271110c5c93f72a135b Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Mon, 11 Sep 2023 01:53:24 +0800 Subject: [PATCH 05/23] move java.util.DecimalDigits to jdk.internal.util.DecimalDigits --- .../java/lang/AbstractStringBuilder.java | 5 +- .../share/classes/java/lang/Integer.java | 28 +---------- .../share/classes/java/lang/Long.java | 28 +---------- .../classes/java/lang/StringConcatHelper.java | 6 ++- .../share/classes/java/lang/System.java | 20 -------- .../jdk/internal/access/JavaLangAccess.java | 50 ------------------- 6 files changed, 11 insertions(+), 126 deletions(-) diff --git a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java index b41b5db37837a..88e33f3ad5b55 100644 --- a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java +++ b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java @@ -25,6 +25,7 @@ package java.lang; +import jdk.internal.util.DecimalDigits; import jdk.internal.math.DoubleToDecimal; import jdk.internal.math.FloatToDecimal; @@ -829,7 +830,7 @@ public AbstractStringBuilder append(char c) { */ public AbstractStringBuilder append(int i) { int count = this.count; - int spaceNeeded = count + Integer.stringSize(i); + int spaceNeeded = count + DecimalDigits.stringSize(i); ensureCapacityInternal(spaceNeeded); if (isLatin1()) { StringLatin1.getChars(i, spaceNeeded, value); @@ -854,7 +855,7 @@ public AbstractStringBuilder append(int i) { */ public AbstractStringBuilder append(long l) { int count = this.count; - int spaceNeeded = count + Long.stringSize(l); + int spaceNeeded = count + DecimalDigits.stringSize(l); ensureCapacityInternal(spaceNeeded); if (isLatin1()) { StringLatin1.getChars(l, spaceNeeded, value); diff --git a/src/java.base/share/classes/java/lang/Integer.java b/src/java.base/share/classes/java/lang/Integer.java index 2b2377a889bbe..7cc0cf1f1dda0 100644 --- a/src/java.base/share/classes/java/lang/Integer.java +++ b/src/java.base/share/classes/java/lang/Integer.java @@ -25,6 +25,8 @@ package java.lang; +import static jdk.internal.util.DecimalDigits.stringSize; + import jdk.internal.misc.CDS; import jdk.internal.misc.VM; import jdk.internal.vm.annotation.ForceInline; @@ -456,32 +458,6 @@ public static String toUnsignedString(int i) { return Long.toString(toUnsignedLong(i)); } - /** - * Returns the string representation size for a given int value. - * - * @param x int value - * @return string size - * - * @implNote There are other ways to compute this: e.g. binary search, - * but values are biased heavily towards zero, and therefore linear search - * wins. The iteration results are also routinely inlined in the generated - * code after loop unrolling. - */ - static int stringSize(int x) { - int d = 1; - if (x >= 0) { - d = 0; - x = -x; - } - int p = -10; - for (int i = 1; i < 10; i++) { - if (x > p) - return i + d; - p = 10 * p; - } - return 10 + d; - } - /** * Parses the string argument as a signed integer in the radix * specified by the second argument. The characters in the string diff --git a/src/java.base/share/classes/java/lang/Long.java b/src/java.base/share/classes/java/lang/Long.java index 91583c26be106..b6257ddb77b40 100644 --- a/src/java.base/share/classes/java/lang/Long.java +++ b/src/java.base/share/classes/java/lang/Long.java @@ -42,6 +42,8 @@ import static java.lang.String.LATIN1; import static java.lang.String.UTF16; +import static jdk.internal.util.DecimalDigits.stringSize; + /** * The {@code Long} class wraps a value of the primitive type {@code * long} in an object. An object of type {@code Long} contains a @@ -486,32 +488,6 @@ public static String toUnsignedString(long i) { return toUnsignedString(i, 10); } - /** - * Returns the string representation size for a given long value. - * - * @param x long value - * @return string size - * - * @implNote There are other ways to compute this: e.g. binary search, - * but values are biased heavily towards zero, and therefore linear search - * wins. The iteration results are also routinely inlined in the generated - * code after loop unrolling. - */ - static int stringSize(long x) { - int d = 1; - if (x >= 0) { - d = 0; - x = -x; - } - long p = -10; - for (int i = 1; i < 19; i++) { - if (x > p) - return i + d; - p = 10 * p; - } - return 19 + d; - } - /** * Parses the string argument as a signed {@code long} in the * radix specified by the second argument. The characters in the diff --git a/src/java.base/share/classes/java/lang/StringConcatHelper.java b/src/java.base/share/classes/java/lang/StringConcatHelper.java index eddfc7e649660..b4e551b8dc977 100644 --- a/src/java.base/share/classes/java/lang/StringConcatHelper.java +++ b/src/java.base/share/classes/java/lang/StringConcatHelper.java @@ -25,6 +25,8 @@ package java.lang; +import static jdk.internal.util.DecimalDigits.stringSize; + import jdk.internal.misc.Unsafe; import jdk.internal.javac.PreviewFeature; import jdk.internal.util.FormatConcatItem; @@ -98,7 +100,7 @@ static long mix(long lengthCoder, char value) { * @return new length and coder */ static long mix(long lengthCoder, int value) { - return checkOverflow(lengthCoder + Integer.stringSize(value)); + return checkOverflow(lengthCoder + stringSize(value)); } /** @@ -109,7 +111,7 @@ static long mix(long lengthCoder, int value) { * @return new length and coder */ static long mix(long lengthCoder, long value) { - return checkOverflow(lengthCoder + Long.stringSize(value)); + return checkOverflow(lengthCoder + stringSize(value)); } /** diff --git a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java index 7be3734c6ed53..e50858b26f1a7 100644 --- a/src/java.base/share/classes/java/lang/System.java +++ b/src/java.base/share/classes/java/lang/System.java @@ -2483,26 +2483,6 @@ public byte[] getBytesNoRepl(String s, Charset cs) throws CharacterCodingExcepti return String.getBytesNoRepl(s, cs); } - public int stringSize(int i) { - return Integer.stringSize(i); - } - - public int stringSize(long i) { - return Long.stringSize(i); - } - - public int getChars(int i, int index, byte[] buf) { - return StringLatin1.getChars(i, index, buf); - } - - public int getChars(long i, int index, byte[] buf) { - return StringLatin1.getChars(i, index, buf); - } - - public short digitPair(int i) { - return StringLatin1.PACKED_DIGITS[i]; - } - public String newStringUTF8NoRepl(byte[] bytes, int off, int len) { return String.newStringUTF8NoRepl(bytes, off, len, true); } diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java index d6b86674dafb3..6ea7abf6b959c 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java @@ -333,56 +333,6 @@ public interface JavaLangAccess { */ byte[] getBytesNoRepl(String s, Charset cs) throws CharacterCodingException; - /** - * Returns the string representation size for a given int value. - * - * @param x int value - * @return string size - */ - int stringSize(int x); - - /** - * Returns the string representation size for a given long value. - * - * @param x long value - * @return string size - */ - int stringSize(long i); - - /** - * For values from 0 to 99 return a short encoding a pair of ASCII-encoded digit characters in little-endian, - * e.g. 0 -> ('0' << 8 | '0'). Used for formatting - */ - short digitPair(int i); - - /** - * Places characters representing the integer i into the - * character array buf. The characters are placed into - * the buffer backwards starting with the least significant - * digit at the specified index (exclusive), and working - * backwards from there. - * - * @param i value to convert - * @param index next index, after the least significant digit - * @param buf target buffer, Latin1-encoded - * @return index of the most significant digit or minus sign, if present - */ - int getChars(int i, int index, byte[] buf); - - /** - * Places characters representing the long i into the - * character array buf. The characters are placed into - * the buffer backwards starting with the least significant - * digit at the specified index (exclusive), and working - * backwards from there. - * - * @param i value to convert - * @param index next index, after the least significant digit - * @param buf target buffer, Latin1-encoded - * @return index of the most significant digit or minus sign, if present - */ - int getChars(long i, int index, byte[] buf); - /** * Returns a new string by decoding from the given utf8 bytes array. * From a849b2ea85d70c6b4dbd5444bfdb1f5224fbdb2b Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Mon, 11 Sep 2023 02:33:42 +0800 Subject: [PATCH 06/23] none static import --- src/java.base/share/classes/java/lang/Integer.java | 5 ++--- src/java.base/share/classes/java/lang/Long.java | 5 ++--- .../share/classes/java/lang/StringConcatHelper.java | 7 +++---- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/java.base/share/classes/java/lang/Integer.java b/src/java.base/share/classes/java/lang/Integer.java index 7cc0cf1f1dda0..4b1e133bcbc16 100644 --- a/src/java.base/share/classes/java/lang/Integer.java +++ b/src/java.base/share/classes/java/lang/Integer.java @@ -25,8 +25,7 @@ package java.lang; -import static jdk.internal.util.DecimalDigits.stringSize; - +import jdk.internal.util.DecimalDigits; import jdk.internal.misc.CDS; import jdk.internal.misc.VM; import jdk.internal.vm.annotation.ForceInline; @@ -428,7 +427,7 @@ private static void formatUnsignedIntUTF16(int val, int shift, byte[] buf, int l */ @IntrinsicCandidate public static String toString(int i) { - int size = stringSize(i); + int size = DecimalDigits.stringSize(i); if (COMPACT_STRINGS) { byte[] buf = new byte[size]; StringLatin1.getChars(i, size, buf); diff --git a/src/java.base/share/classes/java/lang/Long.java b/src/java.base/share/classes/java/lang/Long.java index b6257ddb77b40..4d15e572ccdb9 100644 --- a/src/java.base/share/classes/java/lang/Long.java +++ b/src/java.base/share/classes/java/lang/Long.java @@ -33,6 +33,7 @@ import java.util.Objects; import java.util.Optional; +import jdk.internal.util.DecimalDigits; import jdk.internal.misc.CDS; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.IntrinsicCandidate; @@ -42,8 +43,6 @@ import static java.lang.String.LATIN1; import static java.lang.String.UTF16; -import static jdk.internal.util.DecimalDigits.stringSize; - /** * The {@code Long} class wraps a value of the primitive type {@code * long} in an object. An object of type {@code Long} contains a @@ -458,7 +457,7 @@ private static void formatUnsignedLong0UTF16(long val, int shift, byte[] buf, in * @return a string representation of the argument in base 10. */ public static String toString(long i) { - int size = stringSize(i); + int size = DecimalDigits.stringSize(i); if (COMPACT_STRINGS) { byte[] buf = new byte[size]; StringLatin1.getChars(i, size, buf); diff --git a/src/java.base/share/classes/java/lang/StringConcatHelper.java b/src/java.base/share/classes/java/lang/StringConcatHelper.java index b4e551b8dc977..c3f8b14d30ce7 100644 --- a/src/java.base/share/classes/java/lang/StringConcatHelper.java +++ b/src/java.base/share/classes/java/lang/StringConcatHelper.java @@ -25,8 +25,7 @@ package java.lang; -import static jdk.internal.util.DecimalDigits.stringSize; - +import jdk.internal.util.DecimalDigits; import jdk.internal.misc.Unsafe; import jdk.internal.javac.PreviewFeature; import jdk.internal.util.FormatConcatItem; @@ -100,7 +99,7 @@ static long mix(long lengthCoder, char value) { * @return new length and coder */ static long mix(long lengthCoder, int value) { - return checkOverflow(lengthCoder + stringSize(value)); + return checkOverflow(lengthCoder + DecimalDigits.stringSize(value)); } /** @@ -111,7 +110,7 @@ static long mix(long lengthCoder, int value) { * @return new length and coder */ static long mix(long lengthCoder, long value) { - return checkOverflow(lengthCoder + stringSize(value)); + return checkOverflow(lengthCoder + DecimalDigits.stringSize(value)); } /** From f05fb4230dbc8395dccb661c3e22f7576831fd8a Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Mon, 11 Sep 2023 11:40:01 +0800 Subject: [PATCH 07/23] update related comments --- src/hotspot/share/opto/stringopts.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/share/opto/stringopts.cpp b/src/hotspot/share/opto/stringopts.cpp index fca532bd9a54f..3331a3c11594d 100644 --- a/src/hotspot/share/opto/stringopts.cpp +++ b/src/hotspot/share/opto/stringopts.cpp @@ -1160,7 +1160,7 @@ bool StringConcat::validate_control_flow() { return !fail; } -// Mirror of Integer.stringSize() method, return the count of digits in integer, +// Mirror of DecimalDigits.stringSize() method, return the count of digits in integer, Node* PhaseStringOpts::int_stringSize(GraphKit& kit, Node* arg) { if (arg->is_Con()) { // Constant integer. Compute constant length From 72ea93367f6209f2c09bb7132728ab62e4c4a3d2 Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Mon, 11 Sep 2023 11:40:49 +0800 Subject: [PATCH 08/23] remove duplicate stringSize --- .../time/format/DateTimeFormatterBuilder.java | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java b/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java index edd5e16f8a555..cc258f9c86452 100644 --- a/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java +++ b/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java @@ -121,6 +121,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import jdk.internal.util.DecimalDigits; + import sun.text.spi.JavaTimeDateTimePatternProvider; import sun.util.locale.provider.CalendarDataUtility; import sun.util.locale.provider.LocaleProviderAdapter; @@ -3351,17 +3353,6 @@ boolean isFixedWidth(DateTimeParseContext context) { return false; } - // Simplified variant of Integer.stringSize that assumes positive values - private static int stringSize(int x) { - int p = 10; - for (int i = 1; i < 10; i++) { - if (x < p) - return i; - p = 10 * p; - } - return 10; - } - private static final int[] TENS = new int[] { 1, 10, @@ -3382,7 +3373,7 @@ public boolean format(DateTimePrintContext context, StringBuilder buf) { } int val = field.range().checkValidIntValue(value, field); DecimalStyle decimalStyle = context.getDecimalStyle(); - int stringSize = stringSize(val); + int stringSize = DecimalDigits.stringSize(val); char zero = decimalStyle.getZeroDigit(); if (val == 0 || stringSize < 10 - maxWidth) { // 0 or would round down to 0 From c26eb7081c18a6a60a42edbdbf44f50c7316169b Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Mon, 11 Sep 2023 19:50:36 +0800 Subject: [PATCH 09/23] move java.lang.StringLatin1::getChars to jdk.internal.util.DecimalDigits::getCharsLatin1 --- .../share/classes/java/lang/AbstractStringBuilder.java | 4 ++-- src/java.base/share/classes/java/lang/Integer.java | 2 +- src/java.base/share/classes/java/lang/Long.java | 2 +- src/java.base/share/classes/java/lang/StringConcatHelper.java | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java index 88e33f3ad5b55..04fdcd7ad76bc 100644 --- a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java +++ b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java @@ -833,7 +833,7 @@ public AbstractStringBuilder append(int i) { int spaceNeeded = count + DecimalDigits.stringSize(i); ensureCapacityInternal(spaceNeeded); if (isLatin1()) { - StringLatin1.getChars(i, spaceNeeded, value); + DecimalDigits.getCharsLatin1(i, spaceNeeded, value); } else { StringUTF16.getChars(i, count, spaceNeeded, value); } @@ -858,7 +858,7 @@ public AbstractStringBuilder append(long l) { int spaceNeeded = count + DecimalDigits.stringSize(l); ensureCapacityInternal(spaceNeeded); if (isLatin1()) { - StringLatin1.getChars(l, spaceNeeded, value); + DecimalDigits.getCharsLatin1(l, spaceNeeded, value); } else { StringUTF16.getChars(l, count, spaceNeeded, value); } diff --git a/src/java.base/share/classes/java/lang/Integer.java b/src/java.base/share/classes/java/lang/Integer.java index 4b1e133bcbc16..d8b54285af4de 100644 --- a/src/java.base/share/classes/java/lang/Integer.java +++ b/src/java.base/share/classes/java/lang/Integer.java @@ -430,7 +430,7 @@ public static String toString(int i) { int size = DecimalDigits.stringSize(i); if (COMPACT_STRINGS) { byte[] buf = new byte[size]; - StringLatin1.getChars(i, size, buf); + DecimalDigits.getCharsLatin1(i, size, buf); return new String(buf, LATIN1); } else { byte[] buf = new byte[size * 2]; diff --git a/src/java.base/share/classes/java/lang/Long.java b/src/java.base/share/classes/java/lang/Long.java index 4d15e572ccdb9..720312465af13 100644 --- a/src/java.base/share/classes/java/lang/Long.java +++ b/src/java.base/share/classes/java/lang/Long.java @@ -460,7 +460,7 @@ public static String toString(long i) { int size = DecimalDigits.stringSize(i); if (COMPACT_STRINGS) { byte[] buf = new byte[size]; - StringLatin1.getChars(i, size, buf); + DecimalDigits.getCharsLatin1(i, size, buf); return new String(buf, LATIN1); } else { byte[] buf = new byte[size * 2]; diff --git a/src/java.base/share/classes/java/lang/StringConcatHelper.java b/src/java.base/share/classes/java/lang/StringConcatHelper.java index c3f8b14d30ce7..25d8cf3de31b5 100644 --- a/src/java.base/share/classes/java/lang/StringConcatHelper.java +++ b/src/java.base/share/classes/java/lang/StringConcatHelper.java @@ -251,7 +251,7 @@ static long prepend(long indexCoder, byte[] buf, char value, String prefix) { */ private static long prepend(long indexCoder, byte[] buf, int value) { if (indexCoder < UTF16) { - return StringLatin1.getChars(value, (int)indexCoder, buf); + return DecimalDigits.getCharsLatin1(value, (int)indexCoder, buf); } else { return StringUTF16.getChars(value, (int)indexCoder, buf) | UTF16; } @@ -286,7 +286,7 @@ static long prepend(long indexCoder, byte[] buf, int value, String prefix) { */ private static long prepend(long indexCoder, byte[] buf, long value) { if (indexCoder < UTF16) { - return StringLatin1.getChars(value, (int)indexCoder, buf); + return DecimalDigits.getCharsLatin1(value, (int)indexCoder, buf); } else { return StringUTF16.getChars(value, (int)indexCoder, buf) | UTF16; } From 342bbd2c8d791d9ed7425ee26cf590f10281722d Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Tue, 12 Sep 2023 00:28:06 +0800 Subject: [PATCH 10/23] base PR #15651 API --- .../share/classes/java/time/LocalDate.java | 11 ++++---- .../share/classes/java/time/LocalTime.java | 7 +++--- .../share/classes/java/time/ZoneOffset.java | 7 +++--- .../share/classes/java/util/Date.java | 25 ++++++++++--------- 4 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/java.base/share/classes/java/time/LocalDate.java b/src/java.base/share/classes/java/time/LocalDate.java index 1748422ed412f..dc4058b09b8a5 100644 --- a/src/java.base/share/classes/java/time/LocalDate.java +++ b/src/java.base/share/classes/java/time/LocalDate.java @@ -108,6 +108,7 @@ import jdk.internal.access.JavaLangAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.util.ByteArrayLittleEndian; +import jdk.internal.util.DecimalDigits; /** * A date without a time-zone in the ISO-8601 calendar system, @@ -2169,7 +2170,7 @@ static int yearSize(int year) { if (Math.abs(year) < 1000) { return year < 0 ? 5 : 4; } - return jla.stringSize(year) + (year > 9999 ? 1 : 0); + return DecimalDigits.stringSize(year) + (year > 9999 ? 1 : 0); } int getChars(byte[] buf, int off) { @@ -2186,12 +2187,12 @@ int getChars(byte[] buf, int off) { ByteArrayLittleEndian.setInt( buf, off + (year < 0 ? 1 : 0), - (jla.digitPair(y23) << 16) | jla.digitPair(y01)); + (DecimalDigits.digitPair(y23) << 16) | DecimalDigits.digitPair(y01)); } else { if (year > 9999) { buf[off] = '+'; } - jla.getChars(year, off + yearSize, buf); + DecimalDigits.getCharsLatin1(year, off + yearSize, buf); } off += yearSize; @@ -2199,12 +2200,12 @@ int getChars(byte[] buf, int off) { ByteArrayLittleEndian.setShort( buf, off + 1, - jla.digitPair(month)); // mm + DecimalDigits.digitPair(month)); // mm buf[off + 3] = '-'; ByteArrayLittleEndian.setShort( buf, off + 4, - jla.digitPair(day)); // dd + DecimalDigits.digitPair(day)); // dd return off + 6; } diff --git a/src/java.base/share/classes/java/time/LocalTime.java b/src/java.base/share/classes/java/time/LocalTime.java index e44640f0d5bde..690505908ffe2 100644 --- a/src/java.base/share/classes/java/time/LocalTime.java +++ b/src/java.base/share/classes/java/time/LocalTime.java @@ -97,6 +97,7 @@ import jdk.internal.access.JavaLangAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.util.ByteArrayLittleEndian; +import jdk.internal.util.DecimalDigits; import jdk.internal.vm.annotation.Stable; /** @@ -1682,17 +1683,17 @@ int getChars(byte[] buf, int off) { ByteArrayLittleEndian.setShort( buf, off, - jla.digitPair(hour)); // hh + DecimalDigits.digitPair(hour)); // hh buf[off + 2] = ':'; ByteArrayLittleEndian.setShort( buf, off + 3, - jla.digitPair(minute)); // minute + DecimalDigits.digitPair(minute)); // minute buf[off + 5] = ':'; ByteArrayLittleEndian.setShort( buf, off + 6, - jla.digitPair(second)); // second + DecimalDigits.digitPair(second)); // second return off + 8; } diff --git a/src/java.base/share/classes/java/time/ZoneOffset.java b/src/java.base/share/classes/java/time/ZoneOffset.java index ee1dc0fcaf823..72e2f355c6f85 100644 --- a/src/java.base/share/classes/java/time/ZoneOffset.java +++ b/src/java.base/share/classes/java/time/ZoneOffset.java @@ -91,6 +91,7 @@ import jdk.internal.access.JavaLangAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.util.ByteArrayLittleEndian; +import jdk.internal.util.DecimalDigits; import jdk.internal.vm.annotation.Stable; /** @@ -468,18 +469,18 @@ private static String buildId(int totalSeconds) { ByteArrayLittleEndian.setShort( buf, 1, - jla.digitPair(absHours)); + DecimalDigits.digitPair(absHours)); buf[3] = ':'; ByteArrayLittleEndian.setShort( buf, 4, - jla.digitPair(absMinutes)); + DecimalDigits.digitPair(absMinutes)); if (absSeconds != 0) { buf[6] = ':'; ByteArrayLittleEndian.setShort( buf, 7, - jla.digitPair(absSeconds)); + DecimalDigits.digitPair(absSeconds)); } try { diff --git a/src/java.base/share/classes/java/util/Date.java b/src/java.base/share/classes/java/util/Date.java index a0751e1d03b77..f05e753d9957d 100644 --- a/src/java.base/share/classes/java/util/Date.java +++ b/src/java.base/share/classes/java/util/Date.java @@ -35,6 +35,7 @@ import jdk.internal.access.JavaLangAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.util.ByteArrayLittleEndian; +import jdk.internal.util.DecimalDigits; import sun.util.calendar.BaseCalendar; import sun.util.calendar.CalendarSystem; import sun.util.calendar.CalendarUtils; @@ -1038,7 +1039,7 @@ public String toString() { BaseCalendar.Date date = normalize(); int year = date.getYear(); - int yearSize = year >= 1000 && year <= 9999 ? 4 : jla.stringSize(year); + int yearSize = year >= 1000 && year <= 9999 ? 4 : DecimalDigits.stringSize(year); TimeZone zi = date.getZone(); String shortName = zi != null ? zi.getDisplayName(date.isDaylightTime(), TimeZone.SHORT, Locale.US) : "GMT"; @@ -1057,28 +1058,28 @@ public String toString() { ByteArrayLittleEndian.setShort( buf, 8, - jla.digitPair(date.getDayOfMonth())); // dd + DecimalDigits.digitPair(date.getDayOfMonth())); // dd buf[10] = ' '; ByteArrayLittleEndian.setShort( buf, 11, - jla.digitPair(date.getHours())); // HH + DecimalDigits.digitPair(date.getHours())); // HH buf[13] = ':'; ByteArrayLittleEndian.setShort( buf, 14, - jla.digitPair(date.getMinutes())); // mm + DecimalDigits.digitPair(date.getMinutes())); // mm buf[16] = ':'; ByteArrayLittleEndian.setShort( buf, 17, - jla.digitPair(date.getSeconds())); // ss + DecimalDigits.digitPair(date.getSeconds())); // ss buf[19] = ' '; shortName.getBytes(0, shortNameLength, buf, 20); buf[20 + shortNameLength] = ' '; - jla.getChars(year, buf.length, buf); + DecimalDigits.getCharsLatin1(year, buf.length, buf); try { return jla.newStringNoRepl(buf, StandardCharsets.ISO_8859_1); } catch (CharacterCodingException cce) { @@ -1158,7 +1159,7 @@ public String toGMTString() { .getCalendarDate(getTime(), (TimeZone)null); int year = date.getYear(); - int yearSize = year >= 1000 && year <= 9999 ? 4 : jla.stringSize(year); + int yearSize = year >= 1000 && year <= 9999 ? 4 : DecimalDigits.stringSize(year); int dayOfMonth = date.getDayOfMonth(); byte[] buf = new byte[(dayOfMonth < 10 ? 19 : 20) + yearSize]; @@ -1170,29 +1171,29 @@ public String toGMTString() { ByteArrayLittleEndian.setShort( buf, 0, - jla.digitPair(dayOfMonth)); // dd + DecimalDigits.digitPair(dayOfMonth)); // dd off = 2; } buf[off++] = ' '; convertToAbbr(buf, off, wtb[date.getMonth() + 8]); // EEE buf[off + 3] = ' '; - jla.getChars(year, off + yearSize + 4, buf); + DecimalDigits.getCharsLatin1(year, off + yearSize + 4, buf); off += yearSize + 4; buf[off++] = ' '; ByteArrayLittleEndian.setShort( buf, off, - jla.digitPair(date.getHours())); // HH + DecimalDigits.digitPair(date.getHours())); // HH buf[off + 2] = ':'; ByteArrayLittleEndian.setShort( buf, off + 3, - jla.digitPair(date.getMinutes())); // mm + DecimalDigits.digitPair(date.getMinutes())); // mm buf[off + 5] = ':'; ByteArrayLittleEndian.setShort( buf, off + 6, - jla.digitPair(date.getSeconds())); // mm + DecimalDigits.digitPair(date.getSeconds())); // mm buf[off + 8] = ' '; buf[off + 9] = 'G'; buf[off + 10] = 'M'; From b26cb857d25bc20358c5eaee8b54ca6ce3967495 Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Tue, 12 Sep 2023 09:18:19 +0800 Subject: [PATCH 11/23] rebase PR #15651 last version --- .../java/lang/AbstractStringBuilder.java | 8 +- .../share/classes/java/lang/Integer.java | 30 +++++++- .../share/classes/java/lang/Long.java | 30 +++++++- .../classes/java/lang/StringConcatHelper.java | 4 +- .../jdk/internal/util/DecimalDigits.java | 76 +++++++++++++++++++ 5 files changed, 138 insertions(+), 10 deletions(-) diff --git a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java index 04fdcd7ad76bc..beaef8c589d11 100644 --- a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java +++ b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java @@ -830,10 +830,10 @@ public AbstractStringBuilder append(char c) { */ public AbstractStringBuilder append(int i) { int count = this.count; - int spaceNeeded = count + DecimalDigits.stringSize(i); + int spaceNeeded = count + Integer.stringSize(i); ensureCapacityInternal(spaceNeeded); if (isLatin1()) { - DecimalDigits.getCharsLatin1(i, spaceNeeded, value); + StringLatin1.getChars(i, spaceNeeded, value); } else { StringUTF16.getChars(i, count, spaceNeeded, value); } @@ -855,10 +855,10 @@ public AbstractStringBuilder append(int i) { */ public AbstractStringBuilder append(long l) { int count = this.count; - int spaceNeeded = count + DecimalDigits.stringSize(l); + int spaceNeeded = count + Long.stringSize(l); ensureCapacityInternal(spaceNeeded); if (isLatin1()) { - DecimalDigits.getCharsLatin1(l, spaceNeeded, value); + StringLatin1.getChars(l, spaceNeeded, value); } else { StringUTF16.getChars(l, count, spaceNeeded, value); } diff --git a/src/java.base/share/classes/java/lang/Integer.java b/src/java.base/share/classes/java/lang/Integer.java index d8b54285af4de..8aec90e705866 100644 --- a/src/java.base/share/classes/java/lang/Integer.java +++ b/src/java.base/share/classes/java/lang/Integer.java @@ -427,10 +427,10 @@ private static void formatUnsignedIntUTF16(int val, int shift, byte[] buf, int l */ @IntrinsicCandidate public static String toString(int i) { - int size = DecimalDigits.stringSize(i); + int size = stringSize(i); if (COMPACT_STRINGS) { byte[] buf = new byte[size]; - DecimalDigits.getCharsLatin1(i, size, buf); + StringLatin1.getChars(i, size, buf); return new String(buf, LATIN1); } else { byte[] buf = new byte[size * 2]; @@ -439,6 +439,32 @@ public static String toString(int i) { } } + /** + * Returns the string representation size for a given int value. + * + * @param x int value + * @return string size + * + * @implNote There are other ways to compute this: e.g. binary search, + * but values are biased heavily towards zero, and therefore linear search + * wins. The iteration results are also routinely inlined in the generated + * code after loop unrolling. + */ + static int stringSize(int x) { + int d = 1; + if (x >= 0) { + d = 0; + x = -x; + } + int p = -10; + for (int i = 1; i < 10; i++) { + if (x > p) + return i + d; + p = 10 * p; + } + return 10 + d; + } + /** * Returns a string representation of the argument as an unsigned * decimal value. diff --git a/src/java.base/share/classes/java/lang/Long.java b/src/java.base/share/classes/java/lang/Long.java index 720312465af13..2e6054054d3a5 100644 --- a/src/java.base/share/classes/java/lang/Long.java +++ b/src/java.base/share/classes/java/lang/Long.java @@ -457,10 +457,10 @@ private static void formatUnsignedLong0UTF16(long val, int shift, byte[] buf, in * @return a string representation of the argument in base 10. */ public static String toString(long i) { - int size = DecimalDigits.stringSize(i); + int size = stringSize(i); if (COMPACT_STRINGS) { byte[] buf = new byte[size]; - DecimalDigits.getCharsLatin1(i, size, buf); + StringLatin1.getChars(i, size, buf); return new String(buf, LATIN1); } else { byte[] buf = new byte[size * 2]; @@ -469,6 +469,32 @@ public static String toString(long i) { } } + /** + * Returns the string representation size for a given long value. + * + * @param x long value + * @return string size + * + * @implNote There are other ways to compute this: e.g. binary search, + * but values are biased heavily towards zero, and therefore linear search + * wins. The iteration results are also routinely inlined in the generated + * code after loop unrolling. + */ + static int stringSize(long x) { + int d = 1; + if (x >= 0) { + d = 0; + x = -x; + } + long p = -10; + for (int i = 1; i < 19; i++) { + if (x > p) + return i + d; + p = 10 * p; + } + return 19 + d; + } + /** * Returns a string representation of the argument as an unsigned * decimal value. diff --git a/src/java.base/share/classes/java/lang/StringConcatHelper.java b/src/java.base/share/classes/java/lang/StringConcatHelper.java index 25d8cf3de31b5..cc42a618c16b5 100644 --- a/src/java.base/share/classes/java/lang/StringConcatHelper.java +++ b/src/java.base/share/classes/java/lang/StringConcatHelper.java @@ -110,7 +110,7 @@ static long mix(long lengthCoder, int value) { * @return new length and coder */ static long mix(long lengthCoder, long value) { - return checkOverflow(lengthCoder + DecimalDigits.stringSize(value)); + return checkOverflow(lengthCoder + Long.stringSize(value)); } /** @@ -286,7 +286,7 @@ static long prepend(long indexCoder, byte[] buf, int value, String prefix) { */ private static long prepend(long indexCoder, byte[] buf, long value) { if (indexCoder < UTF16) { - return DecimalDigits.getCharsLatin1(value, (int)indexCoder, buf); + return StringLatin1.getChars(value, (int)indexCoder, buf); } else { return StringUTF16.getChars(value, (int)indexCoder, buf) | UTF16; } diff --git a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java index f55452868a610..96a60ef5819ed 100644 --- a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java +++ b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java @@ -157,4 +157,80 @@ public int size(long value) { public static short digitPair(int i) { return DIGITS[i]; } + + /** + * Returns the string representation size for a given int value. + * + * @param x int value + * @return string size + * + * @implNote There are other ways to compute this: e.g. binary search, + * but values are biased heavily towards zero, and therefore linear search + * wins. The iteration results are also routinely inlined in the generated + * code after loop unrolling. + */ + public static int stringSize(int x) { + int d = 1; + if (x >= 0) { + d = 0; + x = -x; + } + int p = -10; + for (int i = 1; i < 10; i++) { + if (x > p) + return i + d; + p = 10 * p; + } + return 10 + d; + } + + /** + * Places characters representing the integer i into the + * character array buf. The characters are placed into + * the buffer backwards starting with the least significant + * digit at the specified index (exclusive), and working + * backwards from there. + * + * @implNote This method converts positive inputs into negative + * values, to cover the Integer.MIN_VALUE case. Converting otherwise + * (negative to positive) will expose -Integer.MIN_VALUE that overflows + * integer. + * + * @param i value to convert + * @param index next index, after the least significant digit + * @param buf target buffer, Latin1-encoded + * @return index of the most significant digit or minus sign, if present + */ + public static int getCharsLatin1(int i, int index, byte[] buf) { + // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. + int q, r; + int charPos = index; + + boolean negative = i < 0; + if (!negative) { + i = -i; + } + + // Generate two digits per iteration + while (i <= -100) { + q = i / 100; + r = (q * 100) - i; + i = q; + charPos -= 2; + ByteArrayLittleEndian.setShort(buf, charPos, DIGITS[r]); + } + + // We know there are at most two digits left at this point. + if (i < -9) { + charPos -= 2; + ByteArrayLittleEndian.setShort(buf, charPos, DIGITS[-i]); + } else { + buf[--charPos] = (byte)('0' - i); + } + + if (negative) { + buf[--charPos] = (byte)'-'; + } + return charPos; + } } From 70566a7eb59182a27992e12068abdc1e6bc37b4d Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Tue, 12 Sep 2023 09:23:39 +0800 Subject: [PATCH 12/23] revert un-related changes --- .../java/lang/AbstractStringBuilder.java | 1 - .../share/classes/java/lang/Integer.java | 36 +++++++++---------- .../share/classes/java/lang/Long.java | 36 +++++++++---------- .../classes/java/lang/StringConcatHelper.java | 5 ++- 4 files changed, 38 insertions(+), 40 deletions(-) diff --git a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java index beaef8c589d11..b41b5db37837a 100644 --- a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java +++ b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java @@ -25,7 +25,6 @@ package java.lang; -import jdk.internal.util.DecimalDigits; import jdk.internal.math.DoubleToDecimal; import jdk.internal.math.FloatToDecimal; diff --git a/src/java.base/share/classes/java/lang/Integer.java b/src/java.base/share/classes/java/lang/Integer.java index 8aec90e705866..ab7ef34477ba1 100644 --- a/src/java.base/share/classes/java/lang/Integer.java +++ b/src/java.base/share/classes/java/lang/Integer.java @@ -439,6 +439,24 @@ public static String toString(int i) { } } + /** + * Returns a string representation of the argument as an unsigned + * decimal value. + * + * The argument is converted to unsigned decimal representation + * and returned as a string exactly as if the argument and radix + * 10 were given as arguments to the {@link #toUnsignedString(int, + * int)} method. + * + * @param i an integer to be converted to an unsigned string. + * @return an unsigned string representation of the argument. + * @see #toUnsignedString(int, int) + * @since 1.8 + */ + public static String toUnsignedString(int i) { + return Long.toString(toUnsignedLong(i)); + } + /** * Returns the string representation size for a given int value. * @@ -465,24 +483,6 @@ static int stringSize(int x) { return 10 + d; } - /** - * Returns a string representation of the argument as an unsigned - * decimal value. - * - * The argument is converted to unsigned decimal representation - * and returned as a string exactly as if the argument and radix - * 10 were given as arguments to the {@link #toUnsignedString(int, - * int)} method. - * - * @param i an integer to be converted to an unsigned string. - * @return an unsigned string representation of the argument. - * @see #toUnsignedString(int, int) - * @since 1.8 - */ - public static String toUnsignedString(int i) { - return Long.toString(toUnsignedLong(i)); - } - /** * Parses the string argument as a signed integer in the radix * specified by the second argument. The characters in the string diff --git a/src/java.base/share/classes/java/lang/Long.java b/src/java.base/share/classes/java/lang/Long.java index 2e6054054d3a5..a54fd7faeac2c 100644 --- a/src/java.base/share/classes/java/lang/Long.java +++ b/src/java.base/share/classes/java/lang/Long.java @@ -469,6 +469,24 @@ public static String toString(long i) { } } + /** + * Returns a string representation of the argument as an unsigned + * decimal value. + * + * The argument is converted to unsigned decimal representation + * and returned as a string exactly as if the argument and radix + * 10 were given as arguments to the {@link #toUnsignedString(long, + * int)} method. + * + * @param i an integer to be converted to an unsigned string. + * @return an unsigned string representation of the argument. + * @see #toUnsignedString(long, int) + * @since 1.8 + */ + public static String toUnsignedString(long i) { + return toUnsignedString(i, 10); + } + /** * Returns the string representation size for a given long value. * @@ -495,24 +513,6 @@ static int stringSize(long x) { return 19 + d; } - /** - * Returns a string representation of the argument as an unsigned - * decimal value. - * - * The argument is converted to unsigned decimal representation - * and returned as a string exactly as if the argument and radix - * 10 were given as arguments to the {@link #toUnsignedString(long, - * int)} method. - * - * @param i an integer to be converted to an unsigned string. - * @return an unsigned string representation of the argument. - * @see #toUnsignedString(long, int) - * @since 1.8 - */ - public static String toUnsignedString(long i) { - return toUnsignedString(i, 10); - } - /** * Parses the string argument as a signed {@code long} in the * radix specified by the second argument. The characters in the diff --git a/src/java.base/share/classes/java/lang/StringConcatHelper.java b/src/java.base/share/classes/java/lang/StringConcatHelper.java index cc42a618c16b5..eddfc7e649660 100644 --- a/src/java.base/share/classes/java/lang/StringConcatHelper.java +++ b/src/java.base/share/classes/java/lang/StringConcatHelper.java @@ -25,7 +25,6 @@ package java.lang; -import jdk.internal.util.DecimalDigits; import jdk.internal.misc.Unsafe; import jdk.internal.javac.PreviewFeature; import jdk.internal.util.FormatConcatItem; @@ -99,7 +98,7 @@ static long mix(long lengthCoder, char value) { * @return new length and coder */ static long mix(long lengthCoder, int value) { - return checkOverflow(lengthCoder + DecimalDigits.stringSize(value)); + return checkOverflow(lengthCoder + Integer.stringSize(value)); } /** @@ -251,7 +250,7 @@ static long prepend(long indexCoder, byte[] buf, char value, String prefix) { */ private static long prepend(long indexCoder, byte[] buf, int value) { if (indexCoder < UTF16) { - return DecimalDigits.getCharsLatin1(value, (int)indexCoder, buf); + return StringLatin1.getChars(value, (int)indexCoder, buf); } else { return StringUTF16.getChars(value, (int)indexCoder, buf) | UTF16; } From 2a617db524dae9965ceda194c5c04fde54d63b7f Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Tue, 12 Sep 2023 09:25:28 +0800 Subject: [PATCH 13/23] revert un-related changes --- src/hotspot/share/opto/stringopts.cpp | 2 +- src/java.base/share/classes/java/lang/Integer.java | 1 - src/java.base/share/classes/java/lang/Long.java | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/hotspot/share/opto/stringopts.cpp b/src/hotspot/share/opto/stringopts.cpp index 3331a3c11594d..fca532bd9a54f 100644 --- a/src/hotspot/share/opto/stringopts.cpp +++ b/src/hotspot/share/opto/stringopts.cpp @@ -1160,7 +1160,7 @@ bool StringConcat::validate_control_flow() { return !fail; } -// Mirror of DecimalDigits.stringSize() method, return the count of digits in integer, +// Mirror of Integer.stringSize() method, return the count of digits in integer, Node* PhaseStringOpts::int_stringSize(GraphKit& kit, Node* arg) { if (arg->is_Con()) { // Constant integer. Compute constant length diff --git a/src/java.base/share/classes/java/lang/Integer.java b/src/java.base/share/classes/java/lang/Integer.java index ab7ef34477ba1..2b2377a889bbe 100644 --- a/src/java.base/share/classes/java/lang/Integer.java +++ b/src/java.base/share/classes/java/lang/Integer.java @@ -25,7 +25,6 @@ package java.lang; -import jdk.internal.util.DecimalDigits; import jdk.internal.misc.CDS; import jdk.internal.misc.VM; import jdk.internal.vm.annotation.ForceInline; diff --git a/src/java.base/share/classes/java/lang/Long.java b/src/java.base/share/classes/java/lang/Long.java index a54fd7faeac2c..91583c26be106 100644 --- a/src/java.base/share/classes/java/lang/Long.java +++ b/src/java.base/share/classes/java/lang/Long.java @@ -33,7 +33,6 @@ import java.util.Objects; import java.util.Optional; -import jdk.internal.util.DecimalDigits; import jdk.internal.misc.CDS; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.IntrinsicCandidate; From 26d7c7c08f16af376a191ae227a477715331ac48 Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Tue, 12 Sep 2023 09:29:56 +0800 Subject: [PATCH 14/23] revert un-related changes --- .../time/format/DateTimeFormatterBuilder.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java b/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java index cc258f9c86452..edd5e16f8a555 100644 --- a/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java +++ b/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java @@ -121,8 +121,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import jdk.internal.util.DecimalDigits; - import sun.text.spi.JavaTimeDateTimePatternProvider; import sun.util.locale.provider.CalendarDataUtility; import sun.util.locale.provider.LocaleProviderAdapter; @@ -3353,6 +3351,17 @@ boolean isFixedWidth(DateTimeParseContext context) { return false; } + // Simplified variant of Integer.stringSize that assumes positive values + private static int stringSize(int x) { + int p = 10; + for (int i = 1; i < 10; i++) { + if (x < p) + return i; + p = 10 * p; + } + return 10; + } + private static final int[] TENS = new int[] { 1, 10, @@ -3373,7 +3382,7 @@ public boolean format(DateTimePrintContext context, StringBuilder buf) { } int val = field.range().checkValidIntValue(value, field); DecimalStyle decimalStyle = context.getDecimalStyle(); - int stringSize = DecimalDigits.stringSize(val); + int stringSize = stringSize(val); char zero = decimalStyle.getZeroDigit(); if (val == 0 || stringSize < 10 - maxWidth) { // 0 or would round down to 0 From 5d0fa13577bf24030b3849096fe9b6bbfd7af676 Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Tue, 12 Sep 2023 13:45:48 +0800 Subject: [PATCH 15/23] improved ISO_INSTANT format --- .../java/lang/AbstractStringBuilder.java | 26 +++ .../share/classes/java/lang/StringUTF16.java | 2 +- .../share/classes/java/lang/System.java | 8 + .../time/format/DateTimeFormatterBuilder.java | 203 +++++++++++++----- .../jdk/internal/access/JavaLangAccess.java | 4 + .../jdk/internal/util/DecimalDigits.java | 24 +++ 6 files changed, 212 insertions(+), 55 deletions(-) diff --git a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java index b41b5db37837a..b9accc9d6b6b8 100644 --- a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java +++ b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java @@ -35,6 +35,8 @@ import java.util.stream.IntStream; import java.util.stream.StreamSupport; import jdk.internal.util.ArraysSupport; +import jdk.internal.util.ByteArrayLittleEndian; +import jdk.internal.util.DecimalDigits; import jdk.internal.util.Preconditions; import static java.lang.String.COMPACT_STRINGS; @@ -815,6 +817,30 @@ public AbstractStringBuilder append(char c) { return this; } + void appendDigit2(int i) { + ensureCapacityInternal(count + 2); + if (isLatin1()) { + ByteArrayLittleEndian.setShort(value, count, DecimalDigits.digitPair(i)); + } else { + StringUTF16.putPair(value, count, i); + } + count += 2; + } + + void appendDigit3(int i) { + ensureCapacityInternal(count + 3); + int v = DecimalDigits.digitTriple(i); + if (isLatin1()) { + ByteArrayLittleEndian.setShort(value, count, (short) (v >> 8)); + value[count + 2] = (byte) (v >> 24); + } else { + StringUTF16.putChar(value, count, (byte) (v >> 8)); + StringUTF16.putChar(value, count + 1, (byte) (v >> 16)); + StringUTF16.putChar(value, count + 2, (byte) (v >> 24)); + } + count += 3; + } + /** * Appends the string representation of the {@code int} * argument to this sequence. diff --git a/src/java.base/share/classes/java/lang/StringUTF16.java b/src/java.base/share/classes/java/lang/StringUTF16.java index eca2a9b00cb3c..461a5577915a4 100644 --- a/src/java.base/share/classes/java/lang/StringUTF16.java +++ b/src/java.base/share/classes/java/lang/StringUTF16.java @@ -1612,7 +1612,7 @@ static int getChars(long i, int index, byte[] buf) { return charPos; } - private static void putPair(byte[] buf, int charPos, int v) { + static void putPair(byte[] buf, int charPos, int v) { int packed = (int) DecimalDigits.digitPair(v); putChar(buf, charPos, packed & 0xFF); putChar(buf, charPos + 1, packed >> 8); diff --git a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java index e50858b26f1a7..e27be81081d1e 100644 --- a/src/java.base/share/classes/java/lang/System.java +++ b/src/java.base/share/classes/java/lang/System.java @@ -2544,6 +2544,14 @@ public long stringBuilderConcatPrepend(long lengthCoder, byte[] buf, return sb.prepend(lengthCoder, buf); } + public void appendDigit2(StringBuilder sb, int i) { + sb.appendDigit2(i); + } + + public void appendDigit3(StringBuilder sb, int i) { + sb.appendDigit3(i); + } + public String join(String prefix, String suffix, String delimiter, String[] elements, int size) { return String.join(prefix, suffix, delimiter, elements, size); } diff --git a/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java b/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java index edd5e16f8a555..1120cfce2902b 100644 --- a/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java +++ b/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java @@ -121,6 +121,9 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.SharedSecrets; + import sun.text.spi.JavaTimeDateTimePatternProvider; import sun.util.locale.provider.CalendarDataUtility; import sun.util.locale.provider.LocaleProviderAdapter; @@ -159,6 +162,28 @@ * @since 1.8 */ public final class DateTimeFormatterBuilder { + /** + * Hours per day. + */ + static final int HOURS_PER_DAY = 24; + /** + * Minutes per hour. + */ + static final int MINUTES_PER_HOUR = 60; + /** + * Seconds per minute. + */ + static final int SECONDS_PER_MINUTE = 60; + /** + * Seconds per hour. + */ + static final int SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR; + /** + * Seconds per day. + */ + static final int SECONDS_PER_DAY = SECONDS_PER_HOUR * HOURS_PER_DAY; + + private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); /** * Query for a time-zone that is region-only. @@ -3811,69 +3836,139 @@ private InstantPrinterParser(int fractionalDigits) { @Override public boolean format(DateTimePrintContext context, StringBuilder buf) { - // use INSTANT_SECONDS, thus this code is not bound by Instant.MAX - Long inSecs = context.getValue(INSTANT_SECONDS); - Long inNanos = null; - if (context.getTemporal().isSupported(NANO_OF_SECOND)) { - inNanos = context.getTemporal().getLong(NANO_OF_SECOND); - } - if (inSecs == null) { - return false; - } - long inSec = inSecs; - int inNano = NANO_OF_SECOND.checkValidIntValue(inNanos != null ? inNanos : 0); - // format mostly using LocalDateTime.toString - if (inSec >= -SECONDS_0000_TO_1970) { - // current era - long zeroSecs = inSec - SECONDS_PER_10000_YEARS + SECONDS_0000_TO_1970; - long hi = Math.floorDiv(zeroSecs, SECONDS_PER_10000_YEARS) + 1; - long lo = Math.floorMod(zeroSecs, SECONDS_PER_10000_YEARS); - LocalDateTime ldt = LocalDateTime.ofEpochSecond(lo - SECONDS_0000_TO_1970, 0, ZoneOffset.UTC); - if (hi > 0) { - buf.append('+').append(hi); - } - buf.append(ldt); - if (ldt.getSecond() == 0) { - buf.append(":00"); + Instant instant = (Instant) context.getTemporal(); + long seconds = instant.getEpochSecond(); + int nano = instant.getNano(); + + LocalDate date = LocalDate.ofEpochDay( + Math.floorDiv(seconds, SECONDS_PER_DAY)); + + int year = date.getYear(); + int yearAbs = Math.abs(year); + if (yearAbs < 1000) { + if (year < 0) { + buf.append('-'); } + int y01 = yearAbs / 100; + int y23 = yearAbs - y01 * 100; + + jla.appendDigit2(buf, y01); + jla.appendDigit2(buf, y23); } else { - // before current era - long zeroSecs = inSec + SECONDS_0000_TO_1970; - long hi = zeroSecs / SECONDS_PER_10000_YEARS; - long lo = zeroSecs % SECONDS_PER_10000_YEARS; - LocalDateTime ldt = LocalDateTime.ofEpochSecond(lo - SECONDS_0000_TO_1970, 0, ZoneOffset.UTC); - int pos = buf.length(); - buf.append(ldt); - if (ldt.getSecond() == 0) { - buf.append(":00"); - } - if (hi < 0) { - if (ldt.getYear() == -10_000) { - buf.replace(pos, pos + 2, Long.toString(hi - 1)); - } else if (lo == 0) { - buf.insert(pos, hi); - } else { - buf.insert(pos + 1, Math.abs(hi)); - } + if (year > 9999) { + buf.append('+'); } + + buf.append(year); } - // add fraction - if ((fractionalDigits < 0 && inNano > 0) || fractionalDigits > 0) { - buf.append('.'); - int div = 100_000_000; - for (int i = 0; ((fractionalDigits == -1 && inNano > 0) || - (fractionalDigits == -2 && (inNano > 0 || (i % 3) != 0)) || - i < fractionalDigits); i++) { - int digit = inNano / div; - buf.append((char) (digit + '0')); - inNano = inNano - (digit * div); - div = div / 10; - } + buf.append('-'); + jla.appendDigit2(buf, date.getMonthValue()); + buf.append('-'); + jla.appendDigit2(buf, date.getDayOfMonth()); + buf.append('T'); + + LocalTime time = LocalTime.ofSecondOfDay( + Math.floorMod(seconds, SECONDS_PER_DAY)); + + jla.appendDigit2(buf, time.getHour()); + buf.append(':'); + jla.appendDigit2(buf, time.getMinute()); + buf.append(':'); + jla.appendDigit2(buf, time.getSecond()); + + if (fractionalDigits < 0) { + formatNano(buf, nano); + } else { + formatNano(buf, fractionalDigits, nano); } + buf.append('Z'); return true; } + private static void formatNano(StringBuilder buf, int nano) { + if (nano == 0) { + return; + } + + int div = nano / 1000; + int div2 = div / 1000; + + buf.append('.'); + jla.appendDigit3(buf, div2); + + int rem1 = nano - div * 1000; + int rem2 = div - div2 * 1000; + + if (rem1 == 0 && rem2 == 0) { + return; + } + + jla.appendDigit3(buf, rem2); + if (rem1 == 0) { + return; + } + jla.appendDigit3(buf, rem1); + } + + private static void formatNano(StringBuilder buf, int fractionalDigits, int nano) { + if (fractionalDigits == 0) { + return; + } + + buf.append('.'); + + int div = nano / 1000; + int div2 = div / 1000; + + if (fractionalDigits == 1) { + buf.append((char) ('0' + (div2 / 100))); + return; + } + + if (fractionalDigits == 2) { + jla.appendDigit2(buf, div2 / 10); + return; + } + + jla.appendDigit3(buf, div2); + + if (fractionalDigits == 3) { + return; + } + + int rem1 = nano - div * 1000; + int rem2 = div - div2 * 1000; + + if (fractionalDigits == 4) { + buf.append((char) ('0' + (rem2 / 100))); + return; + } + + if (fractionalDigits == 5) { + jla.appendDigit2(buf, rem2 / 10); + return; + } + + jla.appendDigit3(buf, rem2); + + if (fractionalDigits == 6) { + return; + } + + if (fractionalDigits == 7) { + buf.append((char) ('0' + (rem1 / 100))); + return; + } + + if (fractionalDigits == 8) { + jla.appendDigit2(buf, rem1 / 10); + return; + } + + jla.appendDigit3(buf, rem1); + } + @Override public int parse(DateTimeParseContext context, CharSequence text, int position) { // new context to avoid overwriting fields like year/month/day diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java index 6ea7abf6b959c..f2ee3a15f3e0e 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java @@ -434,6 +434,10 @@ public interface JavaLangAccess { @PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES) long stringBuilderConcatPrepend(long lengthCoder, byte[] buf, StringBuilder sb); + void appendDigit2(StringBuilder sb, int i); + + void appendDigit3(StringBuilder sb, int i); + /** * Join strings */ diff --git a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java index 96a60ef5819ed..6f8ba4a36545e 100644 --- a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java +++ b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java @@ -70,6 +70,21 @@ public final class DecimalDigits implements Digits { 0x3039, 0x3139, 0x3239, 0x3339, 0x3439, 0x3539, 0x3639, 0x3739, 0x3839, 0x3939 }; + @Stable + private static final int[] DIGITS_K; + + static { + int[] digits_k = new int[1000]; + for (int i = 0; i < 1000; i++) { + int c0 = i < 10 ? 2 : i < 100 ? 1 : 0; + int c1 = (i / 100) + '0'; + int c2 = ((i / 10) % 10) + '0'; + int c3 = i % 10 + '0'; + digits_k[i] = c0 + (c1 << 8) + (c2 << 16) + (c3 << 24); + } + DIGITS_K = digits_k; + } + /** * Singleton instance of DecimalDigits. */ @@ -158,6 +173,15 @@ public static short digitPair(int i) { return DIGITS[i]; } + /** + * For values from 0 to 999 return a short encoding a triple of ASCII-encoded digit characters in little-endian + * @param i value to convert + * @return a short encoding a triple of ASCII-encoded digit characters + */ + public static int digitTriple(int i) { + return DIGITS_K[i]; + } + /** * Returns the string representation size for a given int value. * From 8b3f0637b469afdcb77128d8692a7dfc2df217a0 Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Tue, 12 Sep 2023 21:00:01 +0800 Subject: [PATCH 16/23] improved DateTimeFormatter.format --- .../time/format/DateTimeFormatterBuilder.java | 434 +++++++++++++----- 1 file changed, 313 insertions(+), 121 deletions(-) diff --git a/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java b/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java index 1120cfce2902b..b436dc9fbfb5d 100644 --- a/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java +++ b/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java @@ -82,6 +82,8 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.chrono.ChronoLocalDate; @@ -2447,7 +2449,20 @@ private DateTimeFormatter toFormatter(Locale locale, ResolverStyle resolverStyle while (active.parent != null) { optionalEnd(); } - CompositePrinterParser pp = new CompositePrinterParser(printerParsers, false); + + CompositePrinterParser pp = null; + + int printerParsersSize = printerParsers.size(); + if (DateCompositePrinterParser.accept(printerParsers)) { + pp = new DateCompositePrinterParser(printerParsers, false); + } else if (TimeCompositePrinterParser.accept(printerParsers)) { + pp = new TimeCompositePrinterParser(printerParsers, false); + } + + if (pp == null) { + pp = new CompositePrinterParser(printerParsers, false); + } + return new DateTimeFormatter(pp, locale, DecimalStyle.STANDARD, resolverStyle, null, chrono, null); } @@ -2513,15 +2528,183 @@ interface DateTimePrinterParser { int parse(DateTimeParseContext context, CharSequence text, int position); } - //----------------------------------------------------------------------- + static final class DateCompositePrinterParser extends CompositePrinterParser { + private final char literal; + private DateCompositePrinterParser(List printerParsers, boolean optional) { + super(printerParsers, optional); + literal = ((CharLiteralPrinterParser) printerParsers.get(1)).literal; + } + + static boolean accept(List printerParsers) { + int printerParsersSize = printerParsers.size(); + if (printerParsersSize != 5) { + return false; + } + + if (printerParsers.get(0) instanceof NumberPrinterParser + && printerParsers.get(1) instanceof CharLiteralPrinterParser + && printerParsers.get(2) instanceof NumberPrinterParser + && printerParsers.get(3) instanceof CharLiteralPrinterParser + && printerParsers.get(4) instanceof NumberPrinterParser + ) { + NumberPrinterParser p0 = (NumberPrinterParser) printerParsers.get(0); + CharLiteralPrinterParser p1 = (CharLiteralPrinterParser) printerParsers.get(1); + NumberPrinterParser p2 = (NumberPrinterParser) printerParsers.get(2); + CharLiteralPrinterParser p3 = (CharLiteralPrinterParser) printerParsers.get(3); + NumberPrinterParser p4 = (NumberPrinterParser) printerParsers.get(4); + if (p0.field == ChronoField.YEAR + && p1.literal == p3.literal + && p0.signStyle == SignStyle.EXCEEDS_PAD + && p0.minWidth == 4 + && p0.maxWidth == 10 + && p0.subsequentWidth == 0 + && p2.field == ChronoField.MONTH_OF_YEAR + && p2.signStyle == SignStyle.NOT_NEGATIVE + && p2.minWidth == 2 + && p2.maxWidth == 2 + && p4.subsequentWidth == 0 + && p4.field == ChronoField.DAY_OF_MONTH + && p4.signStyle == SignStyle.NOT_NEGATIVE + && p4.minWidth == 2 + && p4.maxWidth == 2 + && p4.subsequentWidth == 0 + ) { + return true; + } + } + + + return false; + } + + @Override + public boolean format(DateTimePrintContext context, StringBuilder buf) { + TemporalAccessor temporal = context.getTemporal(); + + LocalDate date = null; + if (temporal instanceof LocalDateTime) { + date = ((LocalDateTime) temporal).toLocalDate(); + } else if (temporal instanceof LocalDate) { + date = (LocalDate) temporal; + } else if (temporal instanceof ZonedDateTime) { + date = ((ZonedDateTime) temporal).toLocalDate(); + } else if (temporal instanceof OffsetDateTime) { + date = ((OffsetDateTime) temporal).toLocalDate(); + } + + if (date != null) { + formatDate(buf, literal, date); + return true; + } + + return super.format(context, buf); + } + } + + static final class TimeCompositePrinterParser extends CompositePrinterParser { + final char literal; + final int fractionalDigits; + private TimeCompositePrinterParser(List printerParsers, boolean optional) { + super(printerParsers, optional); + literal = ((CharLiteralPrinterParser) printerParsers.get(1)).literal; + CompositePrinterParser p3 = (CompositePrinterParser) printerParsers.get(3); + CompositePrinterParser s2 = (CompositePrinterParser) p3.printerParsers[2]; + NanosPrinterParser n = (NanosPrinterParser) s2.printerParsers[0]; + if (n.minWidth == 0 && n.maxWidth == 9) { + fractionalDigits = -2; + } else { + fractionalDigits = n.minWidth; + } + } + + static boolean accept(List printerParsers) { + if (printerParsers.size() != 4) { + return false; + } + + if (printerParsers.get(0) instanceof NumberPrinterParser + && printerParsers.get(1) instanceof CharLiteralPrinterParser + && printerParsers.get(2) instanceof NumberPrinterParser + && printerParsers.get(3) instanceof CompositePrinterParser + ) { + NumberPrinterParser p0 = (NumberPrinterParser) printerParsers.get(0); + CharLiteralPrinterParser p1 = (CharLiteralPrinterParser) printerParsers.get(1); + NumberPrinterParser p2 = (NumberPrinterParser) printerParsers.get(2); + CompositePrinterParser p3 = (CompositePrinterParser) printerParsers.get(3); + + if (p0.field == ChronoField.HOUR_OF_DAY + && p0.signStyle == SignStyle.NOT_NEGATIVE + && p0.minWidth == 2 + && p0.maxWidth == 2 + && p0.subsequentWidth == 0 + && p2.field == ChronoField.MINUTE_OF_HOUR + && p2.signStyle == SignStyle.NOT_NEGATIVE + && p2.minWidth == 2 + && p2.maxWidth == 2 + && p2.subsequentWidth == 0 + && p3.printerParsers.length == 3 + && p3.printerParsers[0] instanceof CharLiteralPrinterParser + && p3.printerParsers[1] instanceof NumberPrinterParser + && p3.printerParsers[2] instanceof CompositePrinterParser + ) { + CharLiteralPrinterParser s0 = (CharLiteralPrinterParser) p3.printerParsers[0]; + NumberPrinterParser s1 = (NumberPrinterParser) p3.printerParsers[1]; + CompositePrinterParser s2 = (CompositePrinterParser) p3.printerParsers[2]; + if (s1.field == ChronoField.SECOND_OF_MINUTE + && s0.literal == p1.literal + && s1.minWidth == 2 + && s1.maxWidth == 2 + && s1.subsequentWidth == 0 + && s2.printerParsers.length == 1 + && s2.printerParsers[0] instanceof NanosPrinterParser + ) { + NanosPrinterParser n = (NanosPrinterParser) s2.printerParsers[0]; + if (n.decimalPoint + && n.field == ChronoField.NANO_OF_SECOND + && n.signStyle == SignStyle.NOT_NEGATIVE + && n.subsequentWidth == 0 + && ((n.minWidth == 0 && n.maxWidth == 9) || n.minWidth == n.maxWidth) + ) { + return true; + } + } + } + } + return false; + } + + @Override + public boolean format(DateTimePrintContext context, StringBuilder buf) { + TemporalAccessor temporal = context.getTemporal(); + + LocalTime time = null; + if (temporal instanceof LocalDateTime) { + time = ((LocalDateTime) temporal).toLocalTime(); + } else if (temporal instanceof LocalTime) { + time = (LocalTime) temporal; + } else if (temporal instanceof ZonedDateTime) { + time = ((ZonedDateTime) temporal).toLocalTime(); + } else if (temporal instanceof OffsetDateTime) { + time = ((OffsetDateTime) temporal).toLocalTime(); + } + + if (time != null) { + formatTime(buf, -2, time); + return true; + } + + return super.format(context, buf); + } + } + //----------------------------------------------------------------------- /** * Composite printer and parser. */ - static final class CompositePrinterParser implements DateTimePrinterParser { + static class CompositePrinterParser implements DateTimePrinterParser { private final DateTimePrinterParser[] printerParsers; private final boolean optional; - private CompositePrinterParser(List printerParsers, boolean optional) { + CompositePrinterParser(List printerParsers, boolean optional) { this(printerParsers.toArray(new DateTimePrinterParser[0]), optional); } @@ -2855,7 +3038,7 @@ static class NumberPrinterParser implements DateTimePrinterParser { final TemporalField field; final int minWidth; final int maxWidth; - private final SignStyle signStyle; + final SignStyle signStyle; final int subsequentWidth; /** @@ -3301,7 +3484,7 @@ public String toString() { * Prints and parses a NANO_OF_SECOND field with optional padding. */ static final class NanosPrinterParser extends NumberPrinterParser { - private final boolean decimalPoint; + final boolean decimalPoint; /** * Constructor. @@ -3818,155 +4001,164 @@ public String toString() { } } - //----------------------------------------------------------------------- - /** - * Prints or parses an ISO-8601 instant. - */ - static final class InstantPrinterParser implements DateTimePrinterParser { - // days in a 400 year cycle = 146097 - // days in a 10,000 year cycle = 146097 * 25 - // seconds per day = 86400 - private static final long SECONDS_PER_10000_YEARS = 146097L * 25L * 86400L; - private static final long SECONDS_0000_TO_1970 = ((146097L * 5L) - (30L * 365L + 7L)) * 86400L; - private final int fractionalDigits; + static void formatDate(StringBuilder buf, char literal, LocalDate date) { + int year = date.getYear(); + int yearAbs = Math.abs(year); + if (yearAbs < 1000) { + if (year < 0) { + buf.append('-'); + } + int y01 = yearAbs / 100; + int y23 = yearAbs - y01 * 100; - private InstantPrinterParser(int fractionalDigits) { - this.fractionalDigits = fractionalDigits; + jla.appendDigit2(buf, y01); + jla.appendDigit2(buf, y23); + } else { + if (year > 9999) { + buf.append('+'); + } + + buf.append(year); } + buf.append(literal); + jla.appendDigit2(buf, date.getMonthValue()); + buf.append(literal); + jla.appendDigit2(buf, date.getDayOfMonth()); + } - @Override - public boolean format(DateTimePrintContext context, StringBuilder buf) { - Instant instant = (Instant) context.getTemporal(); - long seconds = instant.getEpochSecond(); - int nano = instant.getNano(); + static void formatTime(StringBuilder buf, int fractionalDigits, LocalTime time) { + jla.appendDigit2(buf, time.getHour()); + buf.append(':'); + jla.appendDigit2(buf, time.getMinute()); + buf.append(':'); + jla.appendDigit2(buf, time.getSecond()); - LocalDate date = LocalDate.ofEpochDay( - Math.floorDiv(seconds, SECONDS_PER_DAY)); + int nano = time.getNano(); + if (fractionalDigits < 0) { + formatNano(buf, nano); + } else { + formatNano(buf, fractionalDigits, nano); + } + } - int year = date.getYear(); - int yearAbs = Math.abs(year); - if (yearAbs < 1000) { - if (year < 0) { - buf.append('-'); - } - int y01 = yearAbs / 100; - int y23 = yearAbs - y01 * 100; + static void formatNano(StringBuilder buf, int nano) { + if (nano == 0) { + return; + } - jla.appendDigit2(buf, y01); - jla.appendDigit2(buf, y23); - } else { - if (year > 9999) { - buf.append('+'); - } + int div = nano / 1000; + int div2 = div / 1000; - buf.append(year); - } - buf.append('-'); - jla.appendDigit2(buf, date.getMonthValue()); - buf.append('-'); - jla.appendDigit2(buf, date.getDayOfMonth()); - buf.append('T'); + buf.append('.'); + jla.appendDigit3(buf, div2); - LocalTime time = LocalTime.ofSecondOfDay( - Math.floorMod(seconds, SECONDS_PER_DAY)); + int rem1 = nano - div * 1000; + int rem2 = div - div2 * 1000; - jla.appendDigit2(buf, time.getHour()); - buf.append(':'); - jla.appendDigit2(buf, time.getMinute()); - buf.append(':'); - jla.appendDigit2(buf, time.getSecond()); + if (rem1 == 0 && rem2 == 0) { + return; + } - if (fractionalDigits < 0) { - formatNano(buf, nano); - } else { - formatNano(buf, fractionalDigits, nano); - } + jla.appendDigit3(buf, rem2); + if (rem1 == 0) { + return; + } + jla.appendDigit3(buf, rem1); + } - buf.append('Z'); - return true; + static void formatNano(StringBuilder buf, int fractionalDigits, int nano) { + if (fractionalDigits == 0) { + return; } - private static void formatNano(StringBuilder buf, int nano) { - if (nano == 0) { - return; - } + buf.append('.'); - int div = nano / 1000; - int div2 = div / 1000; + int div = nano / 1000; + int div2 = div / 1000; - buf.append('.'); - jla.appendDigit3(buf, div2); + if (fractionalDigits == 1) { + buf.append((char) ('0' + (div2 / 100))); + return; + } - int rem1 = nano - div * 1000; - int rem2 = div - div2 * 1000; + if (fractionalDigits == 2) { + jla.appendDigit2(buf, div2 / 10); + return; + } - if (rem1 == 0 && rem2 == 0) { - return; - } + jla.appendDigit3(buf, div2); - jla.appendDigit3(buf, rem2); - if (rem1 == 0) { - return; - } - jla.appendDigit3(buf, rem1); + if (fractionalDigits == 3) { + return; } - private static void formatNano(StringBuilder buf, int fractionalDigits, int nano) { - if (fractionalDigits == 0) { - return; - } + int rem1 = nano - div * 1000; + int rem2 = div - div2 * 1000; - buf.append('.'); + if (fractionalDigits == 4) { + buf.append((char) ('0' + (rem2 / 100))); + return; + } - int div = nano / 1000; - int div2 = div / 1000; + if (fractionalDigits == 5) { + jla.appendDigit2(buf, rem2 / 10); + return; + } - if (fractionalDigits == 1) { - buf.append((char) ('0' + (div2 / 100))); - return; - } + jla.appendDigit3(buf, rem2); - if (fractionalDigits == 2) { - jla.appendDigit2(buf, div2 / 10); - return; - } + if (fractionalDigits == 6) { + return; + } - jla.appendDigit3(buf, div2); + if (fractionalDigits == 7) { + buf.append((char) ('0' + (rem1 / 100))); + return; + } - if (fractionalDigits == 3) { - return; - } + if (fractionalDigits == 8) { + jla.appendDigit2(buf, rem1 / 10); + return; + } - int rem1 = nano - div * 1000; - int rem2 = div - div2 * 1000; + jla.appendDigit3(buf, rem1); + } - if (fractionalDigits == 4) { - buf.append((char) ('0' + (rem2 / 100))); - return; - } + //----------------------------------------------------------------------- + /** + * Prints or parses an ISO-8601 instant. + */ + static final class InstantPrinterParser implements DateTimePrinterParser { + // days in a 400 year cycle = 146097 + // days in a 10,000 year cycle = 146097 * 25 + // seconds per day = 86400 + private static final long SECONDS_PER_10000_YEARS = 146097L * 25L * 86400L; + private static final long SECONDS_0000_TO_1970 = ((146097L * 5L) - (30L * 365L + 7L)) * 86400L; + private final int fractionalDigits; - if (fractionalDigits == 5) { - jla.appendDigit2(buf, rem2 / 10); - return; - } + private InstantPrinterParser(int fractionalDigits) { + this.fractionalDigits = fractionalDigits; + } - jla.appendDigit3(buf, rem2); + @Override + public boolean format(DateTimePrintContext context, StringBuilder buf) { + Instant instant = (Instant) context.getTemporal(); + long seconds = instant.getEpochSecond(); + int nano = instant.getNano(); - if (fractionalDigits == 6) { - return; - } + LocalDate date = LocalDate.ofEpochDay( + Math.floorDiv(seconds, SECONDS_PER_DAY)); - if (fractionalDigits == 7) { - buf.append((char) ('0' + (rem1 / 100))); - return; - } + formatDate(buf, '-', date); + buf.append('T'); - if (fractionalDigits == 8) { - jla.appendDigit2(buf, rem1 / 10); - return; - } + LocalTime time = LocalTime.ofSecondOfDay( + Math.floorMod(seconds, SECONDS_PER_DAY)); + + formatTime(buf, fractionalDigits, time); - jla.appendDigit3(buf, rem1); + buf.append('Z'); + return true; } @Override From 9040ea0c48eb0e99ade83d59ad4d98a0ad09ea16 Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Wed, 13 Sep 2023 01:11:37 +0800 Subject: [PATCH 17/23] merge from master & revert DateTimeFormatterBuilder --- .../java/lang/AbstractStringBuilder.java | 26 -- .../share/classes/java/lang/System.java | 8 - .../time/format/DateTimeFormatterBuilder.java | 417 +++--------------- .../jdk/internal/access/JavaLangAccess.java | 4 - 4 files changed, 65 insertions(+), 390 deletions(-) diff --git a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java index b9accc9d6b6b8..b41b5db37837a 100644 --- a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java +++ b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java @@ -35,8 +35,6 @@ import java.util.stream.IntStream; import java.util.stream.StreamSupport; import jdk.internal.util.ArraysSupport; -import jdk.internal.util.ByteArrayLittleEndian; -import jdk.internal.util.DecimalDigits; import jdk.internal.util.Preconditions; import static java.lang.String.COMPACT_STRINGS; @@ -817,30 +815,6 @@ public AbstractStringBuilder append(char c) { return this; } - void appendDigit2(int i) { - ensureCapacityInternal(count + 2); - if (isLatin1()) { - ByteArrayLittleEndian.setShort(value, count, DecimalDigits.digitPair(i)); - } else { - StringUTF16.putPair(value, count, i); - } - count += 2; - } - - void appendDigit3(int i) { - ensureCapacityInternal(count + 3); - int v = DecimalDigits.digitTriple(i); - if (isLatin1()) { - ByteArrayLittleEndian.setShort(value, count, (short) (v >> 8)); - value[count + 2] = (byte) (v >> 24); - } else { - StringUTF16.putChar(value, count, (byte) (v >> 8)); - StringUTF16.putChar(value, count + 1, (byte) (v >> 16)); - StringUTF16.putChar(value, count + 2, (byte) (v >> 24)); - } - count += 3; - } - /** * Appends the string representation of the {@code int} * argument to this sequence. diff --git a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java index e27be81081d1e..e50858b26f1a7 100644 --- a/src/java.base/share/classes/java/lang/System.java +++ b/src/java.base/share/classes/java/lang/System.java @@ -2544,14 +2544,6 @@ public long stringBuilderConcatPrepend(long lengthCoder, byte[] buf, return sb.prepend(lengthCoder, buf); } - public void appendDigit2(StringBuilder sb, int i) { - sb.appendDigit2(i); - } - - public void appendDigit3(StringBuilder sb, int i) { - sb.appendDigit3(i); - } - public String join(String prefix, String suffix, String delimiter, String[] elements, int size) { return String.join(prefix, suffix, delimiter, elements, size); } diff --git a/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java b/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java index b436dc9fbfb5d..edd5e16f8a555 100644 --- a/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java +++ b/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java @@ -82,8 +82,6 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; -import java.time.OffsetDateTime; -import java.time.ZonedDateTime; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.chrono.ChronoLocalDate; @@ -123,9 +121,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import jdk.internal.access.JavaLangAccess; -import jdk.internal.access.SharedSecrets; - import sun.text.spi.JavaTimeDateTimePatternProvider; import sun.util.locale.provider.CalendarDataUtility; import sun.util.locale.provider.LocaleProviderAdapter; @@ -164,28 +159,6 @@ * @since 1.8 */ public final class DateTimeFormatterBuilder { - /** - * Hours per day. - */ - static final int HOURS_PER_DAY = 24; - /** - * Minutes per hour. - */ - static final int MINUTES_PER_HOUR = 60; - /** - * Seconds per minute. - */ - static final int SECONDS_PER_MINUTE = 60; - /** - * Seconds per hour. - */ - static final int SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR; - /** - * Seconds per day. - */ - static final int SECONDS_PER_DAY = SECONDS_PER_HOUR * HOURS_PER_DAY; - - private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); /** * Query for a time-zone that is region-only. @@ -2449,20 +2422,7 @@ private DateTimeFormatter toFormatter(Locale locale, ResolverStyle resolverStyle while (active.parent != null) { optionalEnd(); } - - CompositePrinterParser pp = null; - - int printerParsersSize = printerParsers.size(); - if (DateCompositePrinterParser.accept(printerParsers)) { - pp = new DateCompositePrinterParser(printerParsers, false); - } else if (TimeCompositePrinterParser.accept(printerParsers)) { - pp = new TimeCompositePrinterParser(printerParsers, false); - } - - if (pp == null) { - pp = new CompositePrinterParser(printerParsers, false); - } - + CompositePrinterParser pp = new CompositePrinterParser(printerParsers, false); return new DateTimeFormatter(pp, locale, DecimalStyle.STANDARD, resolverStyle, null, chrono, null); } @@ -2528,183 +2488,15 @@ interface DateTimePrinterParser { int parse(DateTimeParseContext context, CharSequence text, int position); } - static final class DateCompositePrinterParser extends CompositePrinterParser { - private final char literal; - private DateCompositePrinterParser(List printerParsers, boolean optional) { - super(printerParsers, optional); - literal = ((CharLiteralPrinterParser) printerParsers.get(1)).literal; - } - - static boolean accept(List printerParsers) { - int printerParsersSize = printerParsers.size(); - if (printerParsersSize != 5) { - return false; - } - - if (printerParsers.get(0) instanceof NumberPrinterParser - && printerParsers.get(1) instanceof CharLiteralPrinterParser - && printerParsers.get(2) instanceof NumberPrinterParser - && printerParsers.get(3) instanceof CharLiteralPrinterParser - && printerParsers.get(4) instanceof NumberPrinterParser - ) { - NumberPrinterParser p0 = (NumberPrinterParser) printerParsers.get(0); - CharLiteralPrinterParser p1 = (CharLiteralPrinterParser) printerParsers.get(1); - NumberPrinterParser p2 = (NumberPrinterParser) printerParsers.get(2); - CharLiteralPrinterParser p3 = (CharLiteralPrinterParser) printerParsers.get(3); - NumberPrinterParser p4 = (NumberPrinterParser) printerParsers.get(4); - if (p0.field == ChronoField.YEAR - && p1.literal == p3.literal - && p0.signStyle == SignStyle.EXCEEDS_PAD - && p0.minWidth == 4 - && p0.maxWidth == 10 - && p0.subsequentWidth == 0 - && p2.field == ChronoField.MONTH_OF_YEAR - && p2.signStyle == SignStyle.NOT_NEGATIVE - && p2.minWidth == 2 - && p2.maxWidth == 2 - && p4.subsequentWidth == 0 - && p4.field == ChronoField.DAY_OF_MONTH - && p4.signStyle == SignStyle.NOT_NEGATIVE - && p4.minWidth == 2 - && p4.maxWidth == 2 - && p4.subsequentWidth == 0 - ) { - return true; - } - } - - - return false; - } - - @Override - public boolean format(DateTimePrintContext context, StringBuilder buf) { - TemporalAccessor temporal = context.getTemporal(); - - LocalDate date = null; - if (temporal instanceof LocalDateTime) { - date = ((LocalDateTime) temporal).toLocalDate(); - } else if (temporal instanceof LocalDate) { - date = (LocalDate) temporal; - } else if (temporal instanceof ZonedDateTime) { - date = ((ZonedDateTime) temporal).toLocalDate(); - } else if (temporal instanceof OffsetDateTime) { - date = ((OffsetDateTime) temporal).toLocalDate(); - } - - if (date != null) { - formatDate(buf, literal, date); - return true; - } - - return super.format(context, buf); - } - } - - static final class TimeCompositePrinterParser extends CompositePrinterParser { - final char literal; - final int fractionalDigits; - private TimeCompositePrinterParser(List printerParsers, boolean optional) { - super(printerParsers, optional); - literal = ((CharLiteralPrinterParser) printerParsers.get(1)).literal; - CompositePrinterParser p3 = (CompositePrinterParser) printerParsers.get(3); - CompositePrinterParser s2 = (CompositePrinterParser) p3.printerParsers[2]; - NanosPrinterParser n = (NanosPrinterParser) s2.printerParsers[0]; - if (n.minWidth == 0 && n.maxWidth == 9) { - fractionalDigits = -2; - } else { - fractionalDigits = n.minWidth; - } - } - - static boolean accept(List printerParsers) { - if (printerParsers.size() != 4) { - return false; - } - - if (printerParsers.get(0) instanceof NumberPrinterParser - && printerParsers.get(1) instanceof CharLiteralPrinterParser - && printerParsers.get(2) instanceof NumberPrinterParser - && printerParsers.get(3) instanceof CompositePrinterParser - ) { - NumberPrinterParser p0 = (NumberPrinterParser) printerParsers.get(0); - CharLiteralPrinterParser p1 = (CharLiteralPrinterParser) printerParsers.get(1); - NumberPrinterParser p2 = (NumberPrinterParser) printerParsers.get(2); - CompositePrinterParser p3 = (CompositePrinterParser) printerParsers.get(3); - - if (p0.field == ChronoField.HOUR_OF_DAY - && p0.signStyle == SignStyle.NOT_NEGATIVE - && p0.minWidth == 2 - && p0.maxWidth == 2 - && p0.subsequentWidth == 0 - && p2.field == ChronoField.MINUTE_OF_HOUR - && p2.signStyle == SignStyle.NOT_NEGATIVE - && p2.minWidth == 2 - && p2.maxWidth == 2 - && p2.subsequentWidth == 0 - && p3.printerParsers.length == 3 - && p3.printerParsers[0] instanceof CharLiteralPrinterParser - && p3.printerParsers[1] instanceof NumberPrinterParser - && p3.printerParsers[2] instanceof CompositePrinterParser - ) { - CharLiteralPrinterParser s0 = (CharLiteralPrinterParser) p3.printerParsers[0]; - NumberPrinterParser s1 = (NumberPrinterParser) p3.printerParsers[1]; - CompositePrinterParser s2 = (CompositePrinterParser) p3.printerParsers[2]; - if (s1.field == ChronoField.SECOND_OF_MINUTE - && s0.literal == p1.literal - && s1.minWidth == 2 - && s1.maxWidth == 2 - && s1.subsequentWidth == 0 - && s2.printerParsers.length == 1 - && s2.printerParsers[0] instanceof NanosPrinterParser - ) { - NanosPrinterParser n = (NanosPrinterParser) s2.printerParsers[0]; - if (n.decimalPoint - && n.field == ChronoField.NANO_OF_SECOND - && n.signStyle == SignStyle.NOT_NEGATIVE - && n.subsequentWidth == 0 - && ((n.minWidth == 0 && n.maxWidth == 9) || n.minWidth == n.maxWidth) - ) { - return true; - } - } - } - } - return false; - } - - @Override - public boolean format(DateTimePrintContext context, StringBuilder buf) { - TemporalAccessor temporal = context.getTemporal(); - - LocalTime time = null; - if (temporal instanceof LocalDateTime) { - time = ((LocalDateTime) temporal).toLocalTime(); - } else if (temporal instanceof LocalTime) { - time = (LocalTime) temporal; - } else if (temporal instanceof ZonedDateTime) { - time = ((ZonedDateTime) temporal).toLocalTime(); - } else if (temporal instanceof OffsetDateTime) { - time = ((OffsetDateTime) temporal).toLocalTime(); - } - - if (time != null) { - formatTime(buf, -2, time); - return true; - } - - return super.format(context, buf); - } - } - //----------------------------------------------------------------------- + //----------------------------------------------------------------------- /** * Composite printer and parser. */ - static class CompositePrinterParser implements DateTimePrinterParser { + static final class CompositePrinterParser implements DateTimePrinterParser { private final DateTimePrinterParser[] printerParsers; private final boolean optional; - CompositePrinterParser(List printerParsers, boolean optional) { + private CompositePrinterParser(List printerParsers, boolean optional) { this(printerParsers.toArray(new DateTimePrinterParser[0]), optional); } @@ -3038,7 +2830,7 @@ static class NumberPrinterParser implements DateTimePrinterParser { final TemporalField field; final int minWidth; final int maxWidth; - final SignStyle signStyle; + private final SignStyle signStyle; final int subsequentWidth; /** @@ -3484,7 +3276,7 @@ public String toString() { * Prints and parses a NANO_OF_SECOND field with optional padding. */ static final class NanosPrinterParser extends NumberPrinterParser { - final boolean decimalPoint; + private final boolean decimalPoint; /** * Constructor. @@ -4001,129 +3793,6 @@ public String toString() { } } - static void formatDate(StringBuilder buf, char literal, LocalDate date) { - int year = date.getYear(); - int yearAbs = Math.abs(year); - if (yearAbs < 1000) { - if (year < 0) { - buf.append('-'); - } - int y01 = yearAbs / 100; - int y23 = yearAbs - y01 * 100; - - jla.appendDigit2(buf, y01); - jla.appendDigit2(buf, y23); - } else { - if (year > 9999) { - buf.append('+'); - } - - buf.append(year); - } - buf.append(literal); - jla.appendDigit2(buf, date.getMonthValue()); - buf.append(literal); - jla.appendDigit2(buf, date.getDayOfMonth()); - } - - static void formatTime(StringBuilder buf, int fractionalDigits, LocalTime time) { - jla.appendDigit2(buf, time.getHour()); - buf.append(':'); - jla.appendDigit2(buf, time.getMinute()); - buf.append(':'); - jla.appendDigit2(buf, time.getSecond()); - - int nano = time.getNano(); - if (fractionalDigits < 0) { - formatNano(buf, nano); - } else { - formatNano(buf, fractionalDigits, nano); - } - } - - static void formatNano(StringBuilder buf, int nano) { - if (nano == 0) { - return; - } - - int div = nano / 1000; - int div2 = div / 1000; - - buf.append('.'); - jla.appendDigit3(buf, div2); - - int rem1 = nano - div * 1000; - int rem2 = div - div2 * 1000; - - if (rem1 == 0 && rem2 == 0) { - return; - } - - jla.appendDigit3(buf, rem2); - if (rem1 == 0) { - return; - } - jla.appendDigit3(buf, rem1); - } - - static void formatNano(StringBuilder buf, int fractionalDigits, int nano) { - if (fractionalDigits == 0) { - return; - } - - buf.append('.'); - - int div = nano / 1000; - int div2 = div / 1000; - - if (fractionalDigits == 1) { - buf.append((char) ('0' + (div2 / 100))); - return; - } - - if (fractionalDigits == 2) { - jla.appendDigit2(buf, div2 / 10); - return; - } - - jla.appendDigit3(buf, div2); - - if (fractionalDigits == 3) { - return; - } - - int rem1 = nano - div * 1000; - int rem2 = div - div2 * 1000; - - if (fractionalDigits == 4) { - buf.append((char) ('0' + (rem2 / 100))); - return; - } - - if (fractionalDigits == 5) { - jla.appendDigit2(buf, rem2 / 10); - return; - } - - jla.appendDigit3(buf, rem2); - - if (fractionalDigits == 6) { - return; - } - - if (fractionalDigits == 7) { - buf.append((char) ('0' + (rem1 / 100))); - return; - } - - if (fractionalDigits == 8) { - jla.appendDigit2(buf, rem1 / 10); - return; - } - - jla.appendDigit3(buf, rem1); - } - //----------------------------------------------------------------------- /** * Prints or parses an ISO-8601 instant. @@ -4142,21 +3811,65 @@ private InstantPrinterParser(int fractionalDigits) { @Override public boolean format(DateTimePrintContext context, StringBuilder buf) { - Instant instant = (Instant) context.getTemporal(); - long seconds = instant.getEpochSecond(); - int nano = instant.getNano(); - - LocalDate date = LocalDate.ofEpochDay( - Math.floorDiv(seconds, SECONDS_PER_DAY)); - - formatDate(buf, '-', date); - buf.append('T'); - - LocalTime time = LocalTime.ofSecondOfDay( - Math.floorMod(seconds, SECONDS_PER_DAY)); - - formatTime(buf, fractionalDigits, time); - + // use INSTANT_SECONDS, thus this code is not bound by Instant.MAX + Long inSecs = context.getValue(INSTANT_SECONDS); + Long inNanos = null; + if (context.getTemporal().isSupported(NANO_OF_SECOND)) { + inNanos = context.getTemporal().getLong(NANO_OF_SECOND); + } + if (inSecs == null) { + return false; + } + long inSec = inSecs; + int inNano = NANO_OF_SECOND.checkValidIntValue(inNanos != null ? inNanos : 0); + // format mostly using LocalDateTime.toString + if (inSec >= -SECONDS_0000_TO_1970) { + // current era + long zeroSecs = inSec - SECONDS_PER_10000_YEARS + SECONDS_0000_TO_1970; + long hi = Math.floorDiv(zeroSecs, SECONDS_PER_10000_YEARS) + 1; + long lo = Math.floorMod(zeroSecs, SECONDS_PER_10000_YEARS); + LocalDateTime ldt = LocalDateTime.ofEpochSecond(lo - SECONDS_0000_TO_1970, 0, ZoneOffset.UTC); + if (hi > 0) { + buf.append('+').append(hi); + } + buf.append(ldt); + if (ldt.getSecond() == 0) { + buf.append(":00"); + } + } else { + // before current era + long zeroSecs = inSec + SECONDS_0000_TO_1970; + long hi = zeroSecs / SECONDS_PER_10000_YEARS; + long lo = zeroSecs % SECONDS_PER_10000_YEARS; + LocalDateTime ldt = LocalDateTime.ofEpochSecond(lo - SECONDS_0000_TO_1970, 0, ZoneOffset.UTC); + int pos = buf.length(); + buf.append(ldt); + if (ldt.getSecond() == 0) { + buf.append(":00"); + } + if (hi < 0) { + if (ldt.getYear() == -10_000) { + buf.replace(pos, pos + 2, Long.toString(hi - 1)); + } else if (lo == 0) { + buf.insert(pos, hi); + } else { + buf.insert(pos + 1, Math.abs(hi)); + } + } + } + // add fraction + if ((fractionalDigits < 0 && inNano > 0) || fractionalDigits > 0) { + buf.append('.'); + int div = 100_000_000; + for (int i = 0; ((fractionalDigits == -1 && inNano > 0) || + (fractionalDigits == -2 && (inNano > 0 || (i % 3) != 0)) || + i < fractionalDigits); i++) { + int digit = inNano / div; + buf.append((char) (digit + '0')); + inNano = inNano - (digit * div); + div = div / 10; + } + } buf.append('Z'); return true; } diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java index f2ee3a15f3e0e..6ea7abf6b959c 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java @@ -434,10 +434,6 @@ public interface JavaLangAccess { @PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES) long stringBuilderConcatPrepend(long lengthCoder, byte[] buf, StringBuilder sb); - void appendDigit2(StringBuilder sb, int i); - - void appendDigit3(StringBuilder sb, int i); - /** * Join strings */ From e4c5b67b6e84fabbd977738e040e50bf0c7805cf Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Wed, 13 Sep 2023 01:16:05 +0800 Subject: [PATCH 18/23] merge from master --- .../jdk/internal/util/DecimalDigits.java | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java index f55452868a610..6f8ba4a36545e 100644 --- a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java +++ b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java @@ -70,6 +70,21 @@ public final class DecimalDigits implements Digits { 0x3039, 0x3139, 0x3239, 0x3339, 0x3439, 0x3539, 0x3639, 0x3739, 0x3839, 0x3939 }; + @Stable + private static final int[] DIGITS_K; + + static { + int[] digits_k = new int[1000]; + for (int i = 0; i < 1000; i++) { + int c0 = i < 10 ? 2 : i < 100 ? 1 : 0; + int c1 = (i / 100) + '0'; + int c2 = ((i / 10) % 10) + '0'; + int c3 = i % 10 + '0'; + digits_k[i] = c0 + (c1 << 8) + (c2 << 16) + (c3 << 24); + } + DIGITS_K = digits_k; + } + /** * Singleton instance of DecimalDigits. */ @@ -157,4 +172,89 @@ public int size(long value) { public static short digitPair(int i) { return DIGITS[i]; } + + /** + * For values from 0 to 999 return a short encoding a triple of ASCII-encoded digit characters in little-endian + * @param i value to convert + * @return a short encoding a triple of ASCII-encoded digit characters + */ + public static int digitTriple(int i) { + return DIGITS_K[i]; + } + + /** + * Returns the string representation size for a given int value. + * + * @param x int value + * @return string size + * + * @implNote There are other ways to compute this: e.g. binary search, + * but values are biased heavily towards zero, and therefore linear search + * wins. The iteration results are also routinely inlined in the generated + * code after loop unrolling. + */ + public static int stringSize(int x) { + int d = 1; + if (x >= 0) { + d = 0; + x = -x; + } + int p = -10; + for (int i = 1; i < 10; i++) { + if (x > p) + return i + d; + p = 10 * p; + } + return 10 + d; + } + + /** + * Places characters representing the integer i into the + * character array buf. The characters are placed into + * the buffer backwards starting with the least significant + * digit at the specified index (exclusive), and working + * backwards from there. + * + * @implNote This method converts positive inputs into negative + * values, to cover the Integer.MIN_VALUE case. Converting otherwise + * (negative to positive) will expose -Integer.MIN_VALUE that overflows + * integer. + * + * @param i value to convert + * @param index next index, after the least significant digit + * @param buf target buffer, Latin1-encoded + * @return index of the most significant digit or minus sign, if present + */ + public static int getCharsLatin1(int i, int index, byte[] buf) { + // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. + int q, r; + int charPos = index; + + boolean negative = i < 0; + if (!negative) { + i = -i; + } + + // Generate two digits per iteration + while (i <= -100) { + q = i / 100; + r = (q * 100) - i; + i = q; + charPos -= 2; + ByteArrayLittleEndian.setShort(buf, charPos, DIGITS[r]); + } + + // We know there are at most two digits left at this point. + if (i < -9) { + charPos -= 2; + ByteArrayLittleEndian.setShort(buf, charPos, DIGITS[-i]); + } else { + buf[--charPos] = (byte)('0' - i); + } + + if (negative) { + buf[--charPos] = (byte)'-'; + } + return charPos; + } } From 8c76799cae41f879c64f6e1c95f9864e85c97419 Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Wed, 13 Sep 2023 09:08:50 +0800 Subject: [PATCH 19/23] remove DIGITS_K --- .../share/classes/java/time/LocalTime.java | 50 ++++++++----------- .../jdk/internal/util/DecimalDigits.java | 24 --------- 2 files changed, 21 insertions(+), 53 deletions(-) diff --git a/src/java.base/share/classes/java/time/LocalTime.java b/src/java.base/share/classes/java/time/LocalTime.java index 690505908ffe2..6484968755e84 100644 --- a/src/java.base/share/classes/java/time/LocalTime.java +++ b/src/java.base/share/classes/java/time/LocalTime.java @@ -98,7 +98,6 @@ import jdk.internal.access.SharedSecrets; import jdk.internal.util.ByteArrayLittleEndian; import jdk.internal.util.DecimalDigits; -import jdk.internal.vm.annotation.Stable; /** * A time without a time-zone in the ISO-8601 calendar system, @@ -136,9 +135,6 @@ public final class LocalTime private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); - @Stable - static final int[] DIGITS_K = new int[1000]; - /** * The minimum supported {@code LocalTime}, '00:00'. * This is the time of midnight at the start of the day. @@ -169,14 +165,6 @@ public final class LocalTime NOON = HOURS[12]; MIN = HOURS[0]; MAX = new LocalTime(23, 59, 59, 999_999_999); - - for (int i = 0; i < 1000; i++) { - int c0 = i < 10 ? 2 : i < 100 ? 1 : 0; - int c1 = (i / 100) + '0'; - int c2 = ((i / 10) % 10) + '0'; - int c3 = i % 10 + '0'; - DIGITS_K[i] = c0 + (c1 << 8) + (c2 << 16) + (c3 << 24); - } } /** @@ -1704,40 +1692,44 @@ static void getNanoChars(byte[] buf, int off, int nano) { int div = nano / 1000; int div2 = div / 1000; - ByteArrayLittleEndian.setInt( + + int div2_k = div2 / 100; + buf[off] = '.'; + buf[off + 1] = (byte) ('0' + div2_k); + ByteArrayLittleEndian.setShort( buf, - off, - DIGITS_K[div2] & 0xffffff00 | '.' + off + 2, + DecimalDigits.digitPair(div2 - div2_k * 100) ); off += 4; int rem1 = nano - div * 1000; - int v; + int rem2; if (rem1 == 0) { - int rem2 = div - div2 * 1000; + rem2 = div - div2 * 1000; if (rem2 == 0) { return; } - - v = DIGITS_K[rem2]; } else { - v = DIGITS_K[div - div2 * 1000]; + rem2 = div - div2 * 1000; } + int rem2_k = rem2 / 100; + buf[off] = (byte) ('0' + rem2_k); ByteArrayLittleEndian.setShort( buf, - off, - (short) (v >> 8) + off + 1, + DecimalDigits.digitPair(rem2 - rem2_k * 100) ); - off += 2; + off += 3; - if (rem1 == 0) { - buf[off] = (byte) (v >> 24); - } else { - ByteArrayLittleEndian.setInt( + if (rem1 != 0) { + int rem1_k = rem1 / 100; + buf[off] = (byte) ('0' + rem1_k); + ByteArrayLittleEndian.setShort( buf, - off, - DIGITS_K[rem1] & 0xffffff00 | (v >> 24) + off + 1, + DecimalDigits.digitPair(rem1 - rem1_k * 100) ); } } diff --git a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java index 6f8ba4a36545e..96a60ef5819ed 100644 --- a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java +++ b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java @@ -70,21 +70,6 @@ public final class DecimalDigits implements Digits { 0x3039, 0x3139, 0x3239, 0x3339, 0x3439, 0x3539, 0x3639, 0x3739, 0x3839, 0x3939 }; - @Stable - private static final int[] DIGITS_K; - - static { - int[] digits_k = new int[1000]; - for (int i = 0; i < 1000; i++) { - int c0 = i < 10 ? 2 : i < 100 ? 1 : 0; - int c1 = (i / 100) + '0'; - int c2 = ((i / 10) % 10) + '0'; - int c3 = i % 10 + '0'; - digits_k[i] = c0 + (c1 << 8) + (c2 << 16) + (c3 << 24); - } - DIGITS_K = digits_k; - } - /** * Singleton instance of DecimalDigits. */ @@ -173,15 +158,6 @@ public static short digitPair(int i) { return DIGITS[i]; } - /** - * For values from 0 to 999 return a short encoding a triple of ASCII-encoded digit characters in little-endian - * @param i value to convert - * @return a short encoding a triple of ASCII-encoded digit characters - */ - public static int digitTriple(int i) { - return DIGITS_K[i]; - } - /** * Returns the string representation size for a given int value. * From 8471814c27ca98cd1e2a8868f0e423c7ac240011 Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Wed, 13 Sep 2023 10:42:47 +0800 Subject: [PATCH 20/23] restore ZoneOffset::buildId, reduce changes --- .../share/classes/java/time/ZoneOffset.java | 49 +++++-------------- 1 file changed, 13 insertions(+), 36 deletions(-) diff --git a/src/java.base/share/classes/java/time/ZoneOffset.java b/src/java.base/share/classes/java/time/ZoneOffset.java index 72e2f355c6f85..3ed7dc8624ec1 100644 --- a/src/java.base/share/classes/java/time/ZoneOffset.java +++ b/src/java.base/share/classes/java/time/ZoneOffset.java @@ -88,9 +88,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import jdk.internal.access.JavaLangAccess; -import jdk.internal.access.SharedSecrets; -import jdk.internal.util.ByteArrayLittleEndian; import jdk.internal.util.DecimalDigits; import jdk.internal.vm.annotation.Stable; @@ -140,8 +137,6 @@ public final class ZoneOffset extends ZoneId implements TemporalAccessor, TemporalAdjuster, Comparable, Serializable { - private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); - /** Cache of time-zone offset by offset in seconds. */ private static final ConcurrentMap SECONDS_CACHE = new ConcurrentHashMap<>(16, 0.75f, 4); /** Cache of time-zone offset by ID. */ @@ -456,37 +451,19 @@ private ZoneOffset(int totalSeconds) { private static String buildId(int totalSeconds) { if (totalSeconds == 0) { return "Z"; - } - - int absTotalSeconds = Math.abs(totalSeconds); - int absHours = absTotalSeconds / SECONDS_PER_HOUR; - int minuteSeconds = absTotalSeconds - absHours * SECONDS_PER_HOUR; - int absMinutes = minuteSeconds / SECONDS_PER_MINUTE; - int absSeconds = minuteSeconds - absMinutes * SECONDS_PER_MINUTE; - - byte[] buf = new byte[6 + (absSeconds != 0 ? 3 : 0)]; - buf[0] = (byte) (totalSeconds < 0 ? '-' : '+'); - ByteArrayLittleEndian.setShort( - buf, - 1, - DecimalDigits.digitPair(absHours)); - buf[3] = ':'; - ByteArrayLittleEndian.setShort( - buf, - 4, - DecimalDigits.digitPair(absMinutes)); - if (absSeconds != 0) { - buf[6] = ':'; - ByteArrayLittleEndian.setShort( - buf, - 7, - DecimalDigits.digitPair(absSeconds)); - } - - try { - return jla.newStringNoRepl(buf, StandardCharsets.ISO_8859_1); - } catch (CharacterCodingException cce) { - throw new AssertionError(cce); + } else { + int absTotalSeconds = Math.abs(totalSeconds); + StringBuilder buf = new StringBuilder(); + int absHours = absTotalSeconds / SECONDS_PER_HOUR; + int absMinutes = (absTotalSeconds / SECONDS_PER_MINUTE) % MINUTES_PER_HOUR; + buf.append(totalSeconds < 0 ? "-" : "+") + .append(absHours < 10 ? "0" : "").append(absHours) + .append(absMinutes < 10 ? ":0" : ":").append(absMinutes); + int absSeconds = absTotalSeconds % SECONDS_PER_MINUTE; + if (absSeconds != 0) { + buf.append(absSeconds < 10 ? ":0" : ":").append(absSeconds); + } + return buf.toString(); } } From fba3979f6f5619c9901150ac960bb66d25e00672 Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Wed, 13 Sep 2023 10:46:15 +0800 Subject: [PATCH 21/23] restore ZoneOffset::buildId, reduce changes --- src/java.base/share/classes/java/time/ZoneOffset.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/java/time/ZoneOffset.java b/src/java.base/share/classes/java/time/ZoneOffset.java index 3ed7dc8624ec1..11b903e7c0297 100644 --- a/src/java.base/share/classes/java/time/ZoneOffset.java +++ b/src/java.base/share/classes/java/time/ZoneOffset.java @@ -88,7 +88,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import jdk.internal.util.DecimalDigits; import jdk.internal.vm.annotation.Stable; /** @@ -457,8 +456,8 @@ private static String buildId(int totalSeconds) { int absHours = absTotalSeconds / SECONDS_PER_HOUR; int absMinutes = (absTotalSeconds / SECONDS_PER_MINUTE) % MINUTES_PER_HOUR; buf.append(totalSeconds < 0 ? "-" : "+") - .append(absHours < 10 ? "0" : "").append(absHours) - .append(absMinutes < 10 ? ":0" : ":").append(absMinutes); + .append(absHours < 10 ? "0" : "").append(absHours) + .append(absMinutes < 10 ? ":0" : ":").append(absMinutes); int absSeconds = absTotalSeconds % SECONDS_PER_MINUTE; if (absSeconds != 0) { buf.append(absSeconds < 10 ? ":0" : ":").append(absSeconds); From d3ad49069e17f129c7d3a4cca7a6e04586f0099c Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Wed, 13 Sep 2023 10:47:20 +0800 Subject: [PATCH 22/23] restore ZoneOffset::buildId, reduce changes --- src/java.base/share/classes/java/time/ZoneOffset.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/java.base/share/classes/java/time/ZoneOffset.java b/src/java.base/share/classes/java/time/ZoneOffset.java index 11b903e7c0297..14ac5fcfb6ba1 100644 --- a/src/java.base/share/classes/java/time/ZoneOffset.java +++ b/src/java.base/share/classes/java/time/ZoneOffset.java @@ -72,8 +72,6 @@ import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.Serializable; -import java.nio.charset.CharacterCodingException; -import java.nio.charset.StandardCharsets; import java.time.temporal.ChronoField; import java.time.temporal.Temporal; import java.time.temporal.TemporalAccessor; From b7a3528c9d26e5c049a85d4a6b278e830fe93979 Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Wed, 13 Sep 2023 22:15:05 +0800 Subject: [PATCH 23/23] simplify LocalDate::getChars --- src/java.base/share/classes/java/time/LocalDate.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/java.base/share/classes/java/time/LocalDate.java b/src/java.base/share/classes/java/time/LocalDate.java index dc4058b09b8a5..d1799ac3f6374 100644 --- a/src/java.base/share/classes/java/time/LocalDate.java +++ b/src/java.base/share/classes/java/time/LocalDate.java @@ -2177,25 +2177,27 @@ int getChars(byte[] buf, int off) { int year = this.year; int yearSize = yearSize(year); int yearAbs = Math.abs(year); + + int yearEnd = off + yearSize; if (yearAbs < 1000) { if (year < 0) { - buf[off] = '-'; + buf[off++] = '-'; } int y01 = yearAbs / 100; int y23 = yearAbs - y01 * 100; ByteArrayLittleEndian.setInt( buf, - off + (year < 0 ? 1 : 0), + off, (DecimalDigits.digitPair(y23) << 16) | DecimalDigits.digitPair(y01)); } else { if (year > 9999) { buf[off] = '+'; } - DecimalDigits.getCharsLatin1(year, off + yearSize, buf); + DecimalDigits.getCharsLatin1(year, yearEnd, buf); } - off += yearSize; + off = yearEnd; buf[off] = '-'; ByteArrayLittleEndian.setShort( buf,