Why StringBuffer has a toStringCache while StringB

2019-04-28 04:15发布

问题:

In JDK 8, StringBuffer class has a toStringCache, while StringBuilder doesn't.

/**
 * A cache of the last value returned by toString. Cleared
 * whenever the StringBuffer is modified.
 */
private transient char[] toStringCache;

But why?

  • One possible reason I can think of is that StringBuffer is already synchronized so a cache can be implemented easier.

  • Or maybe historically StringBuffer was implemented this way so old code depends heavily on this feature?

Given modern JVM with escape analysis and biased locking, is the difference relevant anymore?

回答1:

It might help to consider the historical context. StringBuilder was introduced with Java 5, since it has been recognized, that StringBuffer isn’t well suited for its actual use cases.

The newly introduced StringBuilder has been designed for the major use case of being constructed, used and immediately dropped afterwards, in a purely local context. Therefore, it doesn’t provide any synchronization and it doesn’t bother optimizing the rare case of its toString() method being called multiple times without an in-between change (when does this happen in real life at all?), especially as, indeed, providing the caching feature without sacrificing the performance advantage of no thread synchronization, is somewhere between “hard” to “impossible”.

While StringBuilder is documented to be not thread safe, so you know inconsistent things could happen when calling methods on it concurrently, the class String is guaranteed to be thread safe through immutability, hence, it must not be allowed that StringBuilder’s absence of synchronization can causes inconsistencies in already constructed strings and, not sharing the array between String and StringBuilder at all, is the safest solution.

So why is this optimization there, if it hardly ever has a benefit in real life? Well, because it’s there since a very long time, most likely even since Java 1.0 and it’s not worth changing anything in the class StringBuffer. Its presence may not have any real advantage, but neither has removing it, which would require new testing and so on and could turn out to be the space bar overheating feature for some application…

You may notice that back-then, in Java 1.x, a lot of design decision were made that may look strange today. Overusing synchronized in fundamental classes is one of them, this hardly ever helping optimization another one. At that time, even the implications of immutability were not well understood, which is why we have redundant methods like String.valueOf(char[]) and String.copyValueOf(char[]), plus the opportunity to use new String(char[])



回答2:

I think that your first guess is highly accurate, since StringBuilder is not thread-safe and an instance can be shared across multiple threads, implementing such a cache would require additional synchronization, this would defeat the purpose of StringBuilder in the first place.

As to why this would be needed, it boils down to the new String(...) constructor that is used; in case of StringBuffer that used the constructor String(array, boolean) the comment says:

Package private constructor which shares value array for speed.