Faster JTextPane Text Insertion (Part I)

The Swing JTextPane class provides a flexible way to display styled text, with the ability to control the color, alignment, font, and other attributes of each character or paragraph of the pane’s contents. However, JTextPane has a well-deserved reputation for being slow. The speed problems are particularly noticeable when inserting a large amount of text with varying styles into a text pane, functionality that is needed for applications such as word processors or code editors that support syntax highlighting. In the first part of this article we look at the sources of the speed hit and describe one simple technique for boosting speed. Part II illustrates a more advanced technique for batch updates.

Why is it slow?

Like most other Swing widgets, text components use a Model-View-Controller design to separate the data being displayed (represented by the Model) from the rendering logic (the View) and the interaction logic (the Controller). In the case of text components, the model is an implementation of the javax.swing.text.Document interface. For text components such as JTextPane that support styled text, the javax.swing.text.StyledDocument subinterface defines additional methods. The concrete javax.swing.text.DefaultStyledDocument class (or subclasses thereof) is often used for styled editors.

The slow speed of insertion in a JTextPane has at least two culprits. The first is simply an effect of the Model-View-Controller architecture: If the document into which strings are being inserted is serving as the model for a visible text pane, each insertion will trigger a re-rendering of the display, which may in turn trigger other user interface updates. For interactive editing, this is exactly the behavior that one would want, since the results of user input should be immediately visible when editing text pane content. For initializing a text pane with a large amount of content, or, in general, making large “batch” edits, the overhead of repeated UI updates is signficant.

The second source of sluggishness is an implementation detail of DefaultStyledDocument. Unlike most Swing classes, which should only be modified in the AWT thread, DefaultStyledDocument is designed to be thread-safe. This requires DefaultStyledDocument to implement locking, so that multiple threads do not try to update the document contents at the same time. This locking imposes a small amount of overhead on each insertion or other document modification, which can add up to a long delay for large and complex text insertions.

Note that if an application does not actually require multiple text styles within the same document, the impact of both of these issues can be mitigated to a large extent by simply inserting a single long string rather than a lot of small strings.

Offscreen updates

Avoiding the first of these pitfalls is trivial: You need only ensure that large updates are only performed on document objects that are not attached to text components at the time that the updates are made. For example, instead of retrieving a document object and modifying it:


    ...
    JTextPane jtp = new JTextPane();
    Document doc = jtp.getDocument();

    for (... iteration over large chunk of parsed text ...) {
        ...
        doc.insertString(offset, partOfText, attrsForPartOfText);
        ...
    }
    ...

you should either create a new document and insert into it before telling the text pane to use it:


    ...
    JTextPane jtp = new JTextPane();
    Document doc = new DefaultStyledDocument();

    for (... iteration over large chunk of parsed text ...) {
        ...
        doc.insertString(offset, partOfText, attrsForPartOfText);
        ...
    }
    jtp.setDocument(doc);
    ...

or (if, for example, the text pane uses a custom document class), swap in a blank document and then restore the original document after the inserts:


    ...
    JTextPane jtp = new JTextPane();
    Document doc = jtp.getDocument();
    Document blank = new DefaultStyledDocument();
    jtp.setDocument(blank);

    for (... iteration over large chunk of parsed text ...) {
        ...
        doc.insertString(offset, partOfText, attrsForPartOfText);
        ...
    }
    jtp.setDocument(doc);
    ...

The speed boost from this change can be dramatic. For example, for a roughly 450K document in which each word was a different color and size (therefore requiring different attributes), document initialization time was over three times slower with a document attached to a JTextPane than with an unattached document. Representative times were 151s vs. 460s on a 500Mhz iBook running OSX 10.3.2. (The test code is in Part II of this article.) Of course, a two and a half minute wait for a document to load suggests that there is still room for improvement. In Part II we look at a solution to the second source of sluggishness, per-insertion coordination overhead within DefaultStyledDocument.