树形菜单的实现方式

当我们想要展示层级结构,如文件目录、组织结构或分类目录时,树形菜单是一个直观且有效的解决方案。在Java中,有多种方式可以实现树形菜单,本文将介绍其中几种常见方法,并配有示例代码。


1. 使用递归法

递归法是最直观的实现树形菜单的方法。我们先定义节点结构,然后通过递归方式构建每一个节点的子节点。

实例代码

java 复制代码
class TreeNode {
    int id;
    int parentId;
    String name;
    List<TreeNode> children;

    // 构造函数、getters、setters省略
}

public List<TreeNode> buildTree(List<TreeNode> nodes, int parentId) {
    List<TreeNode> tree = new ArrayList<>();
    for (TreeNode node : nodes) {
        if (node.parentId == parentId) {
            node.children = buildTree(nodes, node.id);
            tree.add(node);
        }
    }
    return tree;
}

2. 使用队列法 (层次遍历)

使用队列实现树形菜单可以有效减少递归的深度,特别是在树形结构很深的情况下。

实例代码

java 复制代码
public List<TreeNode> buildTreeWithQueue(List<TreeNode> nodes) {
    if (nodes == null || nodes.isEmpty()) {
        return Collections.emptyList();
    }

    Map<Integer, TreeNode> nodeMap = nodes.stream().collect(Collectors.toMap(TreeNode::getId, node -> node));
    List<TreeNode> tree = new ArrayList<>();
    Queue<TreeNode> queue = new LinkedList<>(nodes);

    while (!queue.isEmpty()) {
        TreeNode node = queue.poll();
        if (node.parentId == 0) {
            tree.add(node);
        } else {
            TreeNode parent = nodeMap.get(node.parentId);
            if (parent.children == null) {
                parent.children = new ArrayList<>();
            }
            parent.children.add(node);
        }
    }
    return tree;
}

3. 使用Map索引优化

通过使用Map进行索引,可以提高搜索效率,特别是当节点数量非常多时。

实例代码

java 复制代码
public List<TreeNode> buildTreeWithMap(List<TreeNode> nodes) {
    Map<Integer, List<TreeNode>> childrenMap = new HashMap<>();
    List<TreeNode> rootNodes = new ArrayList<>();

    for (TreeNode node : nodes) {
        if (node.parentId == 0) {
            rootNodes.add(node);
        } else {
            childrenMap
                .computeIfAbsent(node.parentId, k -> new ArrayList<>())
                .add(node);
        }
    }

    for (TreeNode rootNode : rootNodes) {
        rootNode.children = getChildren(rootNode.id, childrenMap);
    }

    return rootNodes;
}

private List<TreeNode> getChildren(int parentId, Map<Integer, List<TreeNode>> childrenMap) {
    List<TreeNode> children = childrenMap.get(parentId);
    if (children != null) {
        for (TreeNode child : children) {
            child.children = getChildren(child.id, childrenMap);
        }
    }
    return children;
}

4. 数据库的树形编码的方式

步骤1: 创建数据库表结构

首先,我们需要一个数据库表来存储节点数据:

sql 复制代码
CREATE TABLE tree_nodes (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL,
    path VARCHAR(255) DEFAULT '',
    parent_id INT
);

其中,path字段用于存储从根节点到当前节点的路径,如1/3/5/

步骤2: Java 数据模型

我们需要一个Java类来表示数据库中的节点:

java 复制代码
public class TreeNode {
    private int id;
    private String name;
    private String path;
    private int parentId;

    // Getter, Setter 和 Constructor
}

步骤3: 插入节点

插入一个新的节点时,我们需要计算它的路径:

java 复制代码
public void addNode(String name, int parentId, Connection connection) throws SQLException {
    String path;
    if (parentId == 0) {
        path = "/";
    } else {
        String parentPathQuery = "SELECT path FROM tree_nodes WHERE id = ?";
        try (PreparedStatement stmt = connection.prepareStatement(parentPathQuery)) {
            stmt.setInt(1, parentId);
            ResultSet rs = stmt.executeQuery();
            if (rs.next()) {
                path = rs.getString("path") + parentId + "/";
            } else {
                throw new SQLException("Parent not found");
            }
        }
    }

    String insertQuery = "INSERT INTO tree_nodes (name, path, parent_id) VALUES (?, ?, ?)";
    try (PreparedStatement stmt = connection.prepareStatement(insertQuery)) {
        stmt.setString(1, name);
        stmt.setString(2, path);
        stmt.setInt(3, parentId);
        stmt.executeUpdate();
    }
}

步骤4: 获取某节点的所有子节点

java 复制代码
public List<TreeNode> getChildren(int nodeId, Connection connection) throws SQLException {
    List<TreeNode> children = new ArrayList<>();

    String pathQuery = "SELECT path FROM tree_nodes WHERE id = ?";
    String path;
    try (PreparedStatement stmt = connection.prepareStatement(pathQuery)) {
        stmt.setInt(1, nodeId);
        ResultSet rs = stmt.executeQuery();
        if (rs.next()) {
            path = rs.getString("path") + nodeId + "/";
        } else {
            throw new SQLException("Node not found");
        }
    }

    String childrenQuery = "SELECT * FROM tree_nodes WHERE path LIKE ?";
    try (PreparedStatement stmt = connection.prepareStatement(childrenQuery)) {
        stmt.setString(1, path + "%");
        ResultSet rs = stmt.executeQuery();
        while (rs.next()) {
            TreeNode node = new TreeNode();
            node.setId(rs.getInt("id"));
            node.setName(rs.getString("name"));
            node.setPath(rs.getString("path"));
            node.setParentId(rs.getInt("parent_id"));
            children.add(node);
        }
    }

    return children;
}

总结

以上我们介绍了三种在Java中实现树形菜单的方法,从基础的递归法、队列法到使用Map进行优化。根据实际需求和数据量大小,你可以选择合适的方法进行实现。不过,无论使用哪种方法,都要确保理解树形结构的基础原理和具体实现细节,以确保代码的正确性和效率。 还有一种是路径枚举方式的一个简单例子,它已经足够用于实际应用。当然,还有许多细节可以优化,如路径长度限制、节点移动等。而其他的树形编号方法,如嵌套集模型和闭包表,会有更复杂的逻辑。但总的来说,理解核心概念并将其应用于实际代码是关键。

相关推荐
皮皮林55111 小时前
IDEA 源码阅读利器,你居然还不会?
java·intellij idea
你的人类朋友11 小时前
什么是OpenSSL
后端·安全·程序员
文心快码BaiduComate15 小时前
文心快码入选2025服贸会“数智影响力”先锋案例
前端·后端·程序员
卡尔特斯15 小时前
Android Kotlin 项目代理配置【详细步骤(可选)】
android·java·kotlin
集成显卡15 小时前
windows 下使用 bat 批处理运行 Chrome 无头模式刷一波访问量
windows·程序员
白鲸开源15 小时前
Ubuntu 22 下 DolphinScheduler 3.x 伪集群部署实录
java·ubuntu·开源
ytadpole15 小时前
Java 25 新特性 更简洁、更高效、更现代
java·后端
纪莫15 小时前
A公司一面:类加载的过程是怎么样的? 双亲委派的优点和缺点? 产生fullGC的情况有哪些? spring的动态代理有哪些?区别是什么? 如何排查CPU使用率过高?
java·java面试⑧股
JavaGuide16 小时前
JDK 25(长期支持版) 发布,新特性解读!
java·后端
用户37215742613516 小时前
Java 轻松批量替换 Word 文档文字内容
java