how to reuse StringBuilder in a loop?

130 Views Asked by At

What am trying to do here is to improve speed performance by using StringBuilder outside the loop and clear StringBuilder in each cycle for reuse , but seems like this approach doesn't always work , because I didn't see improvement in speed performance.*

public class Loader implements Runnable {
    private int i;

    public Loader(int i) {
        this.i = i;
    }

    @Override
    public void run() {
        long start = System.currentTimeMillis();
        try {
            PrintWriter writer = new PrintWriter("res/NUMBERS " + i + ".txt");
            char letters[] = {'У', 'К', 'Е', 'Н', 'Х', 'В', 'А', 
                              'Р', 'О', 'С', 'М', 'Т'};
            StringBuilder stringBuilder = new StringBuilder();
            
            for (int regionCode = 1; regionCode <= 199; regionCode++) {
                stringBuilder.setLength(0);
                for (int number = 1; number < 1000; number++) {
                    for (char firstLetter : letters) {
                        for (char secondLetter : letters) {
                            for (char thirdLetter : letters) {
                                stringBuilder.append(firstLetter)
                                        .append(padNumber(number, 3))
                                        .append(secondLetter)
                                        .append(thirdLetter)
                                        .append(padNumber(regionCode, 2))
                                        .append("\n");
                            }
                        }
                    }
                }
                writer.write(stringBuilder.toString());
            }
            writer.flush();
            writer.close();
            System.out.println(System.currentTimeMillis() - start);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static String padNumber(int number, int numberLength) {
        String numberStr = Integer.toString(number);
        int padSize = numberLength - numberStr.length();

        for (int i = 0; i < padSize; i++) {
            numberStr = '0' + numberStr;
        }
        return numberStr;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 1; i++) {
            new Thread(new Loader(i)).start();
        }
    }
}

When I used the StringBuilder inside the loop it took 30 sec for the process to finish, but with this approach shown above is taking 50 sec for the process to finish which was not what I expected.Thanks in advance.

2

There are 2 best solutions below

1
Jacob On

setLength is more time intensive than the StringBuilder's constructor. Granted, you won't see a difference until you start measuring at the nanosecond level, but the idea that you will save time by reusing the same instance of a StringBuilder is faulty.

Something like this would be a great candidate for concurrency if your focus is simple speeding up the process.

3
Ahmed Mera On

To improve the performance:

  1. Using BufferedWriter can improve the performance by reducing the number of I/O operations.
BufferedWriter writer = new BufferedWriter(new FileWriter("out/NUMBERS-" + i + ".txt"));
  1. Using Java's ExecutorService or other parallel processing techniques. This involves dividing the workload among multiple threads to take advantage of the processors.

You would specify an executor service backed by a pool of a certain number of threads. Adjust the number of threads based on your goal and your number of CPU cores. Submit your tasks defined as Runnable or Callable objects.

ExecutorService executorService = Executors.newFixedThreadPool(numThreads);

I tried this and it took 20 seconds (See the foto below)

enter image description here

In Java 21+, using virtual threads is generally best for tasks that involve blocking such as file I/O.

An ExecutorService is AutoCloseable. So we can use try-with-resources syntax to automatically close and shutdown when done.

public static void main(String[] args) {
    int limit = … ;
    try(
        ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor() ;
    ) {
        for (int i = 0; i < limit; i++) {
            executorService.execute(new Loader(i));
        }
    }
}