1

I have to generate a single string containing all the data members of a class. For example, if a class definition is

class A    
 {
   private String field1;
   private String field2;
   private String field3;
   ...
   ...
 }

Then I want to generate a string that will contain field1, field2 and field3 in that order. However, the additional thing that I want to do is ensure the following rules -

  field1 is of length 20. If it is less than 20, pad it with blank spaces.
  field2 is of length 10. If it is less than 10, pad it with blank spaces.
  field1 is of length 15. If it is less than 15, pad it with blank spaces.
  ...
  ...

I plan to use a helper class to build this string. I want to use a StringBuilder to get the final string. So I have something like -

  StringBuilder builder = new StringBuilder();

  Helper.addString(field1,20,builder);

Now the implmentation of this addString function is what I am concerned about. This function will be called thousands of times for different classes. So I want to make it as efficient as possible. The question is, what is the most efficient way? Currently, I have the following implementation -

 public static void addString(String field, int totalLengthOfField, StringBuilder builder)
 {
    int lengthOfField = field.length();
    int numberOfBlankSpacesToPutAfterString = totalLengthOfField - lengthOfField;

    if(numberOfBlankSpacesToPutAfterString >=0)
      {
        builder.append(field);
        for(int i=1; i<= numberOfBlankSpacesToPutAfterString; i++)
          {
            builder.append(" "); // append a blank space
          }
      }
    else
      {
        // throw exception - field is longer than the maximum allowed length.
      }
 }
CodeBlue
  • 14,631
  • 33
  • 94
  • 132
  • What are the performance measurements that are causing your concern? – Jeremy May 17 '12 at 17:45
  • @JeremyHeiler , I just want to make it run as fast as it can. – CodeBlue May 17 '12 at 17:46
  • I'd guess spending a substring of a bunch of spaces would be faster than appending in a loop, but without any timing data, I wouldn't rely on intuition. Do you know you're having performance issues? – Dave Newton May 17 '12 at 17:47
  • @DaveNewton , as of now, I don't know, because the entire application hasn't been built. But I know that this part of the code will be called very frequently. – CodeBlue May 17 '12 at 17:49
  • I don't think you can really improve performance here. The only thing I see is that you could set an initial capacity of the `StringBuilder`. The default is 16 I think so maybe you can tune here a little. – Kai May 17 '12 at 17:50
  • Assuming that appending a hand full of spaces to a string will be the performance bottleneck of a serious application is at least.. doubtful - and I'm being very nice here. In practice I can't imagine this ever being a concern.. – Voo May 17 '12 at 17:54
  • In Guava there is a Strings.padEnd-method that handles char instead of String, that might be quicker. But it's just a guess – Kennet May 17 '12 at 17:55
  • @Voo The maximum length is 20 here just for illustration purposes. In the real application, it could even be 50 or 100. – CodeBlue May 17 '12 at 17:56
  • @Code Well I hope you're calling this method at least 10k times per second. – Voo May 17 '12 at 17:58

7 Answers7

4

Java has the Formatter class that supports creating strings with width definitions for specific fields, much like sprintf() in C. String.format() also uses a Formatter object internally.

That said, Formatter is not all that fast. You might be better off appending strings manually to a StringBuilder and using a small cache of strings with various number of spaces, to avoid always appending single spaces...

For example, if you know that the padding size will always be less than 20 spaces, you could just create a String[] table with strings that have 0, 1, 2... spaces and completely skip the loop. If there is no such restriction, you could still append blocks of, say, 20 spaces at once until you reach the necessary padding size.

thkala
  • 84,049
  • 23
  • 157
  • 201
  • Format could be quite heavy, take a look into http://stackoverflow.com/questions/513600/should-i-use-javas-string-format-if-performance-is-important maybe is a good idea to make a benchmark – Francisco Spaeth May 17 '12 at 18:06
0

Any reason not to use String.format?

Matt
  • 10,434
  • 1
  • 36
  • 45
  • 1
    What do you know, you're right. [here](http://stackoverflow.com/questions/513600/should-i-use-javas-string-format-if-performance-is-important) is another SO question with numbers to back it up. – Matt May 17 '12 at 17:55
  • @Matt Now if only that benchmark was actually valid :) Well except if you're interested in interpreter performance, in that case - a great test it is. – Voo May 17 '12 at 17:56
  • @Voo: I have repeatedly benchmarked `String.format()` in my own code. It is significantly slower than appending to a StringBuilder... – thkala May 17 '12 at 18:00
  • While it's not exactly similar to this case, it's close enough for a qualitative feel: they are both concatenating strings without too much logic (internationalization, etc...). Using the numbers in the second answer, format is _22 times_ slower. I'm not sure that any other factor is big enough to overcome that, but I'd be interested if you have a counterexample. – Matt May 17 '12 at 18:02
  • @thkala Oh I can certainly imagine that, but the shown "proof" is just plain wrong (currentMillis, testing interpreter performance, GC problems,.. it basically violates every possible thing about microbenchmarks). If you've written a serious microbenchmark, posting it here would certainly be a good help. – Voo May 17 '12 at 18:02
0

Use the static method

String.format("%"+numberOfBlankSpacesToPutAfterString+"s",string)'
richarbernal
  • 1,063
  • 2
  • 14
  • 32
0

I can see at least one optimization. Change:

 if(numberOfBlankSpacesToPutAfterString >=0)

To:

 if(numberOfBlankSpacesToPutAfterString >0)

That way you never enter the for loop if you have 0 spaces to add. Not a huge deal, but it sounds like every little bit counts.

Second thing: is the builder object needed? You could be using

 if(numberOfBlankSpacesToPutAfterString >0)
  {
    for(int i=1; i<= numberOfBlankSpacesToPutAfterString; i++)
      {
        field +=(" "); // append a blank space
      }
  }

Reading what someone else mentioned, you could speed things up further by adding spacing of powers of 2.

 if(numberspaces > 16)
      {field +="16spaceshere"
      numberspaces -= 16;
      }
 if(numberspaces > 8)...
 if(numberspaces > 4)...

That would turn it into a maximum of 4 operations on field and 4 on numberspacestoadd, 8 total.

Matt Wilko
  • 26,994
  • 10
  • 93
  • 143
Lawton
  • 143
  • 2
  • 14
  • But then my else condition would need to test for == 0 again before throwing an exception. – CodeBlue May 17 '12 at 17:52
  • Ouch... `+=` in a loop for strings is *slow*. Each invocation creates a new `StringBuilder` object... – thkala May 17 '12 at 17:54
  • Without the builder, you actually create more strings in memory. – CodeBlue May 17 '12 at 17:54
  • Oh. I was thinking that you would have been only calling addString() if you knew that you had to add a string. In other words, calling length prior to calling that function. Then you could pass length as a parameter, so you only had to do the length check once. If you may not need to add a string, then yeah, 0 still needs to be checked – Lawton May 17 '12 at 17:59
0

Consider to check how apache commons (StringUtils) is implemented, they already have same problems as you are having and they already optimized the method:

public static String rightPad(String str, int size, char padChar) {
    if (str == null) {
        return null;
    }
    int pads = size - str.length();
    if (pads <= 0) {
        return str; // returns original String when possible
    }
    if (pads > PAD_LIMIT) {
        return rightPad(str, size, String.valueOf(padChar));
    }
    return str.concat(repeat(padChar, pads));
}

public static String repeat(String str, int repeat) {
    // Performance tuned for 2.0 (JDK1.4)

    if (str == null) {
        return null;
    }
    if (repeat <= 0) {
        return EMPTY;
    }
    int inputLength = str.length();
    if (repeat == 1 || inputLength == 0) {
        return str;
    }
    if (inputLength == 1 && repeat <= PAD_LIMIT) {
        return repeat(str.charAt(0), repeat);
    }

    int outputLength = inputLength * repeat;
    switch (inputLength) {
        case 1 :
            return repeat(str.charAt(0), repeat);
        case 2 :
            char ch0 = str.charAt(0);
            char ch1 = str.charAt(1);
            char[] output2 = new char[outputLength];
            for (int i = repeat * 2 - 2; i >= 0; i--, i--) {
                output2[i] = ch0;
                output2[i + 1] = ch1;
            }
            return new String(output2);
        default :
            StringBuilder buf = new StringBuilder(outputLength);
            for (int i = 0; i < repeat; i++) {
                buf.append(str);
            }
            return buf.toString();
    }
}
Francisco Spaeth
  • 23,493
  • 7
  • 67
  • 106
0

As long as you know the maximum padding with you need (20) it is fairly simple.

private static String spaces = "                    "; // Whatever you need max!

// your code to compute numberOfBlankSpacesToPutAfterString

if(numberOfBlankSpacesToPutAfterString >= 0) {
    builder.append(field);
    builder.append(spaces, 0, numberOfBlankSpacesToPutAfterString);
} else {
    // Report error
}
Has QUIT--Anony-Mousse
  • 76,138
  • 12
  • 138
  • 194
0
import  com.google.common.base.Strings

foo = Strings.repeat(" ", 10)
Christoff Erasmus
  • 925
  • 1
  • 10
  • 26