Swing应用中的数据更新节流技术
在开发Swing应用时,我们经常会遇到需要频繁更新数据的场景,比如实时金融数据展示。如果数据更新的频率过高,可能会导致界面冻结,这是因为大量的重绘事件(painting events)连续触发,导致事件分发线程(EDT)长时间被阻塞。为了避免这种情况,我们可以使用节流(Throttling)技术来控制数据接收的速率。本文将通过一个具体实例,展示如何在Swing应用中实现节流,并应用到JTable数据更新中。
节流器的实现
首先,我们定义一个通用的节流器类Throttler
,用于控制数据更新的频率。这个类使用了Timer
来定时触发更新事件,并确保这些事件在EDT中执行。
public class Throttler {
private Timer timer;
public Throttler(int rate, Runnable updater) {
timer = new Timer(rate, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
timer.stop();
updater.run();
}
});
}
public void updateReceived() {
if (!SwingUtilities.isEventDispatchThread()) {
throw new IllegalArgumentException("updateReceived() must be called in EDT");
}
if (!timer.isRunning()) {
timer.restart();
}
}
}
高频数据服务
接下来,我们创建一个模拟高频数据更新的服务TestDataService
。这个服务会模拟实时金融数据的更新,并通过BiConsumer
接口将更新传递给监听器。
public enum TestDataService {
Instance;
private String[] currencyPairs = {"EUR/USD", "USD/JPY", "GBP/USD", "USD/CHF", "USD/CAD", "AUD/USD", "NZD/USD", "EUR/GBP", "EUR/AUD", "GBP/JPY", "CHF/JPY", "NZD/JPY", "GBP/CAD"};
public void listenToDataUpdate(BiConsumer<String, BigDecimal> forexListener) {
ExecutorService es = Executors.newSingleThreadExecutor();
es.execute(new Runnable() {
@Override
public void run() {
while (true) {
int i = ThreadLocalRandom.current().nextInt(0, currencyPairs.length);
BigDecimal rate = BigDecimal.valueOf(Math.random() * 10);
rate = rate.setScale(2, RoundingMode.CEILING);
forexListener.accept(currencyPairs[i], rate);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
}
}
JTable数据模型
我们使用AbstractTableModel
来创建一个ForexTableModel
类,用于管理JTable的数据。
public class ForexTableModel extends AbstractTableModel {
private Map<String, BigDecimal> forexRates = new TreeMap<>();
private String[] columnNames = {"Currency Pair", "Rate"};
public void updateRateWithoutFiringEvent(String currency, BigDecimal rate) {
forexRates.put(currency, rate);
}
@Override
public String getColumnName(int column) {
return columnNames[column];
}
@Override
public int getRowCount() {
return forexRates.size();
}
@Override
public int getColumnCount() {
return columnNames.length;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
if (columnIndex == 0) {
String currency = getCurrencyByRow(rowIndex);
return currency;
} else if (columnIndex == 1) {
String currency = getCurrencyByRow(rowIndex);
return forexRates.get(currency);
}
return null;
}
private String getCurrencyByRow(int rowIndex) {
return forexRates.keySet().toArray(new String[forexRates.size()])[rowIndex];
}
}
主类与节流应用
最后,我们在主类ThrottlingExampleMain
中应用节流技术,以避免在大量数据更新时界面冻结的问题。
public class ThrottlingExampleMain {
public static void main(String[] args) {
ForexTableModel tableModel = new ForexTableModel();
JTable table = new JTable(tableModel);
listenToForexChanges(tableModel);
JFrame frame = createFrame();
frame.add(new JScrollPane(table));
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private static void listenToForexChanges(ForexTableModel tableModel) {
Throttler throttler = new Throttler(1000, new Runnable() {
@Override
public void run() {
tableModel.fireTableDataChanged();
}
});
TestDataService.Instance.listenToDataUpdate(
(pair, rate) -> SwingUtilities.invokeLater(() -> {
tableModel.updateRateWithoutFiringEvent(pair, rate);
throttler.updateReceived();
}));
}
private static JFrame createFrame() {
JFrame frame = new JFrame("Throttling Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(new Dimension(500, 300));
return frame;
}
}
通过上述代码,我们可以看到,在启用节流的情况下,即使数据更新频率很高,界面也不会冻结。相反,如果没有使用节流技术,界面在大量数据更新时会出现明显的卡顿现象。
总结
节流技术是处理Swing应用中高频数据更新的有效手段。通过控制数据更新的速率,我们可以避免界面冻结,提升用户体验。本文通过一个具体的实例,展示了如何在Swing应用中实现节流,并应用到JTable数据更新中。希望这能帮助你在开发中遇到类似问题时,能够快速找到解决方案。