Java 如何有效地充分扩展大型JTree

Java 如何有效地充分扩展大型JTree,java,performance,swing,jtree,Java,Performance,Swing,Jtree,有几个相关的问题,关于,或者关于,其中一些问题也是针对 然而,没有一个建议的解决方案似乎涵盖了一个简单的应用案例:我有一棵大树(也就是一棵树,它要么非常深,要么非常宽,要么两者都是),我想以编程方式充分地扩展它。p> 下面是一个说明问题的示例:它创建了一个具有100k个节点的树模型。按下按钮将触发对expandAll的调用,该调用尝试使用从相关问题的答案中派生的方法展开所有节点 问题是,扩展这些100k节点大约需要13秒(在一台普通机器上,使用最新的JVM) 导入java.awt.BorderL

有几个相关的问题,关于,或者关于,其中一些问题也是针对

然而,没有一个建议的解决方案似乎涵盖了一个简单的应用案例:我有一棵大树(也就是一棵树,它要么非常深,要么非常宽,要么两者都是),我想以编程方式充分地扩展它。p> 下面是一个说明问题的示例:它创建了一个具有100k个节点的树模型。按下按钮将触发对
expandAll
的调用,该调用尝试使用从相关问题的答案中派生的方法展开所有节点

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

导入java.awt.BorderLayout;
导入java.awt.GridLayout;
导入java.util.function.Consumer;
导入javax.swing.JButton;
导入javax.swing.JFrame;
导入javax.swing.JPanel;
导入javax.swing.JScrollPane;
导入javax.swing.JTree;
导入javax.swing.SwingUtilities;
导入javax.swing.tree.DefaultMutableTreeNode;
导入javax.swing.tree.DefaultTreeModel;
导入javax.swing.tree.TreeModel;
公共类树扩展性能问题
{
公共静态void main(字符串[]args)
{
SwingUtilities.invokeLater(
()->createAndShowGUI());
}
私有静态void createAndShowGUI()
{
JFrame f=新的JFrame();
f、 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f、 getContentPane().setLayout(新的GridLayout(1,0));
f、 getContentPane().add(createTestPanel(
TreeExpansionPerformanceProblem::expandAll);
f、 设置大小(800600);
f、 setLocationRelativeTo(空);
f、 setVisible(真);
}
私有静态JPanel createTestPanel(使用者扩展方法)
{
JPanel panel=newjpanel(newborderlayout());
JTree树=新的JTree(createTestTreeModel());
添加(新的JScrollPane(树),BorderLayout.CENTER);
JPanel buttonPanel=新的JPanel();
JButton expandAllButton=新JButton(“全部展开”);
expandAllButton.addActionListener(e->
{
System.out.println(“扩展…”);
很久以前=System.nanoTime();
expansionMethod.accept(树);
long after=System.nanoTime();
System.out.println(“扩展带”+(在前)/1e6);
});
buttonPanel.add(expandAllButton);
面板。添加(按钮面板,边界布局。南);
返回面板;
}
私有静态void expandAll(JTree树)
{
int r=0;
而(r对于(inti=0;i而言,当完全扩展一棵大树时,会有各种瓶颈,并且有不同的方法可以绕过这些瓶颈

有趣的是,为扩展收集
TreePath
对象并遍历树通常不是最昂贵的部分。根据和中运行的探查器,大部分时间都花在计算模型状态(TreeModel
)和视图(JTree)之间的“映射”上。这主要是指

  • 计算
    JTree的行高
  • 计算
    TreeCellRenderer
并非所有这些计算都可以避免。但是,通过

  • 使用
    JTree\setRowHeight
  • 暂时禁用树的
    treeeexpansionlisteners
下面是一个比较问题中的“天真”方法的例子,该方法需要13秒来扩展一个具有100k个节点的树,而扩展同一棵树只需要1秒的方法稍微快一点

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

    }
}
导入java.awt.BorderLayout;
导入java.awt.Component;
导入java.awt.GridLayout;
导入java.util.array;
导入java.util.List;
导入java.util.function.Consumer;
导入javax.swing.JButton;
导入javax.swing.JFrame;
导入javax.swing.JPanel;
导入javax.swing.JScrollPane;
导入javax.swing.JTree;
导入javax.swing.SwingUtilities;
导入javax.swing.event.TreeExpansionListener;
导入javax.swing.tree.DefaultMutableTreeNode;
导入javax.swing.tree.DefaultTreeModel;
导入javax.swing.tree.TreeCellRenderer;
导入javax.swing.tree.TreeModel;
导入javax.swing.tree.TreePath;
公共类树扩展性能解决方案
{
公共静态void main(字符串[]args)
{
SwingUtilities.invokeLater(
()->createAndShowGUI());
}
私有静态void createAndShowGUI()
{
JFrame f=新的JFrame();
f、 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f、 getContentPane().setLayout(新的GridLayout(1,0));
f、 getContentPane().add(createTestPanel(
TreeExpansionPerformanceSolution::expandAll);
f、 getContentPane().add(createTestPanel(
TreeExpansionPerformanceSolution::expandAllFaster);
f、 设置大小(800600);
f、 setLocationRelativeTo(空);
f、 setVisible(真);
}
私有静态JPanel createTestPanel(使用者扩展方法)
{
JPanel panel=newjpanel(newborderlayout());
JTree树=新的JTree(createTestTreeModel());
添加(新的JScrollPane(树),BorderLayout.CENTER);
JPanel buttonPanel=新的JPanel();
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);
            }
        }

    }
}