Каждый раз, когда вы используете String.format()
, создается новый экземпляр java.util.Formatter
, который впоследствии будет удален.
Просто потому, что мне было любопытно, я немного поиграл с этим, чтобы использовать format()
немного эффективнее, кэшируя объект Formatter
. К сожалению, Formatter
не является поточно-ориентированным, и любые средства синхронизации могут поглотить любой прирост производительности, который вы можете получить от его кеширования.
Наконец, я нашел решение ниже:
import java.util.Formatter;
/*------------------------*\
====** Static Initialisations **===========================================
\*------------------------*/
/**
* The initial buffer size that is used by
* {@link #format(String, Object...)}
* and
* {@link #format(Locale, String, Object...)}
* (per thread): {@value}.
*/
public final static int FORMAT_INITIAL_BUFFERSIZE = 2048;
/**
* The cached
* {@link Formatter}
* instance.
*/
private static final ThreadLocal<Formatter> m_Formatter;
static
{
//---* The cached Formatter instance *---------------------------------
final Supplier<Formatter> initializer = () -> new Formatter( new StringBuilder( FORMAT_INITIAL_BUFFERSIZE ), Locale.getDefault( Locale.Category.FORMAT ) );
m_Formatter = ThreadLocal.<Formatter>withInitial( initializer );
}
/*---------*\
====** Methods **==========================================================
\*---------*/
/**
* Returns a formatted String using the specified
* {@link Locale},
* format String, and arguments.<br>
* <br>This method is meant as a replacement for the method
* {@link java.lang.String#format(String, Object...)};
* that implementation always uses a new instance of
* {@link Formatter}
* for each invocation. The implementation here uses a cached instance
* instead, and as {@code Formatter} is not inherently thread-safe, this
* cached instance is created and stored per thread, therefore no
* synchronisation is needed.<br>
* <br>The performance gain for this implementation is not that impressive
* when compared with that of
* {@link #format(String, Object...)},
* but still in the 10% range.
* {@code java.lang.String.format()}.
*
* @param locale The
* {@link Locale}
* that will be applied during formatting. If {@code locale} is
* {@code null} then no localisation is applied.
* @param format A format String with the syntax as described for the
* {@link Formatter}
* class.
* @param args The arguments referenced by the format specifiers in
* the {@code format} String. If there are more arguments than format
* specifiers, the extra arguments are ignored. The number of
* arguments is variable and may be zero. The maximum number of
* arguments is limited by the maximum dimension of a Java array as
* defined by <cite>The Java™ Virtual Machine
* Specification</cite>. The behaviour on a {@code null} argument
* depends on the conversion.
* @throws IllegalFormatException A format string contains an illegal
* syntax, a format specifier that is incompatible with the given
* arguments, insufficient arguments given the format string, or other
* illegal conditions occurred. For specification of all possible
* formatting errors, see the "Details" section of the
* {@code Formatter} class specification.
* @return A formatted String.
*
* @see java.util.Formatter
*/
@SuppressWarnings( "resource" )
public final static String format( final Locale locale, final String format, final Object... args ) throws IllegalFormatException
{
/*
* When this method StringUtils.format() is used inside an
* implementation of Formattable.formatTo() that in turn was called by
* this method already, the internal out buffer of the formatter
* contains already some text that has to be preserved.
*
* Therefore the current length of the out buffer is kept before new
* contents is added by the call to formatter.format(), and reset after
* the new formatted text was retrieved from that buffer.
*/
final var formatter = m_Formatter.get();
final var result = (StringBuilder) formatter.out();
final var currentPos = result.length();
formatter.format( locale, format, args );
final var retValue = result.substring( currentPos );
result.setLength( currentPos );
//---* Done *----------------------------------------------------------
return retValue;
} // format()
Кэшированный экземпляр Formatter
хранится в экземпляре java.lang.ThreadLocal
, поэтому каждый поток имеет свой собственный экземпляр, и синхронизация не требуется.
Пока он работает; всесторонние тесты пока не выявили никаких проблем, а некоторые (искусственные) тесты показывают прирост производительности примерно на 10% по сравнению с использованием java.lang.String.format()
; это увеличение является постоянным на любом (классе) компьютере, на котором я выполнял тесты.
Очевидным недостатком этого решения является то, что объем памяти для каждого потока, который использует мою реализацию format()
, увеличивается примерно на половину. КБ плюс размер исходного буфера (см. источник).
Но я слабо помню некоторые утверждения, что использование java.lang.ThreadLocal
является злом , хотя я не могу вспомнить ни одной причины, почему ... в основном , вы можете свести это к "есть слухи ...".
Или нет? Есть ли что-то, что я упустил из-за использования java.util.ThreadLocal
, как я это сделал?
Пожалуйста, не обсуждайте, может ли замена String.format()
моим решением быть полезной или нет: я просто сделал это - как было сказано - потому что мне было любопытно, можно ли сделать это быстрее, кэшируя экземпляр Formatter
- больше ничего.
Редактировать (основываясь на некоторых комментариях, которые были добавлены к вопросу):
- Предполагается, что экземпляр
Formatter
никогда не будет удален из потока. Но нет необходимости сохранять размер ассоциированного StringBuilder
в его текущем размере, если он был расширен выше исходного размера. - Существует проблема с
ThreadLocal
в контексте «управляемых сред» (Серверы приложений и веб-серверы), поскольку потоки используются повторно, и поэтому данные, хранящиеся в ThreadLocal
, никогда не будут собираться, поскольку ThreadLocal
никогда не будет освобожден.