我正在尝试使用JScrollPane
创建一些数据的缩略图,但我遇到了性能问题。此示例包含大约100个缩略图,每个图表中包含5000个样本。当我尝试向下滚动并多次向上滚动时,滚动会延迟,CPU负载增加,应用程序内存使用量超过500 Mb。
有没有办法在不减少数据的情况下避免此性能问题?
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.ThermometerPlot;
import org.jfree.data.general.DefaultValueDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
public class ThumbnailChartsTest extends JPanel {
private static final int W = 200;
private static final int H = W;
private static final int N = 5000;
private static final Random random = new Random();
private static ChartPanel createPane() {
final XYSeries series = new XYSeries("Data");
for (int i = 0; i < random.nextInt(N) + N; i++) {
series.add(i, random.nextGaussian());
}
XYSeriesCollection dataset = new XYSeriesCollection(series);
JFreeChart chart = ChartFactory.createXYLineChart("Random", "Domain",
"Range", dataset, PlotOrientation.VERTICAL, false, false, false);
return new ChartPanel(chart, W, H, W, H, W, H,
false, true, true, true, true, true);
}
public static void main(final String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(0, 4));
for (int i=0; i<100; i++){
panel.add(createPane());
}
JScrollPane scrollPane = new JScrollPane(panel,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
f.add(scrollPane);
f.pack();
f.setVisible(true);
}
});
}
}
编辑:我无法理解一件事:为什么在这种情况下内存使用量仍然非常巨大!请看这个插图。
增加: 我认为存在一些误解。
监视器visualVM的堆大小 启动后applet堆大小只有125 Mb,很酷。 但后来我开始测试:多次滚动和调整大小,越来越多 - 上下,上下,更小的帧和更大的帧。堆积大小超过500 Mb!我想这种情况不正常。
添加#2
真实世界的例子:
我的数据大小只有2 Mb,以90个图表(每个2个系列)表示,一个系列包含3000个元素。我已经通过滑块实现了更改数字列。
但是这个小数据堆大小超过1.5 GB!
在执行某些操作后会发生这种情况,例如更改数字列 对于我的CPU(核心2双核2.2GHz),每个绘图表需要大约4秒的时间!由于这个大的延迟,很难控制滑块。
更新
我已经将我的数据下采样到每个缩略图的100个样本。 现在它肯定更快,但仍有大量堆大小的问题。在图片上,一个超过700Mb,这不是一个记录。我很沮丧。
答案 0 :(得分:4)
使用flyweight pattern仅渲染可见图表。 JTable
renderers使用的方法概述为here,并显示在下面的ChartRenderer
中。为了说明,每次显示一个单元格时都会重新创建数据集;滚动,调整大小和切换应用程序以查看效果。虽然这种渲染可以很好地扩展到数万个单元格,但每个图表仍然会呈现N
个数据点。您可以在Scrollable
方法getPreferredScrollableViewportSize()
的实现中限制可见单元格的数量,如下所示。
如何将内存使用量减少到很小的值?
没有一般性答案,但有几种策略可能会有所帮助:
尽可能早地在程序初始化时编写图表,而不是在渲染时;下面的updated示例构建TableModel
个ChartPanel
个实例; ChartRenderer
相应地更简单。
超过几千点的图表实际上是不可读的;考虑截断大型数据集并仅显示完整数据以响应ListSelectionEvent
,图示为here。
平台活动监控可能会产生误导; profile验证实际结果。
启动applet后它不大,小于200 Mb,但滚动后,调整大小等内存使用量达到600 Mb以上的值。为什么呢?
以下代码的典型profiler视图仅显示适度使用情况和滚动后的预期free/used ratio以及garbage collection;您的结果可能会有所不同。
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
/**
* @see https://stackoverflow.com/a/40445144/230513
*/
public class ChartTable {
private static final Random R = new Random();
private static final int N = 5000;
private static final int W = 200;
private static final int H = W;
private void display() {
JFrame f = new JFrame("ChartTable");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
DefaultTableModel model = new DefaultTableModel(
new String[]{"", "", "", ""}, 0) {
@Override
public Class<?> getColumnClass(int columnIndex) {
return ChartPanel.class;
}
};
for (int r = 0; r < 25; r++) {
ChartPanel[] row = new ChartPanel[4];
for (int c = 0; c < row.length; c++) {
final XYSeries series = new XYSeries("Data");
int n = R.nextInt(N);
for (int i = 0; i < n; i++) {
series.add(i, R.nextGaussian());
}
XYSeriesCollection dataset = new XYSeriesCollection(series);
JFreeChart chart = ChartFactory.createXYLineChart(
"Random " + series.getItemCount(), "Domain", "Range", dataset);
ChartPanel chartPanel = new ChartPanel(chart) {
@Override
public Dimension getPreferredSize() {
return new Dimension(W, H);
}
};
row[c] = chartPanel;
}
model.addRow(row);
}
JTable table = new JTable(model) {
@Override
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(4 * W, 2 * H);
}
};
table.setDefaultRenderer(ChartPanel.class, new ChartRenderer());
table.setRowHeight(W);
f.add(new JScrollPane(table));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static class ChartRenderer implements TableCellRenderer {
@Override
public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column) {
return (ChartPanel) value;
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new ChartTable()::display);
}
}