[英]How to fully expand a large JTree efficiently
有幾個相關的問題,與設置新TreeModel時自動擴展JTree或通常與擴展JTree有關,其中一些還旨在提高JTree中許多路徑的性能。
但是,所提出的解決方案似乎都沒有涵蓋可以視為“簡單”應用程序案例的解決方案:我有一棵大樹(即一棵非常深,非常寬或兩者兼有的樹),並且我想完全以編程方式進行擴展。
以下是顯示問題的MCVE :它創建具有100k節點的樹模型。 按下按鈕將觸發對expandAll
的調用,該調用將嘗試使用從對相關問題的答案中得出的方法來擴展所有節點。
問題在於,擴展這10萬個節點大約需要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);
}
}
}
}
是否有任何選項可以使更多節點更有效地擴展?
完全擴展一棵大樹時會遇到各種瓶頸,並且有不同的方法來規避這些問題。
有趣的是,收集TreePath
對象以進行擴展和遍歷樹通常不是最昂貴的部分。 根據在VisualVM和Java Flight Recorder中運行的探查器 ,在計算模型狀態( TreeModel
)和視圖( JTree
)之間的“映射”時,大部分時間都花在了上面。 這主要是指
JTree
的行高 TreeCellRenderer
標簽的邊界 並非所有這些計算都可以避免。 但是,通過以下方法可以大大加快樹的擴展速度
JTree#setRowHeight
設置固定的行高 TreeExpansionListeners
下面是一個MCVE ,它比較了問題的“簡單”方法(將具有10萬個節點的樹擴展需要13秒)和更快的方法(僅需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);
}
}
}
}
筆記:
這是一個自我回答的問題,我希望這個答案可能對其他人有所幫助。 但是, 1秒仍然很慢 。 我也嘗試了其他一些事情,例如設置tree.setLargeModel(true)
,但這並沒有產生積極的效果(實際上,它甚至更慢!)。 大部分時間仍用在樹的視覺狀態的最終更新中,我很高興在這里看到進一步的改進。
可以用涉及DefaultMutableTreeNode#breadthFirstEnumeration
和DefaultTreeModel#getPathToRoot
的幾行代碼來替換expandAllRecursively
方法,而不會犧牲很多性能。 但是以當前形式,該代碼僅在TreeModel
接口上運行,並且可以與任何類型的節點一起使用。
如這里討論的, JTree
已經使用flyweight模式來優化渲染。 我認為您在expandAllFaster()
就足夠了。 擴展所有大於10 5的葉子充其量是笨拙的。 盡管適當的搜索控件可以緩解這種情況,但難以有意義地瀏覽生成的樹。
在Mac OS X TreeUI
委托com.apple.laf.AquaTreeUI
看到了一個有趣的折衷方案。 它遞歸擴展所選節點及其子節點時選擇鍵被按下時,由以下方法測定MouseEvent::isAltDown()
有關詳細信息,請參見名為"aquaFullyExpandNode"
的Action
。
最后,節省了用戶的擴張偏好可能是有價值的,因為例如 。
我正在研究…動態過濾> 100k節點的
JTree
。
着眼於一個基於模型的方法,如建議在這里 ,將搜索到一個單獨的,也許是無模式,對話框。 概括來說,
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.