從零開始搭建前後端分離的NetCore2.2(EF Core CodeFirst+Autofac)+Vue的項目框架之八MemoryCache與redis緩存的使用

来源:https://www.cnblogs.com/levywang/archive/2019/09/11/coreframe_8.html
-Advertisement-
Play Games

1.緩存概念 1.什麼是緩存 這裡要講到的緩存是服務端緩存,簡單的說,緩存就是將一些實時性不高,但訪問又十分頻繁,或者說要很長時間才能取到的數據給存在記憶體當中,當有請求時直接返回,不用經過資料庫或介面獲取。這樣就可以減輕資料庫的負擔。 2.為什麼要用緩存 總的來說就是為了提高響應速度(用戶體驗度), ...


 

 1.緩存概念

  1.什麼是緩存

    這裡要講到的緩存是服務端緩存,簡單的說,緩存就是將一些實時性不高,但訪問又十分頻繁,或者說要很長時間才能取到的數據給存在記憶體當中,當有請求時直接返回,不用經過資料庫或介面獲取。這樣就可以減輕資料庫的負擔。

  2.為什麼要用緩存

    總的來說就是為了提高響應速度(用戶體驗度),減少資料庫訪問頻率。

    在一個用戶看來,軟體使用的體驗度才是關鍵,在對實時性要求不高的情況下,用戶肯定會覺得打開界面的響應速度快,能保證平常工作的應用才是好的。因此為了滿足這個需求,通過使用緩存,就可以保證滿足在正常工作的前提下響應時間儘可能短。

    例如:當客戶端向伺服器請求某個數據時,伺服器先在緩存中找,如果在緩存中,就直接返回,無需查詢資料庫;如果請求的數據不在緩存中,這時再去資料庫中找,找到後返回給客戶端,並將這個資源加入緩存中。這樣下次請求相同資源時,就不需

      要連接資料庫了。而且如果把緩存放在記憶體中,因為對記憶體的操作要比對資料庫操作快得多,這樣請求時間也會縮短。每當數據發生變化的時候(比如,數據有被修改,或被刪除的情況下),要同步的更新緩存信息,確保用戶不會在緩存取到舊的數據。

    如果沒有使用緩存,用戶去請求某個數據,當用戶量和數據逐漸增加的時候,就會發現每次用戶請求的時間越來越長,且資料庫無時不刻都在工作。這樣用戶和資料庫都很痛苦,時間一長,就有可能發生下以下事情:

      1.用戶常抱怨應用打開速度太慢,頁面經常無響應,偶爾還會出現崩潰的情況。

      2.資料庫連接數滿或者說資料庫響應慢(處理不過來)。

      3.當併發量上來的時候,可能會導致資料庫崩潰,使得應用無法正常使用。

 2.選用Redis還是Memcached

  簡單說下這兩者的區別,兩者都是通過key-value的方式進行存儲的,Memcached只有簡單的字元串格式,而Redis還支持更多的格式(list、 set、sorted set、hash table ),緩存時使用到的數據都是在記憶體當中,

  不同的在於Redis支持持久化,集群、簡單事務、發佈/訂閱、主從同步等功能,當斷電或軟體重啟時,Memcached中的數據就已經不存在了,而Redis可以通過讀取磁碟中的數據再次使用。

  這裡提高Windows版的安裝包:傳送門          可視化工具:因文件太大無法上傳到博客園。代碼倉庫中有,需要的私信哦~

 3.兩者在NetCore 中的使用

  Memcached的使用還是相當簡單的,首先在 Startup 類中做以下更改,添加緩存參數  賦值給外部類來方便使用  

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, IMemoryCache memoryCache)
        {
            //....  省略部分代碼
            DemoWeb.MemoryCache = memoryCache;
            //....  省略部分代碼
        }    

 

DemoWeb中的代碼:
 public class DemoWeb
    {
      
        //....省略部分代碼

        /// <summary>
        /// MemoryCache
        /// </summary>
        public static IMemoryCache MemoryCache { get; set; }

        /// <summary>
        /// 獲取當前請求客戶端IP
        /// </summary>
        /// <returns></returns>
        public static string GetClientIp()
        {
            var ip = HttpContext.Request.Headers["X-Forwarded-For"].FirstOrDefault()?.Split(',')[0].Trim();
            if (string.IsNullOrEmpty(ip))
            {
                ip = HttpContext.Connection.RemoteIpAddress.ToString();
            }
            return ip;
        }
    }

  然後創建 MemoryCache 來封裝些緩存的簡單方法

    /// <summary>
    /// MemoryCache緩存
    /// </summary>
    public class MemoryCache
    {
        private static readonly HashSet<string> Keys = new HashSet<string>();

        /// <summary>
        /// 緩存首碼
        /// </summary>
        public string Prefix { get; }

        /// <summary>
        /// 構造函數
        /// </summary>
        /// <param name="prefix"></param>
        public MemoryCache(string prefix)
        {
            Prefix = prefix + "_";
        }

        /// <summary>
        /// 獲取
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public T Get<T>(string key)
        {
            return DemoWeb.MemoryCache.Get<T>(Prefix + key);
        }

        /// <summary>
        /// 設置 無過期時間
        /// </summary>
        /// <param name="key"></param>
        /// <param name="data"></param>
        public void Set(string key, object data)
        {
            key = Prefix + key;
            DemoWeb.MemoryCache.Set(key, data);
            if (!Keys.Contains(key))
            {
                Keys.Add(key);
            }
        }

        /// <summary>
        /// 設置
        /// </summary>
        /// <param name="key"></param>
        /// <param name="data"></param>
        /// <param name="absoluteExpiration"></param>
        public void Set(string key, object data, DateTimeOffset absoluteExpiration)
        {
            key = Prefix + key;
            DemoWeb.MemoryCache.Set(key, data, absoluteExpiration);
            if (!Keys.Contains(key))
            {
                Keys.Add(key);
            }
        }

        /// <summary>
        /// 設置
        /// </summary>
        /// <param name="key"></param>
        /// <param name="data"></param>
        /// <param name="absoluteExpirationRelativeToNow"></param>
        public void Set(string key, object data, TimeSpan absoluteExpirationRelativeToNow)
        {
            key = Prefix + key;
            DemoWeb.MemoryCache.Set(key, data, absoluteExpirationRelativeToNow);
            if (!Keys.Contains(key))
            {
                Keys.Add(key);
            }
        }

        /// <summary>
        /// 設置
        /// </summary>
        /// <param name="key"></param>
        /// <param name="data"></param>
        /// <param name="expirationToken"></param>
        public void Set(string key, object data, IChangeToken expirationToken)
        {
            key = Prefix + key;
            DemoWeb.MemoryCache.Set(key, data, expirationToken);
            if (!Keys.Contains(key))
            {
                Keys.Add(key);
            }
        }

        /// <summary>
        /// 設置
        /// </summary>
        /// <param name="key"></param>
        /// <param name="data"></param>
        /// <param name="options"></param>
        public void Set(string key, object data, MemoryCacheEntryOptions options)
        {
            key = Prefix + key;
            DemoWeb.MemoryCache.Set(key, data, options);
            if (!Keys.Contains(key))
            {
                Keys.Add(key);
            }
        }

        /// <summary>
        /// 移除某個
        /// </summary>
        /// <param name="key"></param>
        public void Remove(string key)
        {
            key = Prefix + key;
            DemoWeb.MemoryCache.Remove(key);
            if (Keys.Contains(key))
            {
                Keys.Remove(key);
            }
        }

        /// <summary>
        /// 清空所有
        /// </summary>
        public void ClearAll()
        {
            foreach (var key in Keys)
            {
                DemoWeb.MemoryCache.Remove(key);
            }
            Keys.Clear();
        }

    }
View Code

  其實接下來就可以直接使用緩存了,但為了方便使用,再建一個緩存類別的中間類來管理。

    public class UserCache
    {
        private static readonly MemoryCache Cache = new MemoryCache("User");

        private static TimeSpan _timeout = TimeSpan.Zero;
        private static TimeSpan Timeout
        {
            get
            {
                if (_timeout != TimeSpan.Zero)
                    return _timeout;
                try
                {
                    _timeout = TimeSpan.FromMinutes(20);
                    return _timeout;
                }
                catch (Exception)
                {
                    return TimeSpan.FromMinutes(10);
                }
            }
        }
        public static void Set(string key,string cache)
        {
            if (string.IsNullOrEmpty(cache))
                return;

            Cache.Set(key, cache, Timeout);
        }


        public static string Get(string key)
        {
            if (string.IsNullOrEmpty(key))
                return default(string);

            return Cache.Get<string>(key);
        }
    }
UserCache

  測試是否可以正常使用:代碼與截圖

        [HttpGet]
        [Route("mecache")]
        public ActionResult ValidToken()
        {
            var key = "tkey";
            UserCache.Set(key, "測試數據");
            return Succeed(UserCache.Get(key));
        }    

  可以清楚的看到 MemoryCache 可以正常使用。  

  那麼接下來將講到如何使用 Redis 緩存。先在需要封裝基礎類的項目 Nuget 包中添加  StackExchange.Redis  依賴。然後添加Redis 連接類

 internal class RedisConnectionFactory
    {
        public string ConnectionString { get; set; }
        public string Password { get; set; }

        public ConnectionMultiplexer CurrentConnectionMultiplexer { get; set; }


        /// <summary>
        /// 設置連接字元串
        /// </summary>
        /// <returns></returns>
        public void SetConnectionString(string connectionString)
        {
            ConnectionString = connectionString;
        }

        /// <summary>
        /// 設置連接字元串
        /// </summary>
        /// <returns></returns>
        public void SetPassword(string password)
        {
            Password = password;
        }

        public ConnectionMultiplexer GetConnectionMultiplexer()
        {
            if (CurrentConnectionMultiplexer == null || !CurrentConnectionMultiplexer.IsConnected)
            {
                if (CurrentConnectionMultiplexer != null)
                {
                    CurrentConnectionMultiplexer.Dispose();
                }

                CurrentConnectionMultiplexer = GetConnectionMultiplexer(ConnectionString);
            }

            return CurrentConnectionMultiplexer;
        }


        private ConnectionMultiplexer GetConnectionMultiplexer(string connectionString)
        {
            ConnectionMultiplexer connectionMultiplexer;

            if (!string.IsNullOrWhiteSpace(Password) && !connectionString.ToLower().Contains("password"))
            {
                connectionString += $",password={Password}";
            }

            var redisConfiguration = ConfigurationOptions.Parse(connectionString);
            redisConfiguration.AbortOnConnectFail = true;
            redisConfiguration.AllowAdmin = false;
            redisConfiguration.ConnectRetry = 5;
            redisConfiguration.ConnectTimeout = 3000;
            redisConfiguration.DefaultDatabase = 0;
            redisConfiguration.KeepAlive = 20;
            redisConfiguration.SyncTimeout = 30 * 1000;
            redisConfiguration.Ssl = false;

            connectionMultiplexer = ConnectionMultiplexer.Connect(redisConfiguration);

            return connectionMultiplexer;
        }
    }
RedisConnectionFactory

  再添加Redis客戶端類

    /// <summary>
    /// Redis Client
    /// </summary>
    public class RedisClient : IDisposable
    {
        public int DefaultDatabase { get; set; } = 0;

        private readonly ConnectionMultiplexer _client;
        private IDatabase _db;

        public RedisClient(ConnectionMultiplexer client)
        {
            _client = client;
            UseDatabase();
        }

        public void UseDatabase(int db = -1)
        {
            if (db == -1)
                db = DefaultDatabase;
            _db = _client.GetDatabase(db);
        }


        public string StringGet(string key)
        {
            return _db.StringGet(key).ToString();
        }


        public void StringSet(string key, string data)
        {
            _db.StringSet(key, data);
        }

        public void StringSet(string key, string data, TimeSpan timeout)
        {
            _db.StringSet(key, data, timeout);
        }


        public T Get<T>(string key)
        {
            var json = StringGet(key);
            if (string.IsNullOrEmpty(json))
            {
                return default(T);
            }

            return json.ToNetType<T>();
        }

        public void Set(string key, object data)
        {
            var json = data.ToJson();
            _db.StringSet(key, json);
        }

        public void Set(string key, object data, TimeSpan timeout)
        {
            var json = data.ToJson();
            _db.StringSet(key, json, timeout);
        }

        /// <summary>
        /// Exist
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public bool Exist(string key)
        {
            return _db.KeyExists(key);
        }

        /// <summary>
        /// Delete
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public bool Delete(string key)
        {
            return _db.KeyDelete(key);
        }

        /// <summary>
        /// Set Expire to Key
        /// </summary>
        /// <param name="key"></param>
        /// <param name="expiry"></param>
        /// <returns></returns>
        public bool Expire(string key, TimeSpan? expiry)
        {
            return _db.KeyExpire(key, expiry);
        }

        /// <summary>
        /// 計數器  如果不存在則設置值,如果存在則添加值  如果key存在且類型不為long  則會異常
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="expiry">只有第一次設置有效期生效</param>
        /// <returns></returns>
        public long SetStringIncr(string key, long value = 1, TimeSpan? expiry = null)
        {
            var nubmer = _db.StringIncrement(key, value);
            if (nubmer == 1 && expiry != null)//只有第一次設置有效期(防止覆蓋)
                _db.KeyExpireAsync(key, expiry);//設置有效期
            return nubmer;
        }

        /// <summary>
        /// 讀取計數器
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public long GetStringIncr(string key)
        {
            var value = StringGet(key);
            return string.IsNullOrWhiteSpace(value) ? 0 : long.Parse(value);
        }

        /// <summary>
        /// 計數器-減少 如果不存在則設置值,如果存在則減少值  如果key存在且類型不為long  則會異常
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public long StringDecrement(string key, long value = 1)
        {
            var nubmer = _db.StringDecrement(key, value);
            return nubmer;
        }



        public void Dispose()
        {
            _client?.Dispose();
        }
    }
RedisClient

  然後再添加Redis連接生成工具類

    public static class RedisFactory
    {
        private static readonly object Locker = new object();

        private static RedisConnectionFactory factory;

        private static void InitRedisConnection()
        {
            try
            {
                factory = new RedisConnectionFactory();
                var connectionString = DemoWeb.Configuration["Redis:ConnectionString"];
#if DEBUG
                connectionString = "127.0.0.1:6379";
#endif
                factory.ConnectionString = connectionString;
                factory.Password = DemoWeb.Configuration["Redis:Pwd"];

            }
            catch (Exception e)
            {
                LogHelper.Logger.Fatal(e, "Redis連接創建失敗。");
            }
        }

        public static RedisClient GetClient()
        {
            //先判斷一輪,減少鎖,提高效率
            if (factory == null || string.IsNullOrEmpty(factory.ConnectionString))
            {
                //防止併發創建
                lock (Locker)
                {
                    InitRedisConnection();
                }
            }

            return new RedisClient(factory.GetConnectionMultiplexer())
            {
                DefaultDatabase = DemoWeb.Configuration["Redis:DefaultDatabase"].ToInt()
            };

        }
    }
RedisFactory

  這裡要使用到前面的靜態擴展方法。請自行添加  傳送門 ,還需要將 Startup 類中的 Configuration 給賦值到 DemoWeb中的 Configuration 欄位值來使用

  在配置文件 appsettings.json 中添加

  "Redis": {
    "ConnectionString": "127.0.0.1:6379",
    "Pwd": "",
    "DefaultDatabase": 0
  }

  再添加Redis緩存使用類

    /// <summary>
    /// Redis緩存
    /// </summary>
    public class RedisCache
    {
        private static RedisClient _client;
        private static RedisClient Client => _client ?? (_client = RedisFactory.GetClient());

        private static string ToKey(string key)
        {
            return $"Cache_redis_{key}";
        }

        /// <summary>
        /// 獲取
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public static T Get<T>(string key)
        {
            try
            {
                var redisKey = ToKey(key);
                return Client.Get<T>(redisKey);
            }
            catch (Exception e)
            {
                LogHelper.Logger.Fatal(e, "RedisCache.Get \n key:{0}", key);
                return default(T);
            }
        }

        /// <summary>
        /// 嘗試獲取
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        private static T TryGet<T>(string key, out bool result)
        {
            result = true;
            try
            {
                var redisKey = ToKey(key);
                return Client.Get<T>(redisKey);
            }
            catch (Exception e)
            {
                LogHelper.Logger.Fatal(e, "RedisCache.TryGet \n key:{0}", key);
                result = false;
                return default(T);
            }
        }

        /// <summary>
        /// 獲取
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="setFunc"></param>
        /// <param name="expiry"></param>
        /// <param name="resolver"></param>
        /// <returns></returns>
        public static T Get<T>(string key, Func<T> setFunc, TimeSpan? expiry = null)
        {
            var redisKey = ToKey(key);
            var result = TryGet<T>(redisKey, out var success);
            if (success && result == null)
            {
                result = setFunc();
                try
                {
                    Set(redisKey, result, expiry);
                }
                catch (Exception e)
                {
                    LogHelper.Logger.Fatal(e, "RedisCache.Get<T> \n key:{0}", key);
                }
            }
            return result;
        }

        /// <summary>
        /// 設置
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="expiry"></param>
        /// <returns></returns>
        public static bool Set<T>(string key, T value, TimeSpan? expiry = null)
        {
            var allRedisKey = ToKey("||Keys||");
            var redisKey = ToKey(key);

            var allkeyRedisValue = Client.StringGet(allRedisKey);
            var keys = allkeyRedisValue.ToNetType<List<string>>() ?? new List<string>();
            if (!keys.Contains(redisKey))
            {
                keys.Add(redisKey);
                Client.Set(allRedisKey, keys);
            }
            if (expiry.HasValue)
            {
                Client.StringSet(redisKey, value.ToJson(), expiry.Value);
            }
            else
            {
                Client.StringSet(redisKey, value.ToJson());
            }

            return true;
        }

        /// <summary>
        /// 重新設置過期時間
        /// </summary>
        /// <param name="key"></param>
        /// <param name="expiry"></param>
        public static void ResetItemTimeout(string key, TimeSpan expiry)
        {
            var redisKey = ToKey(key);
            Client.Expire(redisKey, expiry);
        }

        /// <summary>
        /// Exist
        /// </summary>
        /// <param name="key">原始key</param>
        /// <returns></returns>
        public static bool Exist(string key)
        {
            var redisKey = ToKey(key);
            return Client.Exist(redisKey);
        }

        /// <summary>
        /// 計數器 增加  能設置過期時間的都設置過期時間
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="expiry"></pa

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、instanceof運算符 instanceof是Java語言中的一個二元運算符,它的作用是判斷一個引用類型的變數所指向的對象是否是一個類(或介面、抽象類、父類)的實例,即它左邊的對象是否是它右邊的類的實例該運算符返回boolean類型的數據。 常見的用法為:result=object inst ...
  • [TOC] 原文鏈接: "QRowTable表格控制項(四) 效率優化之 優化數據源" 一、開心一刻 一程式員第一次上女朋友家她媽板著臉問 :你想娶我女兒,有多少存款? 程式員低了下頭:五百! 她媽更鄙視了:才五百塊,買個廁所都不夠! 程式員忙說:不是人民幣! 她媽:就算是美元,還是不夠買廁所! 程式 ...
  • Java中時間API使用詳解 [TOC] 1. 時區概念 國際經度會議(又稱國際子午線會議)上,規定將全球劃分為24個時區(東、西各12個時區)。規定英國的格林尼治天文臺舊址為中時區(零時區)、東1 12區,西1 12區。每個時區橫跨經度15度,時間正好是1小時。最後的東、西第12區各跨經度7.5度 ...
  • 電腦基礎方面的知識,對於一些非科班出身的同學來講,一直是他們心中的痛,而對於科班出身的同學,很多同學在工作之後,也意識到自身所學知識的不足與欠缺,想回頭補補基礎知識。關於電腦基礎的課程很多,內容繁雜,但無論是相關書籍還是大學課程,都有點脫離工作。特別地,電腦基礎知識體系龐雜,想要從零學習或者復 ...
  • 剛剛開始寫微服務,這篇博客就紀念我第一個微服務吧。 1.新建工程 首先要搭建一個微服務環境,我用的是開發環境IDEA,詳細步驟如下: ①打開IDEA File->New->Project 選擇Spring lnitializr 然後點擊next。 填寫包名和項目名字,自己隨便寫,然後next。 進入 ...
  • 一、存儲引擎 1、InnoDB引擎 設計目標是面向線上事務(OLTP)處理的應用。 支持事務、行級鎖、通過多版本併發控制(MVCC)支持高併發、提供一致性非鎖定讀、next-key locking避免幻讀、主鍵聚集索引 2、MyISAM引擎 設計目標是面向OLAP應用。 不支持事務、不支持行鎖、表鎖 ...
  • 在畫類圖的時候,理清類和類之間的關係是重點。類的關係有泛化(Generalization)、實現(Realization)、依賴(Dependency)和關聯(Association)。其中關聯又分為一般關聯關係和聚合關係(Aggregation),合成關係(Composition)。下麵我們結合實 ...
  • 最近做項目,要用到軟體自動化的操作,正好更大家分享一下! 先看看Python操作: 首先是如何打開軟體 第一種利用os模塊,也是最最簡單的一種。假如我想打開記事本,可以: 該函數是非阻塞的。同樣是打開記事本程式,可以這樣寫: 接著是通過句柄操作軟體,給軟體發消息:比如想最大化軟體,將軟體視窗置於最前 ...
一周排行
    -Advertisement-
    Play Games
  • Dapr Outbox 是1.12中的功能。 本文只介紹Dapr Outbox 執行流程,Dapr Outbox基本用法請閱讀官方文檔 。本文中appID=order-processor,topic=orders 本文前提知識:熟悉Dapr狀態管理、Dapr發佈訂閱和Outbox 模式。 Outbo ...
  • 引言 在前幾章我們深度講解了單元測試和集成測試的基礎知識,這一章我們來講解一下代碼覆蓋率,代碼覆蓋率是單元測試運行的度量值,覆蓋率通常以百分比表示,用於衡量代碼被測試覆蓋的程度,幫助開發人員評估測試用例的質量和代碼的健壯性。常見的覆蓋率包括語句覆蓋率(Line Coverage)、分支覆蓋率(Bra ...
  • 前言 本文介紹瞭如何使用S7.NET庫實現對西門子PLC DB塊數據的讀寫,記錄了使用電腦模擬,模擬PLC,自至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1.Windows環境下鏈路層網路訪問的行業標準工具(WinPcap_4_1_3.exe)下載鏈接:http ...
  • 從依賴倒置原則(Dependency Inversion Principle, DIP)到控制反轉(Inversion of Control, IoC)再到依賴註入(Dependency Injection, DI)的演進過程,我們可以理解為一種逐步抽象和解耦的設計思想。這種思想在C#等面向對象的編 ...
  • 關於Python中的私有屬性和私有方法 Python對於類的成員沒有嚴格的訪問控制限制,這與其他面相對對象語言有區別。關於私有屬性和私有方法,有如下要點: 1、通常我們約定,兩個下劃線開頭的屬性是私有的(private)。其他為公共的(public); 2、類內部可以訪問私有屬性(方法); 3、類外 ...
  • C++ 訪問說明符 訪問說明符是 C++ 中控制類成員(屬性和方法)可訪問性的關鍵字。它們用於封裝類數據並保護其免受意外修改或濫用。 三種訪問說明符: public:允許從類外部的任何地方訪問成員。 private:僅允許在類內部訪問成員。 protected:允許在類內部及其派生類中訪問成員。 示 ...
  • 寫這個隨筆說一下C++的static_cast和dynamic_cast用在子類與父類的指針轉換時的一些事宜。首先,【static_cast,dynamic_cast】【父類指針,子類指針】,兩兩一組,共有4種組合:用 static_cast 父類轉子類、用 static_cast 子類轉父類、使用 ...
  • /******************************************************************************************************** * * * 設計雙向鏈表的介面 * * * * Copyright (c) 2023-2 ...
  • 相信接觸過spring做開發的小伙伴們一定使用過@ComponentScan註解 @ComponentScan("com.wangm.lifecycle") public class AppConfig { } @ComponentScan指定basePackage,將包下的類按照一定規則註冊成Be ...
  • 操作系統 :CentOS 7.6_x64 opensips版本: 2.4.9 python版本:2.7.5 python作為腳本語言,使用起來很方便,查了下opensips的文檔,支持使用python腳本寫邏輯代碼。今天整理下CentOS7環境下opensips2.4.9的python模塊筆記及使用 ...