zoukankan      html  css  js  c++  java
  • 第十四节:基于CSRedisCore程序集调用redis各个功能详解

    一. 整体介绍

    1. 说明

     CSRedis 是 redis.io 官方推荐库,支持 redis-trib集群、哨兵、私有分区与连接池管理技术,简易 RedisHelper 静态类, 它主要又两个程序集。

     (1).CSRedisCore:主库,实现对接redis各种功能

     (2).Caching.CSRedis:分布式缓存 CSRedisCore 实现 Microsoft.Extensions.Caching

    相关地址如下:

     GitHub地址:https://github.com/2881099/csredis

     Nuget地址:https://www.nuget.org/packages/CSRedisCore/

    2. 主要特点

     (1).调用方法的时候,可以使用CSRedisClient实例化的对象,也可以使用全局类RedisHelper(需要Initialization初始化一下)

    注:无论是CSRedisClient实例化的对象还是RedisHelper调用的方法和Redis自身cli指令名字完全相同,这一点非常好!!

     (2).官方推荐配置:CSRedisClient is singleton, RedisHelper static class is recommended (CSRedisClient推荐配置单例模式,RedisHelper推荐静态)

     (3).支持geo类型(>=3.2)、stream类型(>=5.0)

     (4).支持主从、哨兵、cluster

    二. 如何集成

    1. 控制台中使用

     前提:通过Nuget安装程序集:CSRedisCore

    (1).用法1:直接实例化CSRedisClient进行使用

    (2).用法2:初始化帮助类RedisHelper帮助类进行使用

    代码分享:

                {
                    //用法1-CSRedisClient实例化的对象(生产环境中把CSRedisClient写成单例类) 
                    var rds = new CSRedis.CSRedisClient("119.45.174.xx:6379,password=123456,defaultDatabase=0");
                    rds.Set("name1", "ypf");
                    var result1 = rds.Get("name1");
                    Console.WriteLine($"name1={result1}");
    
                    //用法2-RedisHelper帮助类
                    RedisHelper.Initialization(new CSRedis.CSRedisClient("119.45.174.xx:6379,password=123456,defaultDatabase=0"));
                    RedisHelper.Set("name2", "ypf2");
                    var result2 = RedisHelper.Get("name2");
                    Console.WriteLine($"name2={result2}");
                }

    ps:CSRedisClient链接字符串的说明

    2. CoreMVC中使用

     前提:通过Nuget安装程序集:CSRedisCore,同样有两种用法

    (1).在ConfigureSevice实例化CSRedisClient,并注册成单例模式;然后实例化RedisHelper帮助类

    (2).在action如果使用CSRedisClient,需要注入后再使用

    (3).再action如果使用RedisHelper帮助类,可以直接使用,不需要注册

    代码分享:

    配置文件

     "RedisStr": "119.45.174.xx:6379,password=123456,defaultDatabase=0"

    ConfigureService:

     public void ConfigureServices(IServiceCollection services)
     {
                //1. 集成Redis的两种方式
                {
                    //用法1-CSRedisClient实例化的对象
                    var rds = new CSRedis.CSRedisClient(Configuration["RedisStr"]);
                    services.AddSingleton(rds);   //注册成全局单例
    
                    //用法2-RedisHelper帮助类
                    RedisHelper.Initialization(rds);
                }
                services.AddControllersWithViews();
     }

    调用:

        public class HomeController : Controller
        {
    
            private CSRedisClient _csredis;
            public HomeController(CSRedisClient csredis)
            {
                this._csredis = csredis;    
            }
            public IActionResult Index()
            {
    
                #region 01-如何集成
                //{
                //    //1. 用法1,需要注入
                //    _csredis.Set("name1", "ypf11");
                //    var result1 = _csredis.Get("name1");
                //    Console.WriteLine($"name1={result1}");
    
    
                //    //2. 用法2,直接使用RedisHelp类即可
                //    RedisHelper.Set("name2", "ypf22");
                //    var result2 = RedisHelper.Get("name2");
                //    Console.WriteLine($"name2={result2}");
                //}
                #endregion
    
                return View();
            }    
        }

    3. CoreMVC缓存集成

     前提:通过Nuget安装程序集【Caching.CSRedis】,注册方式和CoreMVC的默认分布式缓存的方式有点区别,这里更加简单粗暴,直接注册IDistributedCache对象即可

    (1).在ConfigureService中注册IDistributedCache单例对象

    (2).在控制器中注入进行使用即可,包含的方法默认修改的模式相同

    代码分享

    ConfigureService

     //2. 注册基于Redis的分布式缓存
                {
                    var csredis = new CSRedis.CSRedisClient(Configuration["RedisStr"]);
                    services.AddSingleton<IDistributedCache>(new Microsoft.Extensions.Caching.Redis.CSRedisCache(csredis));
    
                    //集群模式
                    //              var csredis = new CSRedis.CSRedisClient(null,
                    //              "127.0.0.1:6371,pass=123,defaultDatabase=11,poolsize=10,ssl=false,writeBuffer=10240,prefix=key前辍",
                    //              "127.0.0.1:6372,pass=123,defaultDatabase=12,poolsize=11,ssl=false,writeBuffer=10240,prefix=key前辍",
                    //              "127.0.0.1:6373,pass=123,defaultDatabase=13,poolsize=12,ssl=false,writeBuffer=10240,prefix=key前辍",
                    //              "127.0.0.1:6374,pass=123,defaultDatabase=14,poolsize=13,ssl=false,writeBuffer=10240,prefix=key前辍");
                    //              services.AddSingleton<IDistributedCache>(new Microsoft.Extensions.Caching.Redis.CSRedisCache(csredis));
                }
    View Code

    调用

      public class HomeController : Controller
        {
            private IDistributedCache _dCache;
            public HomeController(IDistributedCache dCache)
            {
                this._dCache = dCache;
            }
    
            public IActionResult Index()
            {
                #region 02-缓存集成
                {
                    _dCache.SetString("lmr", "1234567");
                    var data = _dCache.GetString("lmr");
                }
                #endregion
    
                return View();
            }
    }

    4.分享一种封装模式(推荐)

    (1). 配置文件中声明缓存类型和链接字符串

    (2). 新建CacheExtension类进行不同类型的缓存初始化(redis自身初始化和redis缓存依赖初始化

    (3). 在Program中进行UseCache

    (4). 进行测试

    代码分享:

    配置文件:

       //缓存类型
      //有两种取值 (Redis:代表使用redis缓存, 并实例化redis相关对象. Memory:代表使用服务端缓存)
      "CacheType": "Redis",
      "RedisStr": "119.45.174.xxx:6379,password=123456,defaultDatabase=0"

    策略类:

     /// <summary>
        /// 缓存扩展
        /// </summary>
        public static class CacheExtension
        {
            /// <summary>
            /// 使用缓存
            /// </summary>
            /// <param name="hostBuilder">建造者</param>
            /// <returns></returns>
            public static IHostBuilder UseCache(this IHostBuilder hostBuilder)
            {
                hostBuilder.ConfigureServices((buidlerContext, services) =>
                {
                    var CacheType = buidlerContext.Configuration["CacheType"].ToString();
                    switch (CacheType)
                    {
                        case "Memory": services.AddDistributedMemoryCache(); break;
                        case "Redis":
                            {
                                //初始化redis的两种使用方式
                                var csredis = new CSRedisClient(buidlerContext.Configuration["RedisStr"].ToString());
                                services.AddSingleton(csredis);
                                RedisHelper.Initialization(csredis);
                                
                                //初始化缓存基于redis
                                services.AddSingleton<IDistributedCache>(new CSRedisCache(csredis));
                            }; break;
                        default: throw new Exception("缓存类型无效");
                    }
                });
                return hostBuilder;
            }
        }

    Program:

    调用:同上面的调用类似

    三. 通用指令、数据类型测试

    1. 通用指令

                {
                    Console.WriteLine("通用指令测试");
                    //1.获取所有key
                    var d1 = RedisHelper.Keys("*");
                    //2. 获取部分key
                    var d2 = RedisHelper.Scan(0, "*", 2);
                    //3. 获取过期时间
                    var d3 = RedisHelper.Ttl("name1");
                    //4. 删除key
                    var d4 = RedisHelper.Del("name1");
                    //5. 判断key是否存在
                    var d5 = RedisHelper.Exists("name1");
                    //6. 设置过期时间
                    var d6 = RedisHelper.Expire("name2", 60);
    
                    Console.WriteLine("执行完成");
                }

    2. String类型

                {
                    Console.WriteLine("String类型测试");
                    RedisHelper.Set("name1", "lmr2");         //默认是不过期的
                    RedisHelper.Set("name2", "lmr2", 30);     //30s过期
                    RedisHelper.IncrBy("count1", 2);         //每次自增2
                    RedisHelper.IncrBy("count2", -1);        //每次自减1
    
                    Console.WriteLine("执行完成");
                }

    3. Hash类型

                {
                    Console.WriteLine("Hash类型测试");
                    RedisHelper.HSet("myhash", "userName", "ypf");
                    RedisHelper.HSet("myhash", "userAge", 20);
                    RedisHelper.HIncrBy("myhash", "count", 1);  //每次自增1
                    //获取key下的所有内容
                    var data1 = RedisHelper.HGetAll("myhash");
                    //指定key-filed
                    var dataValue = RedisHelper.HGet<int>("myhash", "userAge");
                    Console.WriteLine("执行完成");
                }

    4. List类型

                {
                    Console.WriteLine("List类型测试");
                    //左侧插入
                    RedisHelper.LPush("myList", "001");
                    RedisHelper.LPush("myList", "002");
                    RedisHelper.LPush("myList", "003");
                    //右侧插入
                    RedisHelper.RPush("myList", "004");
                    RedisHelper.RPush("myList", "005");
                    RedisHelper.RPush("myList", "006");
                    //左侧删除并返回该元素
                    var d1 = RedisHelper.LPop("myList");
                    //右侧删除并返回该元素
                    var d2 = RedisHelper.RPop("myList");
    
                    //获取指定范围的元素
                    var data1 = RedisHelper.LRange("myList", 0, 2);  //左侧开始,获取前三个元素
                    var data2 = RedisHelper.LRange("myList", 0, -1);  //左侧开始,获取所有
    
                    Console.WriteLine("执行完成");
                }

    5. Set类型

                {
                    Console.WriteLine("Set类型测试");
                    //增加
                    RedisHelper.SAdd("school", "a");
                    RedisHelper.SAdd("school", "b");
                    RedisHelper.SAdd("school", "c");
                    RedisHelper.SAdd("school", "d");
                    RedisHelper.SAdd("school", "e");
                    //删除
                    RedisHelper.SIsMember("school", "b");
                    //获取
                    var data1 = RedisHelper.SMembers("school");
                    //随机获取(不删除)
                    var data2 = RedisHelper.SRandMember("school");
                    Console.WriteLine("执行完成");
                }

    6. SortedSet类型

                {
                    Console.WriteLine("SortedSet类型测试");
                    //1. 增加
                    RedisHelper.ZAdd("mySort", (20, "s1"), (20, "s2"), (20, "s3"), (50, "s4"), (80, "s8"));
    
                    //2. 自增/自减
                    RedisHelper.ZIncrBy("mySort", "s1", 1);
                    RedisHelper.ZIncrBy("mySort", "s2", -2);
                    RedisHelper.ZIncrBy("mySort", "s3", -3);
                    RedisHelper.ZIncrBy("mySort", "s4", 10);
    
                    //3. 获取
                    var d1 = RedisHelper.ZRange("mySort", 0, 2);
                    var d2 = RedisHelper.ZRange("mySort", 0, -1);  //所有元素
    
                    //4.按照score排序获取
                    var s1 = RedisHelper.ZRevRange("mySort", 0, -1); //从高到低排序
    
                    //5. 删除
                    RedisHelper.ZRem("mySort", "s1");
                    Console.WriteLine("执行完成");
                }

    7. Geo类型

                {
                    Console.WriteLine("Geo类型测试");
                    //1. 添加地点经纬度
                    RedisHelper.GeoAdd("myLocation", Convert.ToDecimal(116.20), Convert.ToDecimal(39.56), "北京");
                    RedisHelper.GeoAdd("myLocation", Convert.ToDecimal(120.51), Convert.ToDecimal(30.40), "上海");
    
                    //2. 求两点之间的距离
                    var d1 = RedisHelper.GeoDist("myLocation", "北京", "上海", GeoUnit.km);
    
                    Console.WriteLine("执行完成");
                }

    四. 集群测试

    1. 主从

    2. 哨兵

    下面地址是哨兵的地址

    var csredis = new CSRedis.CSRedisClient("mymaster,password=123,prefix=my_", 
      new [] { "192.169.1.10:26379", "192.169.1.11:26379", "192.169.1.12:26379" });

    3. Redis Cluster

    (1).环境准备

     6个redis实例,建立redis-cluster,地址分别为:192.168.137.202:6379(端口依次次6379-6384),主从关系和节点槽位分配,见下图

    (2).写法1

     写任意个地址即可,其他节点在运行过程中自动增加,确保每个节点密码一致。(原理:它会根据 redis-server 返回的 MOVED | ASK 错误记录slot,自动增加节点 Nodes 属性。)

    如下测试:name1和name2位于不同redis服务器,写入成功。

                {
                    Console.WriteLine("集群测试");
                    RedisHelper.Initialization(new CSRedis.CSRedisClient("192.168.137.202:6379,password=123456,defaultDatabase=0"));
                    RedisHelper.Set("name1", "ypf1");     //对应的槽位在6384端口上
                    RedisHelper.Set("name2", "ypf2");     //对应的槽位在6379端口上
                    var data1 = RedisHelper.Get<String>("name1");
                    var data2 = RedisHelper.Get<String>("name2");
                    Console.WriteLine($"data1={data1}");
                    Console.WriteLine($"data2={data2}");
                    Console.WriteLine("执行完毕");
                }

    注意:这个写法与【分区模式】同时使用时,切记不可设置“prefix=key前辍”(或者全部设置成一样),否则会导致 keySlot 计算结果与服务端不匹配,无法记录 slotCache。

    (3).写法2

     把所有地址都列进去。

    如下测试:name1和name2位于不同redis服务器,写入成功

                {
                    Console.WriteLine("集群测试");
                    var csredis = new CSRedis.CSRedisClient(null,
                                "192.168.137.202:6379,password=123456,defaultDatabase=0",
                                "192.168.137.202:6380,password=123456,defaultDatabase=0",
                                "192.168.137.202:6381,password=123456,defaultDatabase=0",
                                "192.168.137.202:6382,password=123456,defaultDatabase=0",
                                "192.168.137.202:6383,password=123456,defaultDatabase=0",
                                "192.168.137.202:6384,password=123456,defaultDatabase=0");
                    RedisHelper.Initialization(csredis);
    
                    RedisHelper.Set("name1", "ypf1");     //对应的槽位在6384端口上
                    RedisHelper.Set("name2", "ypf2");     //对应的槽位在6379端口上
                    var data1 = RedisHelper.Get<String>("name1");
                    var data2 = RedisHelper.Get<String>("name2");
                    Console.WriteLine($"data1={data1}");
                    Console.WriteLine($"data2={data2}");
    
                    Console.WriteLine("执行完毕");
                }

    特别注意:官方集群(redis-cluster)不支持多 keys 的命令、【管道】、Eval(脚本)等众多杀手级功能。

    五. Lua测试

    lua相关介绍参考:https://www.cnblogs.com/yaopengfei/p/13941841.html

            https://www.cnblogs.com/yaopengfei/p/13826478.html

    1. 在客户端测试

     A. 把Test1.lua文件改为“始终复制”.

     B. 方案1:将lua脚本通过 ScriptLoad 转换成sha, 然后在通过 EvalSHA 调用.

     C. 方案2:直接通过Eval调用lua脚本

    PS:方案1中sha会存放到redis的缓存中,客户端多次调用相比每次都发送lua脚本给redis来说节省带宽。

    代码分享:

    Lua脚本:

    --[[
        一. 方法声明
    ]]--
    
    --1. 方法幂等(防止网络延迟多次下单)
    local function recordOrderSn()
    --(1).获取相关参数
    local requestId = ARGV[1];    --请求ID
    --(2).执行判断业务
    local requestIdNum = redis.call('INCR',requestId);
    --表示第一次请求
    if (requestIdNum==1)                            
    then
    redis.call('expire',requestId,600)  --10min过期
    return 1; --成功
    end;
    --第二次及第二次以后的请求
    if (requestIdNum>1)
    then
    return 0;  --失败
    end;
    end;  --对应的是整个代码块的结束
    
    
    
    --[[
        二. 方法调用   返回值1代表成功,返回:0代表失败
    ]]--
    
    --1.  方法幂等
    local status3 = recordOrderSn();
    if status3 == 0 then
    return 0;    --失败
    end
    return 1;    --成功
    View Code

    调用:

                {
    
                    Console.WriteLine("Lua客户端测试");
    
                    RedisHelper.Initialization(new CSRedis.CSRedisClient("119.45.174.249:6379,password=123456,defaultDatabase=0"));
                    FileStream fileStream1 = new FileStream(@"Luas/Test1.lua", FileMode.Open);
                    string script1 = "";
                    string luaSha1 = "";
                    using (StreamReader reader = new StreamReader(fileStream1))
                    {
                        script1 = reader.ReadToEnd();
                        //将lua脚本转换成sha,同时redis会将其存到脚本缓存中
                        luaSha1 = RedisHelper.ScriptLoad(script1);
                    }
                    //方案1:调用lua对应sha值(这里testKey1没有特别作用)----推荐
                    var d1 = RedisHelper.EvalSHA(luaSha1, "testkey1", "12345-1");
                    Console.WriteLine($"d1={d1}");
    
                    //方案2:直接调用lua脚本(每次都发脚本给redis,浪费带宽,推荐上面的sha模式)
                    var d2 = RedisHelper.Eval(script1, "testkey2", "12345-2");
                    Console.WriteLine($"d2={d2}");
    
                    //清空缓存中的所有脚本
                    //RedisHelper.ScriptFlush();
                    //杀死正在运行的脚本
                    //RedisHelper.ScriptKill();
    
                    Console.WriteLine("执行完成");
    
                }

    2. 在CoreMvc中测试

     直接演示sha的那种模式,调用方式完全类似,但在web程序中,我们可以在项目启动的时候就把所有的脚本都事先转换成lua存放到服务器缓存中,后续直接调用即可.

     后台任务代码:

        /// <summary>
        /// 后台任务,初始化lua文件到服务器缓存中
        /// </summary>
        public class LuasLoadService : BackgroundService
        {
    
            private IMemoryCache _cache;
            public LuasLoadService(IMemoryCache cache)
            {
                _cache = cache;
            }
    
            protected override Task ExecuteAsync(CancellationToken stoppingToken)
            {
                FileStream fileStream1 = new FileStream(@"Luas/Test1.lua", FileMode.Open);
                using (StreamReader reader = new StreamReader(fileStream1))
                {
                    string line = reader.ReadToEnd();
                    string luaSha = RedisHelper.ScriptLoad(line);
    
                    //保存到缓存中
                    _cache.Set<string>("Test1Lua", luaSha);
                }
      
                return Task.CompletedTask;
            }
        }

    注入和调用:

       {
              services.AddHostedService<LuasLoadService>();
       }

    六. 其他 

    1.多个db的用法

    (1).多个CSRedisClient实例

                    //1.多个CSRedisClient实例
                    var connectionString = "119.45.174.xx:6379,password=123456";
                    var redis = new CSRedisClient[14]; //生产中设置成Singleton
                    for (var a = 0; a < redis.Length; a++)
                    {
                        redis[a] = new CSRedisClient(connectionString + ",defaultDatabase=" + a);
                    };
                    redis[1].Set("cs1", "111");
                    redis[2].Set("cs1", "111");

    (2).多个RedisHelper子类

    public class MyHelper1 : RedisHelper<MyHelper1>
        {
        }
     public class MyHelper2 : RedisHelper<MyHelper2>
        {
        }

     调用:

        MyHelper1.Initialization(new CSRedisClient("119.45.174.249:6379,password=123456,defaultDatabase=1"));
        MyHelper2.Initialization(new CSRedisClient("119.45.174.249:6379,password=123456,defaultDatabase=2"));
    
        MyHelper1.Set("cs2", "222");
        MyHelper2.Set("cs2", "222");

    2. 发布订阅

    简单了解即可(可参考:https://www.cnblogs.com/kellynic/p/9952386.html), 专业场景推荐使用RabbitMQ

    (1). 普通的发布订阅

    利用Subscribe和Publish方法

                {
                    Console.WriteLine("发布订阅");
                    RedisHelper.Initialization(new CSRedis.CSRedisClient("119.45.174.249:6379,password=123456,defaultDatabase=0"));
    
                    //程序1:使用代码实现订阅端
                    var sub = RedisHelper.Subscribe(("chan1", msg => Console.WriteLine(msg.Body)));
                    //sub.Disponse(); //停止订阅
    
                    //程序2:使用代码实现发布端
                    RedisHelper.Publish("chan1", "111");
    
    
                    //下面了解即可
                    //{ //sub1, sub2 争抢订阅(只可一端收到消息)
                    //    var sub1 = RedisHelper.SubscribeList("list1", msg => Console.WriteLine($"sub1 -> list1 : {msg}"));
                    //    var sub2 = RedisHelper.SubscribeList("list1", msg => Console.WriteLine($"sub2 -> list1 : {msg}"));
    
                    //    //sub3, sub4, sub5 非争抢订阅(多端都可收到消息)
                    //    var sub3 = RedisHelper.SubscribeListBroadcast("list2", "sub3", msg => Console.WriteLine($"sub3 -> list2 : {msg}"));
                    //    var sub4 = RedisHelper.SubscribeListBroadcast("list2", "sub4", msg => Console.WriteLine($"sub4 -> list2 : {msg}"));
                    //    var sub5 = RedisHelper.SubscribeListBroadcast("list2", "sub5", msg => Console.WriteLine($"sub5 -> list2 : {msg}"));
                    //}
    
                }

    (2). 利用BLPOP+LPUSH实现

    (详细可参考上面文档)

    3. 缓存相关业务简化用法

    详见代码

                {
                    Console.WriteLine("缓存相关业务简化封装");
                    RedisHelper.Initialization(new CSRedis.CSRedisClient("119.45.174.249:6379,password=123456,defaultDatabase=0"));
                    List<string> strList = new List<string>() { "ypf1", "ypf2", "ypf3" };  //模拟db
    
                    //常规写法:先去缓存中找,有数据直接返回,没有数据去查db,然后给缓存赋值,最后返回
                    //{
                    //    var data = RedisHelper.Get<string>("userName");
                    //    if (string.IsNullOrEmpty(data))
                    //    {
                    //        //从db中查找
                    //        data = strList.FirstOrDefault();
                    //        RedisHelper.Set("userName", data, 100); //100s过期
                    //        Console.WriteLine($"data={data}");
                    //    }
                    //    else
                    //    {
                    //        //直接将缓存中的数据返回
                    //        Console.WriteLine($"data={data}");
                    //    }
                    //}
    
                    //封装的写法
                    {
                        //下面两行等价于上面的一坨代码
                        var data = RedisHelper.CacheShell("userName", 100, () => strList.FirstOrDefault());  //100s过期
                        Console.WriteLine($"data={data}");
    
                        //另外还支持hash类型
                        //var t2 = RedisHelper.CacheShell("test1", "1", 100, () => strList.FirstOrDefault());
                        //var t3 = RedisHelper.CacheShell("test2", new[] { "1", "2" }, 10, notCacheFields => new[] {
                        //                       ("1", strList.FirstOrDefault()),
                        //                       ("2", strList.FirstOrDefault())
                        //});
    
                    }
                }

    4. PipeLine管道

    Redis 管道技术可以在服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应。

                {
                    RedisHelper.Initialization(new CSRedis.CSRedisClient("119.45.174.249:6379,password=123456,defaultDatabase=0"));
                    //一次性返回所有请求结果
                    var data = RedisHelper.StartPipe(p => p.Set("userName1", "ypf1").Get("userName1").Set("userName2", "ypf2").Get("userName2"));
                }

    5. Benchmark性能测试

    见官网测试记录

     

    !

    • 作       者 : Yaopengfei(姚鹏飞)
    • 博客地址 : http://www.cnblogs.com/yaopengfei/
    • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
    • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
     
  • 相关阅读:
    【Cocos2d-X游戏实战开发】捕鱼达人之开发前准备工作(一)
    NetBeans + Xdebug 调试WordPress
    【Cocos2d-X游戏实战开发】捕鱼达人之单例对象的设计(二)
    源代码静态分析工具
    Flash Builder 条件编译的实现
    Maven插件之portable-config-maven-plugin(不同环境打包)
    生成8位随机不重复的数字编号
    【剑指Offer学习】【面试题63:二叉搜索树的第k个结点】
    51nod 1413:权势二进制
    leetcode_Isomorphic Strings _easy
  • 原文地址:https://www.cnblogs.com/yaopengfei/p/14211883.html
Copyright © 2011-2022 走看看