diff --git a/src/java.base/share/classes/java/time/format/DateTimeFormatter.java b/src/java.base/share/classes/java/time/format/DateTimeFormatter.java index 16d7193c5561f..3b9b84b56832e 100644 --- a/src/java.base/share/classes/java/time/format/DateTimeFormatter.java +++ b/src/java.base/share/classes/java/time/format/DateTimeFormatter.java @@ -544,6 +544,12 @@ public final class DateTimeFormatter { */ private final ZoneId zone; + /** + * Flag indicating whether this formatter only uses ChronoField instances. + * This is used to optimize the storage of parsed field values in the Parsed class. + */ + final boolean onlyChronoField; + //----------------------------------------------------------------------- /** * Creates a formatter using the specified pattern. @@ -1486,6 +1492,7 @@ public static final TemporalQuery parsedLeapSecond() { this.resolverStyle = Objects.requireNonNull(resolverStyle, "resolverStyle"); this.chrono = chrono; this.zone = zone; + this.onlyChronoField = printerParser.onlyChronoField(); } //----------------------------------------------------------------------- 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 4708094effbde..e330b8cbee01d 100644 --- a/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java +++ b/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java @@ -2580,6 +2580,24 @@ public int parse(DateTimeParseContext context, CharSequence text, int position) } } + /** + * Checks whether this composite printer parser only uses ChronoField instances. + * This is used to optimize the storage of parsed field values in the Parsed class. + * + * @return true if all printer parsers in this composite only use ChronoField instances, + * false otherwise + */ + boolean onlyChronoField() { + for (DateTimePrinterParser pp : printerParsers) { + if ((pp instanceof NumberPrinterParser npp && !(npp.field instanceof ChronoField)) + || (pp instanceof TextPrinterParser tpp && !(tpp.field instanceof ChronoField)) + || (pp instanceof CompositePrinterParser cpp && !cpp.onlyChronoField())) { + return false; + } + } + return true; + } + @Override public String toString() { StringBuilder buf = new StringBuilder(); diff --git a/src/java.base/share/classes/java/time/format/DateTimeParseContext.java b/src/java.base/share/classes/java/time/format/DateTimeParseContext.java index 0a4c7e825a3b6..fff139dc9f38f 100644 --- a/src/java.base/share/classes/java/time/format/DateTimeParseContext.java +++ b/src/java.base/share/classes/java/time/format/DateTimeParseContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, 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 @@ -120,7 +120,7 @@ final class DateTimeParseContext { DateTimeParseContext(DateTimeFormatter formatter) { super(); this.formatter = formatter; - parsed.add(new Parsed()); + parsed.add(new Parsed(formatter.onlyChronoField)); } /** diff --git a/src/java.base/share/classes/java/time/format/Parsed.java b/src/java.base/share/classes/java/time/format/Parsed.java index 1ec956dfa2cd3..12623367ca08d 100644 --- a/src/java.base/share/classes/java/time/format/Parsed.java +++ b/src/java.base/share/classes/java/time/format/Parsed.java @@ -1,5 +1,6 @@ /* - * Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Alibaba Group Holding Limited. 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 @@ -98,6 +99,7 @@ import java.time.temporal.TemporalQueries; import java.time.temporal.TemporalQuery; import java.time.temporal.UnsupportedTemporalTypeException; +import java.util.EnumMap; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -128,7 +130,7 @@ final class Parsed implements TemporalAccessor { /** * The parsed fields. */ - final Map fieldValues = new HashMap<>(); + final Map fieldValues; /** * The parsed zone. */ @@ -169,7 +171,12 @@ final class Parsed implements TemporalAccessor { /** * Creates an instance. */ - Parsed() { + @SuppressWarnings("unchecked") + Parsed(boolean onlyChronoField) { + // Create the EnumMap with raw types and cast it appropriately + // This is safe because ChronoField implements TemporalField + fieldValues = onlyChronoField ? (Map) (Map) new EnumMap<>(ChronoField.class) + : new HashMap<>(); } /** @@ -177,7 +184,7 @@ final class Parsed implements TemporalAccessor { */ Parsed copy() { // only copy fields used in parsing stage - Parsed cloned = new Parsed(); + Parsed cloned = new Parsed(fieldValues instanceof EnumMap); cloned.fieldValues.putAll(this.fieldValues); cloned.zone = this.zone; cloned.zoneNameType = this.zoneNameType; diff --git a/test/micro/org/openjdk/bench/java/time/format/DateTimeFormatterParse.java b/test/micro/org/openjdk/bench/java/time/format/DateTimeFormatterParse.java new file mode 100644 index 0000000000000..cdf6f47083576 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/time/format/DateTimeFormatterParse.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2025, Alibaba Group Holding Limited. 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.format; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; + +import java.util.Locale; +import java.util.Random; +import java.util.TimeZone; +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 DateTimeFormatterParse { + static final DateTimeFormatter formatterLocalTime = DateTimeFormatter.ofPattern("HH:mm:ss"); + static final DateTimeFormatter formatterLocalTimeWithNano = DateTimeFormatter.ofPattern("HH:mm:ss.SSS"); + static final DateTimeFormatter formatterLocalDate = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + static final DateTimeFormatter formatterLocalDateTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"); + static final DateTimeFormatter formatterLocalDateTimeWithNano = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS"); + static final DateTimeFormatter formatterOffsetDateTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSSZZZZZ"); + static final DateTimeFormatter formatterZonedDateTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSSZZZZZ'['VV']'"); + + static final String STR_LOCALTIME = "21:11:48"; + static final String STR_LOCALTIME_WITH_NANO = "21:11:48.456"; + static final String STR_LOCALDATE = "2024-08-01"; + static final String STR_LOCALDATETIME = "2024-08-01T21:11:48"; + static final String STR_LOCALDATETIME_WITH_NANO = "2024-08-01T21:11:48.456"; + static final String STR_INSTANT = "2024-08-12T03:25:54.980339Z"; + static final String STR_OFFSETDATETIME = "2024-08-12T11:50:46.731509+08:00"; + static final String STR_ZONEDDATETIME = "2024-08-12T11:50:46.731509+08:00[Asia/Shanghai]"; + + @Benchmark + public LocalTime parseLocalTime() { + return LocalTime.parse(STR_LOCALTIME, formatterLocalTime); + } + + @Benchmark + public LocalTime parseLocalTimeWithNano() { + return LocalTime.parse(STR_LOCALTIME_WITH_NANO, formatterLocalTimeWithNano); + } + + @Benchmark + public LocalDate parseLocalDate() { + return LocalDate.parse(STR_LOCALDATE, formatterLocalDate); + } + + @Benchmark + public LocalDateTime parseLocalDateTime() { + return LocalDateTime.parse(STR_LOCALDATETIME, formatterLocalDateTime); + } + + @Benchmark + public LocalDateTime parseLocalDateTimeWithNano() { + return LocalDateTime.parse(STR_LOCALDATETIME_WITH_NANO, formatterLocalDateTimeWithNano); + } + + @Benchmark + public OffsetDateTime parseOffsetDateTime() { + return OffsetDateTime.parse(STR_OFFSETDATETIME, formatterOffsetDateTime); + } + + @Benchmark + public ZonedDateTime parseZonedDateTime() { + return ZonedDateTime.parse(STR_ZONEDDATETIME, formatterZonedDateTime); + } + + @Benchmark + public Instant parseInstant() { + return Instant.parse(STR_INSTANT); + } +} \ No newline at end of file