From 4eeadae1dc18843b7c782a8b2fb2866afe57025d Mon Sep 17 00:00:00 2001 From: "lingjun.cg" Date: Mon, 22 Jul 2024 14:38:31 +0800 Subject: [PATCH] [Backport] 8333396: Use StringBuilder internally for java.text.Format.* formatting Summary: java.text.Format.* use StringBuffer internally, but StringBuffer's bad performance character become obvious after obsoleting biased locking. So use StringBuilder if possible in java.text.Formating.*. Testing: All java.text.format jtreg. Reviewers: D-D-H, Yude Lin Issue: https://github.com/dragonwell-project/dragonwell21/issues/83 --- .../text/CharacterIteratorFieldDelegate.java | 6 +- .../share/classes/java/text/ChoiceFormat.java | 14 +- .../java/text/CompactNumberFormat.java | 77 +++++-- .../share/classes/java/text/DateFormat.java | 28 ++- .../classes/java/text/DecimalFormat.java | 78 +++++-- .../java/text/DontCareFieldPosition.java | 6 +- .../classes/java/text/FieldPosition.java | 6 +- .../share/classes/java/text/Format.java | 53 ++++- .../classes/java/text/MessageFormat.java | 19 +- .../share/classes/java/text/NumberFormat.java | 47 +++- .../classes/java/text/SimpleDateFormat.java | 46 +++- .../classes/java/text/StringBufFactory.java | 216 ++++++++++++++++++ .../bench/java/text/DateFormatterBench.java | 79 +++++++ .../java/text/MessageFormatterBench.java | 79 +++++++ 14 files changed, 682 insertions(+), 72 deletions(-) create mode 100644 src/java.base/share/classes/java/text/StringBufFactory.java create mode 100644 test/micro/org/openjdk/bench/java/text/DateFormatterBench.java create mode 100644 test/micro/org/openjdk/bench/java/text/MessageFormatterBench.java diff --git a/src/java.base/share/classes/java/text/CharacterIteratorFieldDelegate.java b/src/java.base/share/classes/java/text/CharacterIteratorFieldDelegate.java index 4d0788491ac..33b811e9ecd 100644 --- a/src/java.base/share/classes/java/text/CharacterIteratorFieldDelegate.java +++ b/src/java.base/share/classes/java/text/CharacterIteratorFieldDelegate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2024, 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 @@ -54,7 +54,7 @@ class CharacterIteratorFieldDelegate implements Format.FieldDelegate { } public void formatted(Format.Field attr, Object value, int start, int end, - StringBuffer buffer) { + Format.StringBuf buffer) { if (start != end) { if (start < size) { // Adjust attributes of existing runs @@ -93,7 +93,7 @@ public void formatted(Format.Field attr, Object value, int start, int end, } public void formatted(int fieldID, Format.Field attr, Object value, - int start, int end, StringBuffer buffer) { + int start, int end, Format.StringBuf buffer) { formatted(attr, value, start, end, buffer); } diff --git a/src/java.base/share/classes/java/text/ChoiceFormat.java b/src/java.base/share/classes/java/text/ChoiceFormat.java index cf87f4452cf..9003e1aa103 100644 --- a/src/java.base/share/classes/java/text/ChoiceFormat.java +++ b/src/java.base/share/classes/java/text/ChoiceFormat.java @@ -384,7 +384,13 @@ public Object[] getFormats() { */ public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition status) { - return format((double)number, toAppendTo, status); + return format((double) number, StringBufFactory.of(toAppendTo), status).asStringBuffer(); + } + + @Override + StringBuf format(long number, StringBuf toAppendTo, + FieldPosition status) { + return format((double) number, toAppendTo, status); } /** @@ -397,6 +403,12 @@ public StringBuffer format(long number, StringBuffer toAppendTo, */ public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition status) { + return format(number, StringBufFactory.of(toAppendTo), status).asStringBuffer(); + } + + @Override + StringBuf format(double number, StringBuf toAppendTo, + FieldPosition status) { // find the number int i; for (i = 0; i < choiceLimits.length; ++i) { diff --git a/src/java.base/share/classes/java/text/CompactNumberFormat.java b/src/java.base/share/classes/java/text/CompactNumberFormat.java index 5b95b945799..1a49ae23ad8 100644 --- a/src/java.base/share/classes/java/text/CompactNumberFormat.java +++ b/src/java.base/share/classes/java/text/CompactNumberFormat.java @@ -521,6 +521,35 @@ public final StringBuffer format(Object number, && ((BigInteger) number).bitLength() < 64)) { return format(((Number) number).longValue(), toAppendTo, fieldPosition); + } else if (number instanceof BigDecimal) { + return format((BigDecimal) number, StringBufFactory.of(toAppendTo), fieldPosition).asStringBuffer(); + } else if (number instanceof BigInteger) { + return format((BigInteger) number, StringBufFactory.of(toAppendTo), fieldPosition).asStringBuffer(); + } else if (number instanceof Number) { + return format(((Number) number).doubleValue(), toAppendTo, fieldPosition); + } else { + throw new IllegalArgumentException("Cannot format " + + number.getClass().getName() + " as a number"); + } + } + + @Override + StringBuf format(Object number, + StringBuf toAppendTo, + FieldPosition fieldPosition) { + + if (number == null) { + throw new IllegalArgumentException("Cannot format null as a number"); + } + + if (number instanceof Long || number instanceof Integer + || number instanceof Short || number instanceof Byte + || number instanceof AtomicInteger + || number instanceof AtomicLong + || (number instanceof BigInteger + && ((BigInteger) number).bitLength() < 64)) { + return format(((Number) number).longValue(), toAppendTo, + fieldPosition); } else if (number instanceof BigDecimal) { return format((BigDecimal) number, toAppendTo, fieldPosition); } else if (number instanceof BigInteger) { @@ -529,7 +558,7 @@ public final StringBuffer format(Object number, return format(((Number) number).doubleValue(), toAppendTo, fieldPosition); } else { throw new IllegalArgumentException("Cannot format " - + number.getClass().getName() + " as a number"); + + number.getClass().getName() + " as a number"); } } @@ -560,12 +589,21 @@ public final StringBuffer format(Object number, public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition) { + fieldPosition.setBeginIndex(0); + fieldPosition.setEndIndex(0); + return format(number, StringBufFactory.of(result), fieldPosition.getFieldDelegate()).asStringBuffer(); + } + + @Override + StringBuf format(double number, StringBuf result, + FieldPosition fieldPosition) { + fieldPosition.setBeginIndex(0); fieldPosition.setEndIndex(0); return format(number, result, fieldPosition.getFieldDelegate()); } - private StringBuffer format(double number, StringBuffer result, + private StringBuf format(double number, StringBuf result, FieldDelegate delegate) { boolean nanOrInfinity = decimalFormat.handleNaN(number, result, delegate); @@ -650,12 +688,21 @@ false, getMaximumIntegerDigits(), getMinimumIntegerDigits(), public StringBuffer format(long number, StringBuffer result, FieldPosition fieldPosition) { + fieldPosition.setBeginIndex(0); + fieldPosition.setEndIndex(0); + return format(number, StringBufFactory.of(result), fieldPosition.getFieldDelegate()).asStringBuffer(); + } + + @Override + StringBuf format(long number, StringBuf result, + FieldPosition fieldPosition) { + fieldPosition.setBeginIndex(0); fieldPosition.setEndIndex(0); return format(number, result, fieldPosition.getFieldDelegate()); } - private StringBuffer format(long number, StringBuffer result, FieldDelegate delegate) { + private StringBuf format(long number, StringBuf result, FieldDelegate delegate) { boolean isNegative = (number < 0); if (isNegative) { number = -number; @@ -726,15 +773,15 @@ isNegative, false, getMaximumIntegerDigits(), * of the prefix and the suffix fields can be * obtained using {@link NumberFormat.Field#PREFIX} * and {@link NumberFormat.Field#SUFFIX} respectively. - * @return the {@code StringBuffer} passed in as {@code result} + * @return the {@code StringBuf} passed in as {@code result} * @throws ArithmeticException if rounding is needed with rounding * mode being set to {@code RoundingMode.UNNECESSARY} * @throws NullPointerException if any of the given parameter * is {@code null} * @see FieldPosition */ - private StringBuffer format(BigDecimal number, StringBuffer result, - FieldPosition fieldPosition) { + private StringBuf format(BigDecimal number, StringBuf result, + FieldPosition fieldPosition) { Objects.requireNonNull(number); fieldPosition.setBeginIndex(0); @@ -742,7 +789,7 @@ private StringBuffer format(BigDecimal number, StringBuffer result, return format(number, result, fieldPosition.getFieldDelegate()); } - private StringBuffer format(BigDecimal number, StringBuffer result, + private StringBuf format(BigDecimal number, StringBuf result, FieldDelegate delegate) { boolean isNegative = number.signum() == -1; @@ -812,15 +859,15 @@ false, getMaximumIntegerDigits(), getMinimumIntegerDigits(), * prefix and the suffix fields can be obtained * using {@link NumberFormat.Field#PREFIX} and * {@link NumberFormat.Field#SUFFIX} respectively. - * @return the {@code StringBuffer} passed in as {@code result} + * @return the {@code StringBuf} passed in as {@code result} * @throws ArithmeticException if rounding is needed with rounding * mode being set to {@code RoundingMode.UNNECESSARY} * @throws NullPointerException if any of the given parameter * is {@code null} * @see FieldPosition */ - private StringBuffer format(BigInteger number, StringBuffer result, - FieldPosition fieldPosition) { + private StringBuf format(BigInteger number, StringBuf result, + FieldPosition fieldPosition) { Objects.requireNonNull(number); fieldPosition.setBeginIndex(0); @@ -828,7 +875,7 @@ private StringBuffer format(BigInteger number, StringBuffer result, return format(number, result, fieldPosition.getFieldDelegate(), false); } - private StringBuffer format(BigInteger number, StringBuffer result, + private StringBuf format(BigInteger number, StringBuf result, FieldDelegate delegate, boolean formatLong) { boolean isNegative = number.signum() == -1; @@ -905,7 +952,7 @@ private String getAffix(boolean isExpanded, boolean isPrefix, boolean isNegative * {@code NumberFormat.Field.SIGN} and * {@code NumberFormat.Field.PREFIX} fields */ - private void appendPrefix(StringBuffer result, String prefix, + private void appendPrefix(StringBuf result, String prefix, FieldDelegate delegate) { append(result, expandAffix(prefix), delegate, getFieldPositions(prefix, NumberFormat.Field.PREFIX)); @@ -921,7 +968,7 @@ private void appendPrefix(StringBuffer result, String prefix, * {@code NumberFormat.Field.SIGN} and * {@code NumberFormat.Field.SUFFIX} fields */ - private void appendSuffix(StringBuffer result, String suffix, + private void appendSuffix(StringBuf result, String suffix, FieldDelegate delegate) { append(result, expandAffix(suffix), delegate, getFieldPositions(suffix, NumberFormat.Field.SUFFIX)); @@ -937,7 +984,7 @@ private void appendSuffix(StringBuffer result, String suffix, * @param positions a list of {@code FieldPosition} in the given * string */ - private void append(StringBuffer result, String string, + private void append(StringBuf result, String string, FieldDelegate delegate, List positions) { if (!string.isEmpty()) { int start = result.length(); @@ -1103,7 +1150,7 @@ private int selectCompactPattern(BigInteger number) { public AttributedCharacterIterator formatToCharacterIterator(Object obj) { CharacterIteratorFieldDelegate delegate = new CharacterIteratorFieldDelegate(); - StringBuffer sb = new StringBuffer(); + StringBuf sb = StringBufFactory.of(); if (obj instanceof Double || obj instanceof Float) { format(((Number) obj).doubleValue(), sb, delegate); diff --git a/src/java.base/share/classes/java/text/DateFormat.java b/src/java.base/share/classes/java/text/DateFormat.java index ed6356e3fd8..17d3606933c 100644 --- a/src/java.base/share/classes/java/text/DateFormat.java +++ b/src/java.base/share/classes/java/text/DateFormat.java @@ -342,6 +342,19 @@ else if (obj instanceof Number) throw new IllegalArgumentException("Cannot format given Object as a Date"); } + @Override + final StringBuf format(Object obj, StringBuf toAppendTo, + FieldPosition fieldPosition) { + if (obj instanceof Date) { + return format((Date) obj, toAppendTo, fieldPosition); + } else if (obj instanceof Number) { + return format(new Date(((Number) obj).longValue()), + toAppendTo, fieldPosition); + } else { + throw new IllegalArgumentException("Cannot format given Object as a Date"); + } + } + /** * Formats a {@link Date} into a date-time string. The formatted * string is appended to the given {@code StringBuffer}. @@ -367,6 +380,11 @@ else if (obj instanceof Number) public abstract StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition); + StringBuf format(Date date, StringBuf toAppendTo, + FieldPosition fieldPosition) { + throw new UnsupportedOperationException("Subclasses should override this method"); + } + /** * Formats a {@link Date} into a date-time string. * @@ -375,8 +393,14 @@ public abstract StringBuffer format(Date date, StringBuffer toAppendTo, */ public final String format(Date date) { - return format(date, new StringBuffer(), - DontCareFieldPosition.INSTANCE).toString(); + if ("java.text".equals(getClass().getPackageName()) + && "java.text".equals(numberFormat.getClass().getPackageName())) { + return format(date, StringBufFactory.of(), + DontCareFieldPosition.INSTANCE).toString(); + } else { + return format(date, new StringBuffer(), + DontCareFieldPosition.INSTANCE).toString(); + } } /** diff --git a/src/java.base/share/classes/java/text/DecimalFormat.java b/src/java.base/share/classes/java/text/DecimalFormat.java index 3e0f1e3246f..7507c6270f0 100644 --- a/src/java.base/share/classes/java/text/DecimalFormat.java +++ b/src/java.base/share/classes/java/text/DecimalFormat.java @@ -537,9 +537,9 @@ public final StringBuffer format(Object number, ((BigInteger)number).bitLength () < 64)) { return format(((Number)number).longValue(), toAppendTo, pos); } else if (number instanceof BigDecimal) { - return format((BigDecimal)number, toAppendTo, pos); + return format((BigDecimal)number, StringBufFactory.of(toAppendTo), pos).asStringBuffer(); } else if (number instanceof BigInteger) { - return format((BigInteger)number, toAppendTo, pos); + return format((BigInteger)number, StringBufFactory.of(toAppendTo), pos).asStringBuffer(); } else if (number instanceof Number) { return format(((Number)number).doubleValue(), toAppendTo, pos); } else { @@ -547,6 +547,28 @@ public final StringBuffer format(Object number, } } + @Override + final StringBuf format(Object number, + StringBuf toAppendTo, + FieldPosition pos) { + if (number instanceof Long || number instanceof Integer || + number instanceof Short || number instanceof Byte || + number instanceof AtomicInteger || + number instanceof AtomicLong || + (number instanceof BigInteger && + ((BigInteger) number).bitLength() < 64)) { + return format(((Number) number).longValue(), toAppendTo, pos); + } else if (number instanceof BigDecimal) { + return format((BigDecimal) number, toAppendTo, pos); + } else if (number instanceof BigInteger) { + return format((BigInteger) number, toAppendTo, pos); + } else if (number instanceof Number) { + return format(((Number) number).doubleValue(), toAppendTo, pos); + } else { + throw new IllegalArgumentException("Cannot format given Object as a Number"); + } + } + /** * Formats a double to produce a string. * @param number The double to format @@ -569,6 +591,12 @@ public final StringBuffer format(Object number, @Override public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition) { + return format(number, StringBufFactory.of(result), fieldPosition).asStringBuffer(); + } + + @Override + StringBuf format(double number, StringBuf result, + FieldPosition fieldPosition) { // If fieldPosition is a DontCareFieldPosition instance we can // try to go to fast-path code. boolean tryFastPath = false; @@ -600,8 +628,8 @@ public StringBuffer format(double number, StringBuffer result, * mode being set to RoundingMode.UNNECESSARY * @return The formatted number string */ - StringBuffer format(double number, StringBuffer result, - FieldDelegate delegate) { + StringBuf format(double number, StringBuf result, + FieldDelegate delegate) { boolean nanOrInfinity = handleNaN(number, result, delegate); if (nanOrInfinity) { @@ -647,7 +675,7 @@ StringBuffer format(double number, StringBuffer result, * @param delegate notified of locations of sub fields * @return true, if number is a NaN; false otherwise */ - boolean handleNaN(double number, StringBuffer result, + boolean handleNaN(double number, StringBuf result, FieldDelegate delegate) { if (Double.isNaN(number) || (Double.isInfinite(number) && multiplier == 0)) { @@ -672,7 +700,7 @@ boolean handleNaN(double number, StringBuffer result, * @return true, if number is a {@code Double.NEGATIVE_INFINITY} or * {@code Double.POSITIVE_INFINITY}; false otherwise */ - boolean handleInfinity(double number, StringBuffer result, + boolean handleInfinity(double number, StringBuf result, FieldDelegate delegate, boolean isNegative) { if (Double.isInfinite(number)) { if (isNegative) { @@ -701,7 +729,7 @@ boolean handleInfinity(double number, StringBuffer result, return false; } - StringBuffer doubleSubformat(double number, StringBuffer result, + StringBuf doubleSubformat(double number, StringBuf result, FieldDelegate delegate, boolean isNegative) { synchronized (digitList) { int maxIntDigits = super.getMaximumIntegerDigits(); @@ -742,6 +770,14 @@ public StringBuffer format(long number, StringBuffer result, fieldPosition.setBeginIndex(0); fieldPosition.setEndIndex(0); + return format(number, StringBufFactory.of(result), fieldPosition.getFieldDelegate()).asStringBuffer(); + } + + StringBuf format(long number, StringBuf result, + FieldPosition fieldPosition) { + fieldPosition.setBeginIndex(0); + fieldPosition.setEndIndex(0); + return format(number, result, fieldPosition.getFieldDelegate()); } @@ -755,8 +791,8 @@ public StringBuffer format(long number, StringBuffer result, * mode being set to RoundingMode.UNNECESSARY * @see java.text.FieldPosition */ - StringBuffer format(long number, StringBuffer result, - FieldDelegate delegate) { + StringBuf format(long number, StringBuf result, + FieldDelegate delegate) { boolean isNegative = (number < 0); if (isNegative) { number = -number; @@ -830,8 +866,8 @@ StringBuffer format(long number, StringBuffer result, * mode being set to RoundingMode.UNNECESSARY * @see java.text.FieldPosition */ - private StringBuffer format(BigDecimal number, StringBuffer result, - FieldPosition fieldPosition) { + private StringBuf format(BigDecimal number, StringBuf result, + FieldPosition fieldPosition) { fieldPosition.setBeginIndex(0); fieldPosition.setEndIndex(0); return format(number, result, fieldPosition.getFieldDelegate()); @@ -846,8 +882,8 @@ private StringBuffer format(BigDecimal number, StringBuffer result, * mode being set to RoundingMode.UNNECESSARY * @return The formatted number string */ - StringBuffer format(BigDecimal number, StringBuffer result, - FieldDelegate delegate) { + StringBuf format(BigDecimal number, StringBuf result, + FieldDelegate delegate) { if (multiplier != 1) { number = number.multiply(getBigDecimalMultiplier()); } @@ -889,8 +925,8 @@ StringBuffer format(BigDecimal number, StringBuffer result, * mode being set to RoundingMode.UNNECESSARY * @see java.text.FieldPosition */ - private StringBuffer format(BigInteger number, StringBuffer result, - FieldPosition fieldPosition) { + private StringBuf format(BigInteger number, StringBuf result, + FieldPosition fieldPosition) { fieldPosition.setBeginIndex(0); fieldPosition.setEndIndex(0); @@ -907,8 +943,8 @@ private StringBuffer format(BigInteger number, StringBuffer result, * mode being set to RoundingMode.UNNECESSARY * @see java.text.FieldPosition */ - StringBuffer format(BigInteger number, StringBuffer result, - FieldDelegate delegate, boolean formatLong) { + StringBuf format(BigInteger number, StringBuf result, + FieldDelegate delegate, boolean formatLong) { if (multiplier != 1) { number = number.multiply(getBigIntegerMultiplier()); } @@ -967,7 +1003,7 @@ StringBuffer format(BigInteger number, StringBuffer result, public AttributedCharacterIterator formatToCharacterIterator(Object obj) { CharacterIteratorFieldDelegate delegate = new CharacterIteratorFieldDelegate(); - StringBuffer sb = new StringBuffer(); + StringBuf sb = StringBufFactory.of(); if (obj instanceof Double || obj instanceof Float) { format(((Number)obj).doubleValue(), sb, delegate); @@ -1760,7 +1796,7 @@ void setDigitList(Number number, boolean isNegative, int maxDigits) { * Complete the formatting of a finite number. On entry, the digitList must * be filled in with the correct digits. */ - private StringBuffer subformat(StringBuffer result, FieldDelegate delegate, + private StringBuf subformat(StringBuf result, FieldDelegate delegate, boolean isNegative, boolean isInteger, int maxIntDigits, int minIntDigits, int maxFraDigits, int minFraDigits) { @@ -1802,7 +1838,7 @@ private StringBuffer subformat(StringBuffer result, FieldDelegate delegate, * @param maxFraDigits maximum fraction digits * @param minFraDigits minimum fraction digits */ - void subformatNumber(StringBuffer result, FieldDelegate delegate, + void subformatNumber(StringBuf result, FieldDelegate delegate, boolean isNegative, boolean isInteger, int maxIntDigits, int minIntDigits, int maxFraDigits, int minFraDigits) { @@ -2089,7 +2125,7 @@ void subformatNumber(StringBuffer result, FieldDelegate delegate, *

* This is used by {@code subformat} to add the prefix/suffix. */ - private void append(StringBuffer result, String string, + private void append(StringBuf result, String string, FieldDelegate delegate, FieldPosition[] positions, Format.Field signAttribute) { diff --git a/src/java.base/share/classes/java/text/DontCareFieldPosition.java b/src/java.base/share/classes/java/text/DontCareFieldPosition.java index 33c086d370b..3117c67e786 100644 --- a/src/java.base/share/classes/java/text/DontCareFieldPosition.java +++ b/src/java.base/share/classes/java/text/DontCareFieldPosition.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2024, 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 @@ -36,10 +36,10 @@ class DontCareFieldPosition extends FieldPosition { private final Format.FieldDelegate noDelegate = new Format.FieldDelegate() { public void formatted(Format.Field attr, Object value, int start, - int end, StringBuffer buffer) { + int end, Format.StringBuf buffer) { } public void formatted(int fieldID, Format.Field attr, Object value, - int start, int end, StringBuffer buffer) { + int start, int end, Format.StringBuf buffer) { } }; diff --git a/src/java.base/share/classes/java/text/FieldPosition.java b/src/java.base/share/classes/java/text/FieldPosition.java index 26d7da7f6ec..f5d659b2ceb 100644 --- a/src/java.base/share/classes/java/text/FieldPosition.java +++ b/src/java.base/share/classes/java/text/FieldPosition.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2024, 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 @@ -294,7 +294,7 @@ private class Delegate implements Format.FieldDelegate { private boolean encounteredField; public void formatted(Format.Field attr, Object value, int start, - int end, StringBuffer buffer) { + int end, Format.StringBuf buffer) { if (!encounteredField && matchesField(attr)) { setBeginIndex(start); setEndIndex(end); @@ -303,7 +303,7 @@ public void formatted(Format.Field attr, Object value, int start, } public void formatted(int fieldID, Format.Field attr, Object value, - int start, int end, StringBuffer buffer) { + int start, int end, Format.StringBuf buffer) { if (!encounteredField && matchesField(attr, fieldID)) { setBeginIndex(start); setEndIndex(end); diff --git a/src/java.base/share/classes/java/text/Format.java b/src/java.base/share/classes/java/text/Format.java index 80352f5bbc5..03b40fbd836 100644 --- a/src/java.base/share/classes/java/text/Format.java +++ b/src/java.base/share/classes/java/text/Format.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2024, 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 @@ -144,7 +144,8 @@ protected Format() { } /** - * Formats an object to produce a string. This is equivalent to + * Formats an object to produce a string. + * This method returns a string that would be equal to the string returned by *

* {@link #format(Object, StringBuffer, FieldPosition) format}(obj, * new StringBuffer(), new FieldPosition(0)).toString(); @@ -156,7 +157,11 @@ protected Format() { * object */ public final String format (Object obj) { - return format(obj, new StringBuffer(), new FieldPosition(0)).toString(); + if ("java.text".equals(getClass().getPackageName())) { + return format(obj, StringBufFactory.of(), new FieldPosition(0)).toString(); + } else { + return format(obj, new StringBuffer(), new FieldPosition(0)).toString(); + } } /** @@ -181,6 +186,12 @@ public abstract StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos); + StringBuf format(Object obj, + StringBuf toAppendTo, + FieldPosition pos) { + throw new UnsupportedOperationException("Subclasses should override this method"); + } + /** * Formats an Object producing an {@code AttributedCharacterIterator}. * You can use the returned {@code AttributedCharacterIterator} @@ -391,7 +402,7 @@ interface FieldDelegate { * NOT modify it. */ public void formatted(Format.Field attr, Object value, int start, - int end, StringBuffer buffer); + int end, StringBuf buffer); /** * Notified when a particular region of the String is formatted. @@ -405,6 +416,38 @@ public void formatted(Format.Field attr, Object value, int start, * NOT modify it. */ public void formatted(int fieldID, Format.Field attr, Object value, - int start, int end, StringBuffer buffer); + int start, int end, StringBuf buffer); + } + + /** + * StringBuf is the minimal common interface of {@code StringBuffer} and {@code StringBuilder}. + * It is used by the various {@code Format} implementations as the internal string buffer. + */ + sealed interface StringBuf + permits StringBufFactory.StringBufferImpl, StringBufFactory.StringBuilderImpl { + + int length(); + + String substring(int start, int end); + + String substring(int start); + + StringBuf append(char c); + + StringBuf append(String str); + + StringBuf append(int i); + + StringBuf append(char[] str, int offset, int len); + + StringBuf append(CharSequence s, int start, int end); + + StringBuf append(StringBuffer sb); + + boolean isProxyStringBuilder(); + + StringBuffer asStringBuffer(); + + StringBuilder asStringBuilder(); } } diff --git a/src/java.base/share/classes/java/text/MessageFormat.java b/src/java.base/share/classes/java/text/MessageFormat.java index 28d1474ad71..6473b8826c9 100644 --- a/src/java.base/share/classes/java/text/MessageFormat.java +++ b/src/java.base/share/classes/java/text/MessageFormat.java @@ -859,12 +859,13 @@ public Format[] getFormats() { public final StringBuffer format(Object[] arguments, StringBuffer result, FieldPosition pos) { - return subformat(arguments, result, pos, null); + return subformat(arguments, StringBufFactory.of(result), pos, null).asStringBuffer(); } /** * Creates a MessageFormat with the given pattern and uses it - * to format the given arguments. This is equivalent to + * to format the given arguments. + * This method returns a string that would be equal to the string returned by *
* (new {@link #MessageFormat(String) MessageFormat}(pattern)).{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString() *
@@ -908,6 +909,12 @@ public static String format(String pattern, Object ... arguments) { public final StringBuffer format(Object arguments, StringBuffer result, FieldPosition pos) { + return subformat((Object[]) arguments, StringBufFactory.of(result), pos, null).asStringBuffer(); + } + + @Override + final StringBuf format(Object arguments, StringBuf result, + FieldPosition pos) { return subformat((Object[]) arguments, result, pos, null); } @@ -947,7 +954,7 @@ public final StringBuffer format(Object arguments, StringBuffer result, * @since 1.4 */ public AttributedCharacterIterator formatToCharacterIterator(Object arguments) { - StringBuffer result = new StringBuffer(); + StringBuf result = StringBufFactory.of(); ArrayList iterators = new ArrayList<>(); if (arguments == null) { @@ -1282,7 +1289,7 @@ protected Object readResolve() throws InvalidObjectException { * {@code arguments} array is not of the type * expected by the format element(s) that use it. */ - private StringBuffer subformat(Object[] arguments, StringBuffer result, + private StringBuf subformat(Object[] arguments, StringBuf result, FieldPosition fp, List characterIterators) { // note: this implementation assumes a fast substring & index. // if this is not true, would be better to append chars one by one. @@ -1392,9 +1399,9 @@ private StringBuffer subformat(Object[] arguments, StringBuffer result, /** * Convenience method to append all the characters in - * {@code iterator} to the StringBuffer {@code result}. + * {@code iterator} to the StringBuf {@code result}. */ - private void append(StringBuffer result, CharacterIterator iterator) { + private void append(StringBuf result, CharacterIterator iterator) { if (iterator.first() != CharacterIterator.DONE) { char aChar; diff --git a/src/java.base/share/classes/java/text/NumberFormat.java b/src/java.base/share/classes/java/text/NumberFormat.java index 6d893bc4ec7..6da4fe461bb 100644 --- a/src/java.base/share/classes/java/text/NumberFormat.java +++ b/src/java.base/share/classes/java/text/NumberFormat.java @@ -284,6 +284,23 @@ public StringBuffer format(Object number, } } + @Override + StringBuf format(Object number, + StringBuf toAppendTo, + FieldPosition pos) { + if (number instanceof Long || number instanceof Integer || + number instanceof Short || number instanceof Byte || + number instanceof AtomicInteger || number instanceof AtomicLong || + (number instanceof BigInteger && + ((BigInteger) number).bitLength() < 64)) { + return format(((Number) number).longValue(), toAppendTo, pos); + } else if (number instanceof Number) { + return format(((Number) number).doubleValue(), toAppendTo, pos); + } else { + throw new IllegalArgumentException("Cannot format given Object as a Number"); + } + } + /** * Parses text from a string to produce a {@code Number}. *

@@ -328,8 +345,13 @@ public final String format(double number) { if (result != null) return result; - return format(number, new StringBuffer(), - DontCareFieldPosition.INSTANCE).toString(); + if ("java.text".equals(getClass().getPackageName())) { + return format(number, StringBufFactory.of(), + DontCareFieldPosition.INSTANCE).toString(); + } else { + return format(number, new StringBuffer(), + DontCareFieldPosition.INSTANCE).toString(); + } } /* @@ -348,8 +370,13 @@ public final String format(double number) { * @see java.text.Format#format */ public final String format(long number) { - return format(number, new StringBuffer(), - DontCareFieldPosition.INSTANCE).toString(); + if ("java.text".equals(getClass().getPackageName())) { + return format(number, StringBufFactory.of(), + DontCareFieldPosition.INSTANCE).toString(); + } else { + return format(number, new StringBuffer(), + DontCareFieldPosition.INSTANCE).toString(); + } } /** @@ -375,6 +402,12 @@ public abstract StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos); + StringBuf format(double number, + StringBuf toAppendTo, + FieldPosition pos) { + throw new UnsupportedOperationException("Subclasses should override this method"); + } + /** * Specialization of format. * @@ -398,6 +431,12 @@ public abstract StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos); + StringBuf format(long number, + StringBuf toAppendTo, + FieldPosition pos) { + throw new UnsupportedOperationException("Subclasses should override this method"); + } + /** * Returns a Long if possible (e.g., within the range [Long.MIN_VALUE, * Long.MAX_VALUE] and with no decimals), otherwise a Double. diff --git a/src/java.base/share/classes/java/text/SimpleDateFormat.java b/src/java.base/share/classes/java/text/SimpleDateFormat.java index 4eb08f6f5f6..14232b49189 100644 --- a/src/java.base/share/classes/java/text/SimpleDateFormat.java +++ b/src/java.base/share/classes/java/text/SimpleDateFormat.java @@ -968,11 +968,18 @@ public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos) { pos.beginIndex = pos.endIndex = 0; + return format(date, StringBufFactory.of(toAppendTo), pos.getFieldDelegate()).asStringBuffer(); + } + + @Override + final StringBuf format(Date date, StringBuf toAppendTo, + FieldPosition pos) { + pos.beginIndex = pos.endIndex = 0; return format(date, toAppendTo, pos.getFieldDelegate()); } // Called from Format after creating a FieldDelegate - private StringBuffer format(Date date, StringBuffer toAppendTo, + private StringBuf format(Date date, StringBuf toAppendTo, FieldDelegate delegate) { // Convert input date to time field list calendar.setTime(date); @@ -1024,7 +1031,7 @@ private StringBuffer format(Date date, StringBuffer toAppendTo, */ @Override public AttributedCharacterIterator formatToCharacterIterator(Object obj) { - StringBuffer sb = new StringBuffer(); + StringBuf sb = StringBufFactory.of(); CharacterIteratorFieldDelegate delegate = new CharacterIteratorFieldDelegate(); @@ -1130,7 +1137,7 @@ else if (obj == null) { * Private member function that does the real date/time formatting. */ private void subFormat(int patternCharIndex, int count, - FieldDelegate delegate, StringBuffer buffer, + FieldDelegate delegate, StringBuf buffer, boolean useDateFormatSymbols) { int maxIntCount = Integer.MAX_VALUE; @@ -1320,7 +1327,11 @@ private void subFormat(int patternCharIndex, int count, } int num = (value / 60) * 100 + (value % 60); - CalendarUtils.sprintf0d(buffer, num, width); + if (buffer.isProxyStringBuilder()) { + CalendarUtils.sprintf0d(buffer.asStringBuilder(), num, width); + } else { + CalendarUtils.sprintf0d(buffer.asStringBuffer(), num, width); + } break; case PATTERN_ISO_ZONE: // 'X' @@ -1340,7 +1351,11 @@ private void subFormat(int patternCharIndex, int count, value = -value; } - CalendarUtils.sprintf0d(buffer, value / 60, 2); + if (buffer.isProxyStringBuilder()) { + CalendarUtils.sprintf0d(buffer.asStringBuilder(), value / 60, 2); + } else { + CalendarUtils.sprintf0d(buffer.asStringBuffer(), value / 60, 2); + } if (count == 1) { break; } @@ -1348,7 +1363,11 @@ private void subFormat(int patternCharIndex, int count, if (count == 3) { buffer.append(':'); } - CalendarUtils.sprintf0d(buffer, value % 60, 2); + if (buffer.isProxyStringBuilder()) { + CalendarUtils.sprintf0d(buffer.asStringBuilder(), value % 60, 2); + } else { + CalendarUtils.sprintf0d(buffer.asStringBuffer(), value % 60, 2); + } break; default: @@ -1382,7 +1401,7 @@ private void subFormat(int patternCharIndex, int count, /** * Formats a number with the specified minimum and maximum number of digits. */ - private void zeroPaddingNumber(int value, int minDigits, int maxDigits, StringBuffer buffer) + private void zeroPaddingNumber(int value, int minDigits, int maxDigits, StringBuf buffer) { // Optimization for 1, 2 and 4 digit numbers. This should // cover most cases of formatting date/time related items. @@ -1425,7 +1444,17 @@ private void zeroPaddingNumber(int value, int minDigits, int maxDigits, StringBu numberFormat.setMinimumIntegerDigits(minDigits); numberFormat.setMaximumIntegerDigits(maxDigits); - numberFormat.format((long)value, buffer, DontCareFieldPosition.INSTANCE); + if (buffer.isProxyStringBuilder()) { + //User can set numberFormat with a user-defined NumberFormat which + //not override format(long, StringBuf, FieldPosition). + if ("java.text".equals(numberFormat.getClass().getPackageName())) { + numberFormat.format((long) value, buffer, DontCareFieldPosition.INSTANCE); + } else { + buffer.append(numberFormat.format((long) value, new StringBuffer(), DontCareFieldPosition.INSTANCE)); + } + } else { + numberFormat.format((long) value, buffer.asStringBuffer(), DontCareFieldPosition.INSTANCE); + } } @@ -2537,5 +2566,4 @@ private void checkNegativeNumberExpression() { originalNumberFormat = numberFormat; } } - } diff --git a/src/java.base/share/classes/java/text/StringBufFactory.java b/src/java.base/share/classes/java/text/StringBufFactory.java new file mode 100644 index 00000000000..bfad0c1093d --- /dev/null +++ b/src/java.base/share/classes/java/text/StringBufFactory.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2024, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 java.text; + +import java.text.Format.StringBuf; + +/** + * {@code StringBufFactory} creates implementations of {@code Format.StringBuf}, + * which is an interface with the minimum overlap required to support {@code StringBuffer} + * and {@code StringBuilder} in {@code Format}. This allows for {@code StringBuilder} to be used + * in place of {@code StringBuffer} to provide performance benefits for JDK internal + * {@code Format} subclasses. + */ +final class StringBufFactory { + + private StringBufFactory() { + } + + static StringBuf of(StringBuffer sb) { + return new StringBufferImpl(sb); + } + + static StringBuf of(StringBuilder sb) { + return new StringBuilderImpl(sb); + } + + static StringBuf of() { + return new StringBuilderImpl(); + } + + final static class StringBufferImpl implements StringBuf { + private final StringBuffer sb; + + StringBufferImpl(StringBuffer sb) { + this.sb = sb; + } + + @Override + public int length() { + return sb.length(); + } + + @Override + public String substring(int start, int end) { + return sb.substring(start, end); + } + + @Override + public String substring(int start) { + return sb.substring(start); + } + + @Override + public StringBuf append(char c) { + sb.append(c); + return this; + } + + @Override + public StringBuf append(String str) { + sb.append(str); + return this; + } + + @Override + public StringBuf append(int i) { + sb.append(i); + return this; + } + + @Override + public StringBuf append(char[] str, int offset, int len) { + sb.append(str, offset, len); + return this; + } + + @Override + public StringBuf append(CharSequence s, int start, int end) { + sb.append(s, start, end); + return this; + } + + @Override + public StringBuf append(StringBuffer asb) { + sb.append(asb); + return this; + } + + @Override + public boolean isProxyStringBuilder() { + return false; + } + + @Override + public StringBuffer asStringBuffer() { + return sb; + } + + @Override + public StringBuilder asStringBuilder() { + throw new AssertionError("Can't cast StringBuffer to StringBuilder"); + } + + @Override + public String toString() { + return sb.toString(); + } + } + + final static class StringBuilderImpl implements StringBuf { + private final StringBuilder sb; + + StringBuilderImpl(StringBuilder sb) { + this.sb = sb; + } + + StringBuilderImpl() { + this.sb = new StringBuilder(); + } + + @Override + public int length() { + return sb.length(); + } + + @Override + public String substring(int start, int end) { + return sb.substring(start, end); + } + + @Override + public String substring(int start) { + return sb.substring(start); + } + + @Override + public StringBuf append(char c) { + sb.append(c); + return this; + } + + @Override + public StringBuf append(String str) { + sb.append(str); + return this; + } + + @Override + public StringBuf append(int i) { + sb.append(i); + return this; + } + + @Override + public StringBuf append(char[] str, int offset, int len) { + sb.append(str, offset, len); + return this; + } + + @Override + public StringBuf append(CharSequence s, int start, int end) { + sb.append(s, start, end); + return this; + } + + @Override + public StringBuf append(StringBuffer asb) { + sb.append(asb); + return this; + } + + + @Override + public boolean isProxyStringBuilder() { + return true; + } + + @Override + public StringBuffer asStringBuffer() { + throw new AssertionError("Can't cast StringBuilder to StringBuffer"); + } + + @Override + public StringBuilder asStringBuilder() { + return sb; + } + + @Override + public String toString() { + return sb.toString(); + } + } +} diff --git a/test/micro/org/openjdk/bench/java/text/DateFormatterBench.java b/test/micro/org/openjdk/bench/java/text/DateFormatterBench.java new file mode 100644 index 00000000000..f9a6340a0fb --- /dev/null +++ b/test/micro/org/openjdk/bench/java/text/DateFormatterBench.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2024, 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.text; + +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.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import java.text.DateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@Fork(3) +@State(Scope.Benchmark) +public class DateFormatterBench { + + private Date date; + + private Object objDate; + + @Setup + public void setup() { + date = new Date(); + objDate = new Date(); + } + + private DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.FULL, Locale.ENGLISH); + + @Benchmark + public String testFormatDate() { + return dateFormat.format(date); + } + + @Benchmark + public String testFormatObject() { + return dateFormat.format(objDate); + } + + public static void main(String... args) throws Exception { + Options opts = new OptionsBuilder().include(DateFormatterBench.class.getSimpleName()).shouldDoGC(true).build(); + new Runner(opts).run(); + } +} diff --git a/test/micro/org/openjdk/bench/java/text/MessageFormatterBench.java b/test/micro/org/openjdk/bench/java/text/MessageFormatterBench.java new file mode 100644 index 00000000000..5d3399cb46d --- /dev/null +++ b/test/micro/org/openjdk/bench/java/text/MessageFormatterBench.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2024, 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.text; + +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.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +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; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import java.text.MessageFormat; +import java.util.Locale; +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@Fork(3) +@State(Scope.Benchmark) +public class MessageFormatterBench { + + private Object[][] values; + + @Setup + public void setup() { + values = new Object[][]{ + new Object[]{Integer.valueOf(13), "MyDisk1"}, + new Object[]{Float.valueOf(25.6f), "MyDisk2"}, + new Object[]{Double.valueOf(123.89), "MyDisk3"}, + new Object[]{Long.valueOf(1234567), "MyDisk4"}, + }; + } + + private MessageFormat messageFormat = new MessageFormat("There is {0} GB of free space on the {1}.", Locale.ENGLISH); + + @Benchmark + @OperationsPerInvocation(4) + public void testMessageFormat(final Blackhole bh) { + for (Object[] value : values) { + bh.consume(messageFormat.format(value)); + } + } + + public static void main(String... args) throws Exception { + Options opts = new OptionsBuilder().include(MessageFormatterBench.class.getSimpleName()).shouldDoGC(true).build(); + new Runner(opts).run(); + } +}