- 100 -
public String sayHiString title, String name {
return hi + title + + name; }
The
String
generated by this method cannot be resolved at compile time because the variables can have any value. The compiler is free to generate code to optimize the
String
creation, but it does not have to. Consequently, the
String
-creation line could be compiled as:
return new StringBuffer .appendhi . appendtitle.append .appendname.toString ;
This is optimal, creating only two objects. On the other hand, the compiler could also leave the line with the default implementation of the concatenation operator, which is equivalent to:
return hi .concattitle.concat .concatname;
This last implementation creates two intermediate
String
objects that are then thrown away, and these are generated every time the method is called.
So, when the
String
can be fully resolved at compile time, the concatenation operator is more efficient than using a
StringBuffer
. But when the
String
cannot be resolved at compile time, the concatenation operator is less efficient than using a
StringBuffer
. One further point is that using the
String
constructor in a
String
definition forces a runtime string creation:
String s = new Stringhi + Mr. + + Buddy;
is compiled as:
String s = new Stringhi Mr. Buddy;
This line uses the compile-time resolved string as a parameter for the
String
constructor to create a new
String
object at runtime. The new
String
object is equal but not identical to the original string:
String s = new Stringhi Mr. Buddy; s == hi Mr. Buddy; is false
s.equalshi Mr. Buddy; is true
5.3 Conversions to Strings
Generally, the JDK methods that convert objects and data types to strings are suboptimal, both in terms of performance and the number of temporary objects used in the conversion procedure. In this
section, we consider how to optimize these conversions.
5.3.1 Converting longs to Strings
Lets start by looking at conversion of
long
values. In the JDK, this is achieved with the
Long.toString
method. Bear in mind that you typically add a converted value to a
StringBuffer
explicitly, or implicitly with the
+
concatenation operator. So it would be nice to avoid the two intermediate temporary objects created while converting the
long
, i.e., the one
char
- 101 - array inside the conversion method, and the returned
String
object that is used just to copy the
char
s into the
StringBuffer
. Avoiding the temporary
char
array is difficult to do, because most fast methods for converting numbers start with the low digits in the number, and you cannot add to the
StringBuffer
from the low to the high digits unless you want all your numbers coming out backwards.
However, with a little work, you can get to a method that is fast and obtains the digits in order. The following code works by determining the magnitude of the number first, then successively stripping
off the highest digit:
Up to radix 36 private static final char[] charForDigit = {
0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,g,h, i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z
}; public static void appendStringBuffer s, long i
{ if i 0
{ convert negative to positive numbers for later algorithm
if i == Long.MIN_VALUE {
cannot make this positive due to integer overflow, so treat it specially
s.append-9223372036854775808; return;
} otherwise append the minus sign, and make the number positive
s.append-; i = -i;
} Get the magnitude of the int
long mag = l_magnitudei; long c;
while mag 1 {
The highest digit c = imag;
s.appendcharForDigit[int c]; remove the highest digit
c = mag; if c = i
i -= c; and go down one magnitude
mag = 10; }
The remaining magnitude is one digit large s.appendcharForDigit[int i];
} private static long l_magnitudelong i
{
if i 10L return 1; else if i 100L return 10L;
else if i 1000L return 100L; else if i 10000L return 1000L;
else if i 100000L return 10000L; else if i 1000000L return 100000L;
else if i 10000000L return 1000000L;
- 102 -
else if i 100000000L return 10000000L; else if i 1000000000L return 100000000L;
else if i 10000000000L return 1000000000L; else if i 100000000000L return 10000000000L;
else if i 1000000000000L return 100000000000L; else if i 10000000000000L return 1000000000000L;
else if i 100000000000000L return 10000000000000L; else if i 1000000000000000L return 100000000000000L;
else if i 10000000000000000L return 1000000000000000L; else if i 100000000000000000L return 10000000000000000L;
else if i 1000000000000000000L return 100000000000000000L; else return 1000000000000000000L;
}
When compared to executing the plain
StringBuffer.appendlong
, the algorithm listed here takes at most 90 of the
StringBuffer
time see Table 5-1
and creates two fewer objects it can be even faster, but Ill leave the more complicated tuning to the next section. If you are writing out
long
values a large number of times, this is a useful speedup. Table 5-1, Time Taken to Append a long to a StringBuffer
VM 1.2 1.3
HotSpot 1.0
1.1.6
JDK long conversion 100 113 227
272 Optimized long conversion
90 103 146
133
There are several things to note about possible variations of this algorithm. First, although the algorithm here is specifically radix 10 decimal, it is easy to change to any radix. To do this, the
reduction in magnitude in the loop has to go down by the radix value, and the
l_magnitude
method has to be altered. For example, for radix 16, hexadecimal, the statement
mag = mag10
becomes
mag = mag16
and the magnitude method for radix 16 looks like:
private static long l_magnitude16long i {
if i 16L return 1; else if i 256L return 16L;
else if i 4096L return 256L; else if i 65536L return 4096L;
else if i 1048576L return 65536L; else if i 16777216L return 1048576L;
else if i 268435456L return 16777216L; else if i 4294967296L return 268435456L;
else if i 68719476736L return 4294967296L; else if i 1099511627776L return 68719476736L;
else if i 17592186044416L return 1099511627776L; else if i 281474976710656L return 17592186044416L;
else if i 4503599627370496L return 281474976710656L; else if i 72057594037927936L return 4503599627370496L;
else if i 1152921504606846976L return 72057594037927936L; else return 1152921504606846976L;
}
Second, because we are working through the digits in written order, this algorithm is suitable for writing directly to a stream or writer such as a
FileWriter
without the need for any temporary objects. This is potentially a large gain, enabling writes to files without generating intermediate
temporary strings.
Finally, if you want formatting added in, the algorithm is again suitable, because you proceed through the number in written order, and also because you have the magnitude at the start. You can
- 103 - easily create another method, similar to
magnitude
, that returns the number of digits in the value. You can put in a comma every three digits as the number is being written or apply whatever
internationalized format is required. This saves you having to write out the number first in a temporary object and then add formatting to it. For example, if you are using integers to fake fixed-
place floating-point numbers, you can insert a point at the correct position without resorting to temporary objects.
5.3.2 Converting ints to Strings