【go语言开发】本地缓存的使用,从简单到复杂写一个本地缓存,并对比常用的开源库

本文主要介绍go语言中本地缓存的使用,首先由简单到复杂手写3个本地缓存示例,使用内置的sync,map等数据结构封装cache,然后介绍常见的一些开源库,以及对比常用的开源库

文章目录

  • 前言
  • 手写本地缓存
    • CacheNormal
    • CacheEx
    • CacheV3
  • 开源库
    • cache2go
    • go-cache
    • bigcache
    • groupcache
    • 本地缓存对比

前言

本地缓存是指将一部分数据存储在应用程序本地内存中,以提高数据访问速度和应用程序性能的技术。
使用本地缓存的优势:

  • 提高应用程序性能
  • 减少网络延迟
  • 改善用户体验
  • 降低外部存储系统的负荷

下面我们从简单到复杂写本地缓存

手写本地缓存

CacheNormal

在 Go 中,你可以使用内置的 sync 包和 map 数据结构来实现本地缓存。

我们首先定义了一个名为 Cache 的结构体,其中包含一个 data 字段,它是一个 map[string]interface{} 类型的数据结构,用于存储键值对。我们使用 sync.RWMutex 来保证并发安全性。

然后,我们定义了 Set 方法和 Get 方法,用于设置和获取缓存值。在 Set 方法中,我们使用互斥锁 mu 来保证并发安全。在 Get 方法中,我们使用读写锁 mu 的读锁来实现并发读取。

package cacheimport ("sync"
)type CacheNormal struct {data map[string]interface{}mu   sync.RWMutex
}func NewCache() *CacheNormal {return &CacheNormal{data: make(map[string]interface{}),}
}func (c *CacheNormal) Set(key string, value interface{}) {c.mu.Lock()defer c.mu.Unlock()c.data[key] = value
}func (c *CacheNormal) Get(key string) (interface{}, bool) {c.mu.RLock()defer c.mu.RUnlock()value, ok := c.data[key]return value, ok
}

代码测试:

package cacheimport ("fmt""testing""time"
)func TestCacheNorm(t *testing.T) {cache := NewCache()// 设置缓存值cache.Set("key1", "value1")cache.Set("key2", "value2")// 读取缓存值value1, ok1 := cache.Get("key1")fmt.Println("Key1:", value1, ok1)value2, ok2 := cache.Get("key2")fmt.Println("Key2:", value2, ok2)// 等待一段时间time.Sleep(5 * time.Second)// 再次读取缓存值value1, ok1 = cache.Get("key1")fmt.Println("Key1:", value1, ok1)value2, ok2 = cache.Get("key2")fmt.Println("Key2:", value2, ok2)
}

结果展示:
在这里插入图片描述
下面我们实现一个带有过期时间的本地缓存。

CacheEx

要实现带有过期时间的本地缓存,可以使用 Go 的 sync 包和 map 数据结构结合定时器(time.Timer)来实现。

我们定义了一个名为 CacheEx 的结构体,其中包含了一个用于存储缓存项的 data 字段,并且还有一个用于接收过期键的通道 expireCh。

通过调用 NewCacheEx 函数创建一个新的缓存对象,该函数会启动一个协程 startCleanup 来定期清理过期的缓存项。

使用 Set 方法来设置缓存值,并指定缓存项的过期时间。在这个方法中,我们使用互斥锁来保证并发安全性,并将缓存项的过期时间和值存储在 data 中。同时,我们还使用 scheduleExpiration 方法来安排过期时的清理操作。

使用 Get 方法来获取缓存值。在这个方法中,我们使用读锁来进行并发读取,并检查缓存项是否过期。如果缓存项存在且未过期,则返回对应的值;否则返回空值。

package cacheimport ("sync""time"
)type CacheEx struct {data     map[string]cacheItemmu       sync.RWMutexexpireCh chan string
}type cacheItem struct {value      interface{}expiration time.Time
}func NewCacheEx() *CacheEx {c := &CacheEx{data:     make(map[string]cacheItem),expireCh: make(chan string),}go c.startCleanup()return c
}func (c *CacheEx) Set(key string, value interface{}, expiration time.Duration) {c.mu.Lock()defer c.mu.Unlock()expireTime := time.Now().Add(expiration)c.data[key] = cacheItem{value:      value,expiration: expireTime,}go c.scheduleExpiration(key, expireTime)
}func (c *CacheEx) Get(key string) (interface{}, bool) {c.mu.RLock()defer c.mu.RUnlock()item, ok := c.data[key]if ok && item.expiration.After(time.Now()) {return item.value, true}return nil, false
}func (c *CacheEx) Delete(key string) {c.mu.Lock()defer c.mu.Unlock()delete(c.data, key)
}func (c *CacheEx) startCleanup() {for {key := <-c.expireChc.Delete(key)}
}func (c *CacheEx) scheduleExpiration(key string, expireTime time.Time) {duration := time.Until(expireTime)timer := time.NewTimer(duration)<-timer.Cc.expireCh <- key
}

代码测试:

func TestCacheExpireTime(t *testing.T) {cache := NewCacheEx()// 设置缓存值,带有过期时间cache.Set("key1", "value1", 2*time.Second)cache.Set("key2", "value2", 5*time.Second)// 读取缓存值value1, ok1 := cache.Get("key1")fmt.Println("Key1:", value1, ok1)value2, ok2 := cache.Get("key2")fmt.Println("Key2:", value2, ok2)// 等待一段时间time.Sleep(3 * time.Second)// 再次读取缓存值value1, ok1 = cache.Get("key1")fmt.Println("Key1:", value1, ok1)value2, ok2 = cache.Get("key2")fmt.Println("Key2:", value2, ok2)
}

结果展示:
在这里插入图片描述

CacheV3

package cacheimport ("sync""time"
)type item struct {value      interface{}expiration int64
}type CacheV3 struct {items       sync.Maplock        sync.RWMutexdefaultTTL  time.DurationmaxCapacity intevictList   []interface{}
}func NewCacheV3(defaultTTL time.Duration, maxCapacity int) *CacheV3 {return &CacheV3{defaultTTL:  defaultTTL,maxCapacity: maxCapacity,evictList:   make([]interface{}, 0, maxCapacity),}
}func (c *CacheV3) Set(key string, value interface{}, ttl time.Duration) {c.lock.Lock()defer c.lock.Unlock()if c.cacheSize() >= c.maxCapacity {c.evict(1)}if ttl == 0 {ttl = c.defaultTTL}expiration := time.Now().Add(ttl).UnixNano()c.items.Store(key, &item{value, expiration})time.AfterFunc(ttl, func() {c.lock.Lock()defer c.lock.Unlock()if _, found := c.items.Load(key); found {c.items.Delete(key)c.evictList = append(c.evictList, key)}})
}func (c *CacheV3) Get(key string) (interface{}, bool) {c.lock.RLock()defer c.lock.RUnlock()if val, found := c.items.Load(key); found {item := val.(*item)if item.expiration > 0 && time.Now().UnixNano() > item.expiration {c.items.Delete(key)return nil, false}return item.value, true}return nil, false
}func (c *CacheV3) evict(count int) {for i := 0; i < count; i++ {key := c.evictList[0]c.evictList = c.evictList[1:]c.items.Delete(key)}
}func (c *CacheV3) cacheSize() int {size := 0c.items.Range(func(_, _ interface{}) bool {size++return true})return size
}

代码测试:

func TestCacheV3(t *testing.T) {c := NewCacheV3(time.Minute, 100)c.Set("key1", "value1", time.Second*30)c.Set("key2", "value2", time.Minute)val, found := c.Get("key1")if found {fmt.Println(val)}time.Sleep(time.Second * 45)val, found = c.Get("key1")if found {fmt.Println(val)}time.Sleep(time.Second * 30)val, found = c.Get("key1")if found {fmt.Println(val)} else {fmt.Println("key1 expired")}
}

结果展示:
在这里插入图片描述

开源库

cache2go

最新代码请参考:https://github.com/muesli/cache2go
以下代码仅供参考

type Item struct {//read write locksync.RWMutexkey  interface{}data interface{}// cache duration.duration time.Duration// create timecreateTime time.Time//last access timeaccessTime time.Time//visit timescount int64// callback after deletingdeleteCallback func(key interface{})
}//create item.
func NewItem(key interface{}, duration time.Duration, data interface{}) *Item {t := time.Now()return &Item{key:            key,duration:       duration,createTime:     t,accessTime:     t,count:          0,deleteCallback: nil,data:           data,}
}//keep alive
func (item *Item) KeepAlive() {item.Lock()defer item.Unlock()item.accessTime = time.Now()item.count++
}func (item *Item) Duration() time.Duration {return item.duration
}func (item *Item) AccessTime() time.Time {item.RLock()defer item.RUnlock()return item.accessTime
}func (item *Item) CreateTime() time.Time {return item.createTime
}func (item *Item) Count() int64 {item.RLock()defer item.RUnlock()return item.count
}func (item *Item) Key() interface{} {return item.key
}func (item *Item) Data() interface{} {return item.data
}func (item *Item) SetDeleteCallback(f func(interface{})) {item.Lock()defer item.Unlock()item.deleteCallback = f
}// table for managing cache items
type Table struct {sync.RWMutex//all cache itemsitems map[interface{}]*Item// trigger cleanupcleanupTimer *time.Timer// cleanup intervalcleanupInterval time.DurationloadData        func(key interface{}, args ...interface{}) *Item// callback after adding.addedCallback func(item *Item)// callback after deletingdeleteCallback func(item *Item)
}func (table *Table) Count() int {table.RLock()defer table.RUnlock()return len(table.items)
}func (table *Table) Foreach(trans func(key interface{}, item *Item)) {table.RLock()defer table.RUnlock()for k, v := range table.items {trans(k, v)}
}func (table *Table) SetDataLoader(f func(interface{}, ...interface{}) *Item) {table.Lock()defer table.Unlock()table.loadData = f
}func (table *Table) SetAddedCallback(f func(*Item)) {table.Lock()defer table.Unlock()table.addedCallback = f
}func (table *Table) SetDeleteCallback(f func(*Item)) {table.Lock()defer table.Unlock()table.deleteCallback = f
}func (table *Table) RunWithRecovery(f func()) {defer func() {if err := recover(); err != nil {fmt.Printf("occur error %v \r\n", err)}}()f()
}func (table *Table) checkExpire() {table.Lock()if table.cleanupTimer != nil {table.cleanupTimer.Stop()}if table.cleanupInterval > 0 {table.log("Expiration check triggered after %v for table", table.cleanupInterval)} else {table.log("Expiration check installed for table")}// in order to not take the lock. use temp items.items := table.itemstable.Unlock()//in order to make timer more precise, update now every loop.now := time.Now()smallestDuration := 0 * time.Secondfor key, item := range items {//take out our things, in order not to take the lock.item.RLock()duration := item.durationaccessTime := item.accessTimeitem.RUnlock()// 0 means valid.if duration == 0 {continue}if now.Sub(accessTime) >= duration {//cache item expired._, e := table.Delete(key)if e != nil {table.log("occur error while deleting %v", e.Error())}} else {//find the most possible expire item.if smallestDuration == 0 || duration-now.Sub(accessTime) < smallestDuration {smallestDuration = duration - now.Sub(accessTime)}}}//trigger next cleantable.Lock()table.cleanupInterval = smallestDurationif smallestDuration > 0 {table.cleanupTimer = time.AfterFunc(smallestDuration, func() {go table.RunWithRecovery(table.checkExpire)})}table.Unlock()
}// add item
func (table *Table) Add(key interface{}, duration time.Duration, data interface{}) *Item {item := NewItem(key, duration, data)table.Lock()table.log("Adding item with key %v and lifespan of %d to table", key, duration)table.items[key] = itemexpDur := table.cleanupIntervaladdedItem := table.addedCallbacktable.Unlock()if addedItem != nil {addedItem(item)}//find the most possible expire item.if duration > 0 && (expDur == 0 || duration < expDur) {table.checkExpire()}return item
}func (table *Table) Delete(key interface{}) (*Item, error) {table.RLock()r, ok := table.items[key]if !ok {table.RUnlock()return nil, errors.New(fmt.Sprintf("no item with key %s", key))}deleteCallback := table.deleteCallbacktable.RUnlock()if deleteCallback != nil {deleteCallback(r)}r.RLock()defer r.RUnlock()if r.deleteCallback != nil {r.deleteCallback(key)}table.Lock()defer table.Unlock()table.log("Deleting item with key %v created on %s and hit %d times from table", key, r.createTime, r.count)delete(table.items, key)return r, nil
}//check exist.
func (table *Table) Exists(key interface{}) bool {table.RLock()defer table.RUnlock()_, ok := table.items[key]return ok
}//if exist, return false. if not exist add a key and return true.
func (table *Table) NotFoundAdd(key interface{}, lifeSpan time.Duration, data interface{}) bool {table.Lock()if _, ok := table.items[key]; ok {table.Unlock()return false}item := NewItem(key, lifeSpan, data)table.log("Adding item with key %v and lifespan of %d to table", key, lifeSpan)table.items[key] = itemexpDur := table.cleanupIntervaladdedItem := table.addedCallbacktable.Unlock()if addedItem != nil {addedItem(item)}if lifeSpan > 0 && (expDur == 0 || lifeSpan < expDur) {table.checkExpire()}return true
}func (table *Table) Value(key interface{}, args ...interface{}) (*Item, error) {table.RLock()r, ok := table.items[key]loadData := table.loadDatatable.RUnlock()if ok {//update visit count and visit time.r.KeepAlive()return r, nil}if loadData != nil {item := loadData(key, args...)if item != nil {table.Add(key, item.duration, item.data)return item, nil}return nil, errors.New("cannot load item")}return nil, nil
}// truncate a table.
func (table *Table) Truncate() {table.Lock()defer table.Unlock()table.log("Truncate table")table.items = make(map[interface{}]*Item)table.cleanupInterval = 0if table.cleanupTimer != nil {table.cleanupTimer.Stop()}
}//support table sort
type ItemPair struct {Key         interface{}AccessCount int64
}type ItemPairList []ItemPairfunc (p ItemPairList) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
func (p ItemPairList) Len() int           { return len(p) }
func (p ItemPairList) Less(i, j int) bool { return p[i].AccessCount > p[j].AccessCount }//return most visited.
func (table *Table) MostAccessed(count int64) []*Item {table.RLock()defer table.RUnlock()p := make(ItemPairList, len(table.items))i := 0for k, v := range table.items {p[i] = ItemPair{k, v.count}i++}sort.Sort(p)var r []*Itemc := int64(0)for _, v := range p {if c >= count {break}item, ok := table.items[v.Key]if ok {r = append(r, item)}c++}return r
}// print log.
func (table *Table) log(format string, v ...interface{}) {//fmt.Printf(format+"\r\n", v)
}func NewTable() *Table {return &Table{items: make(map[interface{}]*Item),}
}

go-cache

https://github.com/patrickmn/go-cache

  • 优点:

    • 简单易用,适合快速集成到现有项目中。
    • 支持过期时间,可以自动淘汰过期的缓存项。
    • 支持多种数据类型的缓存。
  • 缺点:

    • 性能略低于其他库,不适合高并发读写的场景。
    • 不支持分布式缓存。

bigcache

https://github.com/allegro/bigcache

  • 优点:

    • 高性能,适用于需要快速读写大量数据的场景。
    • 使用murmurhash算法来计算哈希值,减少了哈希冲突。
    • 使用多个shard来减少锁竞争。
  • 缺点:

    • 不支持过期时间,只能手动清除过期的缓存项。
    • 内存使用较高,不适合存储大量数据。

groupcache

https://github.com/golang/groupcache

  • 优点:

    • 支持分布式缓存,可以在多台机器上共享缓存。
    • 采用LRU算法来淘汰缓存项,具备一定的缓存性能。
    • 提供一致性哈希算法,可以解决节点扩容等问题。
  • 缺点:

    • 比较复杂,使用起来较为繁琐。
    • 只支持字符串类型的键值对。

本地缓存对比

参考文档:

  • https://zhuanlan.zhihu.com/p/487455942

  • https://www.jianshu.com/p/0ff2e8c61c9c?tdsourcetag=s_pctim_aiomsg

在这里插入图片描述
下面对每个库的详细介绍:

  1. go-cache:
  • 描述:go-cache是一款简单而有效的内存缓存库,支持设置过期时间和GC机制。
  • 并发安全:是,使用Go的sync.Map实现数据的并发安全存储和访问。
  • 存储限制:无,可以存储任意类型的数据。
  • 淘汰策略:默认为LRU(最近最少使用)算法,也支持手动删除过期的缓存项。
  • 分布式支持:不支持。
  1. freecache:
  • 描述:freecache是一款高性能的内存缓存库,使用LRU算法进行缓存项的淘汰。
  • 并发安全:是,使用读写锁实现并发安全访问。
  • 存储限制:固定大小,需要在初始化时指定总共可以缓存的字节数。
  • 淘汰策略:默认为LRU(最近最少使用)算法,不支持自定义。
  • 分布式支持:不支持。
  1. bigcache:
  • 描述:bigcache是一款高性能的内存缓存库,使用murmurhash哈希算法快速查找。
  • 并发安全:是,使用多个读写锁来实现高并发的访问控制。
  • 存储限制:固定大小,需要在初始化时指定最多可以缓存的条目数。
  • 淘汰策略:默认为LRU(最近最少使用)算法,不支持自定义。
  • 分布式支持:不支持。
  1. groupcache:
  • 描述:groupcache是一款支持分布式缓存的库,提供一致性哈希和HTTP请求缓存功能。
  • 并发安全:是,使用读写锁实现并发安全访问。
  • 存储限制:无,可以存储任意类型的数据。
  • 淘汰策略:支持自定义淘汰策略,例如手动删除过期的缓存项。
  • 分布式支持:是,支持分布式缓存,将数据分片存储在多个节点上,通过查询一致性哈希环来确定数据所在的节点。
  1. gocache:
  • 描述:gocache是一款快速、强大的内存缓存库,支持过期时间、并发安全和自定义淘汰策略。
  • 并发安全:是,使用读写锁实现并发安全访问。
  • 存储限制:无,可以存储任意类型的数据。
  • 淘汰策略:默认为LRU(最近最少使用)算法,也支持自定义淘汰策略。
  • 分布式支持:不支持。

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

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

相关文章

基于单片机的太阳能数据采集系统(论文+源码)

1. 系统设计 在本次太阳能数据采集系统的设计中&#xff0c;以AT89C52单片机为主要核心&#xff0c;主要是由LCD液晶显示模块、存储模块、温度检测模块、串口通信模块&#xff0c;光照检测模块等组成&#xff0c;其实现了对太阳能板的温度&#xff0c;光照强度的检测和记录&…

SpringBoot2—开发实用篇3

目录 整合第三方技术 缓存 SpringBoot内置缓存解决方案 SpringBoot整合Ehcache缓存 SpringBoot整合Redis缓存 SpringBoot整合Memcached缓存 SpringBoot整合jetcache缓存 SpringBoot整合j2cache缓存 任务 Quartz Task 邮件 消息 Java处理消息的标准规范 购物订单…

git 常见错误总结(会不断更新中。。)

常见错误 1. 配置部署key后git clone还是拉不下代码 执行以下命令 先添加 SSH 密钥到 SSH 代理&#xff1a; 如果你使用 SSH 代理&#xff08;例如 ssh-agent&#xff09;&#xff0c;将生成的私钥添加到代理中。 ssh-add ~/.ssh/gstplatrontend/id_rsa如果报错以下错误信息…

详谈前端中常用的加/密算法

本文主要详细介绍了在前端开发中常用的加/解密算法&#xff0c;以及前端如何实现。 总的来说&#xff1a;前端加密无论使用哪个加密都一样是有可能性被他人获取到相关的公钥或密钥的&#xff08;比如&#xff1a;拦截请求、查看源代码等&#xff09;&#xff0c;然后进行加密与…

风速预测(五)基于Pytorch的EMD-CNN-LSTM模型

目录 前言 1 风速数据EMD分解与可视化 1.1 导入数据 1.2 EMD分解 2 数据集制作与预处理 2.1 先划分数据集&#xff0c;按照8&#xff1a;2划分训练集和测试集 2.2 设置滑动窗口大小为96&#xff0c;制作数据集 3 基于Pytorch的EMD-CNN-LSTM模型预测 3.1 数据加载&…

中医处方软件西医电子处方系统,一键生成处方单可设置配方模板教程

一、前言 有的诊所是中医和西医都有&#xff0c;医师是全科医师&#xff0c;那么所使用的软件既要能开中药处方也要能开西药处方&#xff0c;而且可以通过一键生成配方&#xff0c;则可以节省很多时间。 下面就以 佳易王诊所卫生室电子处方为例说明 如上图&#xff0c;如果是…

【C++】POCO学习总结(十七):日志系统(级别、通道、格式化、记录流)

【C】郭老二博文之&#xff1a;C目录 1、Poco::Message 日志消息 1.1 说明 所有日志消息都在Poco::Message对象中存储和传输。 头文件&#xff1a;#include “Poco/Message.h” 一条消息包含如下内容&#xff1a;优先级、来源、一个文本、一个时间戳、进程和线程标识符、可选…

微服务组件Sentinel的学习(2)

限流规则 流控模式直接模式关联模式链路模式 流控效果快速失败warm up排队等待 热点参数限流 流控模式 添加限流规则&#xff0c;可点击高级选项&#xff0c;有三种流控模式选择&#xff1a; 直接:统计当前资源的请求&#xff0c;触发闻值时对当前资源直接限流&#xff0c;也是…

Axure之动态面板轮播图

目录 一.介绍 二.好处 三.动态面板轮播图 四.动态面板多方式登录 五.ERP登录 六.ERP的左侧菜单栏 七.ERP的公告栏 今天就到这了哦&#xff01;&#xff01;&#xff01;希望能帮到你了哦&#xff01;&#xff01;&#xff01; 一.介绍 Axure中的动态面板是一个非常有用的组…

2024年视频监控行业发展趋势预测及EasyCVR视频分析技术应用

随着技术的改进&#xff0c;视频监控领域在过去十年迅速发展。与此同时&#xff0c;该行业正在通过先进创新技术&#xff08;如人工智能和云计算等技术&#xff09;的积极商业化&#xff0c;获得了新的增长机会。视频监控系统不再仅仅用于记录图像&#xff0c;而是已经成为全球…

力扣题目学习笔记(OC + Swift) 12. 整数转罗马数字

12. 整数转罗马数字 罗马数字包含以下七种字符&#xff1a; I&#xff0c; V&#xff0c; X&#xff0c; L&#xff0c;C&#xff0c;D 和 M。 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如&#xff0c; 罗马数字 2 写做 II &#xff0c;即为两个并列的 1。12 写做 XI…

LVM异常分析

环境信息 硬件环境 软件环境 相关软件包 云上鲲鹏RH220 操作系统&#xff1a;麒麟V10sp1-0711 系统自带多路径&#xff1a;multipath-tools-0.8.4-6 光纤连接华为存储Oceanstor18500 v5 内核版本&#xff1a;4.19.90 故障描述 云上鲲鹏RH220安装系统麒麟V10sp1-071…

ssm+vue的高校智能培训管理系统分析与设计(有报告)。Javaee项目,ssm vue前后端分离项目。

演示视频&#xff1a; ssmvue的高校智能培训管理系统分析与设计&#xff08;有报告&#xff09;。Javaee项目&#xff0c;ssm vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09…

npm ,yarn 更换使用国内镜像源,阿里源,清华大学源

在平时开发当中&#xff0c;我们经常会使用 Npm&#xff0c;yarn 来构建 web 项目。但是npm默认的源的服务器是在国外的&#xff0c;如果没有梯子的话。会感觉特别特别慢&#xff0c;所以&#xff0c;使用国内的源是非常有必要的。 在这里插入图片描述 Nnpm&#xff0c; yarn …

1130 - Host “WIN-CA4FHERGO9J‘ is not allowed to connect to this MySQL server

1、知识小课堂 1.1 Mysql MySQL是一个关系型数据库管理系统&#xff0c;由瑞典MySQL AB公司开发&#xff0c;属于Oracle旗下产品。它是最流行的关系型数据库管理系统之一&#xff0c;在WEB应用方面&#xff0c;MySQL是最好的RDBMS (Relational Database Management System&am…

elementui + vue2实现表格行的上下移动

场景&#xff1a; 如上&#xff0c;要实现表格行的上下移动 实现&#xff1a; <el-dialogappend-to-bodytitle"条件编辑":visible.sync"dialogVisible"width"60%"><el-table :data"data1" border style"width: 100%&q…

antd+vue:tree组件:父级节点禁止选择并不展示选择框——基础积累

antdvue:tree组件&#xff1a;父级节点禁止选择并不展示选择框——基础积累 1.判断哪些是父节点&#xff0c;给父节点添加disabled属性——this.permissionList是数据源2.通过css样式来处理disabled的父节点3.完整代码如下&#xff1a; 最近在写后台管理系统的时候&#xff0c;…

[GXYCTF2019]Ping Ping Ping (文件执行漏洞)

本题考点&#xff1a; 1、命令联合执行 2、命令绕过空格方法 3、变量拼接 1、命令联合执行 ; 前面的执行完执行后面的| 管道符&#xff0c;上一条命令的输出&#xff0c;作为下一条命令的参数&#xff08;显示后面的执行结果&#xff09;|| 当前面的执行出错时&#xff08;为…

C#winform实现单页面自由切换窗口

一、介绍 这是效果图&#xff0c;由于视频压缩画质很差&#xff0c;看个效果就好。 左侧是打开界面的按钮&#xff0c;点击左侧按钮右侧打开不同窗口&#xff0c;点击右侧窗口中的按钮&#xff0c;也可以切换页面&#xff0c;可以方便的进行返回、下一页等操作。 每个窗口打开…

JavaSE语法之七:封装

文章目录 一、封装的概念二、访问限定符三、封装扩展之包1. 包的概念2. 导入包中的类3. 自定义包4. 常见的包 四、实现封装五、static成员1. 再谈学生类成员变量2. static修饰成员变量3. static修饰成员方法4. static成员变量初始化 六、代码块1. 代码块概念及其分类2. 普通代码…