Loading...

开发笔记:服务端返回三/多级菜单数据的几种不同实现

阅前须知:

本文是我个人的学习笔记,由于本人水平有限,所以代码仅供参考,如有错误,还望指出!(^▽^)

环境:Jdk1.8

数据库字段和Stream实现方法主要参考自某谷的谷粒商城,此商城教程B站可搜

本文主要讲解了以下三种实现方法:

  • 循环实现(for语句)
  • 递归实现(广度优先搜索)
  • Stream实现(Jdk8的)

实现效果:

数据库的字段:

主要关注以下字段:

  • cat_id:分类的id,主键
  • parent_cid:父分类的id(即cat_id)
  • cat_level:层级,如:第一级菜单为1,第二级为2……
  • sort:分类的排序级别(如:在本文中,数值越小排序级别就越高)

建表语句:

drop table if exists pms_category;

/*==============================================================*/
/* Table: pms_category                                          */
/*==============================================================*/
create table pms_category
(
   cat_id               bigint not null auto_increment comment '分类id',
   name                 char(50) comment '分类名称',
   parent_cid           bigint comment '父分类id',
   cat_level            int comment '层级',
   show_status          tinyint comment '是否显示[0-不显示,1显示]',
   sort                 int comment '排序',
   icon                 char(255) comment '图标地址',
   product_unit         char(50) comment '计量单位',
   product_count        int comment '商品数量',
   primary key (cat_id)
);

alter table pms_category comment '商品三级分类';

-- ------------------------------------------------------------------
-- Records of pms_category (全部数据放上来太占篇幅了,所以只放了部分,但足矣)
-- ------------------------------------------------------------------
INSERT INTO `pms_category` VALUES ('1', '图书、音像、电子书刊', '0', '1', '1', '0', null, null, '0');
INSERT INTO `pms_category` VALUES ('2', '手机', '0', '1', '1', '0', null, null, '0');
INSERT INTO `pms_category` VALUES ('22', '电子书刊', '1', '2', '1', '0', null, null, '0');
INSERT INTO `pms_category` VALUES ('23', '音像', '1', '2', '1', '0', null, null, '0');
INSERT INTO `pms_category` VALUES ('34', '手机通讯', '2', '2', '1', '0', null, null, '0');
INSERT INTO `pms_category` VALUES ('35', '运营商', '2', '2', '1', '0', null, null, '0');
INSERT INTO `pms_category` VALUES ('165', '电子书', '22', '3', '1', '1', null, null, '0');
INSERT INTO `pms_category` VALUES ('166', '网络原创', '22', '3', '1', '0', null, null, '0');
INSERT INTO `pms_category` VALUES ('169', '音乐', '23', '3', '1', '0', null, null, '0');
INSERT INTO `pms_category` VALUES ('170', '影视', '23', '3', '1', '0', null, null, '0');
INSERT INTO `pms_category` VALUES ('225', '手机', '34', '3', '1', '0', null, null, '0');
INSERT INTO `pms_category` VALUES ('226', '对讲机', '34', '3', '1', '0', null, null, '0');
INSERT INTO `pms_category` VALUES ('227', '合约机', '35', '3', '1', '0', null, null, '0');
INSERT INTO `pms_category` VALUES ('228', '选号中心', '35', '3', '1', '0', null, null, '0');

实体类:

public class CategoryEntity implements Serializable {
	private static final long serialVersionUID = 1L;
	/**
	 * 分类id
	 */
	private Long catId;
	/**
	 * 分类名称
	 */
	private String name;
	/**
	 * 父分类id
	 */
	private Long parentCid;
	/**
	 * 层级
	 */
	private Integer catLevel;
	/**
	 * 是否显示[0-不显示,1显示]
	 */
	private Integer showStatus;
	/**
	 * 排序
	 */
	private Integer sort;
	/**
	 * 图标地址
	 */
	private String icon;
	/**
	 * 计量单位
	 */
	private String productUnit;
	/**
	 * 商品数量
	 */
	private Integer productCount;
	/**
	 * 子菜单
	 */
	private List<CategoryEntity> categoryEntityList;
}

正文

文中代码也可以看我的代码仓库

for循环实现

以下代码以三级菜单为例,需要多级列表的话还请自行修改噢d=====( ̄▽ ̄*)b

/**
* 	采用直接循环的方式获取三级菜单,主要思路是:先从数据库中获取全部分类数据,然后按照其cat_level字段(也就是层级字段)
*	进行分类,分成三个list,然后在用for循环对三个list中的对象进行父菜单和子菜单的对应构建。
*/
private List<CategoryEntity> getCategoryTreeWithLoop(List<CategoryEntity> allCategoryList) {
    // 由于使用了mybatis-plus,所以这里可以把下面这行代码当成查询全部数据
    List<CategoryEntity> allCategoryList = baseMapper.selectList(null);
    // 下面操作主要是set操作,所以使用一个对设置操作时间复杂度低的数据结构
    List<CategoryEntity> categoryEntityListLevel1 = new LinkedList<>();
    List<CategoryEntity> categoryEntityListLevel2 = new LinkedList<>();
    List<CategoryEntity> categoryEntityListLevel3 = new LinkedList<>();
    for (CategoryEntity categoryEntity : allCategoryList) { // 构建相应的三级菜单列表
        if (Integer.valueOf(1).equals(categoryEntity.getCatLevel())) {
            categoryEntityListLevel1.add(categoryEntity);
        } else if (Integer.valueOf(2).equals(categoryEntity.getCatLevel())){
            categoryEntityListLevel2.add(categoryEntity);
        } else if (Integer.valueOf(3).equals(categoryEntity.getCatLevel())) {
            categoryEntityListLevel3.add(categoryEntity);
        }
    }
    // 按照sort字段的数值大小进行排序,排序规则为:sort数值越小越越前,自定义排序规则代码在文末
    Comparator categoryComparator = new CategoryComparator();
    Collections.sort(categoryEntityListLevel1, categoryComparator);
    Collections.sort(categoryEntityListLevel2, categoryComparator);
    Collections.sort(categoryEntityListLevel3, categoryComparator);
    allCategoryList = null; // help GC
    // 下面操作主要是get操作,所以换成一个获取操作时间复杂度低的数据结构
    List<CategoryEntity> categoryEntityArrayListLevel1 = new ArrayList<>(categoryEntityListLevel1);
    List<CategoryEntity> categoryEntityArrayListLevel2 = new ArrayList<>(categoryEntityListLevel2);
    List<CategoryEntity> categoryEntityArrayListLevel3 = new ArrayList<>(categoryEntityListLevel3);
    categoryEntityListLevel1 = null; // help GC
    categoryEntityListLevel2 = null;
    categoryEntityListLevel3 = null;
    for (CategoryEntity categoryEntity1 : categoryEntityArrayListLevel1) { // 构建一级菜单
        List<CategoryEntity> tmp2 = new LinkedList<>();
        for (CategoryEntity categoryEntity2 : categoryEntityArrayListLevel2) { // 构建二级菜单
            List<CategoryEntity> tmp3 = new LinkedList<>();
            for (CategoryEntity categoryEntity3 : categoryEntityArrayListLevel3) { // 构建三级菜单
                if (categoryEntity2.getCatId().equals(categoryEntity3.getParentCid()))
                    tmp3.add(categoryEntity3);
            }
            categoryEntity2.setCategoryEntityList(tmp3); // 给二级菜单赋予三级菜单
            if (categoryEntity1.getCatId().equals(categoryEntity2.getParentCid()))
                tmp2.add(categoryEntity2);
        }
        categoryEntity1.setCategoryEntityList(tmp2);
    }
    categoryEntityArrayListLevel1.forEach(categoryEntity -> {
        System.out.println("categoryEntity:" + categoryEntity.toString());
    });
    //log.info("listCategoryTree() : method finish");
    //log.info("categoryEntityArrayListLevel1.length :  {}",categoryEntityArrayListLevel1.toArray().length);
    return categoryEntityArrayListLevel1;
}

个人分析,仅供参考:

优点:首先,个人感觉对于简单的业务,循环是一种比较好理解的实现方法;其次,一般情况下,其相对于递归来说,比较好排查错误。

缺点:首先,代码量大,看着不简洁。其次,相对于递归来说,一旦业务复杂起来,其代码量也会线性成长很多,而递归则适合一些复杂的场景,比如这时有一个六级列表,使用递归就会更好理解和维护。

递归实现

以三级菜单为例,我们其实可以把它当成一个深度为3的多叉树。也就是说我们需要把一堆没有组装成多叉树的全部列表数据组装成一个个多叉树就好了,每一个多叉树即代表每一个一级菜单元素,我们只需要返回装有这些多叉树的List就ok了。

弄清思路后,我们来组装多叉树,我个人采用的是广度优先搜索(即层序遍历)的方式,应该是广度优先搜索吧...如果我没弄错的话:

private List<CategoryEntity> getCategoryTreeWithRecursion(List<CategoryEntity> allCategoryList) {
    LinkedList<CategoryEntity> categoryEntityLinkedList = new LinkedList<>();
    // 拿到各为根节点的这一层
    for (CategoryEntity categoryEntity : allCategoryList) {
        if (Integer.valueOf(1).equals(categoryEntity.getCatLevel())) {
            categoryEntityLinkedList.add(categoryEntity);
        }
    }
    ArrayList<CategoryEntity> categoryEntityArrayList = new ArrayList<>(categoryEntityLinkedList);
    categoryEntityLinkedList = null;
    categoryEntityArrayList.sort(new CategoryComparator());
    // 递归构建每一颗树的子树
    for (CategoryEntity categoryEntity : categoryEntityArrayList) {
        categoryEntity.setCategoryEntityList(recur(categoryEntity.getCatId(), allCategoryList));
    }
    return categoryEntityArrayList;
}

// 递归函数
public List<CategoryEntity> recur(Long catId, List<CategoryEntity> allCategoryList) {
    // 递归终止条件
    if (catId == null || allCategoryList.size() == 0 || allCategoryList == null) return null;
    // 构建当前层
    List<CategoryEntity> categoryEntityLinkedList = new LinkedList<>();
    for (CategoryEntity categoryEntity : allCategoryList) {
        if (categoryEntity.getParentCid().equals(catId)) {
            categoryEntityLinkedList.add(categoryEntity);
        }
    }
    // 递归构建下一层
    for (CategoryEntity categoryEntity : categoryEntityLinkedList) {
        categoryEntity.setCategoryEntityList(recur(categoryEntity.getCatId(), allCategoryList));
    }
    if (categoryEntityLinkedList.size() == 0) recur(null, null);
    categoryEntityLinkedList.sort(new CategoryComparator());
    return categoryEntityLinkedList;
}

个人分析,仅供参考:

优点:这个递归方式的优点在于其对于多级列表较优好一点,以上面的三级列表为例,当我们需要四级列表时,我们不需要对代码进行逻辑上的调整。因此,当列表级别增加的话,代码的简洁性和易于理解性会保持住。

缺点:同时,由于递归的性质,我们也要按照实际情况分析和判断其的效率和栈溢出等可能发生的情况。

Stream实现

// 构建层级为1的有序菜单list,然后再递归地构建子菜单
public List<CategoryEntity> getCategoryTreeWithStream() {
    // 可以把下面这行代码当成查询全部菜单数据
    List<CategoryEntity> allCategoryList = baseMapper.selectList(null);
    // 把全部数据中的层级为1的列表取出来,构建
    List<CategoryEntity> level1Menus = allCategoryList.stream().filter(categoryEntity ->
            categoryEntity.getParentCid() == 0
    ).map((menu)->{
        menu.setCategoryEntityList(getChildren(menu,allCategoryList)); // 构建子菜单
        return menu;
    }).sorted(new CategoryComparator()).collect(Collectors.toList());
    return level1Menus;
}

// 递归查找所有菜单的子菜单,其实这里也用到了递归
private List<CategoryEntity> getChildren(CategoryEntity root, List<CategoryEntity> all){
    List<CategoryEntity> children = all.stream().filter(categoryEntity -> {
        return categoryEntity.getParentCid() == root.getCatId();
    }).map(categoryEntity -> {
        categoryEntity.setCategoryEntityList(getChildren(categoryEntity,all));
        return categoryEntity;
    }).sorted(new CategoryComparator()).collect(Collectors.toList());
    return children;
}

个人分析,仅供参考:

优点:以一种声明的方式处理数据,感觉看起来更直观,比较适合类似这的种数据处理的场景。同样,其也是一种简洁、优雅的实现方式。

缺点:首先,我觉得代码不是只写给自己看的,我们要保证别人也能轻松理解你的想法和实现,这样才是一种健康的团队合作。其次,我们也要结合实际场景灵活运用,根据我所查的资料,stream的效率并不是很好,但是如果系统对响应的要求不是很高时或者这些影响对于其他实现方式来说只是产生了微乎其微的影响,那就无所谓了。(由于本人只试过简单的环境的,所以以上仅供参考o(////▽////)q

自定义排序规则

// 排序字段:sort。本文的排序规则为:sort数值越小,优先级别越高。
class CategoryComparator implements Comparator<CategoryEntity>{
    @Override
    public int compare(CategoryEntity o1, CategoryEntity o2) {
        return (o1.getSort() == null? 0 : o1.getSort()) - (o2.getSort() == null? 0 : o2.getSort());
    }
}

总结

以上的实现方法都是先把数据库中的所有数据查询出来,再在服务器上进行数据列表的构建的。其实也可以把构建的主要任务放在数据库一方。我感觉还是那句话,实际场景实际分析灵活运用~~

posted @ 2021-02-05 21:17  _轻舟  阅读(464)  评论(0编辑  收藏  举报