当前位置: 首页 > 知识库问答 >
问题:

如何高效地充分展开大型JTree

景凌
2023-03-14

然而,所提出的解决方案似乎都没有涵盖“简单”应用程序的情况:我有一棵大树(即一棵非常深、非常宽或两者兼而有之的树),我希望以编程方式完全扩展它。

下面是一个显示问题的MCVE:它创建了一个有100k个节点的树模型。按下该按钮会触发对expandAll的调用,该调用尝试使用从相关问题的答案派生的方法展开所有节点。

问题是,扩展这100k个节点大约需要13秒(在一台普通机器上,使用最新的JVM)。

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.util.function.Consumer;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;


public class TreeExpansionPerformanceProblem
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(
            () -> createAndShowGUI());
    }

    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().setLayout(new GridLayout(1,0));

        f.getContentPane().add(createTestPanel(
            TreeExpansionPerformanceProblem::expandAll));

        f.setSize(800,600);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    private static JPanel createTestPanel(Consumer<JTree> expansionMethod)
    {
        JPanel panel = new JPanel(new BorderLayout());
        JTree tree = new JTree(createTestTreeModel());
        panel.add(new JScrollPane(tree), BorderLayout.CENTER);

        JPanel buttonPanel = new JPanel();
        JButton expandAllButton = new JButton("Expand all");
        expandAllButton.addActionListener( e -> 
        {
            System.out.println("Expanding...");
            long before = System.nanoTime();
            expansionMethod.accept(tree);
            long after = System.nanoTime();
            System.out.println("Expanding took "+(after-before)/1e6);

        });
        buttonPanel.add(expandAllButton);
        panel.add(buttonPanel, BorderLayout.SOUTH);
        return panel;
    }

    private static void expandAll(JTree tree)
    {
        int r = 0;
        while (r < tree.getRowCount())
        {
            tree.expandRow(r);
            r++;
        }
    }

    private static TreeModel createTestTreeModel() 
    {
        DefaultMutableTreeNode root = new DefaultMutableTreeNode("JTree");
        addNodes(root, 0, 6, 6, 10);
        return new DefaultTreeModel(root);
    }

    private static void addNodes(DefaultMutableTreeNode node, 
        int depth, int maxDepth, int count, int leafCount)
    {
        if (depth == maxDepth)
        {
            return;
        }
        for (int i=0; i<leafCount; i++)
        {
            DefaultMutableTreeNode leaf = 
                new DefaultMutableTreeNode("depth_"+depth+"_leaf_"+i);
            node.add(leaf);
        }
        if (depth < maxDepth - 1)
        {
            for (int i=0; i<count; i++)
            {
                DefaultMutableTreeNode child = 
                    new DefaultMutableTreeNode("depth_"+depth+"_node_"+i);
                node.add(child);
                addNodes(child, depth+1, maxDepth, count, leafCount);
            }
        }

    }
}

共有1个答案

丌官飞章
2023-03-14

当完全展开一棵大树时,会遇到各种瓶颈,以及不同的方法来规避这些瓶颈。

有趣的是,收集treepath对象进行扩展并遍历树并不是开销最大的部分。根据在VisualVM和Java Flight Recorder中运行的profiler,大部分时间都花在计算模型状态(TreeModel)和视图(JTree)之间的“映射”上。这主要是指

  • 计算JTree
  • 的行高
  • 计算TreeCellRenderer中标签的界限
    null
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridLayout;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.TreeExpansionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;


public class TreeExpansionPerformanceSolution
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(
            () -> createAndShowGUI());
    }

    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().setLayout(new GridLayout(1,0));

        f.getContentPane().add(createTestPanel(
            TreeExpansionPerformanceSolution::expandAll));

        f.getContentPane().add(createTestPanel(
            TreeExpansionPerformanceSolution::expandAllFaster));

        f.setSize(800,600);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    private static JPanel createTestPanel(Consumer<JTree> expansionMethod)
    {
        JPanel panel = new JPanel(new BorderLayout());
        JTree tree = new JTree(createTestTreeModel());
        panel.add(new JScrollPane(tree), BorderLayout.CENTER);

        JPanel buttonPanel = new JPanel();
        JButton expandAllButton = new JButton("Expand all");
        expandAllButton.addActionListener( e -> 
        {
            System.out.println("Expanding...");
            long before = System.nanoTime();
            expansionMethod.accept(tree);
            long after = System.nanoTime();
            System.out.println("Expanding took "+(after-before)/1e6);

        });
        buttonPanel.add(expandAllButton);
        panel.add(buttonPanel, BorderLayout.SOUTH);
        return panel;
    }

    private static void expandAll(JTree tree)
    {
        int r = 0;
        while (r < tree.getRowCount())
        {
            tree.expandRow(r);
            r++;
        }
    }

    private static void expandAllFaster(JTree tree)
    {
        // Determine a suitable row height for the tree, based on the 
        // size of the component that is used for rendering the root 
        TreeCellRenderer cellRenderer = tree.getCellRenderer();
        Component treeCellRendererComponent = 
            cellRenderer.getTreeCellRendererComponent(
                tree, tree.getModel().getRoot(), false, false, false, 1, false);
        int rowHeight = treeCellRendererComponent.getPreferredSize().height + 2;
        tree.setRowHeight(rowHeight);

        // Temporarily remove all listeners that would otherwise
        // be flooded with TreeExpansionEvents
        List<TreeExpansionListener> expansionListeners =
            Arrays.asList(tree.getTreeExpansionListeners());
        for (TreeExpansionListener expansionListener : expansionListeners)
        {
            tree.removeTreeExpansionListener(expansionListener);
        }

        // Recursively expand all nodes of the tree
        TreePath rootPath = new TreePath(tree.getModel().getRoot());
        expandAllRecursively(tree, rootPath);

        // Restore the listeners that the tree originally had
        for (TreeExpansionListener expansionListener : expansionListeners)
        {
            tree.addTreeExpansionListener(expansionListener);
        }

        // Trigger an update for the TreeExpansionListeners
        tree.collapsePath(rootPath);
        tree.expandPath(rootPath);
    }

    // Recursively expand the given path and its child paths in the given tree
    private static void expandAllRecursively(JTree tree, TreePath treePath)
    {
        TreeModel model = tree.getModel();
        Object lastPathComponent = treePath.getLastPathComponent();
        int childCount = model.getChildCount(lastPathComponent);
        if (childCount == 0)
        {
            return;
        }
        tree.expandPath(treePath);
        for (int i=0; i<childCount; i++)
        {
            Object child = model.getChild(lastPathComponent, i);
            int grandChildCount = model.getChildCount(child);
            if (grandChildCount > 0)
            {
                class LocalTreePath extends TreePath
                {
                    private static final long serialVersionUID = 0;
                    public LocalTreePath(
                        TreePath parent, Object lastPathComponent)
                    {
                        super(parent, lastPathComponent);
                    }
                }
                TreePath nextTreePath = new LocalTreePath(treePath, child);
                expandAllRecursively(tree, nextTreePath);
            }
        }
    }


    private static TreeModel createTestTreeModel() 
    {
        DefaultMutableTreeNode root = new DefaultMutableTreeNode("JTree");
        addNodes(root, 0, 6, 6, 10);
        return new DefaultTreeModel(root);
    }

    private static void addNodes(DefaultMutableTreeNode node, 
        int depth, int maxDepth, int count, int leafCount)
    {
        if (depth == maxDepth)
        {
            return;
        }
        for (int i=0; i<leafCount; i++)
        {
            DefaultMutableTreeNode leaf = 
                new DefaultMutableTreeNode("depth_"+depth+"_leaf_"+i);
            node.add(leaf);
        }
        if (depth < maxDepth - 1)
        {
            for (int i=0; i<count; i++)
            {
                DefaultMutableTreeNode child = 
                    new DefaultMutableTreeNode("depth_"+depth+"_node_"+i);
                node.add(child);
                addNodes(child, depth+1, maxDepth, count, leafCount);
            }
        }

    }
}

备注:

>

  • 这是一个自我回答的问题,我希望这个答案可能对其他人有所帮助。尽管如此,1秒还是相当慢的。我还尝试了一些其他方法,例如设置tree.setlargeModel(true),但这并没有产生积极的效果(事实上,它甚至更慢!)。大部分时间仍然花在树的视觉状态的最后更新上,我很乐意看到这里的进一步改进。

    ExpandallRecursively方法可以由涉及DefaultMutableTreenode#BreadthFirstEnumerationDefaultTreemodel#GetPathtoroot的几行替换,而不会牺牲太多性能。但是在当前的形式中,代码只在TreeModel接口上操作,并且应该使用任何类型的节点。

  •  类似资料:
    • 我正在跟踪粒子到三维晶格中。每个晶格元素都有一个对应于展开的3D数组的索引 我对从S1单元到S2单元的过渡感兴趣。由此产生的过渡矩阵M(S1,S2)人口稀少,因为粒子只能在细胞附近到达。不幸的是,使用几何上接近的展开3D阵列单元的索引可能会在索引上有很大差异。例如,位于彼此顶部(例如z和z 1处)的单元格的索引将按宽度*深度移动。因此,如果我尝试累积得到的2D矩阵M(S1,S2),S1和S2将非常

    • 我正在尝试下载大文件( 这是因为它在不使用太多内存的情况下下载文件,但它似乎不必要地无效,因为它不断尝试写入更多数据,而不知道是否有任何新数据到达。这似乎也通过我自己的测试得到了证实,同时在资源非常有限的VM上运行它,因为它似乎使用了更多的CPU,同时下载速度低于python中的类似脚本,并且使用是有原因的。 我想知道是否有一种方法可以让某些东西回调,如果x字节可用,或者它是文件的末尾,那么我就可

    • 问题内容: 我有一个150MB的单张excel文件,使用以下命令在功能非常强大的计算机上打开大约需要7分钟: 有什么办法可以更快地打开excel文件吗?我愿意接受甚至非常古怪的建议(例如hadoop,spark,c,java等)。理想情况下,如果这不是白日梦,我正在寻找一种在30秒内打开文件的方法。另外,上面的示例使用的是python,但不一定必须是python。 注意:这是来自客户端的Excel

    • 问题内容: 我有非常大的XML文件要处理。我想将它们转换为具有颜色,边框,图像,表格和字体的可读PDF。我的机器上没有很多资源,因此,我需要我的应用程序对内存和处理器的寻址非常理想。 我进行了不起眼的研究,以使自己对所使用的技术有所了解,但是我无法确定什么是满足我的要求的最佳编程语言和API。我认为DOM不是一个选择,因为它会占用大量内存,但是带SAX解析器的Java是否可以满足我的要求? 有人还

    • 我正在处理一个非常宽的数据集(1005行*590,718列,1.2g)。将如此大的数据集加载到pandas dataframe中会导致完全由于内存不足而导致代码失败。 我知道Spark可能是处理大型数据集的Pandas的一个很好的替代方案,但是Pandas中是否有任何适合的解决方案来减少加载大型数据时的内存占用?

    • 我有INT数组 我也在寻找类似的问题,但我只发现了java.util.stream.stream .sorted()的大O复杂性,这一点也没有帮助,因为有两个不同的答案(第一个当然是部分错误的,因为arrays.sort并不总是O(n log n))。第二个呢?我还没找到证据。