创建缩略图的正确方法是什么?
What is the right way to create thumbnail charts?
我正在尝试使用 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 的堆大小
启动后小程序堆大小只有 125 Mb,很酷。
但后来我开始测试:多次滚动和调整大小,越来越多——上下,上下,更小的框架和更大的框架。堆大小增长超过 500 Mb!我想这种情况不正常。
添加#2
Real-world 示例:
我的数据大小只有 2 Mb,用 90 个图表表示(每个图表有 2 个系列),一个系列包含 3000 个元素。我已经通过滑块实现了更改数字列。
但是对于这个小数据堆大小增长超过 1.5 GB!
这发生在一些操作之后,例如更改数字列
对于我的 CPU(core 2 duo 2.2GHz),每张图 table 大约需要 4 秒!这么大的延迟很难控制滑块。
更新:
我已经将我的数据下采样到每个缩略图 100 个样本。
现在它肯定更快,但仍然存在巨大堆大小的问题。图片上是700Mb以上的,不是唱片。我很沮丧。
使用flyweight pattern to render only visible charts. The approach, used by JTable
renderers, is outlined here并显示在ChartRenderer
下面看到。为了说明,每次显示一个单元格时都会重新创建数据集;滚动、调整大小和切换应用程序以查看效果。虽然这种渲染可以很好地扩展到数万个单元格,但每个图表仍会渲染 N
个数据点。可以在Scrollable
方法的实现中限制可见单元格的数量,getPreferredScrollableViewportSize()
,如下图
How to reduce memory usage to small values?
没有统一的答案,但有几种策略可能会有所帮助:
尽可能早地在程序初始化时编写图表,而不是在它们被渲染时;下面的 updated 示例构造了 TableModel
个 ChartPanel
实例; ChartRenderer
相对简单。
超过几千点的图表实际上是不可读的;考虑截断大型数据集并仅在响应 ListSelectionEvent
时显示完整数据,已说明 here。
平台 activity 监视器可能具有误导性; profile验证实际结果。
After starting applet it's not big, less than 200 Mb, but after scrolling, resizing etc. memory usage reaches values more than 600 Mb. Why?
一个典型的profiler view of the code below shows only moderate usage and the expected free/used ratio after scrolling and 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
*/
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);
}
}
我正在尝试使用 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 的堆大小
添加#2
Real-world 示例:
我的数据大小只有 2 Mb,用 90 个图表表示(每个图表有 2 个系列),一个系列包含 3000 个元素。我已经通过滑块实现了更改数字列。
但是对于这个小数据堆大小增长超过 1.5 GB!
这发生在一些操作之后,例如更改数字列 对于我的 CPU(core 2 duo 2.2GHz),每张图 table 大约需要 4 秒!这么大的延迟很难控制滑块。
更新:
我已经将我的数据下采样到每个缩略图 100 个样本。
现在它肯定更快,但仍然存在巨大堆大小的问题。图片上是700Mb以上的,不是唱片。我很沮丧。
使用flyweight pattern to render only visible charts. The approach, used by JTable
renderers, is outlined here并显示在ChartRenderer
下面看到。为了说明,每次显示一个单元格时都会重新创建数据集;滚动、调整大小和切换应用程序以查看效果。虽然这种渲染可以很好地扩展到数万个单元格,但每个图表仍会渲染 N
个数据点。可以在Scrollable
方法的实现中限制可见单元格的数量,getPreferredScrollableViewportSize()
,如下图
How to reduce memory usage to small values?
没有统一的答案,但有几种策略可能会有所帮助:
尽可能早地在程序初始化时编写图表,而不是在它们被渲染时;下面的 updated 示例构造了
TableModel
个ChartPanel
实例;ChartRenderer
相对简单。超过几千点的图表实际上是不可读的;考虑截断大型数据集并仅在响应
ListSelectionEvent
时显示完整数据,已说明 here。平台 activity 监视器可能具有误导性; profile验证实际结果。
After starting applet it's not big, less than 200 Mb, but after scrolling, resizing etc. memory usage reaches values more than 600 Mb. Why?
一个典型的profiler view of the code below shows only moderate usage and the expected free/used ratio after scrolling and 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
*/
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);
}
}