springboot框架使用Netty依赖中解码器的作用及实现详解

news2025/2/25 18:29:57

在项目开发 有需求 需要跟硬件通信
也没有mqtt 作为桥接 也不能http 请求 api 所以也不能 json字符串这么爽传输

所以要用tcp 请求 进行数据交互 数据还是16进制的 写法 有帧头 什么的
对于这种物联网的这种对接
我的理解就是 我们做的工作就像翻译
把这些看不懂的 字节流 变成 java 认识的数据

就了解了一下 netty 这个依赖
在springboot 项目种使用这个依赖进行 连接

但是这个依赖 如果不熟悉 使用不当 有可能有内存溢出的风险
所以大家如果自己用这个netty 依赖 做物联网的调用
要小心 最好能找一下 物联网中间件 基于netty封装的

这是我后续 找的一个中间件 也不算很成熟 如果大家有更好用的 欢迎留言 一起分享

http://www.iteaj.com/#/course

但是我发现还是要根据 对接的物联网硬件文档 写自定义解码器 或者编码器
这篇文章主要分享一下 我了解到的 netty 下的 四个解码器 使用方案

概念:
拆包和沾包
是典型的拆包和沾包问题,俗话说就是两端通信,一端发送一端接收,接收的那一端怎么知道是否已经完整的接收了数据?

假设服务端连续发送了两条消息:hello world! / hello client!

由于客户端不知道怎么才算一条消息,怎么才算两条消息,所以读取会有以下几种情况:

1.分两次读取消息,第一次是hello world!,第二次是hello client! 这是正常情况

2.一次就读取完成,hello world!hello client! 这种情况就叫沾包

3.分两次读取消息,第一次是hello ,第二次是world!hello client! 这第一次读取就是拆包,第二次就是沾包

总之就是读取到的信息不完整就是拆包,读取到的信息有额外多的信息就是沾包

解码器就可以来解决这个问题

依赖引入就不说了
先分享代码

netty配置类

package com.netty.server.tpcServer;

import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

import io.netty.handler.codec.LengthFieldBasedFrameDecoder;

import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;


/**
 * User:Json
 * Date: 2024/4/12
 **/
@Component
@Slf4j
public class NettyServer {

     private static final Logger logger = LoggerFactory.getLogger(NettyServer.class);

    // 服务端NIO线程组
    private final EventLoopGroup bossGroup = new NioEventLoopGroup();
    private final EventLoopGroup workGroup = new NioEventLoopGroup();

    public ChannelFuture start(String host, int port) {
        ChannelFuture channelFuture = null;
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            // 1024 就是规定传入的 字节长度的大小
                            // 对于字节 一个字符串占多少字节 int 占多多少字节 long 占多多少字节 如果明白这个 应该很好理解
                            //第一种:LineBasedFrameDecoder:传入的参数是消息最大长度,发送消息的大小必须小于设置值
                            // 行分隔符解码器(结尾根据 “\n” 作为结束标识)
                            //socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));
                            //第二种:DelimiterBasedFrameDecoder 自定义分割器解码器,结尾根据什么作为结束标识可以自定义 比如用 |
                           // socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, Unpooled.copiedBuffer("|".getBytes())));
                           // 第三种: FixedLengthFrameDecoder // 固定长度解码器,发送的消息需要定长
                          //  socketChannel.pipeline().addLast(new FixedLengthFrameDecoder(7));

                            // 上面三个解码器明显的看到不够灵活  所以还有第四种
                            //第四种:LengthFieldBasedFrameDecoder:基于长度的自定义解码器,比较灵活 这个
                            // 这个 解码器 一共有 5个参数
//                            maxFrameLength:最大帧长度。也就是可以接收的数据的最大长度。如果超过,此次数据会被丢弃
//                            lengthFieldOffset:长度域偏移量。存储数据长度的一个偏移量
//                            lengthFieldLength:长度域字节数。存储数据长度的一个大小
//                            lengthAdjustment:数据长度修正。因为长度既可以代表data的长度,也可以是整个消息的长度
//                            initialBytesToStrip:跳过的字节数。可以选择舍弃一部分数据
                            // 可以理解为 我们给数据定义传输规则 就像http请求 定义规则 比如 请求头 数据结构 json 等等
                            // 所以我们作为接收数据的服务端 可以在开发接收数据的时候 定义规则 然后让客户端按照规则来传输数据
                            // 比如 发送一个消息,我们定义的结构为:消息头+数据长度+数据
                            // 我们怎么定义规则呢 比如:
                            // 整体字节为 15个字节
                            // 要发送的数据 "Message" 长度为 7个字节
                            //我们规定 maxFrameLength :1024 接收的最大数据,
                            // lengthFieldOffset:长度域偏移量 一般用来定义消息头 4个字节、
                            // lengthFieldLength:长度域字节数  用于定义数据长度(length)的【值】的大小 4个字节
                            // lengthAdjustment : 0 数据长度修正
                            // 假设我设置的数据长度是20,代表了整个消息体的长度,但是我数据却只有12个字节,这往后读20个字节无疑是错的,所以我们需要修正,怎么修正? 减8 就行
                            // 所以如果你需要修正你的 数据长度,那么lengthAdjustment就是用来修正的。
                            // initialBytesToStrip :8 ,如果在我们业务层只需要 消息体 像消息头 和 数据长度都不需要 那么 initialBytesToStrip就是用来跳过的字节数。
                            // 就比如 消息头规定 4个字节 数据长度 4个字节 所以我们如果只要 消息体里的内容 就需要跳过 8个字节 所以设置8 即可

                            // 获取全部数据
                          //  socketChannel.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024,4,4,0,0));

                            // 只获取消息体里的数据
                            socketChannel.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024,4,4,0,8));


                            // 自定义服务处理 所以再进行服务处理之前 需要先经历 解码器   既然有解码器 也有编码器
                            // 如果我们作为服务端 那就需要编码器 如果我们作为客户端 那就需要解码器
                            // 解码器主要用于 将接收到的字节数据转换成有意义的业务对象。比如java 认识的 实体类对象
                            // 在网络编程中,数据在传输过程中通常是以字节数组的形式进行传递的,
                            // 而业务处理通常需要对这些字节数组进行解析,转换为具体的业务对象。解码器正是完成这一工作的组件
                            // 而且解码器 还需要 处理拆包和沾包 等  所以我们在使用 tcp 进行通信的时候 到业务层处理 的时候 需要先经过解码器
                            socketChannel.pipeline().addLast(new ServerHandler());
                        }

                    });
            // 绑定端口并同步等待
            channelFuture = bootstrap.bind(host, port).sync();
            log.info("======Start Up Success!=========");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return channelFuture;
    }

    public void close() {
        workGroup.shutdownGracefully();
        bossGroup.shutdownGracefully();
        log.info("======Shutdown Netty Server Success!=========");
    }



    //测试 main
    public static void main(String[] args) throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new StringEncoder());
                        }
                    });

            ChannelFuture channelFuture = bootstrap.connect("192.168.0.209", 17070).sync();
            Channel channel = channelFuture.channel();

            // 连续发送多条消息
            for (int i = 0; i < 100; i++) {
                //System.getProperty("line.separator") 获取系统换行符  测试一种情况
               // String message = "Message " + i + System.getProperty("line.separator");
               // String message = "Message " + i+"|";  //测试第二种情况
               // String message = "Message"; // 第三种 固定长度情况
              //  channel.writeAndFlush(message);
                //第四种 自定义解码器
                //[4个字节 + 4个字节 + 数据]
                // 我们传输数据 就要这样拼接数据 消息头 +数据长度+数据
                String message = "Message"+i;
                //消息头 4个字节
                ByteBuf byteBuf = Unpooled.buffer();
                byteBuf.writeInt(102); //【lengthFieldOffset】  4个字节
                // 102 测试随便写的 只要是占4个字节的数字就行 为啥写个102就4个字节 因为102 是int类型 int类型的字节数是4个字节
                // 这个应该是 数据类型基础  int string long  等等 占几个字节的问题

                //数据长度 4个字节  长度也是 int型 所以跟消息头 一样 所以在设置解码器长度参数的时候 【lengthFieldLength】 4个字节就够用
                byteBuf.writeInt(message.getBytes().length);

                //发送的数据
                byteBuf.writeBytes(Unpooled.copiedBuffer(message, CharsetUtil.UTF_8));

                channel.writeAndFlush(byteBuf);
               // Thread.sleep(100); // 可能需要调整间隔时间
            }

            channel.closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }
}

回调类

package com.netty.server.tpcServer;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * User:Json
 * Date: 2024/4/12
 **/
public class ServerHandler extends ChannelInboundHandlerAdapter {

    /**
     * 客户端数据到来时触发
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf) msg;
        //第一种到第三种解码器测试
//        System.out.println("client request: " + buf.toString(CharsetUtil.UTF_8)+"======");
//        SimpleDateFormat sf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
//        String callback = sf.format(new Date());
//        ctx.write(Unpooled.copiedBuffer(callback.getBytes()));

        //第四种 自定义解码器测试

        System.out.println("========================");
       // 获取全部数据
//        System.out.println("消息头:"+buf.readInt());
//        int i = buf.readInt();
//        System.out.println("数据长度:"+i);
//        System.out.println("消息体:"+buf.readBytes(i).toString(CharsetUtil.UTF_8));

        //只获取消息体里的数据
        System.out.println("只获取消息体: "+buf.toString(CharsetUtil.UTF_8));
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

        // 将发送缓冲区的消息全部写到SocketChannel中
        ctx.flush();
    }

    /**
     * 发生异常时触发
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println(cause.getMessage());
        cause.printStackTrace();
        // 释放与ChannelHandlerContext相关联的资源
        ctx.close();
    }
}


springboot 启动时运行

package com.netty.server.init;

import com.netty.server.tpcServer.NettyServer;
import io.netty.channel.ChannelFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * User:Json
 * Date: 2024/4/12
 **/
@Component
@Slf4j
@Order(2)
public class NettyServerInit implements CommandLineRunner {

    @Resource
    private NettyServer nettyServer;

    // 这个 线程  后续优化    比如关闭 等等
    @Override
    public void run(String... args) throws Exception {
        // 开启服务
        ChannelFuture future = nettyServer.start("192.168.0.209", 17070);
        // 在JVM销毁前关闭服务
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                nettyServer.close();
            }
        });
        future.channel().closeFuture().sync();
    }
}

核心测试代码 在 NettyServer 类里

下面逐个分享一下解码器的 意思 :
第一种:

new LineBasedFrameDecoder(1024)

1024 就是规定传入的 字节长度的大小
对于字节 一个字符串占多少字节 int 占多多少字节 long 占多多少字节 如果明白这个 应该很好理解
LineBasedFrameDecoder:传入的参数是消息最大长度,发送消息的大小必须小于设置值
行分隔符解码器(结尾根据 “\n” 作为结束标识)

第二种:

new DelimiterBasedFrameDecoder(1024, Unpooled.copiedBuffer("|".getBytes()))

DelimiterBasedFrameDecoder 自定义分割器解码器,结尾根据什么作为结束标识可以自定义 比如用 |

第三种:

new FixedLengthFrameDecoder(7)

// 固定长度解码器,发送的消息需要定长

第四种:重点

new LengthFieldBasedFrameDecoder(1024,4,4,0,0)

第四种:LengthFieldBasedFrameDecoder:基于长度的自定义解码器,比较灵活 这个
这个 解码器 一共有 5个参数
maxFrameLength:最大帧长度。也就是可以接收的数据的最大长度。如果超过,此次数据会被丢弃
lengthFieldOffset:长度域偏移量。存储数据长度的一个偏移量
lengthFieldLength:长度域字节数。存储数据长度的一个大小
lengthAdjustment:数据长度修正。因为长度既可以代表data的长度,也可以是整个消息的长度
initialBytesToStrip:跳过的字节数。可以选择舍弃一部分数据
可以理解为 我们给数据定义传输规则 就像http请求 定义规则 比如 请求头 数据结构 json 等等
所以我们作为接收数据的服务端 可以在开发接收数据的时候 定义规则 然后让客户端按照规则来传输数据
比如 发送一个消息,我们定义的结构为:消息头+数据长度+数据

对于字节 一个字符串占多少字节 int 占多多少字节 long 占多多少字节 如果明白这个 应该很好理解

我们怎么定义规则呢 比如:
整体字节为 15个字节
要发送的数据 “Message” 长度为 7个字节
我们规定 maxFrameLength :1024 接收的最大数据,
lengthFieldOffset:长度域偏移量 一般用来定义消息头 4个字节、
lengthFieldLength:长度域字节数 用于定义数据长度(length)的【值】的大小 4个字节
lengthAdjustment : 0 数据长度修正
假设我设置的数据长度是20,代表了整个消息体的长度,但是我数据却只有12个字节,这往后读20个字节无疑是错的,所以我们需要修正,怎么修正? 减8 就行
所以如果你需要修正你的 数据长度,那么lengthAdjustment就是用来修正的。
initialBytesToStrip :8 ,如果在我们业务层只需要 消息体 像消息头 和 数据长度都不需要 那么 initialBytesToStrip就是用来跳过的字节数。
就比如 消息头规定 4个字节 数据长度 4个字节 所以我们如果只要 消息体里的内容 就需要跳过 8个字节 所以设置8 即可
结合发送端和 接收数据端 一个例子 整体

接收数据端的 定义

 socketChannel.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024,4,4,0,8));

发送端

     //第四种 自定义解码器
      //[4个字节 + 4个字节 + 数据]
      // 我们传输数据 就要这样拼接数据 消息头 +数据长度+数据
      String message = "Message"+i;
      //消息头 4个字节
     ByteBuf byteBuf = Unpooled.buffer();
 byteBuf.writeInt(102); //【lengthFieldOffset】  4个字节
 // 102 测试随便写的 只要是占4个字节的数字就行 为啥写个102就4个字节 因为102 是int类型 int类型的字节数是4个字节
      // 这个应该是 数据类型基础  int string long  等等 占几个字节的问题

//数据长度 4个字节  长度也是 int型 所以跟消息头 一样 所以在设置解码器长度参数的时候 【lengthFieldLength】 4个字节就够用
  byteBuf.writeInt(message.getBytes().length);

   //发送的数据
  byteBuf.writeBytes(Unpooled.copiedBuffer(message, CharsetUtil.UTF_8));

 channel.writeAndFlush(byteBuf);

测试结果
没有解码器的情况
在这里插入图片描述

有了之后:
在这里插入图片描述

补充: socketChannel.pipeline().addLast()

这里addLast() 可以添加很多 各种处理器

比如
解码器
编码器
编码器
日志处理器
超时处理器

等等 使用的时候 添加顺序也要注意一下

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1880810.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Pytest集成Allure生成测试报告

# 运行并输出报告在Report文件夹下 查看生成的allure报告 1. 生成allure报告&#xff1a;pycharm terminal中输入命令&#xff1a;产生报告文件夹 pytest -s --alluredir../report 2. pycharm terminal中输入命令&#xff1a;查看生成的allure报告 allure serve ../report …

spring boot初始化的几个总结

spring intializr File->New->Project 注意&#xff1a;Spring Initializer中 Java版本选择模块已经不支持1.8了。 Spring Boot 3.x要求 Java最低版本为17&#xff0c; 最新的SpringBoot版本已经要求Java22了 所以&#xff0c;你可以升级Java版本&#xff0c;使用Spri…

VMware虚拟机迁移:兼用性踩坑和复盘

文章目录 方法失败情况分析&#xff1a;参考文档 方法 虚拟机关机&#xff0c;整个文件夹压缩后拷贝到新机器中&#xff0c;开机启用即可 成功的情况&#xff1a; Mac (intel i5) -> Mac (intel i7)Mac (intel, MacOS - VMware Fusion) -> DELL (intel, Windows - VMw…

6月28日PolarDB开源社区长沙站,NineData联合创始人周振兴将带来《数据库DevOps最佳实践》主题分享

6月28日&#xff08;周五&#xff09;&#xff0c;PolarDB 开源社区将来到湖南长沙&#xff0c;与湖南的开发者朋友们一起进行数据库技术交流&#xff01;NineData 联合创始人周振兴受邀参加&#xff0c;并将带来《数据库 DevOps 最佳实践》的主题分享。 本次活动议程&#xff…

Jmeter下载、安装及配置

1 Jmeter介绍 Jmeter是进行负载测试的工具&#xff0c;可以在任何支持Java虚拟机环境的平台上运行&#xff0c;比如Windows、Linux、Mac。 Jmeter模拟一组用户向目标服务器发送请求&#xff0c;并统计目标服务器的性能信息&#xff0c;比如CPU、memory usage。 2 Jmeter下载 …

如何利用AI生成可视化图表(统计图、流程图、思维导图……)免代码一键绘制图表

由于目前的AI生成图表工具存在以下几个方面的问题&#xff1a; 大多AI图表平台是纯英文&#xff0c;对国内用户来说不够友好&#xff1b;部分平台在生成图表前仍需选择图表类型、配置项&#xff0c;操作繁琐&#xff1b;他们仍需一份规整的数据表格&#xff0c;需要人为对数据…

C语言 | Leetcode C++题解之第199题二叉树的右视图

题目&#xff1a; 题解&#xff1a; #define MAX_NODE_NUM 100 int* rightSideView(struct TreeNode* root, int* returnSize){if (root NULL) {*returnSize 0;return NULL;}int *res (int *)malloc(sizeof(int) * MAX_NODE_NUM);int cnt 0;struct TreeNode **record (st…

PCIe物理层_CTLE(continuous time linear equalizer)

1.CTLE&#xff08;continuous time linear equalizer&#xff09; 的作用 信号在介质的传输过程中存在趋肤效应(skin effiect)和能量损耗&#xff0c;在接收端数据会存在失真&#xff0c;并且呈现出低通特性。什么意思呢&#xff1f;就是低频率的信号衰减幅度小&#xff0c…

【数据结构】(C语言):二叉搜索树

二叉搜索树&#xff1a; 树不是线性的&#xff0c;是层级结构。基本单位是节点&#xff0c;每个节点最多2个子节点。有序。每个节点&#xff0c;其左子节点都比它小&#xff0c;其右子节点都比它大。每个子树都是一个二叉搜索树。每个节点及其所有子节点形成子树。可以是空树。…

昇思25天学习打卡营第7天|网络构建

昇思25天学习打卡营第7天|网络构建 前言函数式自动微分函数与计算图微分函数与梯度计算Stop GradientAuxiliary data神经网络梯度计算 个人任务打卡&#xff08;读者请忽略&#xff09;个人理解与总结 前言 非常感谢华为昇思大模型平台和CSDN邀请体验昇思大模型&#xff01;从今…

绿色算力|暴雨发布浸没式液冷服务器

随着数字经济的飞速发展和AI创新应用的不断突破&#xff0c;算力规模持续增长&#xff0c;最新发布的《数字中国发展报告&#xff08;2023年&#xff09;》显示&#xff0c;2023年中国算力总规模达到230EFLOPS&#xff0c;居全球第二位。 服务器作为算力基础设施底座&#xff…

mac安装navicate

1.下载好之后点击安装包安装 2.一般情况下会提示安全性什么的,点击允许即可,然后会出现如下界面,点击安装即可. 3.点击打开 4.然后出现如下界面,点击Enter 5.将安装包拖入即可. 6.等待安装即可 7.安装完成后会在启动台看到Navicat16 的图标 8.然后打开软件界面如下:

Variables Reference for vscode

Predefined variables Visual Studio Code 支持在调试、任务配置文件以及一些特定的设置中使用变量替换。这些变量可以使用 ${variableName} 语法在 launch.json 和 tasks.json 文件的某些键和值字符串中使用。 Predefined variables Visual Studio Code 支持以下预定义变量…

[图解]分析模式高阶+课程讲解03物品模式

1 00:00:00,280 --> 00:00:03,440 下一个要探讨的模式是物品模式 2 00:00:04,310 --> 00:00:08,300 说是物品模式&#xff0c;实际上更多的说物品规格 3 00:00:09,210 --> 00:00:12,560 首先&#xff0c;我们要区分一下物品和物品规格的定义 4 00:00:14,440 -->…

205.Mit6.S081-实验二 system calls

Lab2:system calls 在上一个实验室中&#xff0c;您使用系统调用编写了一些实用程序。在本实验室中&#xff0c;您将向xv6添加一些新的系统调用&#xff0c;这将帮助您了解它们是如何工作的&#xff0c;并使您了解xv6内核的一些内部结构。您将在以后的实验室中添加更多系统调用…

人工智能期末复习笔记(更新中)

分类问题 分类&#xff1a;根据已知样本的某些特征&#xff0c;判断一个新的样本属于哪种已知的样本类 垃圾分类、图像分类 怎么解决分类问题 分类和回归的区别 1. 逻辑回归分类 用于解决分类问题的一种模型。根据数据特征或属性&#xff0c;计算其归属于某一类别 的概率P,…

[Cloud Networking] OSPF

OSPF 开放式最短路径优先&#xff08;Open Shortest Path First&#xff09;是一种动态路由协议&#xff0c;它属于链路状态路由协议&#xff0c;具有路由变化收敛速度快、无路由环路、支持变长子网掩码和汇总、层次区域划分等优点。 1 OSPF Area 为了适应大型网络&#xff0…

C程序设计谭浩强第五版

程序习题 第一章1、第5题2、第6题 第三章1、第2题2、第2题3、第3题4、第4题Tips 第一章 1、第5题 编写一个C程序,运行时输出以下图形: #include <stdio.h> int main() {for (int i 0; i < 4; i) // 输出4行循环控制{for (int j 0; j < i; j) //第几行就输出几…

leetcode-19-回溯-组合问题(剪枝、去重)

引自代码随想录 一、[77]组合 给定两个整数 n 和 k&#xff0c;返回 1 ... n 中所有可能的 k 个数的组合。 示例: 输入: n 4, k 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4]] 1、大致逻辑 k为树的深度&#xff0c;到叶子节点的路径即为一个结果 开始索引保证不…

一、Redis简介

一、Redis介绍与一般应用 1.1 基本了解 Redis全称Remote Dictionary Server(远程字典服务)&#xff0c; 是一个开源的高性能键值存储系统&#xff0c;通常用作数据库、缓存和消息代理。使用ANSI C语言编写遵守BSD协议&#xff0c;是一个高性能的Key-Value数据库提供了丰富的数…