【进阶篇】Java 项目中对使用递归的明白分享

打印 上一主题 下一主题

主题 835|帖子 835|积分 2505

【进阶篇】Java 项目中对使用递归的明白分享


目录

前言

笔者在最近的项目开辟中,遇到了两个父子关系紧密相关的场景:批评树布局、部门树布局。具体的需求如:找出某条批评下的所有子批评id聚集,找出某个部门下所有的子部门id聚集。
在之前的项目开辟经验中,递归使用得是较少的,但作为一个在数据布局操作中遍历树节点的解决方案,我还是拿出来作为技能积累举行记载以及分享。
一、什么是递归

1.1基本概念

这里就有必要简朴介绍一下关于递归的基本概念了。
在 Java 中,递归是指在方法的定义中调用自身的过程,递归是基于方法调用栈的原理实现的:当一个方法被调用时,会在调用栈中创建一个对应的栈帧,包罗方法的参数、局部变量和返回地址等信息。在递归中,方法会在自身的定义中调用自身,这会导致多个类似方法的栈帧依次入栈。当满足终止条件时,递归开始回溯,栈帧依次出栈,方法得以执行完毕。
递归的关键是定义好递归的终止条件和递归调用的条件。如果没有适当的终止条件或递归调用的条件不满足,递归大概会陷入无限循环,导致栈内存溢出。
1.2优缺点

优点:

  • 简化问题:递归能够将复杂问题分解成更小规模的子问题,简化了问题的解决过程;
  • 实现高效算法:递归在某些算法中能够实现高效的解决方法,如数据布局操作中遍历树节点等。
缺点:

  • 栈溢出风险:递归大概导致方法调用栈过深,造成栈内存溢出;
  • 性能损耗:递归调用需要创建多个栈帧,对系统资源有一定的斲丧;
  • 可读性不高:递归的使用需要谨慎,不公道地使用大概造成代码难以明白和调试。
1.3与迭代的区别


  • 迭代(Iteration)
    迭代常见于 for 循环中:比如有一个聚集 A,对 A 举行 foreach,在内部设置条件,符合条件后将聚会集某个元素的值替换成别的值。
  迭代示例简图
  1.     @Test
  2.     public void iterationTest(){
  3.         ArrayList<String> list = new ArrayList<>();
  4.         list.add("计算机技术");
  5.         list.add("土木工程");
  6.         list.add("市场营销");
  7.         list.forEach(val -> {
  8.             if (val.contains("计算机")){
  9.                 log.info("迭代前的的专业名称:{}", val);
  10.                 String str = val.replace(val, "计算机科学与技术");
  11.                 log.info("迭代后的的专业名称:{}", str);
  12.             }
  13.         });
  14.     }
复制代码
结果为:
   
    迭代结果简图

  • 递归(Recursion)

递归的例子会在下一小节具体给出。
二、现实案例

下面笔者以递归获取某个批评id下面所有的子级批评id为例子,向大家介绍这个递归的过程。
首先,这里给出一个简朴的数据库批评表的 demo,id 是主键id 也是批评唯一 id,parent_id 是该条批评的父批评 id,status 为1表示审核通过的状态。
其中,我们可以简朴发现:这里21为第一层,28和29为第二层、31和32为第三层,草图如下所示:
批评id简朴层级示意图那么,我们如何将21、28、29、31、32都放进一个聚集里返回呢?下面的代码示例可以给你一个参考。
但是,在看代码之前,有个问题请你思索一下:
从21开始后,遍历的路线是21-28-29?还是21-28-31?还是21-29-32?或者是21-28-31-29-32?
下面是经过脱敏处理后的参看代码示例,解释都写得比力清楚了:
  1.     /**
  2.      * 这里可以看作是外部接口的调用,会得到递归的结果
  3.      * @param id
  4.      */
  5.     private List<Integer> getIdListMethod(Integer id){
  6.         ArrayList<Integer> idList = new ArrayList<>();
  7.         this.getAllIdByRecursion(id, idList);
  8.         log.info("递归后得到的id集合:{}", idList);
  9.         return idList;
  10.     }
  11.     /**
  12.      * 这里是递归的过程
  13.      * @param id
  14.      * @param idList
  15.      */
  16.     private void getAllIdByRecursion(Integer id, List<Integer> idList){
  17.         LambdaQueryWrapper<Comment> wrapper = new LambdaQueryWrapper<>();
  18.         //先把该id下所有的第一级子id找到
  19.         wrapper.eq(Comment::getParentId, id).eq(Comment::getStatus, NumberUtils.INTEGER_ONE);
  20.         List<Comment> commentList = this.list(wrapper);
  21.         for (Comment children : commentList){
  22.             this.getAllIdByRecursion(children.getId(), idList);
  23.         }
  24.         log.info("放入集合的id为:{}", id);
  25.         idList.add(id);
  26.     }
复制代码
上面问题的答案是:递归后得到的id聚集:[21,28,31,29,32],缘故起因就是:迭代会从一棵树开始遍历到底,没有元素了再从头开始遍历,依次迭代,类似于深度优先遍历。
比如:21下面有两个子id:28和29,那么会先走21-28-31这棵树,到底了后接着按照29-32遍历。
三、改进方案

我根据本身的开辟经验,可以从控制递归层数和改用 Stream 这两种办法来对递归举行改进。
3.1控制递归层数

JVM 默认控制的递归最大深度限定在 1000 层,可以通过设置 JVM 参数来控制其深度,如:
  1. java -Xss5m #表示将每个线程的栈内存大小设置为5MB,已经是比较大了
复制代码
或者在代码层面对递归的层数举行控制:
  1.         int depth = 0;
  2.         //递归方法调用
  3.         for (int i = 0; i < 20; i++) {
  4.             depth++;
  5.         }
  6.         if (depth > 100){
  7.             //其它操作
  8.         }
复制代码
3.1用 Stream 遍历

焦点思路是:先数据库全量查询(10万条以内),内存中使用 Stream 流操作、Lambda 表达式、Java 地址引用举行筛选。
适用于数据总量不多的情况,如:部门树,部门数目一样平常情况是比力固定的,一个组织或者公司最多也就几百上千个部门。
详情可以看我这篇文章:https://www.cnblogs.com/CodeBlogMan/p/17965824
四、文章小结

笔者确实不推荐在项目中过度使用递归,但是公道使用的话也能成为解决特定问题的一个利器,至于怎么拿捏这个度,那就要看大家的具体情况了。
Java 项目中对使用递归的明白分享到这里就结束了,文章如有不足和错误,或者你有更好的解决思路,欢迎大家的指正和交流!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

曹旭辉

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表