当前位置: 首页 > news >正文

C# 通用缓存类开发:开启高效编程之门

引言

嘿,各位 C# 开发者们!在当今快节奏的软件开发领域,提升应用程序的性能就如同给跑车装上涡轮增压,能让你的项目在激烈的竞争中脱颖而出。而构建一个高效的 C# 通用缓存类,无疑是实现这一目标的强大武器。

想象一下,用户在访问你的应用时,数据能够如同闪电般迅速加载,无需漫长的等待,这不仅能提升用户体验,还能增强用户对你应用的忠诚度。无论是 Web 应用、桌面程序还是移动应用,缓存技术都能发挥关键作用,显著减少数据获取的时间,降低数据库的负载压力。

在接下来的内容中,我将带领大家一步步深入探索 C# 通用缓存类的开发奥秘,从基础概念到实际代码实现,再到应对各种复杂场景的策略,让你全面掌握这一强大的技术,为你的 C# 项目注入强大的性能动力。准备好了吗?让我们一起开启这场充满挑战与惊喜的开发之旅吧!

一、缓存基础探秘

(一)缓存是什么

缓存,简单来说,就像是程序的 “快捷仓库”。它将那些被频繁访问的数据暂时存储在一个易于快速获取的地方 。当程序需要再次使用这些数据时,无需重新去原始数据源(如数据库)获取,也不用进行复杂的计算,直接从缓存中读取即可。这就好比你把常用的工具放在随手可及的抽屉里,需要时能立刻拿到,大大节省了寻找的时间。

在软件开发中,缓存的作用不可小觑。它能显著提升应用程序的性能,就像给程序装上了涡轮增压引擎。以一个电商网站为例,商品的基本信息,如名称、价格、图片等,这些数据在用户浏览商品页面时会被频繁请求。如果每次用户访问都从数据库中读取,随着访问量的增加,数据库的压力会越来越大,响应速度也会逐渐变慢。而通过设置缓存,将这些常用的商品信息存储在缓存中,当用户再次访问时,直接从缓存获取,大大减少了数据获取的时间,提升了用户体验。

此外,缓存还能有效减少对外部服务的依赖。很多应用程序需要调用第三方接口获取数据,而这些接口的响应时间可能不稳定,甚至会出现网络波动导致请求失败的情况。通过缓存数据,在第三方接口不可用或者响应缓慢时,应用程序依然可以从缓存中获取数据,维持基本的功能运转,提高了应用程序的稳定性和可靠性。

(二)为何要使用缓存

在实际应用场景中,缓存的优势体现得淋漓尽致。以网站数据加载为例,当用户访问一个新闻网站时,首页展示的新闻列表数据通常是相对固定的,不会频繁更新。如果每次用户刷新页面都要从数据库中重新查询新闻列表,不仅会增加数据库的负载,还会导致页面加载缓慢。通过缓存技术,将新闻列表数据缓存起来,在一定时间内,无论有多少用户访问首页,都直接从缓存中读取数据,极大地提高了页面的加载速度,让用户能够快速获取到新闻内容。

从资源消耗的角度来看,缓存可以有效降低数据库的访问压力。数据库的处理能力是有限的,大量的并发查询可能会使其不堪重负。通过缓存,将一部分频繁查询的数据存储在缓存中,减少了对数据库的直接访问次数,使得数据库能够更高效地处理其他必要的请求。这就好比在繁忙的交通路口设置了一些临时停车场,让部分车辆暂时停靠,缓解了主干道的交通拥堵。

缓存还能提升应用程序的可扩展性。随着业务的发展,用户量和数据量不断增加,如果没有缓存机制,应用程序可能很快就会因为性能问题而无法满足用户需求。而合理使用缓存,可以在不显著增加硬件成本的情况下,提升应用程序的性能,使其能够轻松应对不断增长的业务压力,为业务的拓展提供有力支持。

二、C# 通用缓存类系统架构

(一)CacheManager

CacheManager 就像是缓存世界的 “大管家” ,在整个缓存系统中扮演着极为重要的角色。它是我们进行缓存操作的主要入口点,为我们提供了一系列简洁而强大的方法,用于管理缓存项的增删查改。

想象一下,你有一个装满各种物品的仓库,CacheManager 就负责管理这个仓库的进出。当你需要将一些常用的数据存储到缓存中时,就像把物品存入仓库,调用 CacheManager 的 Set 方法即可轻松实现。而当你后续需要使用这些数据时,无需再去原始数据源费力查找,直接通过 CacheManager 的 Get 方法,就如同在仓库中快速找到对应的物品,高效地获取到缓存中的数据。

如果某些缓存数据已经不再需要,或者已经过期失效,CacheManager 的 Remove 方法就派上了用场,它能像清理仓库中的废弃物品一样,将这些无用的缓存项从缓存中移除,释放宝贵的资源。通过 CacheManager 的统一管理,我们能够更加方便、高效地操作缓存,让缓存系统为应用程序的性能提升发挥最大的作用。

(二)ICacheProvider

ICacheProvider 作为缓存管理器与实际存储之间的桥梁,其重要性不言而喻。它定义了一套基本的操作规范,就像是制定了一套统一的 “交通规则”,确保缓存管理器与不同类型的实际存储之间能够进行顺畅、准确的交互。

无论是将数据存储到内存中,还是保存到磁盘文件里,又或是其他类型的存储方式,ICacheProvider 都为这些操作提供了标准的接口定义。例如,Get 方法用于从存储中获取指定键对应的缓存项,就像是从仓库的特定位置取出物品;Set 方法负责将数据存入存储,如同将物品放入仓库的指定位置;Remove 方法则用于从存储中删除指定键的缓存项,类似于从仓库中清理掉不需要的物品。

有了 ICacheProvider 定义的这些基本操作,缓存管理器在进行缓存操作时,无需关心实际存储的具体实现细节,只需要按照 ICacheProvider 定义的接口进行调用即可。这使得我们的缓存系统具有高度的灵活性和可扩展性,方便我们根据实际需求轻松切换不同的缓存存储方式,而不会对上层的缓存管理逻辑产生太大影响。

(三)MemoryCacheProvider

MemoryCacheProvider 是众多缓存实现方式中最为简单直接的一种,它巧妙地借助了.NET 内置的 MemoryCache 类,将缓存项直接存储在内存之中。这种方式就好比把常用的工具放在伸手可及的桌面上,当程序需要获取缓存数据时,能够以极快的速度从内存中读取,大大提高了数据的访问效率。

在实际应用场景中,对于那些数据量相对较小、对读取速度要求极高的场景,MemoryCacheProvider 堪称最佳选择。例如,在一个实时性要求很高的股票交易监控系统中,需要频繁获取最新的股票价格数据。由于股票价格数据更新频繁,但每次获取的数据量不大,并且对响应速度要求极高,此时使用 MemoryCacheProvider 将股票价格数据缓存到内存中,当用户请求最新的股票价格时,能够瞬间从内存中获取到数据,满足系统对实时性的严格要求。

MemoryCacheProvider 的优势不仅仅在于读取速度快,它还具有实现简单、易于理解和使用的特点。在代码实现上,只需要创建一个 MemoryCache 实例,并通过该实例调用相应的方法进行缓存项的操作即可,无需复杂的配置和额外的依赖。这使得开发者能够快速上手,在项目中轻松集成内存缓存功能,为应用程序的性能优化迈出重要的一步。

(四)DiskCacheProvider

DiskCacheProvider 采用了一种不同的存储策略,它将缓存项存储在磁盘文件系统中。与内存存储相比,磁盘存储的速度虽然相对较慢,但却具有更高的可靠性和持久性。就好比把重要的文件存放在保险柜里,虽然取用可能需要多花一点时间,但不用担心数据会因为系统断电或其他意外情况而丢失。

在实际应用中,DiskCacheProvider 适用于那些数据量较大、对数据持久性要求较高的场景。例如,一个大型的文件存储系统,其中包含大量的用户上传的文件元数据。这些元数据虽然不经常变动,但数据量庞大,不适合全部存储在内存中。此时,使用 DiskCacheProvider 将这些文件元数据缓存到磁盘上,既能保证数据的安全性和持久性,又能在需要时通过磁盘读取获取到相应的数据。

DiskCacheProvider 在实现上,通过将缓存项序列化为二进制格式,并存储在磁盘的指定文件中。当需要获取缓存项时,再从文件中读取并反序列化。例如,在一个新闻网站的后台管理系统中,对于一些历史新闻数据的缓存,由于这些数据量较大且相对稳定,使用 DiskCacheProvider 将其缓存到磁盘上。当管理员需要查询历史新闻时,系统能够从磁盘缓存中读取到相应的数据,虽然读取速度可能比内存缓存稍慢,但完全能够满足业务需求,同时保证了数据的长期存储和可靠性。

三、缓存管理器的设计与实现

(一)缓存管理器接口定义

在 C# 通用缓存类的开发中,缓存管理器接口定义是构建整个缓存系统的基石。我们通过定义ICacheManager接口,为缓存操作提供了一套清晰、统一的规范。这就好比制定了一份详细的建筑蓝图,后续的缓存实现都将依据此蓝图进行构建。

public interface ICacheManager
{T Get<T>(string key);void Set<T>(string key, T value);void Remove(string key);
}

在这段代码中,Get(string key)方法犹如一把精准的 “数据钥匙”,它的作用是根据传入的唯一键key,从缓存中精准地获取对应类型T的数据。这里的T是泛型类型参数,它使得该方法具有极高的通用性,可以获取任意类型的数据,极大地增强了缓存系统的灵活性。例如,在一个电商系统中,如果要获取某个商品的详细信息,就可以通过Get(“product_123”)这样的调用方式,从缓存中获取到对应的商品对象Product。

Set(string key, T value)方法则像是一个 “数据仓库管理员”,负责将指定类型T的数据value,以给定的键key存储到缓存中。同样,泛型T的使用使得该方法能够存储各种类型的数据。继续以上述电商系统为例,当我们从数据库中查询到商品的详细信息后,就可以通过Set(“product_123”, product)将商品信息存储到缓存中,以便后续快速访问。

Remove(string key)方法则如同缓存的 “清理工”,它会根据传入的键key,将对应的缓存项从缓存中彻底移除。例如,当商品信息发生更新时,为了保证缓存中的数据与数据库中的最新数据一致,就需要调用Remove(“product_123”)方法,将旧的商品缓存项删除,以便下次获取时能够从数据库中获取最新数据并重新缓存 。

通过定义这样的接口,我们将缓存的核心操作进行了抽象,使得上层应用在使用缓存时,无需关心缓存的具体实现细节,只需要按照接口定义的方法进行调用即可。这不仅提高了代码的可维护性和可扩展性,还使得缓存系统的替换变得更加容易。例如,如果后续需要将缓存的存储方式从内存缓存切换为分布式缓存,只需要实现新的缓存管理器类并实现ICacheManager接口,上层应用代码无需进行大量修改,只需更换缓存管理器的实例即可。

(二)缓存管理器实现

在实现缓存管理器时,我们创建了CacheManager类,它肩负着具体执行缓存操作的重要使命。通过依赖注入的方式,CacheManager与ICacheProvider建立了紧密的协作关系,这种设计模式就像是为缓存系统安装了一个灵活的 “引擎切换装置”,使得我们能够轻松地在不同的缓存存储方式之间进行切换。

public class CacheManager : ICacheManager
{private readonly ICacheProvider _cacheProvider;public CacheManager(ICacheProvider cacheProvider){_cacheProvider = cacheProvider;}public T Get<T>(string key){return (T)_cacheProvider.Get(key);}public void Set<T>(string key, T value){_cacheProvider.Set(key, value);}public void Remove(string key){_cacheProvider.Remove(key);}
}

在CacheManager类中,首先定义了一个私有字段_cacheProvider,它用于存储通过构造函数注入的ICacheProvider实例。构造函数public CacheManager(ICacheProvider cacheProvider)接收一个ICacheProvider类型的参数cacheProvider,并将其赋值给_cacheProvider字段。这一过程就像是为缓存管理器选择了一个合适的 “存储引擎”,后续的所有缓存操作都将通过这个 “引擎” 来执行。

Get(string key)方法的实现非常简洁,它直接调用_cacheProvider.Get(key)方法从缓存中获取数据,并将返回的对象强制转换为类型T。这里通过依赖注入,CacheManager无需知道_cacheProvider的具体实现类,只需要调用其Get方法即可。例如,如果_cacheProvider是MemoryCacheProvider实例,那么它将从内存缓存中获取数据;如果是DiskCacheProvider实例,则会从磁盘缓存中获取数据。

Set(string key, T value)方法同样通过调用_cacheProvider.Set(key, value)方法,将数据存储到由_cacheProvider所代表的缓存存储中。这一过程实现了数据的持久化,无论是内存缓存还是磁盘缓存,都能通过这一统一的接口进行数据存储。

Remove(string key)方法则调用_cacheProvider.Remove(key)方法,将指定键的缓存项从缓存中移除。通过这种方式,CacheManager实现了对缓存项的增删查改操作,并且通过依赖注入的方式,将具体的缓存实现细节封装在ICacheProvider的实现类中,使得CacheManager的代码更加简洁、清晰,也提高了代码的可维护性和可扩展性。例如,如果需要添加一种新的缓存存储方式,只需要创建一个实现ICacheProvider接口的新类,并在需要使用该缓存方式的地方通过依赖注入将其传递给CacheManager即可,无需修改CacheManager的核心代码。

四、缓存提供者的实现细节

(一)缓存提供者接口定义

缓存提供者接口定义是实现不同缓存存储方式的基础规范,它就像是一把万能钥匙,为缓存管理器开启了通往各种存储介质的大门。通过定义ICacheProvider接口,我们明确了缓存操作与实际存储之间交互的标准方式。

public interface ICacheProvider
{object Get(string key);void Set(string key, object value);void Remove(string key);
}

在这个接口中,Get(string key)方法扮演着 “数据查找者” 的角色,它接收一个唯一标识缓存项的键key作为参数,然后在对应的存储介质中进行精准查找,最终返回与该键关联的缓存值。这个值的类型被定义为object,这意味着它具有很强的通用性,可以是任何类型的数据,无论是简单的数值、字符串,还是复杂的对象。例如,在一个社交媒体应用中,如果要获取某个用户的个人资料,就可以通过Get(“user_123”)这样的调用,从缓存中获取到该用户的详细信息对象。

Set(string key, object value)方法则如同 “数据存储师”,负责将数据存储到缓存中。它接收两个参数,键key用于唯一标识这个缓存项,方便后续的查找和管理;值value就是要存储的数据,可以是任意类型的对象。例如,当我们从数据库中查询到用户的最新动态后,就可以通过Set(“user_123_dynamic”, dynamicData)将用户的动态数据存储到缓存中,以便下次快速展示给用户。

Remove(string key)方法就像是缓存的 “清理卫士”,根据传入的键key,在缓存中找到对应的缓存项,并将其彻底删除。这在数据更新或不再需要某些缓存数据时非常有用。例如,当用户修改了自己的个人资料后,为了保证缓存中的数据与最新的数据库记录一致,就需要调用Remove(“user_123”)方法,删除旧的缓存数据,以便下次获取时能够从数据库中获取最新数据并重新缓存。

通过定义这样的接口,我们将缓存操作与具体的存储实现分离开来,使得代码具有更高的可维护性和可扩展性。后续在实现不同的缓存提供者时,只需要遵循这个接口定义,就能轻松地将新的缓存存储方式集成到我们的缓存系统中。

(二)内存缓存提供者实现

内存缓存提供者的实现借助了.NET 强大的内置工具MemoryCache类,它为我们提供了一种高效、便捷的内存缓存解决方案。MemoryCache类就像是一个超级快速的 “数据抽屉”,能够让我们在内存中快速存储和检索数据。

public class MemoryCacheProvider : ICacheProvider
{private readonly MemoryCache _cache;public MemoryCacheProvider(){_cache = new MemoryCache(new MemoryCacheOptions());}public object Get(string key){return _cache.Get(key);}public void Set(string key, object value){_cache.Set(key, value);}public void Remove(string key){_cache.Remove(key);}
}

在MemoryCacheProvider类中,首先定义了一个私有字段_cache,它是MemoryCache类型的实例,用于实际管理内存缓存。构造函数public MemoryCacheProvider()负责初始化这个_cache实例,通过创建一个新的MemoryCache对象,并传入一个MemoryCacheOptions对象来配置缓存的一些基本参数。这里使用默认的MemoryCacheOptions,意味着采用默认的缓存配置,如缓存的过期策略、内存限制等。

Get(string key)方法的实现非常简洁明了,它直接调用_cache.Get(key)方法,从内存缓存中获取与指定键key对应的缓存项。由于MemoryCache类的高效实现,这个操作能够在极短的时间内完成,非常适合对读取速度要求极高的场景。例如,在一个实时游戏排行榜系统中,频繁地获取玩家的排名数据,使用MemoryCacheProvider可以快速地从内存中获取最新的排名信息,保证玩家能够及时看到自己和其他玩家的排名变化。

Set(string key, object value)方法同样简单,它通过调用_cache.Set(key, value)方法,将指定的值value以给定的键key存储到内存缓存中。无论是简单的游戏得分数据,还是复杂的游戏角色信息,都可以通过这个方法轻松地存储到内存缓存中。

Remove(string key)方法则调用_cache.Remove(key)方法,从内存缓存中删除与指定键key对应的缓存项。当游戏中的某些数据发生变化,如玩家升级导致排名发生改变时,就可以使用这个方法删除旧的缓存数据,以便下次获取时能够更新缓存。

通过这种方式,MemoryCacheProvider实现了对内存缓存的基本操作,利用MemoryCache类的强大功能,为我们的缓存系统提供了高效的内存缓存支持。它的简单实现和高效性能,使得在许多场景下成为了缓存的首选方式之一。

(三)磁盘缓存提供者实现

磁盘缓存提供者的实现为我们提供了一种将缓存数据持久化存储在磁盘文件系统中的方式。这种方式虽然在读取速度上可能稍逊于内存缓存,但却具有数据持久化的优势,就像一个坚固的 “数据仓库”,能够在系统重启或内存资源紧张时,依然保留缓存数据。

public class DiskCacheProvider : ICacheProvider
{private const string CacheDirectory = "Cache";public object Get(string key){string filePath = Path.Combine(CacheDirectory, $"{key}.dat");if (!File.Exists(filePath)){return null;}using (FileStream fs = File.OpenRead(filePath)){BinaryFormatter formatter = new BinaryFormatter();return formatter.Deserialize(fs);}}public void Set(string key, object value){string filePath = Path.Combine(CacheDirectory, $"{key}.dat");using (FileStream fs = File.Create(filePath)){BinaryFormatter formatter = new BinaryFormatter();formatter.Serialize(fs, value);}}public void Remove(string key){string filePath = Path.Combine(CacheDirectory, $"{key}.dat");if (File.Exists(filePath)){File.Delete(filePath);}}
}

在DiskCacheProvider类中,首先定义了一个常量CacheDirectory,它指定了缓存文件存储的目录为 “Cache”。这个目录将用于存放所有的缓存文件,方便管理和组织。

Get(string key)方法负责从磁盘中读取缓存数据。它首先根据传入的键key构建出对应的缓存文件路径filePath,通过Path.Combine(CacheDirectory, $“{key}.dat”)将缓存目录和键名组合成完整的文件路径,其中.dat是自定义的文件扩展名,用于标识缓存文件。接着,使用File.Exists(filePath)方法检查该文件是否存在,如果文件不存在,说明缓存中没有对应的数据,直接返回null。如果文件存在,则使用FileStream打开该文件进行读取操作,并通过BinaryFormatter进行反序列化,将文件中的二进制数据转换回原始的对象形式,最后返回该对象。例如,在一个电商系统中,如果要获取某个商品的详细信息缓存,首先构建出对应的文件路径,检查文件是否存在,若存在则读取并反序列化,得到商品的详细信息对象。

Set(string key, object value)方法用于将数据存储到磁盘缓存中。同样先根据键key构建出文件路径filePath,然后使用File.Create(filePath)方法创建一个新的文件,如果文件已存在则会覆盖原有文件。接着,通过BinaryFormatter将传入的对象value序列化为二进制格式,并使用FileStream将其写入到文件中。这样,数据就被成功地存储到了磁盘缓存中。例如,当从数据库中查询到商品的最新价格和库存信息后,就可以通过这个方法将这些信息存储到磁盘缓存中。

Remove(string key)方法则负责从磁盘中删除缓存文件。它先根据键key构建出文件路径filePath,然后使用File.Exists(filePath)检查文件是否存在,如果存在则调用File.Delete(filePath)方法将文件删除,从而实现从磁盘缓存中移除对应的缓存项。例如,当商品信息发生重大变更,如商品下架时,就可以使用这个方法删除对应的缓存文件,确保下次获取时不会得到旧的无效数据。

通过这种方式,DiskCacheProvider实现了基于磁盘文件系统的缓存操作,为我们的缓存系统提供了一种可靠的数据持久化存储方案。在一些对数据持久性要求较高,且对读取速度要求相对不是特别苛刻的场景中,如一些历史数据的缓存、大型文件元数据的缓存等,磁盘缓存提供者发挥着重要的作用。

五、在项目中使用缓存管理器

(一)使用示例代码展示

下面通过一段完整的示例代码,展示如何在项目中灵活运用我们精心构建的缓存管理器。这段代码涵盖了从创建不同类型的缓存提供者,到构建缓存管理器,再到执行各种缓存操作的全过程。

using System;public class Program
{public static void Main(){// 创建内存缓存提供者ICacheProvider memoryCacheProvider = new MemoryCacheProvider();// 创建磁盘缓存提供者ICacheProvider diskCacheProvider = new DiskCacheProvider();// 使用内存缓存提供者创建缓存管理器ICacheManager memoryCacheManager = new CacheManager(memoryCacheProvider);// 使用磁盘缓存提供者创建缓存管理器ICacheManager diskCacheManager = new CacheManager(diskCacheProvider);// 使用内存缓存memoryCacheManager.Set("MyKey", "Hello, World!");Console.WriteLine(memoryCacheManager.Get<string>("MyKey"));// 使用磁盘缓存diskCacheManager.Set("MyDiskKey", "Hello, Disk World!");Console.WriteLine(diskCacheManager.Get<string>("MyDiskKey"));// 清除缓存memoryCacheManager.Remove("MyKey");diskCacheManager.Remove("MyDiskKey");}
}

(二)代码详细解释

  1. 创建缓存提供者
    • ICacheProvider memoryCacheProvider = new MemoryCacheProvider();:这行代码创建了一个MemoryCacheProvider实例,它负责将缓存数据存储在内存中。MemoryCacheProvider利用了.NET 内置的MemoryCache类,能够实现快速的数据读写操作,适用于对性能要求极高且数据量相对较小的场景。
    • ICacheProvider diskCacheProvider = new DiskCacheProvider();:此代码创建了DiskCacheProvider实例,它将缓存数据持久化存储在磁盘文件系统中。虽然磁盘读写速度相对内存较慢,但对于数据量较大、对数据持久性要求较高的场景,如存储大量的历史数据或配置信息,DiskCacheProvider是一个可靠的选择。
  1. 创建缓存管理器
    • ICacheManager memoryCacheManager = new CacheManager(memoryCacheProvider);:通过将memoryCacheProvider传递给CacheManager的构造函数,创建了一个基于内存缓存的缓存管理器memoryCacheManager。这个缓存管理器将负责管理内存中的缓存项,通过它可以方便地进行缓存的增删查改操作。
    • ICacheManager diskCacheManager = new CacheManager(diskCacheProvider);:同样,将diskCacheProvider传递给CacheManager的构造函数,创建了基于磁盘缓存的缓存管理器diskCacheManager。它用于管理存储在磁盘上的缓存项,为需要持久化缓存数据的场景提供支持。
  1. 使用内存缓存
    • memoryCacheManager.Set(“MyKey”, “Hello, World!”);:调用memoryCacheManager的Set方法,将键为MyKey,值为Hello, World!的字符串数据存储到内存缓存中。这里的Set方法会将数据传递给底层的MemoryCacheProvider,由它完成实际的存储操作。
    • Console.WriteLine(memoryCacheManager.Get(“MyKey”));:使用memoryCacheManager的Get方法,根据键MyKey从内存缓存中获取对应的数据,并将其转换为字符串类型后输出到控制台。Get方法会从MemoryCacheProvider中查找并返回相应的缓存值。
  1. 使用磁盘缓存
    • diskCacheManager.Set(“MyDiskKey”, “Hello, Disk World!”);:通过diskCacheManager的Set方法,将键为MyDiskKey,值为Hello, Disk World!的字符串数据存储到磁盘缓存中。DiskCacheProvider会将数据序列化为二进制格式,并存储在磁盘的指定文件中。
    • Console.WriteLine(diskCacheManager.Get(“MyDiskKey”));:调用diskCacheManager的Get方法,根据键MyDiskKey从磁盘缓存中读取数据,反序列化后将其转换为字符串类型,并输出到控制台。这个过程展示了如何从磁盘缓存中获取所需的数据。
  1. 清除缓存
    • memoryCacheManager.Remove(“MyKey”);:调用memoryCacheManager的Remove方法,根据键MyKey从内存缓存中删除对应的缓存项。这一步操作确保了内存缓存中不再存在该键值对,释放了相应的内存资源。
    • diskCacheManager.Remove(“MyDiskKey”);:使用diskCacheManager的Remove方法,根据键MyDiskKey从磁盘缓存中删除对应的缓存文件。这样可以保证磁盘缓存中的数据与实际需求保持一致,避免无效数据占用磁盘空间。

六、缓存失效策略深度解析

(一)基于时间的过期

基于时间的过期策略,是缓存失效策略中最为常见且基础的一种。其核心原理是为每个缓存项设定一个明确的有效时间,就如同给食品贴上保质期标签一样。当这个预设的有效时间到期后,缓存项就会被视为过期,自动从缓存中失效,或者在下次被访问时被清理掉。

在实际应用中,设定缓存项有效时间的方式多种多样。在 C# 中使用System.Web.Caching.Cache类时 ,可以通过以下代码来设置一个带有绝对过期时间的缓存项:

Cache cache = HttpRuntime.Cache;
cache.Insert("MyKey", "MyValue", null, DateTime.Now.AddMinutes(30), TimeSpan.Zero);

在这段代码中,DateTime.Now.AddMinutes(30)表示该缓存项将在当前时间的 30 分钟后过期。通过这种方式,我们可以确保缓存中的数据在一定时间范围内保持相对的新鲜度。

再比如,使用MemoryCache类时,也能轻松实现类似的功能:

var cacheOptions = new MemoryCacheOptions();
var cache = new MemoryCache(cacheOptions);
cache.Set("AnotherKey", "AnotherValue", DateTimeOffset.Now.AddHours(1));

这里DateTimeOffset.Now.AddHours(1)指定了缓存项在 1 小时后过期。这种基于时间的过期策略实现方式简单直接,适用于大多数数据更新频率相对稳定的场景。例如,在一个新闻资讯网站中,新闻列表数据的更新频率通常不会太快,我们可以将新闻列表的缓存设置为 30 分钟过期,这样既能保证在一段时间内用户能够快速获取新闻列表,又能确保在一定时间后缓存数据得到更新,展示最新的新闻资讯。

(二)基于空间的过期

基于空间的过期策略,主要用于应对缓存空间有限的情况。其工作机制类似于在一个容量固定的仓库中存储货物,当仓库快装满时,就需要清理出一些空间来存放新的货物。在缓存系统中,当缓存空间的使用量达到预设的上限时,基于空间的过期策略就会启动,自动淘汰掉一些旧的缓存项,以释放出足够的空间来存储新的数据。

在实现这一策略时,常见的算法有多种。其中,LRU(Least Recently Used,最近最少使用)算法是较为常用的一种。LRU 算法的核心思想是,如果一个数据在最近一段时间内很少被访问,那么在未来它被再次访问的概率也相对较低。因此,当缓存空间不足时,LRU 算法会优先淘汰那些最近最少使用的缓存项。

以一个简单的示例来说明 LRU 算法的工作过程。假设我们有一个缓存空间,最多能容纳 3 个缓存项,分别为 A、B、C。当用户依次访问了 A、B、C 后,缓存中的数据顺序为 C、B、A(C 为最近访问的,A 为最久未访问的)。此时,如果缓存空间已满,而又有新的数据 D 需要存入缓存,根据 LRU 算法,最久未被访问的 A 就会被淘汰,缓存中的数据变为 D、C、B。

除了 LRU 算法,LFU(Least Frequently Used,最不经常使用)算法也常被用于基于空间的过期策略。LFU 算法则是根据数据的访问频率来决定淘汰哪些缓存项。它认为,那些访问频率较低的数据在未来被再次访问的可能性也较小,因此在缓存空间不足时,优先淘汰访问频率最低的缓存项。

例如,在一个文件缓存系统中,随着文件的不断缓存,缓存空间逐渐减少。当达到空间上限时,使用 LRU 算法可以将那些长时间未被访问的文件缓存项淘汰掉,为新的文件缓存腾出空间。这样,既能保证缓存中始终保留着近期被频繁访问的文件,又能有效地管理缓存空间,确保缓存系统的高效运行。

(三)基于事件的过期

基于事件的过期策略,主要应用于那些数据更新与特定事件紧密相关的场景。其核心原理是,当数据源发生某些特定的变化事件时,与之对应的缓存项能够自动被清除,从而保证缓存中的数据与数据源始终保持一致。

在实际应用中,这种策略有着广泛的应用场景。在一个电商系统中,商品的库存数据是实时变化的。当商品的库存数量发生改变时,这就是一个关键的事件。此时,与该商品相关的缓存项,如商品详情页面的缓存、购物车中该商品的缓存等,都需要及时失效,以确保用户看到的是最新的库存信息。否则,可能会出现用户看到有库存但实际无法购买的情况,严重影响用户体验。

在实现基于事件的过期策略时,通常需要借助一些事件监听和发布机制。以使用 Redis 作为缓存为例,可以利用 Redis 的发布 / 订阅功能。当数据源发生变化时,系统发布一个特定的事件消息到 Redis 的某个频道上,而缓存管理模块则订阅这个频道。当缓存管理模块接收到该事件消息时,就会根据消息的内容,找到对应的缓存项并将其删除。

例如,在一个博客系统中,当博主发布了一篇新文章或者对已有文章进行了修改时,这是一个文章更新事件。此时,系统可以发布一个事件消息到 Redis 的 “article_update” 频道上。缓存管理模块订阅了这个频道,当接收到消息后,会自动删除与该文章相关的缓存项,如文章详情页面的缓存、文章列表的缓存等。这样,当用户再次访问这些页面时,系统会从数据库中获取最新的文章数据并重新缓存,保证用户看到的是最新的内容。

在一些分布式系统中,还可以使用消息队列来实现基于事件的过期策略。当数据源发生变化时,将事件消息发送到消息队列中,缓存管理模块从消息队列中获取消息并处理相应的缓存失效操作。这种方式可以有效地解耦数据源和缓存系统,提高系统的可扩展性和稳定性。

七、缓存使用的注意事项

(一)一致性问题

在缓存的使用过程中,一致性问题是一个需要重点关注的方面。由于缓存中的数据是数据源的副本,当数据源中的数据发生变化时,如果缓存未能及时更新,就会出现缓存与数据源不一致的情况。

以一个电商系统为例,商品的库存数据存放在数据库中,同时在缓存中也存有一份副本以提高查询速度。当用户下单购买商品时,数据库中的库存数据会相应减少,但如果此时缓存没有及时更新,其他用户查询该商品库存时,得到的仍然是缓存中的旧数据,就会出现库存显示与实际库存不一致的问题。这可能导致用户看到有库存但实际无法购买的情况,严重影响用户体验。

为了确保缓存与数据源的数据同步,我们可以采取以下解决方案。在数据更新时,采用先更新数据源,再删除缓存的策略。当商品库存发生变化时,首先在数据库中更新库存数据,然后将对应的缓存项删除。这样,下次查询该商品库存时,由于缓存中不存在该项,系统会从数据库中读取最新数据并重新缓存,从而保证了缓存与数据源的一致性。

也可以使用消息队列来实现缓存的异步更新。当数据源发生变化时,将更新消息发送到消息队列中,缓存系统监听消息队列,接收到消息后再对缓存进行相应的更新。这种方式可以解耦数据源和缓存系统,提高系统的可扩展性和稳定性。

(二)并发问题

当多个线程同时访问缓存时,可能会引发一系列问题,如数据竞争、脏读、幻读等。这些问题可能导致缓存数据的不一致性,进而影响整个系统的正确性和稳定性。

数据竞争是指多个线程同时对缓存中的同一数据进行读写操作,由于线程执行顺序的不确定性,可能会导致数据的错误更新。例如,在一个多线程的电商系统中,多个线程同时尝试对商品的销量数据进行更新,如果没有适当的同步机制,可能会出现部分更新丢失的情况,导致最终的销量数据不准确。

为了解决多线程访问缓存时的并发问题,我们可以采用多种解决方案。使用锁机制是一种常见的方法。在 C# 中,可以使用lock关键字来实现互斥访问。当一个线程需要对缓存中的数据进行更新时,先获取锁,确保在同一时间只有一个线程能够进行更新操作,其他线程需要等待锁的释放。示例代码如下:

private static readonly object _lockObject = new object();
public void UpdateCacheData(string key, object value)
{lock (_lockObject){// 执行缓存更新操作cache[key] = value;}
}

使用并发集合也是一种有效的解决方案。在 C# 的System.Collections.Concurrent命名空间中,提供了一些线程安全的集合类,如ConcurrentDictionary。ConcurrentDictionary内部实现了高效的并发控制机制,允许多个线程同时对集合进行读写操作,而无需额外的锁机制。示例代码如下:

private static readonly ConcurrentDictionary<string, object> cache = new ConcurrentDictionary<string, object>();
public void UpdateCacheData(string key, object value)
{cache[key] = value;
}

(三)缓存击穿问题

缓存击穿是指在高并发的情况下,某个热点数据的缓存过期瞬间,大量的请求同时访问该数据,由于缓存失效,这些请求会直接穿透到后端数据库,给数据库带来巨大的压力,甚至可能导致数据库崩溃。

在一个热门商品的抢购场景中,该商品的库存信息被缓存。当缓存过期的瞬间,大量用户同时请求购买该商品,由于缓存中没有库存数据,所有请求都会直接访问数据库,可能导致数据库负载过高,无法正常响应其他请求。

为了解决缓存击穿问题,我们可以采用以下解决方案。使用互斥锁是一种常见的方法。在缓存失效时,通过互斥锁(如Mutex)来控制只有一个请求能够访问数据库并重新加载缓存数据,其他请求则等待锁的释放。示例代码如下:

private static readonly Mutex _mutex = new Mutex();
public object GetData(string key)
{object data = cache.Get(key);if (data == null){_mutex.WaitOne();try{data = cache.Get(key);if (data == null){data = LoadDataFromDatabase(key);cache.Set(key, data);}}finally{_mutex.ReleaseMutex();}}return data;
}

还可以采用逻辑过期的方法。在缓存数据时,同时设置一个逻辑过期时间。当数据被读取时,判断是否逻辑过期,如果过期,则启动一个异步线程去更新缓存数据,而当前请求仍然返回缓存中的旧数据。这样可以避免大量请求同时穿透到数据库,示例代码如下:

public class CacheData
{public object Value { get; set; }public DateTime ExpirationTime { get; set; }
}
private static readonly Dictionary<string, CacheData> cache = new Dictionary<string, CacheData>();
public object GetData(string key)
{if (cache.TryGetValue(key, out CacheData cacheData)){if (cacheData.ExpirationTime > DateTime.Now){return cacheData.Value;}else{// 启动异步线程更新缓存Task.Run(() => UpdateCacheAsync(key));return cacheData.Value;}}return null;
}
private async Task UpdateCacheAsync(string key)
{object newData = await LoadDataFromDatabaseAsync(key);lock (cache){if (cache.TryGetValue(key, out CacheData cacheData) && cacheData.ExpirationTime <= DateTime.Now){cache[key] = new CacheData { Value = newData, ExpirationTime = DateTime.Now.AddMinutes(10) };}}
}

八、总结与展望

在这次 C# 通用缓存类开发的冒险中,我们一起深入探索了缓存的基础概念,精心构建了包含 CacheManager、ICacheProvider、MemoryCacheProvider 和 DiskCacheProvider 的通用缓存类系统架构。通过详细的代码实现,我们让缓存管理器能够灵活管理缓存,同时掌握了内存和磁盘两种缓存提供者的工作方式。在实际使用方面,我们学会了如何在项目中运用缓存管理器,并深入探讨了缓存失效策略,如基于时间、空间和事件的过期策略。此外,还着重强调了在使用缓存时需要注意的一致性、并发和缓存击穿等问题及其解决方案。

展望未来,随着技术的飞速发展,缓存技术在软件开发中的地位将愈发重要。在高并发、大数据的应用场景中,缓存技术将持续发挥关键作用,显著提升系统的性能和响应速度。未来,缓存技术有望朝着智能化、自动化的方向大步迈进。例如,通过人工智能和机器学习技术,缓存系统或许能够自动学习应用程序的数据访问模式,精准预测哪些数据需要缓存,以及何时更新缓存,从而实现更加高效、智能的缓存管理。

在分布式系统中,缓存技术也将迎来新的发展机遇和挑战。如何实现分布式缓存的高效一致性,确保在多个节点之间数据的准确同步,将是未来研究和发展的重点方向。同时,随着硬件技术的不断进步,新的存储介质可能会涌现,这也将为缓存技术的创新提供更多的可能性,推动缓存技术向更高性能、更低延迟的方向不断发展。

相关文章:

C# 通用缓存类开发:开启高效编程之门

引言 嘿&#xff0c;各位 C# 开发者们&#xff01;在当今快节奏的软件开发领域&#xff0c;提升应用程序的性能就如同给跑车装上涡轮增压&#xff0c;能让你的项目在激烈的竞争中脱颖而出。而构建一个高效的 C# 通用缓存类&#xff0c;无疑是实现这一目标的强大武器。 想象一…...

Mac安装Homebrew

目录 安装修改homeBrew源常用命令安装卸载软件升级软件相关清理相关 安装 官网 https://brew.sh/不推荐官网安装方式&#xff08;很慢很慢或者安装失败联网失败&#xff09; 检测是否安装homebrewbrew -v执行安装命令 苹果电脑 常规安装脚本 &#xff08;推荐 完全体 几分钟就…...

MySQL面试题2025 每日20道【其四】

1、你们生产环境的 MySQL 中使用了什么事务隔离级别&#xff1f;为什么&#xff1f; 中等 在生产环境中&#xff0c;MySQL数据库的事务隔离级别通常由开发团队或数据库管理员根据应用的需求来设定。MySQL支持四种标准的事务隔离级别&#xff1a; 读未提交&#xff08;Read Unc…...

maven 微服务项目多 包版本问题

mvn dependency:tree查看jar包直接的关系 找到重复的包!!!! 可以查看包版本问题 [INFO] | - org.jpedal:OpenViewerFX:jar:6.6.14:compile [INFO] | | - org.eclipse.birt.runtime.3_7_1:org.mozilla.javascript:jar:1.7.2:compile [INFO] | | - bouncycastle:bcprov-j…...

skipcrossnets模型详解及代码复现

模型背景 在SkipcrossNets模型提出之前,多模态融合在自动驾驶领域已取得显著进展。然而,传统的两流网络仅在特定层进行融合,需要大量人工尝试来设置,并且随着网络深度增加,模态特征差异增大,容易影响性能。 为解决这些问题,研究人员开发了SkipcrossNets模型,旨在提供…...

【0397】Postgres内核 checkpoint process ⑦ 获取 delaying checkpoint VXIDs(delayChkpt)

1. Top-level transactions 顶级事务(Top-level transactions)通过由 PGPROC 字段 backendId 和 lxid 组成的 VirtualTransactionIDs 来标识。对于已准备的事务,LocalTransactionId 是一个普通的 XID。这些在短期内保证唯一,但在数据库重启或 XID 滚转后会被重新使用;因此…...

Go语言-学习一

简介&#xff1a;Go语言经过多年的快速发展&#xff0c;已经被光广泛应用到各个领域&#xff0c;成为当前最热的计算机语言之一。Go自带许多高级特性及常用工具&#xff0c;使Go一出世就以高并发和高性能为程序员所追捧。 go语言特点 1、静态类型、编译型、开源 2、脚本化&…...

学习记录-统计记录场景下的Redis写请求合并优化实践

学习记录-使用Redis合并写请求来优化性能 1.业务背景 学习进度的统计功能:为了更精确的记录用户上一次播放的进度&#xff0c;采用的方案是&#xff1a;前端每隔15秒就发起一次请求&#xff0c;将播放记录写入数据库。但问题是&#xff0c;提交播放记录的业务太复杂了&#x…...

Java中json的一点理解

一、Java中json字符串与json对象 1、json本质 json是一种数据交换格式。 常说的json格式的字符串 > 发送和接收时都只是一个字符串&#xff0c;它遵循json这种格式。 2、前后端交互传输的json是什么&#xff1f; 前后端交互传输的json都是json字符串 比如&#xff1a;…...

用于牙科的多任务视频增强

Multi-task Video Enhancement for Dental Interventions 2022 miccai Abstract 微型照相机牢牢地固定在牙科手机上&#xff0c;这样牙医就可以持续地监测保守牙科手术的进展情况。但视频辅助牙科干预中的视频增强减轻了低光、噪音、模糊和相机握手等降低视觉舒适度的问题。…...

二、vue智能Ai对话(高仿通义千问)流式进阶版

1、安装依赖、启动 // 安装依赖 npm install // 启动服务 node server.js 2、浏览器运行html 3、流式进阶版视频地址&#xff1a;流式进阶版视频 4、各位如有需要&#xff0c;请下载源码包。...

Python新春烟花

目录 系列文章 写在前面 技术需求 完整代码 下载代码 代码分析 1. 程序初始化与显示设置 2. 烟花类 (Firework) 3. 粒子类 (Particle) 4. 痕迹类 (Trail) 5. 烟花更新与显示 6. 主函数 (fire) 7. 游戏循环 8. 总结 注意事项 写在后面 系列文章 序号直达链接爱…...

《自动驾驶与机器人中的SLAM技术》ch4:基于预积分和图优化的 GINS

前言&#xff1a;预积分图优化的结构 1 预积分的图优化顶点 这里使用 《自动驾驶与机器人中的SLAM技术》ch4&#xff1a;预积分学 中提到的散装的形式来实现预积分的顶点部分&#xff0c;所以每个状态被分为位姿&#xff08;&#xff09;、速度、陀螺零偏、加计零偏四种顶点&am…...

「2024·我的成长之路」:年终反思与展望

文章目录 1. 前言2.创作历程2.1 摆烂期2.2 转变期3. 上升期 2. 个人收获3.经验分享4. 展望未来 1. 前言 2025年1月16日&#xff0c;2024年博客之星入围公布&#xff0c;很荣幸获得了这次入围的机会。2024年对我个人是里程碑的一年&#xff0c;是意义非凡的一年&#xff0c;是充…...

P8738 [蓝桥杯 2020 国 C] 天干地支

两种方法 #include<bits/stdc.h> using namespace std;int main(){int year;cin>>year;string tg[10] {"geng", "xin", "ren", "gui","jia", "yi", "bing", "ding", "wu&…...

Linux网络 TCP socket

TCP简介 TCP&#xff08;Transmission Control Protocol&#xff09;是一种面向连接的、可靠的、基于字节流的传输层通信协议。它位于OSI模型的第四层&#xff0c;主要为应用层提供数据传输服务。TCP通过三次握手建立连接&#xff0c;确保数据在发送和接收过程中的准确性和顺序…...

IntelliJ IDEA 2023.3 中配置 Spring Boot 项目的热加载

IntelliJ IDEA 2023.3 中配置 Spring Boot 项目的热加载 在 IntelliJ IDEA 2023.3 中配置 Spring Boot 项目的热加载&#xff0c;可以让你在不重启应用的情况下看到代码修改的效果。以下是详细的配置步骤&#xff1a; 添加 spring-boot-devtools 依赖 在 pom.xml 文件中添加 …...

【网络协议】【http】【https】RSA+AES-TLS1.2

【网络协议】【http】【https】RSAAES-TLS1.2 https并不是一个协议 而是在传输层之间添加了SSL/TLS协议 TLS 协议用于应用层协议&#xff08;如 HTTP&#xff09;和传输层&#xff08;如 TCP&#xff09;之间&#xff0c;增加了一层安全性来解决 HTTP 存在的问题&#xff0c;H…...

Java中如何安全地停止线程?

大家好&#xff0c;我是锋哥。今天分享关于【Java中如何安全地停止线程?】面试题。希望对大家有帮助&#xff1b; Java中如何安全地停止线程? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在Java中&#xff0c;安全地停止线程是一项重要的任务&#xff0c;尤其…...

01.04、回文排序

01.04、[简单] 回文排序 1、题目描述 给定一个字符串&#xff0c;编写一个函数判定其是否为某个回文串的排列之一。回文串是指正反两个方向都一样的单词或短语。排列是指字母的重新排列。回文串不一定是字典当中的单词。 2、解题思路 回文串的特点&#xff1a; 一个回文串在…...

[深度学习]多层神经网络

多层神经网络 文章目录 多层神经网络单个神经元人类大脑神经与神经元神经元与矩阵神经元的串联激活函数激活函数位置神经网络的三种表现形式神经网络的参数&#xff08;可训练的&#xff09; 深度学习的训练过程全连接网络过拟合和欠拟合 单个神经元 一个神经元实际表示的数据公…...

JavaScript语言的正则表达式

JavaScript语言的正则表达式详解 正则表达式&#xff08;Regular Expression&#xff0c;简称Regex或RegExp&#xff09;是一种强大的文本处理工具&#xff0c;可以在字符串中执行模式匹配和替换操作。在JavaScript中&#xff0c;正则表达式是处理字符串时不可或缺的部分&…...

yolo系列模型为什么坚持使用CNN网络?

在深度学习领域&#xff0c;目标检测是一项至关重要的任务&#xff0c;而YOLO&#xff08;You Only Look Once&#xff09;系列模型无疑是这一领域的佼佼者。YOLO以其高效、准确的特点&#xff0c;在实时目标检测任务中占据了重要地位。然而&#xff0c;随着Transformer模型在自…...

Kotlin语言的数据结构

Kotlin语言的数据结构导论 Kotlin是一种现代化的编程语言&#xff0c;具有简洁、安全和高效的特点。Kotlin不仅支持面向对象编程&#xff0c;还融入了函数式编程的概念&#xff0c;使得开发者能够以更优雅的方式处理数据。在构建复杂应用时&#xff0c;数据结构的选择及其实现…...

光纤接口、GTX高速收发器基础知识学习、光口眼图测试--FPGA学习笔记28

----素材来源原子哥 一、光纤接口简介 光纤接口是用来连接光纤线缆的物理接口&#xff0c;简称为光口。其原理是利用了光从光密介质进入光疏介质从而发生了全反射。通常有 FC、 SC、 ST、 LC、 D4、 DIN、 MU、 MT 等等各种形式接口。 &#xff08;1&#xff09; SC 型光纤接…...

【k8s】k8s部署Argo CD

1、创建 Argo CD 命名空间&#xff1a; 先创建一个专用的命名空间 argocd 用于部署 Argo CD。 kubectl create namespace argocd 2、安装 Argo CD&#xff1a; 使用 kubectl 从 Argo CD 官方 GitHub 仓库安装它。运行以下命令来安装所有的 Argo CD 组件&#xff1a; kubectl a…...

PHP礼品兑换系统小程序

&#x1f381; 礼品兑换系统&#xff1a;革新企业礼品管理&#xff0c;专属神器来袭&#xff01; &#x1f4bb; 一款专为追求高效与个性化的现代企业量身打造的礼品兑换系统&#xff0c;它基于强大的ThinkPHP框架与前沿的Uniapp技术栈深度融合&#xff0c;不仅完美适配礼品卡…...

【SSH端口转发:实现安全的远程端口映射】

SSH端口转发&#xff1a;实现安全的远程端口映射 在网络应用开发和运维过程中&#xff0c;我们经常需要进行端口转发来实现各种网络访问需求。今天我要分享一个使用SSH进行端口转发的实用脚本&#xff0c;并详细讲解其工作原理。 脚本内容 免密 ssh-copy-id -p 20080 rootxx…...

2024年第十五届蓝桥杯青少组国赛(c++)真题—快速分解质因数

快速分解质因数 完整题目和在线测评可点击下方链接前往&#xff1a; 快速分解质因数_C_少儿编程题库学习中心-嗨信奥https://www.hixinao.com/tiku/cpp/show-3781.htmlhttps://www.hixinao.com/tiku/cpp/show-3781.html 若如其他赛事真题可自行前往题库中心查找&#xff0c;题…...

为什么你的 Qt 应用程序会出现 xcb 插件错误

有朋友咨询为什么他们的 Qt 应用程序在统信 UOS ARM 版本下运行&#xff0c;提示如下错误&#xff1a; qt.qpa.plugin: Could not find the Qt platform plugin "xcb" in "" This application failed to start because no Qt platform plugin could be i…...

ANSYS HFSS 中的相控天线阵列仿真方法

概述 相控天线阵列系统广泛使用&#xff0c;从国防雷达应用到商业 5G 应用。设计这些天线阵列涉及复杂的数学运算&#xff0c;需要全波仿真。Ansys HFSS 全场 3D 电磁仿真软件可以在合理的时间内以较低的计算成本仿真复杂的相控阵天线系统&#xff0c;同时考虑复杂激励、环境&…...

【记录】Jenkins版本及JDK关系介绍的官网地址

Redhat Jenkins Packages...

66,【6】buuctf web [HarekazeCTF2019]Avatar Uploader 1

进入靶场 习惯性输入admin 还想用桌面上的123.png 发现不行 看看给的源码 <?php // 关闭错误报告&#xff0c;可能会隐藏一些错误信息&#xff0c;在开发阶段可考虑开启&#xff08;例如 error_reporting(E_ALL)&#xff09; error_reporting(0); // 引入配置文件&#x…...

MECD+: 视频推理中事件级因果图推理--VLM长视频因果推理

论文链接&#xff1a;https://arxiv.org/pdf/2501.07227v1 1. 摘要及主要贡献点 摘要&#xff1a; 视频因果推理旨在从因果角度对视频内容进行高层次的理解。然而&#xff0c;目前的研究存在局限性&#xff0c;主要表现为以问答范式执行&#xff0c;关注包含孤立事件和基本因…...

pycharm+pyside6+desinger实现查询汉字笔顺GIF动图

一、引言 这学期儿子语文期末考试有一道这样的题目&#xff1a; 这道题答案是B&#xff0c;儿子做错了选了C。我告诉他“车字旁”和“车”的笔顺是不一样的&#xff0c;因为二者有一个笔画是不一样的&#xff0c;“车字旁”下边那笔是“提”&#xff0c;而“车”字是“横”&am…...

拟合算法 (matlab工具箱)

拟合算法&#xff1a; 1线性最小二乘法拟合 使用matlab进行求解 拟合优度&#xff1a;R^2 拟合优度的matlab代码&#xff1a; 2,Matlab工具箱的教学 一些函数: 拟合算法&#xff1a; 插值算法中&#xff0c;得到的多项式f(x)要经过所有样本点。但是如果样本点太多&#…...

联想电脑怎么用u盘装系统_联想电脑用u盘装win10系统教程

联想电脑怎么重装系统&#xff1f;在当今科技发展迅猛的时代&#xff0c;联想电脑已经成为了人们生活中不可或缺的一部分。然而&#xff0c;随着时间的推移&#xff0c;我们可能会遇到一些问题&#xff0c;例如系统崩溃或者需要更换操作系统。这时&#xff0c;使用U盘来重新安装…...

WPF2-在xaml为对象的属性赋值

1. AttributeValue方式 1.1. 简单属性赋值1.2. 对象属性赋值 2. 属性标签的方式给属性赋值3. 标签扩展 (Markup Extensions) 3.1. StaticResource3.2. Binding 3.2.1. 普通 Binding3.2.2. ElementName Binding3.2.3. RelativeSource Binding3.2.4. StaticResource Binding (带参…...

什么是报文的大端和小端,有没有什么记忆口诀?

在计算机科学中&#xff0c;**大端&#xff08;Big-Endian&#xff09;和小端&#xff08;Little-Endian&#xff09;**是两种不同的字节序&#xff08;即多字节数据在内存中的存储顺序&#xff09;。理解这两种字节序对于网络通信、文件格式解析以及跨平台编程等非常重要。 1…...

【2024 博客之星评选】请继续保持Passion

我尝试复盘自己2024年走的路&#xff0c;希望能给诸君一些借鉴。 文章目录 回头望感想与收获成长与教训今年计划感恩一些体己话 回头望 回望我的2024年&#xff0c;年初拿高绩效&#xff0c;但感觉逐渐被公司一点点剥离出中心&#xff1b;年中一直在学习防患于未然&#xff1b…...

网络通信---MCU移植LWIP

使用的MCU型号为STM32F429IGT6&#xff0c;PHY为LAN7820A 目标是通过MCU的ETH给LWIP提供输入输出从而实现基本的Ping应答 OK废话不多说我们直接开始 下载源码 LWIP包源码&#xff1a;lwip源码 -在这里下载 ST官方支持的ETH包&#xff1a;ST-ETH支持包 这里下载 创建工程 …...

Redis源码-redisObject

解释 redis中&#xff0c;所有的数据类型最终都转换成了redisObject&#xff0c;该结构体的定义&#xff0c;在文件server.h中。 参数说明 参数名说明unsigned type:4对象对应的数据类型unsigned encoding:4对象的编码方式unsigned lru:LRU_BITSLRU算法清空对象&#xff0c…...

YOLOv10-1.1部分代码阅读笔记-tuner.py

tuner.py ultralytics\engine\tuner.py 目录 tuner.py 1.所需的库和模块 2.class Tuner: 1.所需的库和模块 # Ultralytics YOLO &#x1f680;, AGPL-3.0 license# 此模块提供用于对象检测、实例分割、图像分类、姿势估计和多对象跟踪的 Ultralytics YOLO 模型的超参数调…...

【数据结构】二分查找

&#x1f6a9; WRITE IN FRONT &#x1f6a9; &#x1f50e; 介绍&#xff1a;"謓泽"正在路上朝着"攻城狮"方向"前进四" &#x1f50e;&#x1f3c5; 荣誉&#xff1a;2021|2022年度博客之星物联网与嵌入式开发TOP5|TOP4、2021|2222年获评…...

iOS-支付相关

支付宝支付 #import <AlipaySDK/AlipaySDK.h> //orderStrAliPay为服务端传的订单信息 //fromScheme为应用配置的schemeUrl标识&#xff0c;用户支付包支付成功后跳转会本应用内 //callback回调需要在- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url 中调…...

ubuntu16.04 VSCode下cmake+clang+lldb调试c++

VSCode下cmakeclanglldb调试c Ubuntu16.04 安装OpenCV4.5.4 文章目录 VSCode下cmakeclanglldb调试c1.安装clangclangdcmake2、打开VSCode&#xff0c;安装扩展插件3、编译4、Debug4.1 创建launch.json。4.2 配置setting.json 5. vscode安装配置clang-format插件5.1 Linux系统安…...

学Python的人…

学Python的人… 一、Python能干什么&#xff1f; 1.爬虫&#xff1a;前几年&#xff0c;深度学习还没发展起来的时候&#xff0c;书店里Python就和爬虫挂钩&#xff0c;因为Python写爬虫确实方便。 2.数据分析&#xff1a;Python有各种的数据分析库可以方便使用&#xff0…...

GDB相比IDE有什么优点

GDB(GNU Debugger)相比于集成开发环境(IDE)具有一些独特的优点,主要体现在其灵活性、可定制性和低级控制能力。具体来说,GDB有以下几个优点: 1. 轻量级且无依赖 GDB是一个命令行工具,不依赖于任何复杂的图形界面或大型库,这使得它非常适合在资源受限的环境中使用,比…...

Docker 镜像加速的配置

解决拉取镜像报错&#xff1a;Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while 在使用 Docker 过程中&#xff0c;拉取镜像的速度常常会受到网络状况的影响&#xff0c;尤其是在国内网络环境下&#xff0c;…...

分布式多卡训练(DDP)踩坑

多卡训练最近在跑yolov10版本的RT-DETR&#xff0c;用来进行目标检测。 单卡训练语句&#xff08;正常运行&#xff09;&#xff1a; python main.py多卡训练语句&#xff1a; 需要通过torch.distributed.launch来启动&#xff0c;一般是单节点&#xff0c;其中CUDA_VISIBLE…...