创建缩略图的正确方法是什么?

时间:2016-11-05 17:30:53

标签: java swing thumbnails jscrollpane jfreechart

我正在尝试使用JScrollPane创建一些数据的缩略图,但我遇到了性能问题。此示例包含大约100个缩略图,每个图表中包含5000个样本。当我尝试向下滚动并多次向上滚动时,滚动会延迟,CPU负载增加,应用程序内存使用量超过500 Mb。

有没有办法在不减少数据的情况下避免此性能问题?

enter image description here

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);
        }
    });

}
}

编辑:我无法理解一件事:为什么在这种情况下内存使用量仍然非常巨大!请看这个插图。

image

增加: 我认为存在一些误解。

监视器visualVM的堆大小 enter image description here 启动后applet堆大小只有125 Mb,很酷。 但后来我开始测试:多次滚动和调整大小,越来越多 - 上下,上下,更小的帧和更大的帧。堆积大小超过500 Mb!我想这种情况不正常。

添加#2

真实世界的例子:

我的数据大小只有2 Mb,以90个图表(每个2个系列)表示,一个系列包含3000个元素。我已经通过滑块实现了更改数字列。 enter image description here

但是这个小数据堆大小超过1.5 GB!

enter image description here

在执行某些操作后会发生这种情况,例如更改数字列 对于我的CPU(核心2双核2.2GHz),每个绘图表需要大约4秒的时间!由于这个大的延迟,很难控制滑块。

更新

我已经将我的数据下采样到每个缩略图的100个样本。 现在它肯定更快,但仍有大量堆大小的问题。在图片上,一个超过700Mb,这不是一个记录。我很沮丧。 enter image description here

1 个答案:

答案 0 :(得分:4)

使用flyweight pattern仅渲染可见图表。 JTable renderers使用的方法概述为here,并显示在下面的ChartRenderer中。为了说明,每次显示一个单元格时都会重新创建数据集;滚动,调整大小和切换应用程序以查看效果。虽然这种渲染可以很好地扩展到数万个单元格,但每个图表仍然会呈现N个数据点。您可以在Scrollable方法getPreferredScrollableViewportSize()的实现中限制可见单元格的数量,如下所示。

  

如何将内存使用量减少到很小的值?

没有一般性答案,但有几种策略可能会有所帮助:

  • 尽可能早地在程序初始化时编写图表,而不是在渲染时;下面的updated示例构建TableModelChartPanel个实例; ChartRenderer相应地更简单。

  • 超过几千点的图表实际上是不可读的;考虑截断大型数据集并仅显示完整数据以响应ListSelectionEvent,图示为here

  • 平台活动监控可能会产生误导; profile验证实际结果。

image

  

启动applet后它不大,小于200 Mb,但滚动后,调整大小等内存使用量达到600 Mb以上的值。为什么呢?

以下代码的典型profiler视图仅显示适度使用情况和滚动后的预期free/used ratio以及garbage collection;您的结果可能会有所不同。

profile

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);
    }
}
相关问题