我构建了一个简单的 Java 程序,用于登录 JTextArea 组件。
JTextArea _log = new JTextArea();
_log.setEditable(false);
JScrollPane scrollLog = new JScrollPane(_log);
scrollLog.setPreferredSize(getMaximumSize());
add(scrollLog);
问题是这样的日志记录平均需要15ms:
public void log(String info) {
_log.append(info + "\n");
}
这比使用
System.out.println
记录要慢得多(!)。记录日志花费的时间比算法的整个运行时间还要多!
为什么 JTextArea 这么慢?有办法改善吗?
我对算法使用单独的线程,并使用
SwingUtilities.invokeLater
更新 UI 中的日志。
算法平均在 130 毫秒后完成工作,但 JTextArea 平均在 6000 毫秒后完成
append
。
我尝试使用包含 2500 个字符的字符串
setText
来测试这一点。在这种情况下,操作平均需要 1000 毫秒。
我尝试使用另一个控制器,然后是 JTextArea,我得到了相同的结果。
Swing 组件处理大字符串是否困难?我能做什么呢?
我只是用这段代码进行测试:
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);
}
}
for 循环的平均时间为 1870 毫秒。
这是我运行的唯一代码(包括问题顶部的
_log
声明)
JTextArea 并不慢。
远离 System.out.println。
System.out.println() 在单独的线程上执行。
日志花费的时间比算法的洞运行时间还要多!
因此,您的算法可能在
Event Dispatch Thread (EDT)
上执行,该线程与将文本附加到文本区域的逻辑相同。因此,在算法完成之前,文本区域无法重新绘制自身。
解决方案是为长时间运行的算法使用单独的线程。
或者也许更好的选择是使用
SwingWorker
,这样您就可以运行算法并将结果“发布”到文本区域。
阅读 Swing 教程中关于 Concurrency 的部分,了解更多信息和
SwingWorker
的工作示例。
编辑:
//log.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
上面的行导致了问题。第一次测试我得到 125,当我继续点击按钮时得到 45。
不需要该属性。文本仍显示在文本窗格的左侧。如果您想要右对齐文本,那么您需要使用
JTextPane
并将文本窗格的属性设置为右对齐。
这就是为什么您应该始终发布
MCVE
。我们不可能从您最初的问题中猜出您正在使用该方法。
编辑2:
使用 JTextPane 的对齐功能:
SimpleAttributeSet center = new SimpleAttributeSet();
StyleConstants.setAlignment(center, StyleConstants.ALIGN_CENTER);
textPane.getStyledDocument().setParagraphAttributes(0, doc.getLength(), center, false);
现在添加到文档中的任何新文本都应该居中对齐。您可以将其更改为右侧。
JTextArea
难以置信地慢,并且与您从哪个线程调用它无关。如果你对其进行采样,它会花费所有时间在 BoxView.layout()、GlyphView.getMinimumSpan()、TextMeasurer.calcLineBreak()、Strike.getGlyphOutline() 等方法中,所有这些方法都是从 getPreferredSize() 调用的,尽管它在第一次计算后最少缓存布局信息。
我刚刚做了一个快速测试,在只读 JTextArea 中显示相对较小的日志文件(1.8 MB,14500 行)在布局和首次出现之前大约需要 21.5 秒。
要获得更快更快的方法,请尝试使用 JList
代替。扫描您显示的文本,找出换行符应该在哪里(在换行符上或在您达到所需的行长度时,具体取决于您是想要长行还是将其换行到窗口宽度)。显然,这假设您使用的是等宽字体,但您可能希望日志查看器能够使用这种字体。但是如果您使用 JList 而不是 JTextArea,速度的提高是令人眼花缭乱的。从 21.5 秒缩短至 70 毫秒,速度提高约 250 倍!
缺点是,如果您希望能够从日志查看器中选择和复制文本(我还没有做到这一点!),那么它需要更多的工作,但如果您只需要显示大量文本,那么 JList 是无限的比 JTextArea 更快。