Search code examples
javaswing

Swing is very slow with long strings


I built a simple Java program that logs in a JTextArea component.

JTextArea _log = new JTextArea();
_log.setEditable(false);
JScrollPane scrollLog = new JScrollPane(_log);
scrollLog.setPreferredSize(getMaximumSize());
add(scrollLog);

The problem is that logging like this takes 15ms on average:

public void log(String info) {
    _log.append(info + "\n");
}

This is far(!) slower than logging using System.out.println. Logging takes more time than the whole running time of the algorithm!

Why is the JTextArea is so slow? Is there a way to improve it?

EDIT 1:

I am using separate thread for the algorithm, and using SwingUtilities.invokeLater to update the log in the UI.

The algorithm tread finish his work after 130ms on average, but the JTextArea finish his appends after 6000ms on avarage.

EDIT 2:

I tried to test this by use setText of string that contains 2500 charaters. In that case the operation took 1000ms on average. I tried to use another controller then JTextArea and I get same results.

Is it hard for Swing components to deal with large strings? What can I do about it?

EDIT 3:

I just test with this code:

public class Test extends JFrame {

    public Test() {
        final JTextArea log = new JTextArea();
        log.setEditable(false);
        log.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
        JScrollPane scrollLog = new JScrollPane(log);
        scrollLog.setPreferredSize(getMaximumSize());

        JButton start = new JButton("Start");
        start.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {

                long start = System.nanoTime();
                for (int i = 0; i < 2500; i++) {
                    log.append("a\n");
                }
                long end = System.nanoTime();
                System.out.println((end - start) / 1000000.0);
            }
        });

        JPanel panel = new JPanel();
        panel.setLayout(new GridLayout(2, 1));
        panel.add(scrollLog);
        panel.add(start);
        add(panel);
    }

    public static void main(String[] args) {
         Test frame = new Test();
         frame.setSize(600,500);
         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
         frame.setVisible(true);
    }
}

The time of that for loop is 1870ms on avarage.

This is the only code that I ran (include the declaration of _log at the top of the question)


Solution

  • A JTextArea is not slow.

    Far(!) away from System.out.println.

    System.out.println() executes on a separate Thread.

    The log takes more time then the hole running time of the algorithm!

    So your algorithm is probably executing on the Event Dispatch Thread (EDT) which is the same Thread as the logic that appends text to the text area. So the text area can't repaint itself until the algorithm is finished.

    The solution is to use a separate Thread for the long running algorithm.

    Or maybe a better choice is to use a SwingWorker so you can run the algorithm and "publish" results to the text area.

    Read the section from the Swing tutorial on Concurrency for more information and a working example of a SwingWorker.

    Edit:

    //log.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
    

    The above line is causing the problem. I get 125 for the first test and 45 when I keep clicking the button.

    That property is not needed. The text is still displayed on the left side of the text pane. If you want right aligned text then you need to use a JTextPane and set the attributes of the text pane to be right aligned.

    That is why you should always post an MCVE. There is no way we could have guessed from your original question that you were using that method.

    Edit2:

    Use the alignment feature of a JTextPane:

    SimpleAttributeSet center = new SimpleAttributeSet();
    StyleConstants.setAlignment(center, StyleConstants.ALIGN_CENTER);
    textPane.getStyledDocument().setParagraphAttributes(0, doc.getLength(), center, false);
    

    Now any new text you add to the document should be center aligned. You can change this to right.