7天用Go从零实现分布式缓存GeeCache(学习)(3)

目录结构

├── geecache
│   ├── byteview.go
│   ├── cache.go
│   ├── consistenthash
│   │   ├── consistenthash.go
│   │   └── consistenthash_test.go
│   ├── geecache.go
│   ├── go.mod
│   ├── http.go
│   ├── lru
│   │   └── lru.go
│   ├── peers.go
│   └── singleflight
│       └── singleflight.go
├── go.mod
└── main└── main.go 

在这里插入图片描述


文件分析

1. lru/lru.go

1.1 结构体定义
  • Cache

    type Cache struct {maxBytes  int64nbytes    int64ll        *list.Listcache     map[string]*list.ElementOnEvicted func(key string, value Value)
    }
    
    • 字段解释
      • maxBytes:缓存的最大容量(字节数)。
      • nbytes:当前已使用的缓存大小(字节数)。
      • ll:双向链表,*list.List,用于记录元素的访问顺序。
      • cache:字典(哈希表),map[string]*list.Element,键为字符串,值为链表元素的指针,方便快速查找。
      • OnEvicted:可选的回调函数,当元素被移除时调用。
  • entry

    type entry struct {key   stringvalue Value
    }
    
    • 字段解释
      • key:缓存项的键。
      • value:缓存项的值,实现了 Value 接口。
  • Value 接口

    type Value interface {Len() int
    }
    
    • 方法
      • Len():返回值所占的字节数。
1.2 函数签名
  • 构造函数

    func New(maxBytes int64, onEvicted func(string, Value)) *Cache
    
  • 方法

    • 添加元素:

      func (c *Cache) Add(key string, value Value)
      
    • 获取元素:

      func (c *Cache) Get(key string) (value Value, ok bool)
      
    • 移除最久未使用的元素:

      func (c *Cache) RemoveOldest()
      
    • 获取缓存中元素的数量:

      func (c *Cache) Len() int
      
1.3 函数调用关系
  • Cache.Add 方法:

    • 检查键是否存在于缓存中。
      • 如果存在,更新值,移动元素到链表头部。
      • 如果不存在,创建新的元素,添加到链表头部。
    • 更新已使用的缓存大小 nbytes
    • 调用 Cache.RemoveOldest,在超过容量时移除最久未使用的元素。
  • Cache.Get 方法:

    • cache 字典中查找键。
      • 如果找到,移动元素到链表头部,返回值。
      • 如果未找到,返回 nil
  • Cache.RemoveOldest 方法:

    • 从链表尾部获取最久未使用的元素。
    • 从链表和 cache 字典中移除该元素。
    • 更新已使用的缓存大小 nbytes
    • 如果设置了 OnEvicted 回调函数,调用该函数。

2. consistenthash/consistenthash.go

2.1 结构体定义
  • Hash 类型

    type Hash func(data []byte) uint32
    
    • 定义哈希函数类型,接收 []byte,返回 uint32
  • Map

    type Map struct {hash     Hashreplicas intkeys     []inthashMap  map[int]string
    }
    
    • 字段解释
      • hash:哈希函数,类型为 Hash,用于计算键的哈希值。
      • replicas:每个真实节点的虚拟节点数。
      • keys:哈希环,存储所有虚拟节点的哈希值(已排序)。
      • hashMap:虚拟节点哈希值与真实节点名称的映射表。
2.2 函数签名
  • 构造函数

    func New(replicas int, fn Hash) *Map
    
  • 方法

    • 添加节点:

      func (m *Map) Add(keys ...string)
      
    • 获取节点:

      func (m *Map) Get(key string) string
      
2.3 函数调用关系
  • Map.Add 方法:

    • 对于每个真实节点,创建 replicas 个虚拟节点。
    • 计算每个虚拟节点的哈希值,添加到 keys 切片中。
    • 更新 hashMap 映射表。
    • 调用 sort.Ints(m.keys) 对哈希环进行排序。
  • Map.Get 方法:

    • 计算给定 key 的哈希值。
    • 使用 sort.Searchkeys 切片中查找第一个大于等于该哈希值的虚拟节点索引。
    • 通过 hashMap 获取对应的真实节点。

3. singleflight/singleflight.go

3.1 结构体定义
  • call

    type call struct {wg  sync.WaitGroupval interface{}err error
    }
    
    • 字段解释
      • wg:同步等待组,用于等待请求完成。
      • val:请求的结果。
      • err:请求的错误信息。
  • Group

    type Group struct {mu sync.Mutexm  map[string]*call
    }
    
    • 字段解释
      • mu:互斥锁,保护对 m 的并发访问。
      • m:存储进行中的请求,键为请求的 key
3.2 函数签名
  • 方法

    func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error)
    
3.3 函数调用关系
  • Group.Do 方法:

    • 检查请求的 key 是否正在进行中。
      • 如果是,等待已有的请求完成,返回结果。
      • 如果不是,创建新的 call,添加到 m 中。
    • 执行传入的函数 fn,获取结果。
    • m 中删除已完成的请求。

4. byteview.go

4.1 结构体定义
  • ByteView

    type ByteView struct {b []byte
    }
    
    • 字段解释
      • b:存储实际数据的字节切片。
4.2 函数签名
  • 方法

    • 获取长度:

      func (v ByteView) Len() int
      
    • 返回数据的拷贝:

      func (v ByteView) ByteSlice() []byte
      
    • 返回字符串表示:

      func (v ByteView) String() string
      
  • 辅助函数

    func cloneBytes(b []byte) []byte
    
4.3 函数调用关系
  • ByteView.Len 方法:

    • 返回 b 的长度。
  • ByteView.ByteSlice 方法:

    • 调用 cloneBytes 返回 b 的拷贝。
  • ByteView.String 方法:

    • b 转换为字符串。
  • cloneBytes 函数:

    • 创建新的字节切片,复制 b 的内容。

5. cache.go

5.1 结构体定义
  • cache

    type cache struct {mu         sync.Mutexlru        *lru.CachecacheBytes int64
    }
    
    • 字段解释
      • mu:互斥锁,保护对 lru 的并发访问。
      • lru:指向 lru.Cache 的指针,实际的缓存实现。
      • cacheBytes:缓存的最大容量。
5.2 函数签名
  • 方法

    • 添加缓存项:

      func (c *cache) add(key string, value ByteView)
      
    • 获取缓存项:

      func (c *cache) get(key string) (value ByteView, ok bool)
      
5.3 函数调用关系
  • cache.add 方法:

    • 加锁保护。
    • 检查 lru 是否为 nil,如果是则初始化。
    • 调用 lru.Add 方法添加缓存项。
  • cache.get 方法:

    • 加锁保护。
    • 检查 lru 是否为 nil
    • 调用 lru.Get 方法获取缓存项。

6. geecache.go

6.1 结构体和接口定义
  • Group

    type Group struct {name      stringgetter    GettermainCache cachepeers     PeerPickerloader    *singleflight.Group
    }
    
    • 字段解释
      • name:缓存组名称。
      • getter:数据加载接口,实现 Getter 接口。
      • mainCache:主缓存,类型为前述的 cache
      • peers:节点选择器,实现 PeerPicker 接口。
      • loader:单次请求防护,使用 singleflight.Group
  • Getter 接口

    type Getter interface {Get(key string) ([]byte, error)
    }
    
  • GetterFunc 类型

    type GetterFunc func(key string) ([]byte, error)
    
    • 方法

      func (f GetterFunc) Get(key string) ([]byte, error)
      
6.2 函数签名
  • 构造函数

    func NewGroup(name string, cacheBytes int64, getter Getter) *Group
    
  • 全局函数

    func GetGroup(name string) *Group
    
  • Group 方法

    • 获取缓存项:

      func (g *Group) Get(key string) (ByteView, error)
      
    • 注册节点选择器:

      func (g *Group) RegisterPeers(peers PeerPicker)
      
  • 内部方法

    • 加载数据:

      func (g *Group) load(key string) (value ByteView, err error)
      
    • 本地加载数据:

      func (g *Group) getLocally(key string) (ByteView, error)
      
    • 从远程节点加载数据:

      func (g *Group) getFromPeer(peer PeerGetter, key string) (ByteView, error)
      
    • 填充缓存:

      func (g *Group) populateCache(key string, value ByteView)
      
6.3 函数调用关系
  • NewGroup 函数:

    • 创建 Group 实例,初始化 mainCacheloader
  • Group.Get 方法:

    • 调用 mainCache.get 获取缓存项。
    • 如果缓存未命中,调用 g.load 加载数据。
  • Group.load 方法:

    • 使用 g.loader.Do 确保并发情况下对相同的 key 只会加载一次。
    • 优先调用 g.peers.PickPeer 选择远程节点,调用 g.getFromPeer 从远程获取数据。
    • 如果远程获取失败或没有远程节点,调用 g.getLocally 本地加载数据。
  • Group.getLocally 方法:

    • 调用 g.getter.Get 从本地数据源获取数据。
    • 调用 g.populateCache 将数据填充到缓存中。
  • Group.getFromPeer 方法:

    • 调用 peer.Get 从远程节点获取数据。
  • Group.populateCache 方法:

    • 调用 mainCache.add 将数据添加到缓存中。

7. http.go

7.1 结构体和接口定义
  • HTTPPool

    type HTTPPool struct {self        stringbasePath    stringmu          sync.Mutexpeers       *consistenthash.MaphttpGetters map[string]*httpGetter
    }
    
    • 字段解释
      • self:当前节点的地址。
      • basePath:处理请求的基础路径。
      • mu:互斥锁,保护并发访问。
      • peers:一致性哈希环,类型为 *consistenthash.Map
      • httpGetters:远程节点的 httpGetter 映射。
  • httpGetter

    type httpGetter struct {baseURL string
    }
    
    • 字段解释
      • baseURL:远程节点的基础 URL。
7.2 函数签名
  • 构造函数

    func NewHTTPPool(self string) *HTTPPool
    
  • HTTPPool 方法

    • 日志记录:

      func (p *HTTPPool) Log(format string, v ...interface{})
      
    • 处理 HTTP 请求:

      func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request)
      
    • 设置远程节点:

      func (p *HTTPPool) Set(peers ...string)
      
    • 选择远程节点:

      func (p *HTTPPool) PickPeer(key string) (PeerGetter, bool)
      
  • httpGetter 方法

    func (h *httpGetter) Get(group string, key string) ([]byte, error)
    
7.3 函数调用关系
  • NewHTTPPool 函数:

    • 创建 HTTPPool 实例,初始化 selfbasePath
  • HTTPPool.ServeHTTP 方法:

    • 解析请求路径,获取 groupkey
    • 调用 GetGroup 获取对应的 Group
    • 调用 group.Get 获取缓存项。
    • 将结果写入响应。
  • HTTPPool.Set 方法:

    • 创建一致性哈希环 consistenthash.Map
    • 调用 p.peers.Add 添加节点。
    • 为每个节点创建对应的 httpGetter
  • HTTPPool.PickPeer 方法:

    • 调用 p.peers.Get 根据 key 选择远程节点。
    • 返回对应的 httpGetter
  • httpGetter.Get 方法:

    • 构造请求 URL。
    • 发送 HTTP GET 请求获取数据。

8. peers.go

8.1 接口定义
  • PeerPicker 接口

    type PeerPicker interface {PickPeer(key string) (peer PeerGetter, ok bool)
    }
    
  • PeerGetter 接口

    type PeerGetter interface {Get(group string, key string) ([]byte, error)
    }
    
8.2 接口关系
  • HTTPPool 实现了 PeerPicker 接口。
  • httpGetter 实现了 PeerGetter 接口。

总体调用关系和结构体组合

1. 结构体组合情况

  • Group

    • 包含 mainCache,类型为 cache
    • 包含 loader,类型为 *singleflight.Group
    • 包含 peers,接口类型为 PeerPicker
  • cache

    • 包含 lru,类型为 *lru.Cache
  • lru.Cache

    • 使用 Go 标准库的 container/list 作为底层实现。
  • HTTPPool

    • 包含 peers,类型为 *consistenthash.Map
    • 包含 httpGetters,存储 httpGetter
  • consistenthash.Map

    • 包含 hashMap,映射虚拟节点哈希值到真实节点。

2. 函数调用关系概览

  • 客户端请求数据:

    • 调用 Group.Get 方法。
      • 尝试从本地缓存 mainCache 获取数据。
      • 如果缓存未命中,调用 Group.load 加载数据。
        • 使用 singleflight.Group 确保并发情况下只加载一次。
        • 如果设置了 peers,调用 PeerPicker.PickPeer 选择远程节点。
          • 如果找到远程节点,调用 PeerGetter.Get 从远程获取数据。
        • 如果未找到远程节点或远程获取失败,调用 Group.getLocally 本地加载数据。
          • 调用 Getter.Get 从数据源获取数据。
          • 调用 Group.populateCache 将数据添加到缓存。
  • HTTPPool 处理 HTTP 请求:

    • 调用 ServeHTTP 方法。
      • 解析请求,获取 groupkey
      • 调用 Group.Get 获取数据。
      • 将数据返回给客户端。
  • httpGetter 作为远程节点的客户端:

    • 实现 PeerGetter 接口。
    • 调用 Get 方法,通过 HTTP 请求从远程节点获取数据。

详细说明组合部分

总体概览

主要结构体及其关系:

  • Group:缓存的核心,代表一个命名空间,负责处理缓存的获取和加载。

    • 包含一个 cache 实例(mainCache),用于存储本地缓存数据。
    • 使用 Getter 接口定义的 getter 来从数据源加载数据。
    • 使用 singleflight.Grouploader)防止缓存击穿时的并发重复请求。
    • 可选地包含一个 PeerPicker 接口(peers),用于在分布式环境中选择节点。
  • cache:缓存的具体实现,内部使用 LRU 算法管理数据。

    • 包含一个 lru.Cache 实例(lru),负责存储缓存数据和 LRU 淘汰策略。
    • 使用互斥锁 sync.Mutexmu)确保并发安全。
  • lru.Cache:LRU 缓存的实现,使用双向链表和字典存储数据。

    • 包含一个双向链表 *list.Listll)和一个字典 map[string]*list.Elementcache),实现 O(1) 的访问和淘汰。
    • 每个缓存项是一个 entry,包含键和值。
  • ByteView:表示缓存值的不可变视图,实现了 lru.Value 接口。

    • 内部存储一个字节切片 []byteb)。
  • consistenthash.Map:一致性哈希的实现,用于在多个节点间分配键。

    • 维护了一个哈希环(keys)和虚拟节点到真实节点的映射(hashMap)。
  • HTTPPool:实现了 PeerPicker 接口,管理节点间的 HTTP 通信。

    • 包含一个一致性哈希的 consistenthash.Mappeers)和节点到 httpGetter 的映射(httpGetters)。
  • httpGetter:实现了 PeerGetter 接口,用于通过 HTTP 从远程节点获取数据。

  • singleflight.Group:用于防止并发请求相同的键时,重复加载数据。

下面,我们将逐一介绍各个结构体的定义、它们之间的组合关系,以及它们在系统中的角色。

结构体组合详情

1. Group 结构体

文件geecache/geecache.go

type Group struct {name      string              // 缓存的命名空间getter    Getter              // 缓存未命中时获取源数据的回调mainCache cache               // 并发安全的本地缓存peers     PeerPicker          // 分布式场景下的节点选择器loader    *singleflight.Group // 防止缓存击穿的请求合并
}
组合关系
  • 包含 cache 实例Group 内嵌了一个 cache 类型的字段 mainCache,用于管理本地的缓存数据。

  • 使用 Getter 接口Group 通过 getter 字段持有一个实现了 Getter 接口的实例,用于在缓存未命中时加载源数据。

  • 持有 PeerPicker 接口Group 通过 peers 字段持有一个实现了 PeerPicker 接口的实例(如 HTTPPool),用于在分布式环境中根据键选择远程节点。

  • 使用 singleflight.GroupGroup 使用 loader 字段来持有一个 singleflight.Group 实例,防止并发请求相同的键时重复加载数据。

功能描述
  • Group 是缓存的核心结构,负责提供缓存的获取、加载以及与其他节点的交互。

2. cache 结构体

文件geecache/cache.go

type cache struct {mu         sync.Mutex // 互斥锁,保护并发访问lru        *lru.Cache // LRU 缓存的实例cacheBytes int64      // 最大缓存字节数
}
组合关系
  • 包含 lru.Cache 实例cache 结构体内部持有一个指向 lru.Cache 的指针 lru,用于实际存储缓存数据和执行 LRU 淘汰策略。

  • 使用互斥锁cache 使用互斥锁 mu 来保护对 lru 的并发访问,确保线程安全。

功能描述
  • cache 是对 lru.Cache 的封装,增加了并发控制,并提供了延迟初始化的机制。

3. lru.Cache 结构体

文件geecache/lru/lru.go

type Cache struct {maxBytes  int64                    // 最大缓存容量nbytes    int64                    // 当前已使用的缓存容量ll        *list.List               // 双向链表,记录访问顺序cache     map[string]*list.Element // 键到链表节点的映射OnEvicted func(key string, value Value) // 可选的回调函数
}
  • entry 结构体
type entry struct {key   string // 键value Value  // 值,实现了 `Value` 接口
}
组合关系
  • 使用标准库的 list.Listlru.Cache 使用双向链表 ll 来记录缓存项的访问顺序,以便实现 LRU 淘汰策略。

  • 使用字典存储缓存项cache 字段是一个字典,映射键到链表节点,方便快速查找缓存项。

  • 定义了 Value 接口:缓存项的值需要实现 Value 接口,该接口定义了 Len() 方法,用于计算值的大小。

功能描述
  • lru.Cache 实现了基本的 LRU 缓存功能,提供了添加、获取和移除缓存项的方法。

4. ByteView 结构体

文件geecache/byteview.go

type ByteView struct {b []byte // 存储实际的数据
}
组合关系
  • 实现了 lru.Value 接口ByteView 实现了 Len() 方法,满足 lru.Cache 对值类型的要求。

  • 不可变性ByteView 通过只暴露数据的拷贝,确保了数据的不可变性,防止外部修改缓存中的数据。

功能描述
  • ByteView 是缓存值的载体,封装了字节切片,提供了只读的方法访问数据。

5. consistenthash.Map 结构体

文件geecache/consistenthash/consistenthash.go

type Map struct {hash     Hash           // 哈希函数replicas int            // 每个节点的虚拟节点数keys     []int          // 哈希环,存储所有虚拟节点的哈希值hashMap  map[int]string // 虚拟节点与真实节点的映射
}
组合关系
  • 使用哈希函数Map 通过 hash 字段持有一个哈希函数,用于计算键和节点的哈希值。

  • 维护虚拟节点和真实节点的映射hashMap 字段存储了虚拟节点的哈希值与真实节点名称的映射关系。

功能描述
  • consistenthash.Map 实现了一致性哈希算法,用于在分布式环境中根据键选择节点,确保数据分布的均衡性和稳定性。

6. HTTPPool 结构体

文件geecache/http.go

type HTTPPool struct {self        string                 // 本机地址basePath    string                 // HTTP 路径前缀mu          sync.Mutex             // 互斥锁peers       *consistenthash.Map    // 一致性哈希环,存储所有节点httpGetters map[string]*httpGetter // 远程节点的 HTTP 客户端
}
组合关系
  • 实现 PeerPicker 接口HTTPPool 实现了 PeerPicker 接口中的 PickPeer 方法,能够根据键选择远程节点。

  • 使用 consistenthash.MapHTTPPool 使用一致性哈希环 peers 来管理所有的节点,并根据键选择对应的节点。

  • 维护节点到 httpGetter 的映射httpGetters 字段存储了远程节点地址到 httpGetter 的映射,用于与远程节点进行通信。

功能描述
  • HTTPPool 负责处理 HTTP 请求,并管理节点间的通信,在分布式缓存中扮演重要角色。

7. httpGetter 结构体

文件geecache/http.go

type httpGetter struct {baseURL string // 远程节点的地址
}
组合关系
  • 实现 PeerGetter 接口httpGetter 实现了 PeerGetter 接口中的 Get 方法,能够通过 HTTP 从远程节点获取数据。
功能描述
  • httpGetter 是一个 HTTP 客户端,用于向远程节点发送请求并获取数据。

8. PeerPickerPeerGetter 接口

文件geecache/peers.go

type PeerPicker interface {PickPeer(key string) (peer PeerGetter, ok bool)
}type PeerGetter interface {Get(group string, key string) ([]byte, error)
}
组合关系
  • Group 使用 PeerPickerGroup 通过持有 PeerPicker 接口的实例,能够在分布式环境中根据键选择远程节点。

  • HTTPPool 实现 PeerPickerHTTPPool 实现了 PickPeer 方法,能够根据键选择合适的远程节点。

  • httpGetter 实现 PeerGetterhttpGetter 实现了 Get 方法,能够从远程节点获取数据。

功能描述
  • 这两个接口定义了节点选择和数据获取的行为,使得 Group 能够在本地和远程之间灵活地获取数据。

结构体之间的交互关系

下面,我们将以数据获取的流程为例,详细说明各个结构体是如何交互的。

数据获取流程

  1. 客户端请求数据:客户端调用 Group.Get(key) 方法,尝试获取指定键的数据。

  2. Group 检查本地缓存Group 调用 mainCache.get(key) 方法,尝试从本地缓存中获取数据。

    • cache.get(key) 方法内部加锁,确保线程安全。
    • 如果 lru 未初始化,则返回未命中。
    • 如果缓存命中,返回数据。
  3. 缓存未命中,使用 singleflight 防止并发请求

    • Group 调用 loader.Do(key, func() (interface{}, error)) 方法,确保相同的键在并发情况下只会请求一次。
  4. 尝试从远程节点获取数据

    • Group 检查是否配置了 peersPeerPicker 接口的实现)。

    • 如果配置了,调用 peers.PickPeer(key) 方法,选择远程节点。

    • 如果成功选择到远程节点,调用 getFromPeer(peer, key) 方法,从远程节点获取数据。

      • getFromPeer 调用 peer.Get(group.Name, key) 方法。
      • peer 是一个实现了 PeerGetter 接口的实例,如 httpGetter
      • 远程节点处理请求,返回数据。
  5. 从本地数据源获取数据

    • 如果未配置远程节点,或者从远程节点获取失败,Group 调用 getLocally(key) 方法,从本地数据源加载数据。

      • getLocally 调用 getter.Get(key) 方法,从本地数据源获取数据。
      • getter 是一个实现了 Getter 接口的实例,由用户提供。
  6. 将数据添加到缓存

    • 无论数据是从远程节点还是本地数据源获取的,Group 都会调用 populateCache(key, value) 方法,将数据添加到本地缓存。

      • populateCache 调用 mainCache.add(key, value) 方法。
      • cache.add 方法内部加锁,确保线程安全。
      • 如果 lru 未初始化,进行初始化。
      • 调用 lru.Add(key, value) 方法,将数据添加到缓存中。
  7. 返回数据给客户端Group 将获取到的数据返回给客户端。

结构体交互图示

Client|v
Group|-- mainCache (cache)|       |-- lru (lru.Cache)||-- peers (PeerPicker)|       |-- HTTPPool|               |-- consistenthash.Map|               |-- httpGetters (map[string]*httpGetter)||-- getter (Getter)||-- loader (*singleflight.Group)

结构体组合的优势

  • 模块化设计:各个结构体职责明确,方便维护和扩展。

  • 接口解耦:通过接口(GetterPeerPickerPeerGetter),各个模块之间实现了松耦合,方便替换和扩展。

  • 组合复用:通过组合(composition)而非继承,各个结构体可以灵活地嵌入和复用。

  • 并发安全:在需要并发控制的地方,使用互斥锁 sync.Mutex,确保线程安全。

  • 性能优化:使用 singleflight.Group 防止缓存击穿时的并发重复请求,提高系统性能。

总结

  • 缓存模块lru 包提供了基础的 LRU 缓存功能,被 cache 包装,cache 又被 Group 使用。
  • 数据加载模块Group 使用 Getter 接口从本地数据源加载数据,使用 singleflight.Group 防止并发重复加载。
  • 分布式节点管理consistenthash 包实现了一致性哈希算法,HTTPPool 使用它来选择远程节点。
  • 通信模块HTTPPool 作为服务端处理请求,httpGetter 作为客户端请求远程节点的数据。

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

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

相关文章

力扣 LeetCode 142. 环形链表II(Day2:链表)

解题思路&#xff1a; 使用set判断是否重复添加&#xff0c;如果set加入不进去证明之前到达过该节点&#xff0c;有环 public class Solution {public ListNode detectCycle(ListNode head) {Set<ListNode> set new HashSet<>();ListNode cur head;while (cur …

CLion配置QT开发环境

一、将qmake工程转为cmake工程&#xff08;方法一&#xff1a;用工具转换并做适当修改&#xff09; 1、工具链接&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1grW2QY3sW8X2JaHWM_ePPw 提取码&#xff1a;7at4 工具源码:https://github.com/milahu/qmake2cmake 2、执行…

《AI 使生活更美好》

《AI 使生活更美好》 当我们步入科技腾飞的时代&#xff0c;人工智能&#xff08;AI&#xff09;如同一颗璀璨的新星&#xff0c;照亮了我们生活的每一个角落。它以惊人的速度改变着我们的世界&#xff0c;从医疗到教育&#xff0c;从交通到娱乐&#xff0c;AI 正以前所未有的力…

项目模块十七:HttpServer模块

一、项目模块设计思路 目的&#xff1a;实现HTTP服务器搭建 思想&#xff1a;设计请求路由表&#xff0c;记录请求方法与对应业务的处理函数映射关系。用户实现请求方法和处理函数添加到路由表&#xff0c;服务器只接受请求并调用用户的处理函数即可。 处理流程&#xff1a; …

ODOO学习笔记(1):ODOO的SWOT分析和技术优势是什么?

ODOO是一款开源的企业管理软件套件&#xff0c;广泛应用于企业管理中。它由比利时的Odoo S.A.公司开发&#xff0c;最初名为OpenERP&#xff0c;现在已经成为全球流行的ERP解决方案之一。ODOO集成了ERP、CRM、电子商务和CMS等多种功能模块&#xff0c;适用于各种规模的企业应用…

出海攻略,如何一键保存Facebook视频素材

提词宝&#xff1a;快速保存Facebook视频教程 目标人群与痛点 目标人群&#xff1a;经常在Facebook上浏览视频但不知道如何保存的用户&#xff0c;包括学生、内容创作者、营销从业者&#xff0c;以及需要保存重要视频素材的人。 痛点与场景&#xff1a; 看到喜欢的视频&…

【Playwright + Python】系列(十)利用 Playwright 完美处理 Dialogs 对话框

哈喽&#xff0c;大家好&#xff0c;我是六哥&#xff01;今天我来给大家分享一下如何使用playwight处理Dialogs对话框&#xff0c;面向对象为功能测试及零基础小白&#xff0c;这里我尽量用大白话的方式举例讲解&#xff0c;力求所有人都能看懂&#xff0c;建议大家先收藏&…

LLM - 使用 LLaMA-Factory 微调大模型 Qwen2-VL SFT(LoRA) 图像数据集 教程 (2)

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/143725947 免责声明&#xff1a;本文来源于个人知识与公开资料&#xff0c;仅用于学术交流&#xff0c;欢迎讨论&#xff0c;不支持转载。 LLaMA-…

自动驾驶3D目标检测综述(一)

文章地址&#xff1a;[2206.09474] 3D Object Detection for Autonomous Driving: A Comprehensive Survey (arxiv.org) 这篇综述简单易懂&#xff0c;非常合适对自动驾驶和3D目标检测感兴趣的小白阅读&#xff0c;对相关算法进行初步理解。 目录 一、摘要 &#xff08;一&a…

回归分析学习

学习视频链接&#xff1a; 【回归分析,一套搞定】&#xff01;全网最通俗易懂的回归分析教程&#xff0c;我终于学明白了&#xff01;_哔哩哔哩_bilibili 相关分析&#xff1a;2个或两个以上的变量之间的相关程度及大小的统计方法&#xff1b; 回归分析&#xff1a;存在相关关…

LabVIEW 实现 find_nearest_neighbors 功能(二维平面上的最近邻查找)

1. 背景介绍 在数据分析和图像处理领域&#xff0c;经常需要查找给定点的最近邻居点。在LabVIEW中&#xff0c;计算二维平面上多个点之间的欧氏距离&#xff0c;并返回距离最近的几个点是一种常见操作。find_nearest_neighbors 函数用于实现这个功能。 2. 欧氏距离计算 在二维…

后端:Aop 面向切面编程

文章目录 1. Aop 初步学习面向切面编程&#xff0c;EnableAspectJAutoProxy2. AOP的核心概念3. 前置通知&#xff08;Before&#xff09;4. 后置通知&#xff08;After&#xff09;5. 返回通知&#xff08;AfterReturning&#xff09;6. 异常通知&#xff08;AfterThrowing&…

无插件H5播放器EasyPlayer.js网页web无插件播放器vue和react详细介绍

EasyPlayer.js H5播放器&#xff0c;是一款能够同时支持HTTP、HTTP-FLV、HLS&#xff08;m3u8&#xff09;、WS、WEBRTC、FMP4视频直播与视频点播等多种协议&#xff0c;支持H.264、H.265、AAC、G711A、Mp3等多种音视频编码格式&#xff0c;支持MSE、WASM、WebCodec等多种解码方…

管家婆财贸ERP BB045.销售批量收款

最低适用版本: 财贸系列 22.8 插件简要功能说明: 销售类单据支持批量收款,简化收款做单流程更多细节描述见下方详细文档插件操作视频: 进销存类定制插件--销售批量收款 插件详细功能文档: 1. 应用中心增加菜单【销售批量收款】 a. 参考23.0应用中心-移动管理-物联宝-【…

基于MATLAB+opencv人脸疲劳检测

我们可以通过多种方式从现实世界中获取数字图像&#xff0c;比如&#xff1a;数码相机、扫描仪、计算机扫描和磁共振成像等等。在这些情况中&#xff0c;虽然我们肉眼看到的是图像&#xff0c;但是当需要将图像在数字设备中变换传输时&#xff0c;图像的每个像素则对应一个数值…

Prompt 工程

Prompt 工程 1. Prompt 工程简介 “预训练-提示预测”范式是近年来自然语言处理&#xff08;NLP&#xff09;领域的一个重要趋势&#xff0c;它与传统的“预训练-微调-预测”范式相比&#xff0c;提供了一种更为灵活和高效的模型应用方式。 Prompt工程是指在预训练的大型语言…

【Python TensorFlow】进阶指南(续篇一)

在前两篇文章中&#xff0c;我们介绍了TensorFlow的基础知识及其在实际应用中的初步使用&#xff0c;并探讨了更高级的功能和技术细节。本篇将继续深入探讨TensorFlow的高级应用&#xff0c;包括但不限于模型压缩、模型融合、迁移学习、强化学习等领域&#xff0c;帮助读者进一…

yolov7论文翻译

YOLOv7: Trainable bag-of-freebies sets new state-of-the-art for real-time object detectors 论文&#xff1a;https://arxiv.org/abs/2207.02696 代码&#xff1a;https://github.com/WongKinYiu/yolov7 摘要 YOLOv7 在速度和准确性方面均超越了所有已知的目标检测器&a…

Java基于SpringBoot+Vue的宠物共享平台的设计与实现(附源码,文档)

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…