JTable 在最小化时对更新的使用率很高 cpu

JTable high cpu usage on updates when minimized

更新: 所以我找出了导致这个问题的原因。 当我的 Java window 最小化时,我的 table 正在渲染每一行 。有谁知道如何防止这种情况?

我有一个 JXTable,每秒不断更新、删除和添加行数据。我们说的是平均每秒修改 10-20 行。

通常,CPU 使用率在 5% 到 10% 之间。当我们每秒更新数百次 table 时,它可以达到 15%。

但是,我们注意到当我们的 Java window 最小化时,无论何时进行任何更新,我们的 CPU 使用率每次都会达到 25%。我们设置了一个脚本,每 5 秒添加一行,当该行出现时,我们看到 CPU 使用率达到 25%。

我能想到的唯一解释就是使用SwingUtilities.invokeAndWait()。我在后台线程中修改行数据,并对各种 fireTableDataChanged() 方法使用 invokeAndWait。

我使用 invokeAndWait 是因为我需要按顺序触发我的事件。例如,我删除一些行,调用 fireTableRowsDeleted(),然后我添加一些行并调用 fireTableRowsInserted()。

知道为什么我的 CPU 使用率仅在 table 更新且我的 window 最小化时才达到 25% 吗?

JTable 使用 flyweight pattern to render only visible cells, but the host OS may by interfering, e.g. by trying to animate the minimized icon; profile to be sure. Whatever the cause, publish() updates destined for the TableModel in the doInBackground() implementation of a SwingWorker, as shown here. The worker will coalesce the updates and deliver them to process() at a sustainable rate,~30 Hz。然后,您的 TableModel 可以以对您的应用程序有意义的方式延迟触发更新事件。

I use invokeAndWait because I need to fire off my events in order

不需要使用invokeAndWait()。您可以只使用 invokeLater()。事件仍将按照收到的顺序执行。

the various fireTableDataChanged() methods.

您不应调用 fireTableDataChanged。 TableModel 将调用适当的 fireXXX() 方法。当您可能只更改几行时,为什么要重新绘制整个 table。如有必要,RepaintManager 会将多个绘制请求合并为一个。

编辑:

这是我手边的一些代码。所有更新都是在 EDT 上对模型完成的,代码不会调用 fireXXX(...) 方法:

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.table.*;

public class TableThread extends JFrame
    implements ActionListener, Runnable
{
    JTable table;
    DefaultTableModel model;
    int count;

    public TableThread()
    {
        String[] columnNames = {"Date", "String", "Integer", "Decimal", "Boolean"};
        Object[][] data =
        {
            {new Date(), "A", new Integer(1), new Double(5.1), new Boolean(true)},
            {new Date(), "B", new Integer(2), new Double(6.2), new Boolean(false)},
            {new Date(), "C", new Integer(3), new Double(7.3), new Boolean(true)},
            {new Date(), "D", new Integer(4), new Double(8.4), new Boolean(false)}
        };

        model = new DefaultTableModel(data, columnNames);
        table = new JTable( model );
        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        table.setIgnoreRepaint(false);

        JScrollPane scrollPane = new JScrollPane( table );
        getContentPane().add( scrollPane );

        JButton button = new JButton( "Start Thread to Update Table" );
        button.addActionListener( this );
        getContentPane().add(button, BorderLayout.SOUTH );
    }

    public void actionPerformed(ActionEvent e)
    {
        new Thread( this ).start();
        table.requestFocus();
    }

    public void run()
    {
        Random random = new Random();

        while (true)
        {
            final int row = random.nextInt( table.getRowCount() );

            SwingUtilities.invokeLater(new Runnable()
            {
                public void run()
                {
                    table.setValueAt( new Integer(count++), row, 2);
                    table.setRowSelectionInterval(row, row);
                    Object[] aRow = { new Date(), "f", row, new Double(123), new Boolean(true) };
                    model.addRow( aRow );
                }
            });

            try { Thread.sleep(500); }
            catch(Exception e) {}
        }
    }

    public static void main(String[] args)
    {
        TableThread frame = new TableThread();
        frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
        frame.pack();
        frame.setVisible(true);
    }
}

无论框架是可见的还是最小化的,CPU都是一致的。

如果您需要更多帮助,那么 post 像上面的代码一样的适当 SSCCE 来演示问题。

在 JXTable 中,只要模型发生变化就会调用此方法:

protected void postprocessModelChange(TableModelEvent e) {
    if (forceRevalidate && filteredRowCountChanged) {
        resizeAndRepaint();
    }
    filteredRowCountChanged = false;
    forceRevalidate = false;
}

resizeAndRepaint() 调用似乎是在 window 最小化时强制重绘每一行。如下覆盖似乎可以解决问题:

@Override
protected void resizeAndRepaint()
{
    JFrame window = (JFrame)  SwingUtilities.getAncestorOfClass(JFrame.class, this);
    if(window != null && window.getState() != JFrame.ICONIFIED)
    {
        super.resizeAndRepaint();
    }
}