1. 树的简单遍历思路及写法

①数据准备

Menu实体类

/*** @date: 2022/7/25* @FileName: Menu* @author: Yan* @Des:*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Menu {private Integer id;private String name;private Integer parentId;private List<Menu> children;public Menu(Integer id, String name, Integer parentId) {this.id = id;this.name = name;this.parentId = parentId;}}
复制代码

创建测试数据List


/*** @date: 2022/7/25* @FileName: TreeController* @author: Yan* @Des:*/
@RestController
@RequestMapping("/tree")
public class TreeController {public static List<Menu> getMenus(){List<Menu> menus = Arrays.asList(new Menu(1, "A公司", 0),new Menu(2, "a销售部", 14),new Menu(3, "财税部", 1),new Menu(4, "商务部", 1),new Menu(5, "综合部", 1),new Menu(6, "a销售1部", 2),new Menu(7, "a销售2部", 2),new Menu(8, "a销售3部", 2),new Menu(9, "a销售4部", 2),new Menu(10, "b销售部", 14),new Menu(11, "b销售1部", 10),new Menu(12, "b销售2部", 10),new Menu(13, "人事部", 1),new Menu(14, "销售部", 1));return menus;}
}
复制代码

② 使用Lambda的语法糖遍历及制作树

在TreeController中,写入:

    @RequestMappingpublic List<Menu> getTree(){List<Menu> menus = getMenus();List<Menu> menusTree = menus.stream().filter(menu -> menu.getParentId() == 0).map(menu -> {menu.setChildren(getChildrens(menu, menus));return menu;}).collect(Collectors.toList());return menusTree;}public static List<Menu> getChildrens(Menu root, List<Menu> allMenus){List<Menu> childrenTree = allMenus.stream().filter(menu -> Objects.equals(menu.getParentId(), root.getId())).peek(menu -> menu.setChildren(getChildrens(menu, allMenus))).collect(Collectors.toList());return childrenTree;}
复制代码

③ 查看运行结果

可以看到,我们成功的返回了树形结构

问题的出现

最近看公司项目,经常会碰到一些场景是需要以树形结构展示的,比如说部门树,设备树,分类树等等,但是感觉好像都是需要用到的时候现写的,但是大体的思路是一样的,感觉重复写就有点冗余了,而且对应的树形结构构建又相对麻烦,我就想着抽离一下,做成一个通用的工具类TreeUtil,于是翻了一下别的大佬的文章,学着写了这样的一个工具类。

问题的解决

2. 制作工具

2.1 TreeNode接口

TreeNode用来表示每个树节点的抽象,即需要生成树的对象需要实现此接口。


/*** @date: 2022/7/25* @FileName: TreeNode* @author: Yan* @Des: 树节点父类,所有需要使用TreeUtils工具类形成树形结构等操作的节点都需要实现该接口*/
public interface TreeNode<T, RC, LC> {/*** 获取树结点id* @return*/T getTreeNodeId();/*** 获取该节点的父节点id* @return*/T getParentId();/*** 判断该节点是否为根节点,默认判定* @Des 可以用于简单树的组件* @return*/boolean isRoot();/*** 自定义父结点的判定规则* @param rootCondition* @return*/boolean isRoot(RC rootCondition);/*** 自定义子节点(叶子结点)的判定规则* @param leafCondition* @return*/boolean isChildren(LC leafCondition);/*** 判断是否有子节点* @return*/boolean hasChild();/*** 设置结点的子节点列表* @param children*/void setChildren(List<? extends TreeNode<T, RC, LC>> children);/*** 获取所有子节点* @return*/List<? extends TreeNode<T, RC, LC>> getChildren();/*** 获取树的深度* @return*/Integer getLevel();/*** 设置树的深度*/void setLevel(Integer level);
}
复制代码

泛型说明:

T 主要定义返回值的类型

RC(rootCondition) 主要是定义根节点 (也就是父节点) 的自定义判定规则需要用到的参数类型

LC(leafCondition) 主要是定义叶子结点(也就是子节点)的自定义判定规则需要用到的参数类型

2.2 TreeUtil工具类


回调函数——函数式接口

用于遍历树作自定义操作使用

/*** @date: 2022/7/28* @FileName: Handle* @author: Yan* @Des:    定义一个函数式接口*/
@FunctionalInterface
public interface FunctionHandle <N, K, V> {void callback(N node, Map<K,V> result);
}
复制代码

泛型说明:

N 表示传入回调函数的结点参数类型

K 表示结果集的key

V 表示结果集的value


非Lambda表达式写法(⭐适用于所有版本)

/*** @date: 2022/7/25* @FileName: TreeUtils* @author: Yan* @Des: TreeUtils用来生成树形结构,以及获取所有叶子节点等操作*/
public class TreeUtils {/*** 根据所有树节点列表,按默认条件生成含有所有树形结构的列表* 主要用于组建简单树形结构* @param allData 树形节点列表* @param <E>   节点类型* @return 树形结构列表*/public static <E extends TreeNode<?,?,?>> List<E> generateTrees(List<E> allData) {List<E> roots = new ArrayList<>();// 使用迭代器操作list元素for (Iterator<E> iterator = allData.iterator(); iterator.hasNext(); ) {E node = iterator.next();if (node.isRoot()) {node.setLevel(0);// 获取所有的根节点roots.add(node);// 从所有节点列表中删除该节点,以免后续重复遍历该节点iterator.remove();}}roots.forEach(r -> {getChildren(r, allData);});return roots;}/*** 根据所有树节点列表,按自定义条件---->获取符合条件的父节点* @param allData   所有树形结构结点* @param rootCondition 父节点的判定规则* @param <E> 结点类型* @param <RC> 父节点自定义判定方法的参数类型* @return 按自定义条件获取符合条件的父节点*/public static <E extends TreeNode<?, RC, ?>, RC> List<E> generateTrees(List<E> allData, RC rootCondition) {List<E> roots = new ArrayList<>();// 使用迭代器操作list元素for (Iterator<E> iterator = allData.iterator(); iterator.hasNext(); ) {E node = iterator.next();// 按条件筛选根节点if (node.isRoot(rootCondition)) {node.setLevel(0);// 获取所有的根节点roots.add(node);// 从所有节点列表中删除该节点,以免后续重复遍历该节点iterator.remove();}}// 返回按条件查询到的父节点return roots;}/*** 给父节点填充叶子结点* @param parent    父节点* @param nodes 所有结点集合* @param <T>   父节点的对象类型* @param <E>   集合的类型*/@SuppressWarnings("all")public static <T extends TreeNode, E extends TreeNode> void getChildren(T parent, List<E> allData) {List<E> children = new ArrayList<>();for (Iterator<E> ite = allData.iterator(); ite.hasNext(); ) {E node = ite.next();// 找出与当前父节点关联的叶子结点if (Objects.equals(node.getParentId(), parent.getTreeNodeId())) {node.setLevel(parent.getLevel() + 1);children.add(node);// 从所有节点列表中删除该节点,以免后续重复遍历该节点ite.remove();}}System.out.println(children);// 如果孩子为空,则直接返回,否则继续递归设置孩子的孩子if (children.isEmpty()) {return;}parent.setChildren(children);// 继续递归叶子结点的遍历子节点children.forEach(m -> {// 递归设置子节点getChildren(m, allData);});}/*** 按照自定义的规则,给父节点填充叶子结点* @param parent    父节点* @param allData   所有树形结构结点* @param leafConfition 叶子结点的自定义判定规则的参数类型* @param <T>   父节点对象类型* @param <E>   集合类型* @param <LC>  参数类型*/@SuppressWarnings("all")public static <T extends TreeNode, E extends TreeNode<?, ?, LC>, LC> void getChildren(T parent, List<E> allData, LC leafConfition) {List<E> children = new ArrayList<>();Object parentId = parent.getTreeNodeId();for (Iterator<E> ite = allData.iterator(); ite.hasNext(); ) {E node = ite.next();// 按自定义条件筛选子节点,null则表示没有自定义条件if (Objects.isNull(leafConfition) || node.isChildren(leafConfition)){// 找出与当前父节点关联的叶子结点if (Objects.equals(node.getParentId(), parentId)) {node.setLevel(parent.getLevel() + 1);children.add(node);// 从所有节点列表中删除该节点,以免后续重复遍历该节点ite.remove();}}}// 如果孩子为空,则直接返回,否则继续递归设置孩子的孩子if (children.isEmpty()) {return;}parent.setChildren(children);// 继续递归叶子结点的遍历子节点children.forEach(m -> {// 递归设置子节点getChildren(m, allData);});}/*** 根据获取特定结点的子树,通过isParent来判断是否需要父节点* @param root  根节点(子节点)* @param allData   全部结点数据* @param isParent  是否需要父节点* @param <T>   结点的对象类型* @return*/public static <T extends TreeNode<?, ?, ?>> List getTreeByNode(T root, List<T> allData, boolean isParent){List<T> tree = new ArrayList<>();if (isParent){root.setLevel(0);tree.add(root);// 包含父节点// 填充叶子结点tree.forEach(child -> getChildren(child, allData));return tree;} else {// 不含父节点// 填充叶子结点getChildren(root, allData);return root.getChildren();}}public static void main(String[] args) {Boolean test = null;}/*** 根据获取特定结点的子树,通过isParent来判断是否需要父节点,通过leafConfition来按照自定义的规则给父节点填充子节点* @param root  根节点(子节点)* @param allData   全部结点数据* @param isParent  是否需要父节点* @param leafCondition 定义叶子结点的判定参数* @param <T>   结点的对象类型* @param <LC>  自定义叶子结点的判定参数的对象类型* @return*/public static <T extends TreeNode<?, ?, LC>, LC> List getTreeByNode(T root, List<T> allData, boolean isParent, LC leafCondition){List<T> tree = new ArrayList<>();if (isParent){root.setLevel(0);tree.add(root);// 包含父节点// 填充叶子结点tree.forEach(child -> getChildren(child, allData, leafCondition));return tree;} else {// 不含父节点// 填充叶子结点getChildren(root, allData, leafCondition);return root.getChildren();}}/*** 根据结点id获取特定结点的子树,通过isParent来判断是否需要父节点* @param rootId    结点id* @param allData   所有结点数据* @param isParent  是否需要父结点* @param <T>   结点id的对象类型* @param <E>   集合的类型* @return*/public static <T, E extends TreeNode<?, ?, ?>> List getTreeByNodeId(T rootId, List<E> allData, boolean isParent){List<E> tree = new ArrayList<>();E root = null;for (Iterator<E> iterator = allData.iterator(); iterator.hasNext(); ){E node = iterator.next();if (rootId.equals(node.getTreeNodeId())){node.setLevel(0);tree.add(node);root = node;iterator.remove();}}System.out.println(root);// 是否需要父节点if (isParent){// 需要父节点// 填充叶子结点tree.forEach(child -> getChildren(child, allData));return tree;} else {// 不需要父节点// 填充叶子结点getChildren(root, allData);return root.getChildren();}}/*** 根据结点id获取特定结点的子树,通过isParent来判断是否需要父节点* @param rootId    结点id* @param allData   所有结点数据* @param isParent  是否需要父结点* @param leafCondition 定义叶子结点的判定参数* @param <T>   结点id的对象类型* @param <E>   集合的类型* @param <LC>  自定义叶子结点的判定参数的对象类型* @return*/public static <T, E extends TreeNode<?, ?, LC>, LC> List getTreeByNodeId(T rootId, List<E> allData, boolean isParent, LC leafCondition){List<E> tree = new ArrayList<>();E root = null;for (Iterator<E> iterator = allData.iterator(); iterator.hasNext(); ){E node = iterator.next();if (Objects.equals(rootId, node.getTreeNodeId())){root.setLevel(0);tree.add(node);root = node;iterator.remove();}}System.out.println(root);// 是否需要父节点if (isParent){// 需要父节点// 填充叶子结点tree.forEach(child -> getChildren(child, allData, leafCondition));return tree;} else {// 不需要父节点// 填充叶子结点getChildren(root, allData, leafCondition);return root.getChildren();}}/*** 遍历树型结构,并且根据回调函数执行相应的操作处理* @param tree  树* @param handle    回调函数* @param <E>   集合类型* @param <K>   结果集的key* @param <V>   结果集的value* @return  返回一个结果集的map*/public static <E extends TreeNode<?,?,?>, K, V> Map<K,V> traverseTree(List<E> tree, FunctionHandle<E, K, V> handle){Map<K, V> resultMap = new HashMap<>();for (Iterator<E> iterator = tree.iterator(); iterator.hasNext(); ){E node = iterator.next();if (handle != null){handle.callback(node, resultMap);}if (node.hasChild()){recursiveTree(node.getChildren(), resultMap, handle);}}return resultMap;}/*** 递归遍历子树,获取相应的处理结果* @param children  子树集合* @param resultMap 结果集* @param handle    回调函数* @param <E>   集合类型*/public static <E extends TreeNode<?,?,?>> void recursiveTree(List<E> children, Map resultMap, FunctionHandle handle){for (Iterator<E> iterator = children.iterator(); iterator.hasNext(); ){E child = iterator.next();if (handle != null){handle.callback(child, resultMap);}if (child.hasChild()){recursiveTree(child.getChildren(), resultMap, handle);}}}}
复制代码

3. 使用工具

① 定义一个类,实现TreeNode接口

这里以树形菜单Menu为例

根据Menu类创建一个属性一样的的MenuVo类,并且实现TreeNode接口,通过对TreeNode接口方法的实现来让MenuVo变成一个树形节点。

/*** @date: 2022/7/26* @FileName: MenuVo* @author: Yan* @Des:*/
@Data
public class MenuVo implements TreeNode<Integer, Boolean, Integer> {private Integer id;private String name;private Integer parentId;private Integer Level;private List<MenuVo> children;public MenuVo(Integer id, String name, Integer parentId) {this.id = id;this.name = name;this.parentId = parentId;}@Overridepublic Integer getTreeNodeId() {return this.id;}@Overridepublic Integer getParentId() {return this.parentId;}@Overridepublic boolean isRoot() {// 默认判定return Objects.equals(this.parentId, 0);}@Overridepublic boolean isRoot(Boolean rootCondition) {// 自定义的父节点判定规则if (rootCondition){return Objects.equals(this.id, 14);} else {// 都不符合就走默认判定条件return isRoot();}}@Overridepublic boolean isChildren(Integer leafCondition) {// 自定义结点判定规则// 这里自定义规则当传入的参数等于1的时候 ——> 要销售部,只要销售部的带有“a”的部门名作为子树节点if (leafCondition.equals(1)){if (this.name.contains("a")){return true;} else {return false;}} else {// 都不符合就表示该节点不是自定义规则中要的结点return false;}}@Overridepublic boolean hasChild() {return !Objects.isNull(this.children);}@Overridepublic void setChildren(List children) {this.children = children;}@Overridepublic List<MenuVo> getChildren(){return this.children;}}
复制代码

② 数据准备

    // 获取测试数据public static List<MenuVo> getMenusVo(){List<MenuVo> menus = Arrays.asList(new MenuVo(1, "A公司", 0),new MenuVo(2, "a销售部", 14),new MenuVo(3, "财税部", 1),new MenuVo(4, "商务部", 1),new MenuVo(5, "综合部", 1),new MenuVo(6, "a销售1部", 2),new MenuVo(7, "a销售2部", 2),new MenuVo(8, "a销售3部", 2),new MenuVo(9, "a销售4部", 2),new MenuVo(10, "b销售部", 14),new MenuVo(11, "b销售1部", 10),new MenuVo(12, "b销售2部", 10),new MenuVo(13, "人事部", 1),new MenuVo(14, "销售部", 1));return menus;}
复制代码

③ 按照业务需求进行使用

创建一个Controller,发送相应的请求,查看返回结果

    @GetMapping("/test")public List<Object> getTree2(){List<MenuVo> menusVoList = new ArrayList<>(Arrays.asList(new MenuVo(1, "A公司", 0),new MenuVo(2, "a销售部", 14),new MenuVo(3, "财税部", 1),new MenuVo(4, "商务部", 1),new MenuVo(5, "综合部", 1),new MenuVo(6, "a销售1部", 2),new MenuVo(7, "a销售2部", 2),new MenuVo(8, "a销售3部", 2),new MenuVo(9, "a销售4部", 2),new MenuVo(10, "b销售部", 14),new MenuVo(11, "b销售1部", 10),new MenuVo(12, "b销售2部", 10),new MenuVo(13, "人事部", 1),new MenuVo(14, "销售部", 1)));// 组装树形结构List<MenuVo> menuVotree = TreeUtils.generateTrees(menusVoList);// 遍历树,回调处理得出相应结果Map<Integer, String> result = TreeUtils.traverseTree(menuVotree, (treeNode, resultMap) -> {if (treeNode.getName().contains("a")) {resultMap.put(treeNode.getTreeNodeId(), treeNode.getName());}});List<Object> objects = new ArrayList<>();objects.add(result);// 按自定义条件组装树
//        List<MenuVo> menuVoTreeByCondition = TreeUtils.generateTrees(menusVoList,true);
//        menuVoTreeByCondition.forEach(node -> TreeUtils.getChildren(node, menusVoList, 1));// 获取对应结点的子树,true表示要留有父节点,false表示不留不父节点
//        List<MenuVo> childTreeByParent = TreeUtils.getTreeByNode(new MenuVo(14, "销售部", 1), menusVoList, true);// 通过父节点id获取其子树,true表示要留有父节点,false表示不留不父节点
//        List<MenuVo> treeNodesList = TreeUtils.getTreeByNodeId(14, menusVoList, true);return objects;}
复制代码

4. TreeUtil工具类的api介绍

① 简单组装树

按照默认条件,简单将所有的结点组装成树

// 组装树形结构
List<MenuVo> menuVotree = TreeUtils.generateTrees(menusVoList);
复制代码

运行结果

② 按自定义条件组装树

按照自定义条件,将结点列表组装成树

获取id为14,切子部门中名字带有“a”的子部门

// 按自定义条件组装树
// 步骤① 按照自定义的规则筛选父节点
List<MenuVo> menuVoTreeByCondition = TreeUtils.generateTrees(menusVoList,true);
// 步骤② 将得到的父节点按自定义的子节点判定规则填充子节点
menuVoTreeByCondition.forEach(node -> TreeUtils.getChildren(node, menusVoList, 1));
复制代码
  • 步骤① 这里可以看NodeTree的实现类的IsRoot方法说明,我为了测试,自定义的条件为要id=14的结点为父节点
  • 步骤② 这里按条件给父节点填充子节点的判定规则是自定义的,在isChildren中有相应的判定,我这里为了测试:自定义规则当传入的参数等于1的时候 ——> 要销售部,只要销售部的带有“a”的部门名作为子树节点

运行结果

③ 按结点获取对应子树集合

根据结点获取子树,true表示要留有父节点,false表示不留不父节点

// 根据树形结构,获取对应树节点的子节点List<MenuVo> childTreeByParent = TreeUtils.getTreeByNode(new MenuVo(14, "销售部", 1), menusVoList, true);
复制代码

运行结果

④ 按结点id获取对应的子树集合

通过父节点id获取其子树,true表示要留有父节点,false表示不留不父节点

// 通过父节点id获取其子树,true表示要留有父节点,false表示不留不父节点
List<MenuVo> treeNodesList = TreeUtils.getTreeByNodeId(14, menus, true);
复制代码

运行结果

⑤ 遍历树的同时做自定义操作

遍历树型结构的同时,做相应的自定义操作

找出部门名字含有或者b的部门名,存入结果集并返回

// 组装树形结构
List<MenuVo> menuVotree = TreeUtils.generateTrees(menusVoList);// 遍历树,回调处理得出相应结果
Map<Integer, String> result = TreeUtils.traverseTree(menuVotree, (treeNode, resultMap) -> {// 找出部门名字含有或者b的部门名,存入结果集并返回if (treeNode.getName().contains("b")) {resultMap.put(treeNode.getTreeNodeId(), treeNode.getName());}
});
复制代码

运行结果

统计各个部门的子部门数

// 组装树形结构
List<MenuVo> menuVotree = TreeUtils.generateTrees(menusVoList);// 遍历树,回调处理得出相应结果
Map<String, Integer> result = TreeUtils.traverseTree(menuVotree, (treeNode, resultMap) -> {if (!treeNode.hasChild()){if (treeNode.getName().contains("销售")){resultMap.put("销售部", resultMap.get("销售部") == null ? 1 : resultMap.get("销售部") + 1);}if (treeNode.getName().contains("财税")){resultMap.put("财税部", resultMap.get("财税部") == null ? 1 : resultMap.get("财税部") + 1);}if (treeNode.getName().contains("商务")){resultMap.put("商务部", resultMap.get("商务部") == null ? 1 : resultMap.get("商务部") + 1);}if (treeNode.getName().contains("综合")){resultMap.put("综合部", resultMap.get("综合部") == null ? 1 : resultMap.get("综合部") + 1);}}
});
复制代码

运行结果

摸鱼三天,我写了一个通用的组建树TreeUtil工具相关推荐

  1. 为了去重复,写了一个通用的比较容器类,可以用在需要比较的地方,且支持Lamda表达式...

    为了去重复,写了一个通用的比较容器类,可以用在需要比较的地方,且支持Lamda表达式,代码如下: public class DataComparer<T>:IEqualityCompare ...

  2. 一个通用的Java正则匹配工具(检查邮箱名、电话号码、用户密码、邮政编码等合法性的工具类)

    一个通用的Java正则匹配工具(检查邮箱名.电话号码.用户密码.邮政编码等合法性的工具类). 一个通用且常用的Java正则匹配工具,用以检查邮箱名.电话号码.用户密码.邮政编码等合法性. import ...

  3. 上班聊天,摸鱼神器,手写一款即时通讯工具(附源码!!!)

    文章目录 即时通讯工具 客户端 服务端 1.链接 2.登录 3.其他方法 3.1.读取客户端的消息 3.2.给客户端发送消息 3.3.日志记录 3.4.工具集合 3.5.ChatSocket 服务端部 ...

  4. JS_小工具_自己写了一个画像素画的小工具

    自己抽空做了一个画像素画的小工具,界面比较简单,但也可以画着玩.呵呵. 有意思的地方在于可以把画的内容保存为一个数组,可以很方便地还原成图案... 主要的JS代码: //画板表格 var iconTa ...

  5. 写了一个管理桌面图标的小工具

    有时想想自己是个程序员也挺幸福的,起码对电脑系统有些小小的需求自己就可以搞定. 平时我喜欢把常用的工具都堆在桌面上,用的时候只要 win+D 就能看到省了鼠标点来点去,日积月累桌面上已经被各种图标给摆 ...

  6. 自己手写的一个关于中英文切换时间的工具类

    自己写项目的时候,由于有中英文版本切换的需要,所以针对各单项时间显示的TextView也需要显示不同的时间.我做的是相簿的部分,有把相机每天拍摄的照片按照时间分类,然后每一天的照片分别展示成一个列表的 ...

  7. 曝光,程序员的 10 个摸鱼神器

    问:程序员该不该上班摸鱼? 答:认真上班是劳动换取报酬,上班摸鱼才是从老板那赚钱. 曝光,程序员的 10 个摸鱼神器 摸鱼一时爽,一直摸一直爽 方案一:实物摸鱼 方案二:命令行斗地主 方案三:假装系统 ...

  8. 程序员如何摸鱼, 10 个摸鱼神器分享给大家

    问:程序员该不该上班摸鱼? 答:认真上班是劳动换取报酬,上班摸鱼才是从老板那赚钱. 曝光,程序员的 10 个摸鱼神器 摸鱼一时爽,一直摸一直爽 方案一:实物摸鱼 方案二:命令行斗地主 方案三:假装系统 ...

  9. 义正词严的摸鱼 | 摸鱼系列

    一时兴起,想写一些自言自语的东西,原有的系列先暂停一周更新,我努力重构一下笔者的人设试图挽回持续下滑的阅读量. 摸鱼系列,主要写一些自言自语的,轻松的东西. 大家都上班摸鱼的经历,而且有各种各样好玩的 ...

最新文章

  1. 面试官:react和vue有什么区别吗?
  2. shell字符串操作
  3. 揭秘视频千倍压缩背后的技术原理之预测技术
  4. Java 面向对象语言基础
  5. python裁剪图片并保存_python – 如何从图像中剪切轮廓并将其保存到新文件中
  6. 特斯拉已撤回德国电池工厂建厂补贴申请 原有望获得近13亿美元
  7. Visual Studio Code 1.44 发布
  8. 团体程序设计天梯赛-练习集-L1-036. A乘以B
  9. 简易新闻客户端android
  10. 三维激光雷达点云处理分类
  11. 黑客为什么不攻击网贷平台?
  12. VCSA5.5升级6.5u1提示VUM出错和证书错误的解决办法
  13. 我所钟爱的电影之一-文艺片
  14. PhpSpreadsheet中文文档 | 简介
  15. 身份证验证判断、身份证正则表达式、15位、18位身份证验证
  16. Tcp三次握手、四次握手、数据传输
  17. meta http-equiv=“X-UA-Compatible“ content=““ 的作用
  18. 论随机数AC大法(1)
  19. java流程控制原理与方法_1.从本质上看,计算机控制系统的工作原理可归纳为三个步骤,以下不属这三个步骤的是 ( )。_学小易找答案...
  20. Computer Organization and Design The Hardware/Software Interface: RISC-V Edition

热门文章

  1. 程序设计阶段性总结报告二
  2. MATLAB-矩阵的输入
  3. wrcoef2函数_二维离散小波变换函数使用总结
  4. php取FBOX数据,如何实现如下功能
  5. 绕不开的TCP之三次握手
  6. 为什么国内搜索不到国外服务器网站?
  7. windows删除大量文件的优秀方式
  8. 浅析LruCache原理
  9. English digest
  10. Qt编程(一) Qt框架简介