1
0
mirror of https://github.com/charlienet/go-mixed.git synced 2025-07-18 08:32:40 +08:00

17 Commits

Author SHA1 Message Date
04aecd4abc new 2022-11-21 16:18:54 +08:00
1f8789e7eb RemoveAt 2022-11-18 17:35:01 +08:00
299e09f8e3 ignore 2022-11-18 16:58:00 +08:00
ea846a321a cache 2022-11-18 16:57:23 +08:00
278c8b4cb7 maps 2022-11-18 16:57:15 +08:00
cf30b4eb4c collection 2022-11-18 16:56:56 +08:00
f3ca69f159 list 2022-11-18 16:56:44 +08:00
bd85140a78 redis 2022-11-18 16:56:12 +08:00
b76be4ce6b update 2022-11-18 16:53:59 +08:00
6e24cf5bdc boom filter 2022-11-18 16:52:43 +08:00
abe445f5e6 list 2022-11-16 17:31:28 +08:00
ac346274c1 update 2022-11-10 10:37:47 +08:00
823cd62148 update 2022-11-09 17:28:42 +08:00
d49c02924c expr 2022-11-09 17:21:30 +08:00
9203c3719f 日历相关计算 2022-11-09 17:16:24 +08:00
132bb0d0e2 add lock 2022-10-10 11:29:07 +08:00
716a199c9b 添加存储 2022-10-10 11:07:59 +08:00
61 changed files with 2102 additions and 318 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
*.o
fs/logs/**
.idea/**

View File

@ -1,92 +1,113 @@
package bloom
import (
"github.com/bits-and-blooms/bitset"
"github.com/charlienet/go-mixed/locker"
"math"
"github.com/charlienet/go-mixed/bytesconv"
"github.com/charlienet/go-mixed/expr"
"github.com/charlienet/go-mixed/hash"
"github.com/go-redis/redis/v8"
)
const DEFAULT_SIZE = 2 << 24
var seeds = []uint{7, 11, 13, 31, 37, 61}
type simplehash struct {
cap uint
seed uint
type bitStore interface {
Clear()
Set(pos ...uint) error
Test(pos ...uint) (bool, error)
}
type BloomFilter struct {
size int // 布隆过滤器大小
set *bitset.BitSet // 位图
funcs [6]simplehash // 哈希函数
lock locker.RWLocker
bits uint // 布隆过滤器大小
funcs uint // 哈希函数数量
store bitStore // 位图存储
}
type bloomOptions struct {
Size int
redisClient *redis.Client
redisKey string
}
type option func(*bloomOptions)
// 布隆过滤器中所有位长度,请根据存储数量进行评估
func WithSize(size int) option {
func WithRedis(redis *redis.Client, key string) option {
return func(bo *bloomOptions) {
bo.Size = size
bo.redisClient = redis
bo.redisKey = key
}
}
func NewBloomFilter(opts ...option) *BloomFilter {
opt := &bloomOptions{
Size: DEFAULT_SIZE,
}
// New 初始化布隆过滤器
// https://pages.cs.wisc.edu/~cao/papers/summary-cache/node8.html
func New(expectedInsertions uint, fpp float64, opts ...option) *BloomFilter {
opt := &bloomOptions{}
for _, f := range opts {
f(opt)
}
bits := optimalNumOfBits(expectedInsertions, fpp)
k := optimalNumOfHashFunctions(bits, expectedInsertions)
bf := &BloomFilter{
size: opt.Size,
lock: locker.NewRWLocker(),
bits: bits,
funcs: k,
store: expr.Ternary[bitStore](
opt.redisClient == nil,
newMemStore(bits),
newRedisStore(opt.redisClient, opt.redisKey, bits)),
}
for i := 0; i < len(bf.funcs); i++ {
bf.funcs[i] = simplehash{uint(opt.Size), seeds[i]}
}
bf.set = bitset.New(uint(opt.Size))
return bf
}
func (bf *BloomFilter) Add(value string) {
funcs := bf.funcs[:]
for _, f := range funcs {
bf.set.Set(f.hash(value))
func (bf *BloomFilter) Add(data string) {
offsets := bf.geOffsets([]byte(data))
bf.store.Set(offsets...)
}
func (bf *BloomFilter) ExistString(data string) (bool, error) {
return bf.Exists(bytesconv.StringToBytes(data))
}
func (bf *BloomFilter) Contains(value string) bool {
if value == "" {
return false
}
ret := true
funcs := bf.funcs[:]
for _, f := range funcs {
ret = ret && bf.set.Test(f.hash(value))
func (bf *BloomFilter) Exists(data []byte) (bool, error) {
if data == nil || len(data) == 0 {
return false, nil
}
return ret
offsets := bf.geOffsets(data)
isSet, err := bf.store.Test(offsets...)
if err != nil {
return false, err
}
return isSet, nil
}
func (bf *BloomFilter) geOffsets(data []byte) []uint {
offsets := make([]uint, bf.funcs)
for i := uint(0); i < bf.funcs; i++ {
offsets[i] = uint(hash.Murmur3(append(data, byte(i))) % uint64(bf.bits))
}
return offsets
}
// 清空布隆过滤器
func (bf *BloomFilter) Clear() {
bf.set.ClearAll()
bf.store.Clear()
}
func (s simplehash) hash(value string) uint {
var result uint = 0
for i := 0; i < len(value); i++ {
result = result*s.seed + uint(value[i])
// 计算优化的位图长度,
// n 期望放置元素数量,
// p 预期的误判概率
func optimalNumOfBits(n uint, p float64) uint {
return (uint)(-float64(n) * math.Log(p) / (math.Log(2) * math.Log(2)))
}
return (s.cap - 1) & result
// 计算哈希函数数量
func optimalNumOfHashFunctions(m, n uint) uint {
return uint(math.Round(float64(m) / float64(n) * math.Log(2)))
}

View File

@ -2,17 +2,21 @@ package bloom_test
import (
"fmt"
"math"
"strconv"
"testing"
"github.com/charlienet/go-mixed/bloom"
"github.com/charlienet/go-mixed/rand"
"github.com/charlienet/go-mixed/sys"
"github.com/go-redis/redis/v8"
"github.com/stretchr/testify/assert"
)
const ()
func TestBloom(t *testing.T) {
b := bloom.NewBloomFilter()
b := bloom.New(1000, 0.03)
for i := 0; i < 1000000; i++ {
b.Add(strconv.Itoa(i))
@ -20,51 +24,94 @@ func TestBloom(t *testing.T) {
v := "6943553521463296-1635402930"
t.Log(b.Contains(v))
t.Log(b.ExistString(v))
b.Add(v)
t.Log(b.Contains(v))
t.Log(b.ExistString(v))
fmt.Println("过滤器中包含值:", b.Contains(strconv.Itoa(9999)))
fmt.Println("过滤器中包含:", b.Contains("ss"))
isSet, err := b.ExistString(strconv.Itoa(9999))
fmt.Println("过滤器中包含:", isSet, err)
isSet, err = b.ExistString("ss")
fmt.Println("过滤器中未包含:", isSet, err)
t.Log(sys.ShowMemUsage())
}
func TestSize(t *testing.T) {
bloom.NewBloomFilter(bloom.WithSize(1 << 2))
func TestOptimize(t *testing.T) {
expectedInsertions := 1000000 // 期望存储数据量
falseProbability := 0.00002 // 预期误差
bits := uint(float64(-expectedInsertions) * math.Log(falseProbability) / (math.Log(2) * math.Log(2)))
hashSize := uint(math.Round(float64(bits) / float64(expectedInsertions) * math.Log(2)))
t.Log(bits)
t.Log(hashSize)
}
func TestRedis(t *testing.T) {
client := redis.NewClient(&redis.Options{
Addr: "192.168.2.222:6379",
Password: "123456",
})
bf := bloom.New(10000, 0.03, bloom.WithRedis(client, "bloom:test"))
for i := 0; i < 100; i++ {
bf.Add(strconv.Itoa(i))
}
for i := 0; i < 100; i++ {
isSet, err := bf.ExistString(strconv.Itoa(i))
if err != nil {
t.Fatal(err)
}
if !isSet {
t.Log(i, isSet)
}
}
for i := 101; i < 200; i++ {
isSet, err := bf.ExistString(strconv.Itoa(i))
t.Log(isSet, err)
}
}
func TestClear(t *testing.T) {
bf := bloom.NewBloomFilter()
bf := bloom.New(1000, 0.03)
v := "abc"
bf.Add(v)
assert.True(t, bf.Contains(v))
isSet, _ := bf.ExistString(v)
assert.True(t, isSet)
bf.Clear()
assert.False(t, bf.Contains(v))
isSet, _ = bf.ExistString(v)
assert.False(t, isSet)
}
func TestParallel(t *testing.T) {
f := bloom.NewBloomFilter()
f := bloom.New(1000, 0.03)
for i := 0; i < 10000; i++ {
v := rand.Hex.Generate(10)
f.Add(v)
assert.True(t, f.Contains(v))
isSet, _ := f.ExistString(v)
assert.True(t, isSet)
}
}
func BenchmarkFilter(b *testing.B) {
f := bloom.NewBloomFilter()
f := bloom.New(1000, 0.03)
b.RunParallel(func(p *testing.PB) {
for p.Next() {
v := rand.Hex.Generate(10)
f.Add(v)
f.Contains(v)
f.ExistString(v)
// assert.True(b, f.Contains(v))

51
bloom/mem_store.go Normal file
View File

@ -0,0 +1,51 @@
package bloom
import (
"github.com/bits-and-blooms/bitset"
"github.com/charlienet/go-mixed/locker"
)
type memStore struct {
size uint
set *bitset.BitSet // 内存位图
lock locker.RWLocker // 同步锁
}
func newMemStore(size uint) *memStore {
return &memStore{
size: size,
set: bitset.New(size),
lock: locker.NewRWLocker(),
}
}
func (s *memStore) Clear() {
s.lock.Lock()
defer s.lock.Unlock()
s.set.ClearAll()
}
func (s *memStore) Set(offsets ...uint) error {
s.lock.Lock()
defer s.lock.Unlock()
for _, p := range offsets {
s.set.Set(p)
}
return nil
}
func (s *memStore) Test(offsets ...uint) (bool, error) {
s.lock.RLock()
defer s.lock.RUnlock()
for _, p := range offsets {
if !s.set.Test(p) {
return false, nil
}
}
return true, nil
}

116
bloom/redis_store.go Normal file
View File

@ -0,0 +1,116 @@
package bloom
import (
"context"
"errors"
"strconv"
"time"
"github.com/go-redis/redis/v8"
)
const (
// ARGV:偏移量offset数组
// KYES[1]: setbit操作的key
// 全部设置为1
setScript = `
for _, offset in ipairs(ARGV) do
redis.call("setbit", KEYS[1], offset, 1)
end
`
//ARGV:偏移量offset数组
//KYES[1]: setbit操作的key
//检查是否全部为1
testScript = `
for _, offset in ipairs(ARGV) do
if tonumber(redis.call("getbit", KEYS[1], offset)) == 0 then
return false
end
end
return true
`
)
var ErrTooLargeOffset = errors.New("超出最大偏移量")
var _ bitStore = &redisBitSet{}
// 使用Redis存储位图
type redisBitSet struct {
store *redis.Client
key string
bits uint
}
func newRedisStore(store *redis.Client, key string, bits uint) *redisBitSet {
return &redisBitSet{
store: store,
key: key,
bits: bits,
}
}
func (s *redisBitSet) Set(offsets ...uint) error {
args, err := s.buildOffsetArgs(offsets)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500)
defer cancel()
_, err = s.store.Eval(ctx, setScript, []string{s.key}, args).Result()
//底层使用的是go-redis,redis.Nil表示操作的key不存在
//需要针对key不存在的情况特殊判断
if err == redis.Nil {
return nil
} else if err != nil {
return err
}
return nil
}
func (s *redisBitSet) Test(offsets ...uint) (bool, error) {
args, err := s.buildOffsetArgs(offsets)
if err != nil {
return false, err
}
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500)
defer cancel()
resp, err := s.store.Eval(ctx, testScript, []string{s.key}, args).Result()
// key 不存在,表示还未存放任何数据
if err == redis.Nil {
return false, nil
} else if err != nil {
return false, err
}
exists, ok := resp.(int64)
if !ok {
return false, nil
}
return exists == 1, nil
}
func (s *redisBitSet) Clear() {
}
func (r *redisBitSet) buildOffsetArgs(offsets []uint) ([]string, error) {
args := make([]string, 0, len(offsets))
for _, offset := range offsets {
if offset >= r.bits {
return nil, ErrTooLargeOffset
}
args = append(args, strconv.FormatUint(uint64(offset), 10))
}
return args, nil
}

24
bloom/redis_store_test.go Normal file
View File

@ -0,0 +1,24 @@
package bloom
import (
"testing"
"github.com/go-redis/redis/v8"
)
func TestRedisStore(t *testing.T) {
client := redis.NewClient(&redis.Options{
Addr: "192.168.2.222:6379",
Password: "123456",
})
store := newRedisStore(client, "abcdef", 10000)
err := store.Set(1, 2, 3, 9, 1223)
if err != nil {
t.Fatal(err)
}
t.Log(store.Test(1))
t.Log(store.Test(1, 2, 3))
t.Log(store.Test(4, 5, 8))
}

View File

@ -5,10 +5,21 @@ import (
"encoding/hex"
)
const hextable = "0123456789ABCDEF"
const hexTable = "0123456789ABCDEF"
type BytesResult []byte
// FromHexString 从十六进制获取
func FromHexString(s string) (BytesResult, error) {
b, err := hex.DecodeString(s)
return BytesResult(b), err
}
func FromBase64String(s string) (BytesResult, error) {
b, err := base64.StdEncoding.DecodeString(s)
return BytesResult(b), err
}
func (r BytesResult) Hex() string {
return hex.EncodeToString(r)
}
@ -19,8 +30,8 @@ func (r BytesResult) UppercaseHex() string {
re := r[:]
for _, v := range re {
dst[j] = hextable[v>>4]
dst[j+1] = hextable[v&0x0f]
dst[j] = hexTable[v>>4]
dst[j+1] = hexTable[v&0x0f]
j += 2
}

View File

@ -20,3 +20,8 @@ func Decode(b []byte, out any) error {
dec := gob.NewDecoder(buf)
return dec.Decode(out)
}
func MsgPackage() {
// msgpack.NewEncoder()
}

38
cache/cache.go vendored
View File

@ -7,6 +7,7 @@ import (
"time"
"github.com/charlienet/go-mixed/bytesconv"
"github.com/charlienet/go-mixed/json"
"github.com/charlienet/go-mixed/locker"
"github.com/charlienet/go-mixed/logx"
)
@ -22,6 +23,7 @@ type Cache struct {
distributdCache DistributdCache // 分布式缓存
publishSubscribe PublishSubscribe // 发布订阅
lock locker.ChanLocker // 资源锁
stats *Stats // 缓存命中计数
qps *qps // 访问计数
logger logx.Logger // 日志记录
}
@ -148,3 +150,39 @@ func (c *Cache) getFromSource(ctx context.Context, key string, fn LoadFunc) erro
return c.getFromSource(ctx, key, fn)
}
}
func (c *Cache) marshal(value any) ([]byte, error) {
switch value := value.(type) {
case nil:
return nil, nil
case []byte:
return value, nil
case string:
return []byte(value), nil
}
b, err := json.Marshal(value)
return b, err
}
func (c *Cache) unmarshal(b []byte, value any) error {
if len(b) == 0 {
return nil
}
switch value := value.(type) {
case nil:
return nil
case *[]byte:
clone := make([]byte, len(b))
copy(clone, b)
*value = clone
return nil
case *string:
*value = string(b)
return nil
}
err := json.Unmarshal(b, value)
return err
}

7
cache/local_cache.go vendored Normal file
View File

@ -0,0 +1,7 @@
package cache
type LocalCache interface {
Set(key string, data []byte)
Get(key string) ([]byte, bool)
Del(key string)
}

20
cache/stats.go vendored Normal file
View File

@ -0,0 +1,20 @@
package cache
import "sync/atomic"
type Stats struct {
Hits uint64
Misses uint64
}
func (s *Stats) AddHits() {
atomic.AddUint64(&s.Hits, 1)
}
func (s *Stats) AddMisses() {
atomic.AddUint64(&s.Misses, 1)
}
func (c *Cache) Stats() *Stats {
return c.stats
}

52
cache/tiny_lfu.go vendored Normal file
View File

@ -0,0 +1,52 @@
package cache
import (
"time"
"github.com/charlienet/go-mixed/locker"
"github.com/vmihailenco/go-tinylfu"
)
type TinyLFU struct {
mu locker.Locker
lfu *tinylfu.T
ttl time.Duration
}
func NewTinyLFU(size int, ttl time.Duration) *TinyLFU {
return &TinyLFU{
mu: locker.NewLocker(),
lfu: tinylfu.New(size, 100000),
ttl: ttl,
}
}
func (c *TinyLFU) Set(key string, b []byte, expire time.Duration) {
c.mu.Lock()
defer c.mu.Unlock()
c.lfu.Set(&tinylfu.Item{
Key: key,
Value: b,
ExpireAt: time.Now().Add(c.ttl),
})
}
func (c *TinyLFU) Get(key string) ([]byte, bool) {
c.mu.Lock()
defer c.mu.Unlock()
val, ok := c.lfu.Get(key)
if !ok {
return nil, false
}
return val.([]byte), true
}
func (c *TinyLFU) Del(key string) {
c.mu.Lock()
defer c.mu.Unlock()
c.lfu.Del(key)
}

57
cache/tiny_lfu_test.go vendored Normal file
View File

@ -0,0 +1,57 @@
package cache_test
import (
"context"
"fmt"
"testing"
"time"
"github.com/charlienet/go-mixed/cache"
"github.com/charlienet/go-mixed/rand"
)
func TestTinyGet(t *testing.T) {
strFor := func(i int) string {
return fmt.Sprintf("a string %d", i)
}
keyName := func(i int) string {
return fmt.Sprintf("key-%00000d", i)
}
mycache := cache.NewTinyLFU(1000, 1*time.Second)
size := 50000
// Put a bunch of stuff in the cache with a TTL of 1 second
for i := 0; i < size; i++ {
key := keyName(i)
mycache.Set(key, []byte(strFor(i)), time.Second*2)
}
// Read stuff for a bit longer than the TTL - that's when the corruption occurs
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
done := ctx.Done()
loop:
for {
select {
case <-done:
// this is expected
break loop
default:
i := rand.Intn(size)
key := keyName(i)
b, ok := mycache.Get(key)
if !ok {
continue loop
}
got := string(b)
expected := strFor(i)
if got != expected {
t.Fatalf("expected=%q got=%q key=%q", expected, got, key)
}
}
}
}

210
calendar/calendar.go Normal file
View File

@ -0,0 +1,210 @@
package calendar
import (
"time"
)
var WeekStartDay time.Weekday = time.Sunday
type Calendar struct {
time.Time
weekStartsAt time.Weekday
}
func BeginningOfMinute() Calendar {
return Create(time.Now()).BeginningOfMinute()
}
func BeginningOfHour() Calendar {
return Create(time.Now()).BeginningOfHour()
}
func BeginningOfDay() Calendar {
return Create(time.Now()).BeginningOfDay()
}
func BeginningOfWeek() Calendar {
return Create(time.Now()).BeginningOfWeek()
}
func BeginningOfMonth() Calendar {
return Create(time.Now()).BeginningOfMonth()
}
func BeginningOfQuarter() Calendar {
return Create(time.Now()).BeginningOfQuarter()
}
func BeginningOfYear() Calendar {
return Create(time.Now()).BeginningOfYear()
}
func EndOfMinute() Calendar {
return Create(time.Now()).EndOfMinute()
}
func EndOfHour() Calendar {
return Create(time.Now()).EndOfHour()
}
func EndOfDay() Calendar {
return Create(time.Now()).EndOfDay()
}
func EndOfWeek() Calendar {
return Create(time.Now()).EndOfWeek()
}
func EndOfMonth() Calendar {
return Create(time.Now()).EndOfMonth()
}
func EndOfQuarter() Calendar {
return Create(time.Now()).EndOfQuarter()
}
func EndOfYear() Calendar {
return Create(time.Now()).EndOfYear()
}
func (c Calendar) WeekStartsAt(day time.Weekday) Calendar {
return Calendar{
Time: c.Time,
weekStartsAt: day,
}
}
func (c Calendar) BeginningOfMinute() Calendar {
return Calendar{Time: c.Truncate(time.Minute)}
}
func (c Calendar) BeginningOfHour() Calendar {
y, m, d := c.Date()
return Calendar{
Time: time.Date(y, m, d, c.Hour(), 0, 0, 0, c.Location()),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) BeginningOfDay() Calendar {
y, m, d := c.Date()
return Calendar{
Time: time.Date(y, m, d, 0, 0, 0, 0, c.Location()),
}
}
func (c Calendar) BeginningOfWeek() Calendar {
t := c.BeginningOfDay()
weekday := int(t.Weekday())
if c.weekStartsAt != time.Sunday {
weekStartDayInt := int(c.weekStartsAt)
if weekday < weekStartDayInt {
weekday = weekday + 7 - weekStartDayInt
} else {
weekday = weekday - weekStartDayInt
}
}
return Calendar{
Time: t.AddDate(0, 0, -weekday),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) BeginningOfMonth() Calendar {
y, m, _ := c.Date()
return Calendar{
Time: time.Date(y, m, 1, 0, 0, 0, 0, c.Location()),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) BeginningOfQuarter() Calendar {
month := c.BeginningOfMonth()
offset := (int(month.Month()) - 1) % 3
return Calendar{
Time: month.AddDate(0, -offset, 0),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) BeginningOfYear() Calendar {
y, _, _ := c.Date()
return Calendar{
Time: time.Date(y, time.January, 1, 0, 0, 0, 0, c.Location()),
weekStartsAt: c.weekStartsAt}
}
func (c Calendar) EndOfMinute() Calendar {
n := c.BeginningOfMinute()
return Calendar{
Time: n.Add(time.Minute - time.Nanosecond),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) EndOfHour() Calendar {
n := c.BeginningOfHour()
return Calendar{
Time: n.Add(time.Hour - time.Nanosecond),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) EndOfDay() Calendar {
y, m, d := c.Date()
return Calendar{
Time: time.Date(y, m, d, 23, 59, 59, int(time.Second-time.Nanosecond), c.Location()),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) EndOfWeek() Calendar {
n := c.BeginningOfWeek()
return Calendar{
Time: n.AddDate(0, 0, 7).Add(-time.Nanosecond),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) EndOfMonth() Calendar {
n := c.BeginningOfMonth()
return Calendar{
Time: n.AddDate(0, 1, 0).Add(-time.Nanosecond),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) EndOfQuarter() Calendar {
n := c.BeginningOfQuarter()
return Calendar{
Time: n.AddDate(0, 3, 0).Add(-time.Nanosecond),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) EndOfYear() Calendar {
n := c.BeginningOfYear()
return Calendar{
Time: n.AddDate(1, 0, 0).Add(-time.Nanosecond),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) ToTime() time.Time {
return c.Time
}

38
calendar/calendar_test.go Normal file
View File

@ -0,0 +1,38 @@
package calendar_test
import (
"testing"
"time"
"github.com/charlienet/go-mixed/calendar"
"github.com/stretchr/testify/assert"
)
var format = "2006-01-02 15:04:05.999999999"
func TestToday(t *testing.T) {
t.Log(calendar.Today())
}
func TestBeginningOf(t *testing.T) {
a := assert.New(t)
n := time.Date(2022, 11, 9, 14, 28, 34, 123456789, time.UTC)
a.Equal("2022-11-06 00:00:00", calendar.Create(n).BeginningOfWeek().String())
a.Equal("2022-11-07 00:00:00", calendar.Create(n).WeekStartsAt(time.Monday).BeginningOfWeek().Format(format))
a.Equal("2022-11-09 14:00:00", calendar.Create(n).BeginningOfHour().Format(format))
a.Equal("2022-11-01 00:00:00", calendar.Create(n).BeginningOfMonth().Format(format))
a.Equal("2022-10-01 00:00:00", calendar.Create(n).BeginningOfQuarter().Format(format))
}
func TestEndOf(t *testing.T) {
a := assert.New(t)
n := time.Date(2022, 11, 9, 14, 28, 34, 123456789, time.UTC)
a.Equal("2022-11-09 14:28:59.999999999", calendar.Create(n).EndOfMinute().Format(format))
a.Equal("2022-11-09 14:59:59.999999999", calendar.Create(n).EndOfHour().Format(format))
a.Equal("2022-11-09 23:59:59.999999999", calendar.Create(n).EndOfDay().Format(format))
a.Equal("2022-11-30 23:59:59.999999999", calendar.Create(n).EndOfMonth().Format(format))
a.Equal("2022-12-31 23:59:59.999999999", calendar.Create(n).EndOfQuarter().Format(format))
a.Equal("2022-12-31 23:59:59.999999999", calendar.Create(n).EndOfYear().Format(format))
}

40
calendar/creator.go Normal file
View File

@ -0,0 +1,40 @@
package calendar
import "time"
func Now() Calendar {
return Create(time.Now())
}
func Today() Calendar {
return Now().BeginningOfDay()
}
func Create(t time.Time) Calendar {
return Calendar{
Time: t,
weekStartsAt: WeekStartDay,
}
}
func CreateFromTimestamp(timestamp int64) Calendar {
return Create(time.Unix(timestamp, 0))
}
func CreateFromTimestampMilli(timestamp int64) Calendar {
return Create(time.Unix(timestamp/1e3, (timestamp%1e3)*1e6))
}
func CreateFromTimestampMicro(timestamp int64) Calendar {
return Create(time.Unix(timestamp/1e6, (timestamp%1e6)*1e3))
}
func CreateFromTimestampNano(timestamp int64) Calendar {
return Create(time.Unix(timestamp/1e9, timestamp%1e9))
}
func create(year, month, day, hour, minute, second, nanosecond int) Calendar {
return Calendar{
Time: time.Date(year, time.Month(month), day, hour, minute, second, nanosecond, time.Local),
}
}

View File

@ -1,4 +1,4 @@
package dateconv
package calendar
import "time"

74
calendar/output.go Normal file
View File

@ -0,0 +1,74 @@
package calendar
import "time"
// 布局模板常量
const (
ANSICLayout = time.ANSIC
UnixDateLayout = time.UnixDate
RubyDateLayout = time.RubyDate
RFC822Layout = time.RFC822
RFC822ZLayout = time.RFC822Z
RFC850Layout = time.RFC850
RFC1123Layout = time.RFC1123
RFC1123ZLayout = time.RFC1123Z
RssLayout = time.RFC1123Z
KitchenLayout = time.Kitchen
RFC2822Layout = time.RFC1123Z
CookieLayout = "Monday, 02-Jan-2006 15:04:05 MST"
RFC3339Layout = "2006-01-02T15:04:05Z07:00"
RFC3339MilliLayout = "2006-01-02T15:04:05.999Z07:00"
RFC3339MicroLayout = "2006-01-02T15:04:05.999999Z07:00"
RFC3339NanoLayout = "2006-01-02T15:04:05.999999999Z07:00"
ISO8601Layout = "2006-01-02T15:04:05-07:00"
ISO8601MilliLayout = "2006-01-02T15:04:05.999-07:00"
ISO8601MicroLayout = "2006-01-02T15:04:05.999999-07:00"
ISO8601NanoLayout = "2006-01-02T15:04:05.999999999-07:00"
RFC1036Layout = "Mon, 02 Jan 06 15:04:05 -0700"
RFC7231Layout = "Mon, 02 Jan 2006 15:04:05 GMT"
DayDateTimeLayout = "Mon, Jan 2, 2006 3:04 PM"
DateTimeLayout = "2006-01-02 15:04:05"
DateTimeMilliLayout = "2006-01-02 15:04:05.999"
DateTimeMicroLayout = "2006-01-02 15:04:05.999999"
DateTimeNanoLayout = "2006-01-02 15:04:05.999999999"
ShortDateTimeLayout = "20060102150405"
ShortDateTimeMilliLayout = "20060102150405.999"
ShortDateTimeMicroLayout = "20060102150405.999999"
ShortDateTimeNanoLayout = "20060102150405.999999999"
DateLayout = "2006-01-02"
DateMilliLayout = "2006-01-02.999"
DateMicroLayout = "2006-01-02.999999"
DateNanoLayout = "2006-01-02.999999999"
ShortDateLayout = "20060102"
ShortDateMilliLayout = "20060102.999"
ShortDateMicroLayout = "20060102.999999"
ShortDateNanoLayout = "20060102.999999999"
TimeLayout = "15:04:05"
TimeMilliLayout = "15:04:05.999"
TimeMicroLayout = "15:04:05.999999"
TimeNanoLayout = "15:04:05.999999999"
ShortTimeLayout = "150405"
ShortTimeMilliLayout = "150405.999"
ShortTimeMicroLayout = "150405.999999"
ShortTimeNanoLayout = "150405.999999999"
)
func (c Calendar) String() string {
return c.ToDateTimeString()
}
func (c Calendar) ToDateTimeString() string {
return c.Format(DateTimeLayout)
}
func (c Calendar) ToDateTimeInt() int {
return c.ToShortDateInt()*1000000 + c.Hour()*10000 + c.Minute()*100 + c.Second()
}
func (c Calendar) ToShortDateInt() int {
return c.Year()*10000 + int(c.Month())*100 + c.Day()
}
func (c Calendar) ToMonthInt() int {
return c.Year()*100 + int(c.Month())
}

19
calendar/output_test.go Normal file
View File

@ -0,0 +1,19 @@
package calendar_test
import (
"testing"
"time"
"github.com/charlienet/go-mixed/calendar"
"github.com/stretchr/testify/assert"
)
func TestDayInt(t *testing.T) {
assert := assert.New(t)
n := time.Date(2022, 11, 9, 14, 28, 34, 123456789, time.UTC)
assert.Equal(20221109, calendar.Create(n).ToShortDateInt())
assert.Equal(202211, calendar.Create(n).ToMonthInt())
assert.Equal(20221109142834, calendar.Create(n).ToDateTimeInt())
}

View File

@ -0,0 +1,15 @@
package deque
import "github.com/charlienet/go-mixed/locker"
type Deque[T any] struct {
locker locker.RWLocker
}
func New[T any]() *Deque[T] {
return &Deque[T]{
locker: locker.EmptyLocker,
}
}

View File

@ -0,0 +1 @@
package deque_test

View File

@ -0,0 +1,199 @@
package list
import (
"github.com/charlienet/go-mixed/locker"
)
const minCapacity = 16
type ArrayList[T any] struct {
buf []T
head int
tail int
minCap int
list[T]
}
func NewArrayList[T any](elems ...T) *ArrayList[T] {
minCap := minCapacity
size := len(elems)
for minCap < size {
minCap <<= 1
}
var tail int = size
var buf []T
if len(elems) > 0 {
buf = make([]T, minCap)
copy(buf, elems)
}
l := &ArrayList[T]{
list: list[T]{size: size, locker: locker.EmptyLocker},
buf: buf,
tail: tail,
minCap: minCap,
}
// for _, v := range elems {
// l.PushBack(v)
// }
return l
}
func (l *ArrayList[T]) PushFront(v T) {
l.locker.Lock()
defer l.locker.Unlock()
l.grow()
l.head = l.prev(l.head)
l.buf[l.head] = v
l.size++
}
func (l *ArrayList[T]) PushBack(v T) {
l.locker.Lock()
defer l.locker.Unlock()
l.grow()
l.buf[l.tail] = v
l.tail = l.next(l.tail)
l.size++
}
func (l *ArrayList[T]) PopFront() T {
l.locker.Lock()
defer l.locker.Unlock()
if l.size <= 0 {
panic("list: PopFront() called on empty list")
}
ret := l.buf[l.head]
var zero T
l.buf[l.head] = zero
l.head = l.next(l.head)
l.size--
l.shrink()
return ret
}
func (l *ArrayList[T]) PopBack() T {
l.locker.Lock()
defer l.locker.Unlock()
l.tail = l.prev(l.tail)
ret := l.buf[l.tail]
var zero T
l.buf[l.tail] = zero
l.size--
l.shrink()
return ret
}
func (l *ArrayList[T]) RemoveAt(at int) T {
if at < 0 || at >= l.Size() {
panic(ErrorOutOffRange)
}
l.locker.Lock()
defer l.locker.Unlock()
rm := (l.head + at) & (len(l.buf) - 1)
if at*2 < l.size {
for i := 0; i < at; i++ {
prev := l.prev(rm)
l.buf[prev], l.buf[rm] = l.buf[rm], l.buf[prev]
rm = prev
}
return l.PopFront()
}
swaps := l.size - at - 1
for i := 0; i < swaps; i++ {
next := l.next(rm)
l.buf[rm], l.buf[next] = l.buf[next], l.buf[rm]
rm = next
}
return l.PopBack()
}
func (l *ArrayList[T]) Front() T {
l.locker.RLock()
defer l.locker.RUnlock()
return l.buf[l.head]
}
func (l *ArrayList[T]) Back() T {
l.locker.RLock()
defer l.locker.RUnlock()
return l.buf[l.tail]
}
func (l *ArrayList[T]) ForEach(fn func(T)) {
l.locker.RLock()
defer l.locker.RUnlock()
n := l.head
for i := 0; i < l.size; i++ {
fn(l.buf[n])
n = l.next(n)
}
}
func (q *ArrayList[T]) prev(i int) int {
return (i - 1) & (len(q.buf) - 1)
}
func (l *ArrayList[T]) next(i int) int {
return (i + 1) & (len(l.buf) - 1)
}
func (l *ArrayList[T]) grow() {
if l.size != len(l.buf) {
return
}
if len(l.buf) == 0 {
if l.minCap == 0 {
l.minCap = minCapacity
}
l.buf = make([]T, l.minCap)
return
}
l.resize()
}
func (l *ArrayList[T]) shrink() {
if len(l.buf) > l.minCap && (l.size<<2) == len(l.buf) {
l.resize()
}
}
// resize resizes the list to fit exactly twice its current contents. This is
// used to grow the list when it is full, and also to shrink it when it is
// only a quarter full.
func (l *ArrayList[T]) resize() {
newBuf := make([]T, l.size<<1)
if l.tail > l.head {
copy(newBuf, l.buf[l.head:l.tail])
} else {
n := copy(newBuf, l.buf[l.head:])
copy(newBuf[n:], l.buf[:l.tail])
}
l.head = 0
l.tail = l.size
l.buf = newBuf
}

View File

@ -0,0 +1,43 @@
package list_test
import (
"testing"
"github.com/charlienet/go-mixed/collections/list"
)
func TestNewArrayList(t *testing.T) {
l := list.NewArrayList(1, 2, 3)
l.ForEach(func(i int) {
t.Log(i)
})
}
func TestArrayPushBack(t *testing.T) {
l := list.NewArrayList[int]()
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
l.ForEach(func(i int) {
t.Log(i)
})
}
func TestArrayPushFront(t *testing.T) {
l := list.NewArrayList[int]()
l.PushFront(1)
l.PushFront(2)
l.PushFront(3)
l.PushBack(99)
l.PushBack(88)
l.ForEach(func(i int) {
t.Log(i)
})
}

View File

@ -0,0 +1,163 @@
package list
import (
"github.com/charlienet/go-mixed/locker"
)
type LinkedList[T any] struct {
list[T]
front, tail *LinkedNode[T]
}
type LinkedNode[T any] struct {
Value T
Prev, Next *LinkedNode[T]
}
// NewLinkedList 初始化链表
func NewLinkedList[T any](elems ...T) *LinkedList[T] {
l := &LinkedList[T]{
list: list[T]{locker: locker.EmptyLocker},
}
for _, e := range elems {
l.pushBackNode(&LinkedNode[T]{Value: e})
}
return l
}
func (l *LinkedList[T]) PushBack(v T) *LinkedList[T] {
l.locker.Lock()
defer l.locker.Unlock()
l.pushBackNode(&LinkedNode[T]{Value: v})
return l
}
func (l *LinkedList[T]) PushFront(v T) *LinkedList[T] {
l.locker.Lock()
defer l.locker.Unlock()
l.pushFrontNode(&LinkedNode[T]{Value: v})
return l
}
func (l *LinkedList[T]) FrontNode() *LinkedNode[T] {
return l.front
}
func (l *LinkedList[T]) Front() T {
return l.FrontNode().Value
}
func (l *LinkedList[T]) BackNode() *LinkedNode[T] {
return l.tail
}
func (l *LinkedList[T]) Back() T {
if l.size == 0 {
panic(ErrorOutOffRange)
}
return l.tail.Value
}
func (l *LinkedList[T]) ForEach(fn func(T) bool) {
l.locker.RLock()
defer l.locker.RUnlock()
for current := l.front; current != nil; current = current.Next {
if fn(current.Value) {
break
}
}
}
func (l *LinkedList[T]) GetAt(i int) T {
if i <= l.Size() {
var n int
for current := l.front; current != nil; current = current.Next {
if n == i {
return current.Value
}
n++
}
}
return *new(T)
}
func (l *LinkedList[T]) Remove(n *LinkedNode[T]) {
l.locker.Lock()
defer l.locker.Unlock()
if n.Next != nil {
n.Next.Prev = n.Prev
} else {
l.tail = n.Prev
}
if n.Prev != nil {
n.Prev.Next = n.Next
} else {
l.front = n.Next
}
n.Next = nil
n.Prev = nil
l.size--
}
func (l *LinkedList[T]) RemoveAt(index int) {
l.locker.Lock()
defer l.locker.Unlock()
var i int
for current := l.front; current != nil; current = current.Next {
if i == index {
// 重连接
current.Prev.Next = current.Next
current.Next.Prev = current.Prev
current.Prev = nil
current.Next = nil
l.size--
break
}
i++
}
}
func (l *LinkedList[T]) pushBackNode(n *LinkedNode[T]) {
n.Next = nil
n.Prev = l.tail
if l.tail != nil {
l.tail.Next = n
} else {
l.front = n
}
l.tail = n
l.size++
}
func (l *LinkedList[T]) pushFrontNode(n *LinkedNode[T]) {
n.Next = l.front
n.Prev = nil
if l.front != nil {
l.front.Prev = n
} else {
l.tail = n
}
l.front = n
l.size++
}

View File

@ -0,0 +1,103 @@
package list_test
import (
"testing"
"github.com/charlienet/go-mixed/collections/list"
"github.com/stretchr/testify/assert"
)
func TestPushBack(t *testing.T) {
l := list.NewLinkedList[int]()
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
l.ForEach(func(i int) bool {
t.Log(i)
return false
})
}
func TestPushFront(t *testing.T) {
l := list.NewLinkedList[int]()
l.PushFront(1)
l.PushFront(2)
l.PushFront(3)
l.ForEach(func(i int) bool {
t.Log(i)
return false
})
}
func TestRemoveAt(t *testing.T) {
l := list.NewLinkedList[int]()
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
l.RemoveAt(1)
l.ForEach(func(i int) bool {
t.Log(i)
return false
})
t.Log()
l.RemoveAt(0)
l.ForEach(func(i int) bool {
t.Log(i)
return false
})
}
func TestSize(t *testing.T) {
l := list.NewLinkedList[int]()
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
assert.Equal(t, 3, l.Size())
l.RemoveAt(0)
assert.Equal(t, 2, l.Size())
}
func TestLinkedListToSlice(t *testing.T) {
l := list.NewLinkedList[int]()
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
s := l.ToSlice()
t.Log(s)
}
func BenchmarkLinkedList(b *testing.B) {
l := list.NewLinkedList[int]()
l.Synchronize()
for i := 0; i < b.N; i++ {
l.PushBack(i)
}
}
func TestRemoveNode(t *testing.T) {
l := list.NewLinkedList(1, 2, 3, 4, 5)
// l.ForEach(func(i int) bool {
// t.Log(i)
// return false
// })
l.RemoveAt(1)
for currnet := l.FrontNode(); currnet != nil; currnet = currnet.Next {
t.Logf("%p %+v", currnet, currnet)
}
}

35
collections/list/list.go Normal file
View File

@ -0,0 +1,35 @@
package list
import (
"errors"
"github.com/charlienet/go-mixed/locker"
)
var ErrorOutOffRange = errors.New("out of range")
type List[T any] interface {
}
type list[T any] struct {
size int
locker locker.RWLocker
}
func (l *list[T]) Synchronize() {
l.locker = locker.NewRWLocker()
}
func (l *list[T]) ForEach(fn func(T) bool) { panic("Not Implemented") }
func (l *LinkedList[T]) ToSlice() []T {
s := make([]T, 0, l.Size())
l.ForEach(func(t T) bool {
s = append(s, t)
return false
})
return s
}
func (l *list[T]) Size() int { return l.size }

View File

@ -1 +0,0 @@
package collections

View File

@ -1,11 +0,0 @@
package collections
import "github.com/charlienet/go-mixed/locker"
type options struct {
mu locker.RWLocker
}
func emptyLocker() locker.RWLocker {
return locker.EmptyLocker
}

View File

@ -0,0 +1,35 @@
package queue
import (
"github.com/charlienet/go-mixed/collections/list"
)
type Queue[T any] struct {
list list.LinkedList[T]
}
func NewQueue[T any]() *Queue[T] {
return &Queue[T]{}
}
func (q *Queue[T]) Synchronize() *Queue[T] {
q.list.Synchronize()
return q
}
func (q *Queue[T]) Push(v T) {
}
func (q *Queue[T]) Pop(v T) {
}
func (q *Queue[T]) Size() int {
return q.list.Size()
}
func (q *Queue[T]) IsEmpty() bool {
return false
}

View File

@ -0,0 +1 @@
package queue_test

View File

@ -0,0 +1 @@
package rbtree

View File

@ -0,0 +1 @@
package rbtree

View File

@ -1,42 +0,0 @@
package collections
import "sync"
type rw_queue[T any] struct {
q Queue[T]
mu sync.Mutex
}
func (q *rw_queue[T]) Push(v T) {
q.mu.Lock()
q.q.Put(v)
q.mu.Unlock()
}
func (q *rw_queue[T]) Pop() T {
q.mu.Lock()
defer q.mu.Unlock()
return q.q.Poll()
}
func (q *rw_queue[T]) Peek() T {
q.mu.Lock()
defer q.mu.Unlock()
return q.q.Peek()
}
func (q *rw_queue[T]) Size() int {
q.mu.Lock()
defer q.mu.Unlock()
return q.q.Size()
}
func (q *rw_queue[T]) IsEmpty() bool {
q.mu.Lock()
defer q.mu.Unlock()
return q.q.IsEmpty()
}

View File

@ -1,4 +1,4 @@
package collections
package stack
import "sync"

View File

@ -1,13 +1,13 @@
package collections_test
package stack_test
import (
"testing"
"github.com/charlienet/go-mixed/collections"
"github.com/charlienet/go-mixed/collections/stack"
)
func TestStack(t *testing.T) {
arrayStack := collections.NewArrayStack[string]()
arrayStack := stack.NewArrayStack[string]()
arrayStack.Push("cat")
arrayStack.Push("dog")
arrayStack.Push("hen")

View File

@ -1,12 +0,0 @@
package dateconv_test
import (
"testing"
"github.com/charlienet/go-mixed/dateconv"
)
func TestToday(t *testing.T) {
today := dateconv.Today()
t.Log(dateconv.TimeToString(&today))
}

View File

@ -1,44 +0,0 @@
package dateconv
import (
"time"
)
const (
layoutDate = "2006-01-02"
layoutTime = "2006-01-02 15:04:05"
layoutTimeMilli = "2006-01-02 15:04:05.000"
layoutChineseDate = "2006年01月02日"
layoutChineseTime = "2006年01月02日 15:04:05"
)
func Today() time.Time {
t := time.Now()
year, month, day := t.Date()
return time.Date(year, month, day, 0, 0, 0, 0, t.Location())
}
// 日期转换为整数(如:20211222
func DateToInt(date time.Time) int {
return date.Year()*10000 + int(date.Month())*100 + date.Day()
}
// 日期转换为字符串
func DateToString(date *time.Time) string { return formatTime(date, layoutDate) }
// 时间转换为字符串
func TimeToString(date *time.Time) string { return formatTime(date, layoutTime) }
// 日期转换为中文
func DateToChinese(t *time.Time) string { return formatTime(t, layoutChineseDate) }
// 时间转换为中文
func TimeToChinese(t *time.Time) string { return formatTime(t, layoutChineseTime) }
func formatTime(t *time.Time, f string) string {
if t == nil || t.IsZero() {
return ""
}
return t.Format(f)
}

View File

@ -1,10 +0,0 @@
package dateconv
import (
"testing"
)
func TestParseDuration(t *testing.T) {
t.Log(ParseDuration(""))
t.Log(ParseDuration("abc"))
}

View File

@ -1,9 +1,128 @@
package expr
// 如为真返回参数一,否则返回参数二
func If[T any](e bool, v1, v2 T) T {
func Ternary[T any](e bool, v1, v2 T) T {
if e {
return v1
}
return v2
}
func TernaryF[T any](condition bool, ifFunc func() T, elseFunc func() T) T {
if condition {
return ifFunc()
}
return elseFunc()
}
type ifElse[T any] struct {
result T
done bool
}
func If[T any](condition bool, result T) *ifElse[T] {
if condition {
return &ifElse[T]{result, true}
}
var t T
return &ifElse[T]{t, false}
}
func IfF[T any](condition bool, resultF func() T) *ifElse[T] {
if condition {
return &ifElse[T]{resultF(), true}
}
var t T
return &ifElse[T]{t, false}
}
func (i *ifElse[T]) ElseIf(condition bool, result T) *ifElse[T] {
if !i.done && condition {
i.result = result
i.done = true
}
return i
}
func (i *ifElse[T]) ElseIfF(condition bool, resultF func() T) *ifElse[T] {
if !i.done && condition {
i.result = resultF()
i.done = true
}
return i
}
func (i *ifElse[T]) Else(result T) T {
if i.done {
return i.result
}
return result
}
func (i *ifElse[T]) ElseF(resultF func() T) T {
if i.done {
return i.result
}
return resultF()
}
type switchCase[T comparable, R any] struct {
predicate T
result R
done bool
}
func Switch[T comparable, R any](predicate T) *switchCase[T, R] {
var result R
return &switchCase[T, R]{
predicate,
result,
false,
}
}
func SwitchF[T comparable, R any](predicate func() T) *switchCase[T, R] {
return Switch[T, R](predicate())
}
func (s *switchCase[T, R]) Case(val T, result R) *switchCase[T, R] {
if !s.done && s.predicate == val {
s.result = result
s.done = true
}
return s
}
func (s *switchCase[T, R]) CaseF(val T, cb func() R) *switchCase[T, R] {
if !s.done && s.predicate == val {
s.result = cb()
s.done = true
}
return s
}
func (s *switchCase[T, R]) Default(result R) R {
if !s.done {
s.result = result
}
return s.result
}
func (s *switchCase[T, R]) DefaultF(cb func() R) R {
if !s.done {
s.result = cb()
}
return s.result
}

View File

@ -1,9 +1,31 @@
package expr
import "testing"
import (
"testing"
func TestIf(t *testing.T) {
"github.com/stretchr/testify/assert"
)
func TestTernary(t *testing.T) {
v1 := 10
v2 := 4
t.Log(If(v1 > v2, v1, v2))
t.Log(Ternary(v1 > v2, v1, v2))
}
func TestIf(t *testing.T) {
is := assert.New(t)
is.Equal(1, If(true, 1).ElseIf(false, 2).Else(3))
is.Equal(1, If(true, 1).ElseIf(true, 2).Else(3))
is.Equal(2, If(false, 1).ElseIf(true, 2).Else(3))
is.Equal(3, If(false, 1).ElseIf(false, 2).Else(3))
}
func TestSwitch(t *testing.T) {
is := assert.New(t)
is.Equal(1, Switch[int, int](42).Case(42, 1).Case(1, 2).Default(3))
is.Equal(1, Switch[int, int](42).Case(42, 1).Case(42, 2).Default(3))
is.Equal(1, Switch[int, int](42).Case(1, 1).Case(42, 2).Default(3))
is.Equal(1, Switch[int, int](42).Case(1, 1).Case(1, 2).Default(3))
}

10
go.mod
View File

@ -3,7 +3,7 @@ module github.com/charlienet/go-mixed
go 1.18
require (
github.com/bits-and-blooms/bitset v1.3.0
github.com/bits-and-blooms/bitset v1.3.3
github.com/cespare/xxhash/v2 v2.1.2
github.com/go-playground/universal-translator v0.18.0
github.com/json-iterator/go v1.1.12
@ -13,6 +13,7 @@ require (
)
require (
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect
@ -25,19 +26,24 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/tebeka/strftime v0.1.5 // indirect
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
require (
github.com/alicebob/miniredis/v2 v2.23.0
github.com/allegro/bigcache/v3 v3.0.2
github.com/alphadose/haxmap v1.0.2
github.com/antonfisher/nested-logrus-formatter v1.3.1
github.com/coocood/freecache v1.2.1
github.com/coocood/freecache v1.2.2
github.com/dlclark/regexp2 v1.7.0
github.com/go-redis/redis/v8 v8.11.5
github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.8.0
github.com/vmihailenco/go-tinylfu v0.2.2
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect

41
go.sum
View File

@ -1,28 +1,34 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.23.0 h1:+lwAJYjvvdIVg6doFHuotFjueJ/7KY10xo/vm3X3Scw=
github.com/alicebob/miniredis/v2 v2.23.0/go.mod h1:XNqvJdQJv5mSuVMc0ynneafpnL/zv52acZ6kqeS0t88=
github.com/allegro/bigcache/v3 v3.0.2 h1:AKZCw+5eAaVyNTBmI2fgyPVJhHkdWder3O9IrprcQfI=
github.com/allegro/bigcache/v3 v3.0.2/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I=
github.com/alphadose/haxmap v1.0.2 h1:ZZwFf15DcsAz4O+SyqrpH/xeO5Plh7mNRXDM9QIcWQQ=
github.com/alphadose/haxmap v1.0.2/go.mod h1:Pq2IXbl9/ytYHfrIAd7rIVtZQ2ezdIhZfvdqOizDeWY=
github.com/antonfisher/nested-logrus-formatter v1.3.1 h1:NFJIr+pzwv5QLHTPyKz9UMEoHck02Q9L0FP13b/xSbQ=
github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA=
github.com/bits-and-blooms/bitset v1.2.2 h1:J5gbX05GpMdBjCvQ9MteIg2KKDExr7DrgK+Yc15FvIk=
github.com/bits-and-blooms/bitset v1.2.2/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/bits-and-blooms/bitset v1.3.0 h1:h7mv5q31cthBTd7V4kLAZaIThj1e8vPGcSqpPue9KVI=
github.com/bits-and-blooms/bitset v1.3.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/bits-and-blooms/bitset v1.3.3 h1:R1XWiopGiXf66xygsiLpzLo67xEYvMkHw3w+rCOSAwg=
github.com/bits-and-blooms/bitset v1.3.3/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coocood/freecache v1.2.1 h1:/v1CqMq45NFH9mp/Pt142reundeBM0dVUD3osQBeu/U=
github.com/coocood/freecache v1.2.1/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj7rthiQ3vk=
github.com/coocood/freecache v1.2.2 h1:UPkJCxhRujykq1jXuwxAPgDHnm6lKGrLZPnuHzgWRtE=
github.com/coocood/freecache v1.2.2/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj7rthiQ3vk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@ -80,15 +86,12 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@ -98,16 +101,16 @@ github.com/tebeka/strftime v0.1.5 h1:1NQKN1NiQgkqd/2moD6ySP/5CoZQsKa1d3ZhJ44Jpmg
github.com/tebeka/strftime v0.1.5/go.mod h1:29/OidkoWHdEKZqzyDLUyC+LmgDgdHo4WAFCDT7D/Ig=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/vmihailenco/go-tinylfu v0.2.2 h1:H1eiG6HM36iniK6+21n9LLpzx1G9R3DJa2UjUjbynsI=
github.com/vmihailenco/go-tinylfu v0.2.2/go.mod h1:CutYi2Q9puTxfcolkliPq4npPuofg9N9t8JVrjzwa3Q=
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw=
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20220713135740-79cabaa25d75 h1:x03zeu7B2B11ySp+daztnwM5oBJ/8wGUSqrwcw9L0RA=
golang.org/x/exp v0.0.0-20220713135740-79cabaa25d75/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -125,20 +128,18 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e h1:NHvCuwuS43lGnYhten69ZWqi2QOj/CiDNcKbVqwVoew=
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=

View File

@ -43,7 +43,7 @@ func (r *rangeSegment) Contains(ip netip.Addr) bool {
return ip.Compare(r.start) >= 0 && ip.Compare(r.end) <= 0
}
// IP范围判断支持以下规则:
// NewRange IP范围判断支持以下规则:
// 单IP地址如 192.168.100.2
// IP范围, 如 192.168.100.120-192.168.100.150
// 掩码模式,如 192.168.2.0/24

View File

@ -2,12 +2,13 @@ package json
import "github.com/charlienet/go-mixed/bytesconv"
// StructToJsonIndent 结构转换为带格式字符串
func StructToJsonIndent(obj any) string {
b, _ := MarshalIndent(obj, "", " ")
return bytesconv.BytesToString(b)
}
// 结构转换为json字符串
// StructToJson 结构转换为json字符串
func StructToJson(obj any) string {
b, _ := Marshal(obj)
return bytesconv.BytesToString(b)

View File

@ -11,7 +11,7 @@ type countLocker struct {
Count int32
}
// 资源锁
// SourceLocker 资源锁
type SourceLocker struct {
m RWLocker
locks map[string]*countLocker
@ -43,13 +43,13 @@ func (s *SourceLocker) Lock(key string) {
l2.Lock()
fmt.Println("二次检查加锁")
} else {
new := NewLocker()
s.locks[key] = &countLocker{Locker: new, Count: 1}
n := NewLocker()
s.locks[key] = &countLocker{Locker: n, Count: 1}
s.m.Unlock()
fmt.Printf("新锁准备加锁:%p\n", new)
new.Lock()
fmt.Printf("新锁准备加锁:%p\n", n)
n.Lock()
fmt.Println("初始加锁")
}
@ -85,10 +85,10 @@ func (s *SourceLocker) TryLock(key string) bool {
s.m.RUnlock()
s.m.Lock()
new := NewLocker()
s.locks[key] = &countLocker{Locker: new, Count: 1}
n := NewLocker()
s.locks[key] = &countLocker{Locker: n, Count: 1}
s.m.Unlock()
return new.TryLock()
return n.TryLock()
}
}

View File

@ -7,17 +7,16 @@ import (
"github.com/charlienet/go-mixed/bytesconv"
"github.com/charlienet/go-mixed/hash"
"golang.org/x/exp/constraints"
)
var defaultNumOfBuckets = runtime.GOMAXPROCS(runtime.NumCPU())
type concurrnetMap[K constraints.Ordered, V any] struct {
type concurrnetMap[K hashable, V any] struct {
buckets []Map[K, V]
numOfBuckets uint64
}
func NewConcurrentMap[K constraints.Ordered, V any](maps ...map[K]V) *concurrnetMap[K, V] {
func NewConcurrentMap[K hashable, V any](maps ...map[K]V) *concurrnetMap[K, V] {
num := defaultNumOfBuckets
buckets := make([]Map[K, V], num)
@ -57,16 +56,16 @@ func (m *concurrnetMap[K, V]) Exist(key K) bool {
return mm.Exist(key)
}
func (m *concurrnetMap[K, V]) Iter() <-chan *Entry[K, V] {
num := int(m.numOfBuckets)
ch := make(chan *Entry[K, V], m.Count())
for i := 0; i < num; i++ {
c := m.buckets[i].Iter()
ch <- <-c
}
// func (m *concurrnetMap[K, V]) Iter() <-chan *Entry[K, V] {
// num := int(m.numOfBuckets)
// ch := make(chan *Entry[K, V], m.Count())
// for i := 0; i < num; i++ {
// c := m.buckets[i].Iter()
// ch <- <-c
// }
return ch
}
// return ch
// }
func (m *concurrnetMap[K, V]) Keys() []K {
keys := make([]K, m.Count())
@ -111,27 +110,6 @@ func (m *concurrnetMap[K, V]) ForEach(f func(K, V) bool) {
wg.Wait()
}
func (m *concurrnetMap[K, V]) Clone() Map[K, V] {
num := int(m.numOfBuckets)
buckets := make([]Map[K, V], m.numOfBuckets)
for i := 0; i < num; i++ {
buckets[i] = m.buckets[i].Clone()
}
return &concurrnetMap[K, V]{
buckets: buckets,
numOfBuckets: m.numOfBuckets,
}
}
func (m *concurrnetMap[K, V]) Clear() {
for i := 0; i < int(m.numOfBuckets); i++ {
m.buckets[i].Clear()
}
}
func (m *concurrnetMap[K, V]) Count() int {
var count int
for i := 0; i < int(m.numOfBuckets); i++ {

View File

@ -2,15 +2,14 @@ package maps
import (
"github.com/charlienet/go-mixed/locker"
"golang.org/x/exp/constraints"
)
type hashMap[K constraints.Ordered, V any] struct {
type hashMap[K hashable, V any] struct {
m map[K]V
opt *options
}
func NewHashMap[K constraints.Ordered, V any](maps ...map[K]V) *hashMap[K, V] {
func NewHashMap[K hashable, V any](maps ...map[K]V) *hashMap[K, V] {
m := make(map[K]V)
if len(maps) > 0 {
m = Merge(maps...)

View File

@ -25,3 +25,8 @@ func TestForEach(t *testing.T) {
assert.True(t, hashMap.Exist(k))
}
}
func TestSynchronize(t *testing.T) {
mep := NewHashMap[string, string]().Synchronize()
mep.Set("aaaa", "bbb")
}

81
maps/haxmap.go Normal file
View File

@ -0,0 +1,81 @@
package maps
import (
"github.com/alphadose/haxmap"
)
type haxHashable interface {
int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | uintptr | float32 | float64 | string | complex64 | complex128
}
var _ Map[string, string] = &haxmapWrapper[string, string]{}
type haxmapWrapper[K haxHashable, V any] struct {
mep *haxmap.Map[K, V]
}
func NewHaxmap[K haxHashable, V any](size int) *haxmapWrapper[K, V] {
return &haxmapWrapper[K, V]{
mep: haxmap.New[K, V](uintptr(size)),
}
}
func (m *haxmapWrapper[K, V]) Set(k K, v V) {
m.mep.Set(k, v)
}
func (m *haxmapWrapper[K, V]) Get(k K) (V, bool) {
return m.mep.Get(k)
}
func (m *haxmapWrapper[K, V]) Keys() []K {
keys := make([]K, 0, m.mep.Len())
m.mep.ForEach(func(k K, v V) bool {
keys = append(keys, k)
return true
})
return keys
}
func (m *haxmapWrapper[K, V]) Values() []V {
values := make([]V, 0, m.mep.Len())
m.mep.ForEach(func(k K, v V) bool {
values = append(values, v)
return true
})
return values
}
func (m *haxmapWrapper[K, V]) Exist(k K) bool {
return false
}
func (m *haxmapWrapper[K, V]) Delete(key K) {
m.mep.Del(key)
}
func (m *haxmapWrapper[K, V]) ToMap() map[K]V {
mm := make(map[K]V, m.mep.Len())
m.mep.ForEach(func(k K, v V) bool {
mm[k] = v
return true
})
return mm
}
func (m *haxmapWrapper[K, V]) ForEach(fn func(K, V) bool) {
}
func (m *haxmapWrapper[K, V]) Count() int {
return int(m.mep.Len())
}
func (m *haxmapWrapper[K, V]) Clear() {
}

View File

@ -6,7 +6,11 @@ import (
"golang.org/x/exp/constraints"
)
type Map[K constraints.Ordered, V any] interface {
type hashable interface {
constraints.Integer | constraints.Float | ~string
}
type Map[K hashable, V any] interface {
Set(key K, value V) // 设置值
Get(key K) (value V, ok bool) // 获取值
Exist(key K) bool // 键是否存在
@ -14,19 +18,17 @@ type Map[K constraints.Ordered, V any] interface {
Keys() []K // 获取所有键
Values() []V // 获取所有值
ToMap() map[K]V // 转换为map
Clone() Map[K, V] // 复制
Clear() // 清空
Count() int // 数量
Iter() <-chan *Entry[K, V] // 迭代器
// Iter() <-chan *Entry[K, V] // 迭代器
ForEach(f func(K, V) bool) // ForEach
}
type Entry[K constraints.Ordered, V any] struct {
type Entry[K hashable, V any] struct {
Key K
Value V
}
func Merge[K comparable, V any](mm ...map[K]V) map[K]V {
func Merge[K hashable, V any](mm ...map[K]V) map[K]V {
ret := make(map[K]V)
for _, m := range mm {
for k, v := range m {
@ -38,7 +40,7 @@ func Merge[K comparable, V any](mm ...map[K]V) map[K]V {
}
// 按照键值生成字符串
func Join[K constraints.Ordered, V any](m Map[K, V], sep string, f func(k K, v V) string) string {
func Join[K hashable, V any](m Map[K, V], sep string, f func(k K, v V) string) string {
slice := make([]string, 0, m.Count())
m.ForEach(func(k K, v V) bool {

View File

@ -2,23 +2,21 @@ package maps
import (
"sync"
"golang.org/x/exp/constraints"
)
var _ Map[string, any] = &rw_map[string, any]{}
type rw_map[K constraints.Ordered, V any] struct {
type rw_map[K hashable, V any] struct {
m Map[K, V]
mu sync.RWMutex
}
func NewRWMap[K constraints.Ordered, V any](maps ...map[K]V) *rw_map[K, V] {
func NewRWMap[K hashable, V any](maps ...map[K]V) *rw_map[K, V] {
merged := Merge(maps...)
return &rw_map[K, V]{m: NewHashMap(merged)}
}
func newRWMap[K constraints.Ordered, V any](m Map[K, V]) *rw_map[K, V] {
func newRWMap[K hashable, V any](m Map[K, V]) *rw_map[K, V] {
return &rw_map[K, V]{m: m}
}
@ -74,28 +72,13 @@ func (m *rw_map[K, V]) Count() int {
return m.m.Count()
}
func (m *rw_map[K, V]) Iter() <-chan *Entry[K, V] {
m.mu.RLock()
defer m.mu.RUnlock()
// func (m *rw_map[K, V]) Iter() <-chan *Entry[K, V] {
// m.mu.RLock()
// defer m.mu.RUnlock()
return m.m.Iter()
}
// return m.m.Iter()
// }
func (m *rw_map[K, V]) ForEach(f func(K, V) bool) {
m.mu.RLock()
cloned := m.m.Clone()
m.mu.RUnlock()
cloned.ForEach(f)
}
func (m *rw_map[K, V]) Clone() Map[K, V] {
return newRWMap(m.m.Clone())
}
func (m *rw_map[K, V]) Clear() {
m.mu.Lock()
m.m.Clear()
m.mu.Unlock()
}

View File

@ -3,29 +3,27 @@ package maps
import (
"fmt"
"golang.org/x/exp/constraints"
"golang.org/x/exp/slices"
xmaps "golang.org/x/exp/maps"
)
var (
_ Map[string, any] = &sorted_map[string, any]{}
_ SortedMap[string, any] = &sorted_map[string, any]{}
)
type SortedMap[K constraints.Ordered, V any] interface {
type SortedMap[K hashable, V any] interface {
Map[K, V]
Asc() SortedMap[K, V]
Desc() SortedMap[K, V]
}
type sorted_map[K constraints.Ordered, V any] struct {
type sorted_map[K hashable, V any] struct {
keys []K
maps Map[K, V]
}
func NewSortedMap[K constraints.Ordered, V any](maps ...map[K]V) *sorted_map[K, V] {
func NewSortedMap[K hashable, V any](maps ...map[K]V) *sorted_map[K, V] {
merged := Merge(maps...)
return &sorted_map[K, V]{
keys: xmaps.Keys(merged),
@ -33,7 +31,7 @@ func NewSortedMap[K constraints.Ordered, V any](maps ...map[K]V) *sorted_map[K,
}
}
func NewSortedByMap[K constraints.Ordered, V any](m Map[K, V]) *sorted_map[K, V] {
func NewSortedByMap[K hashable, V any](m Map[K, V]) *sorted_map[K, V] {
return &sorted_map[K, V]{maps: m, keys: m.Keys()}
}
@ -43,6 +41,8 @@ func (m *sorted_map[K, V]) Get(key K) (V, bool) {
func (m *sorted_map[K, V]) Set(key K, value V) {
m.maps.Set(key, value)
slices.Sort(m.keys)
m.keys = append(m.keys, key)
}
@ -61,15 +61,6 @@ func (m *sorted_map[K, V]) Count() int {
return m.maps.Count()
}
func (m *sorted_map[K, V]) Clear() {
m.keys = make([]K, 0)
m.maps.Clear()
}
func (m *sorted_map[K, V]) Clone() Map[K, V] {
return &sorted_map[K, V]{maps: m.maps.Clone(), keys: m.Keys()}
}
func (m *sorted_map[K, V]) Iter() <-chan *Entry[K, V] {
c := make(chan *Entry[K, V], m.Count())
go func() {

View File

@ -3,21 +3,33 @@ package mathx
import (
"github.com/charlienet/go-mixed/expr"
"golang.org/x/exp/constraints"
"unsafe"
)
// MaxInt returns the larger one of v1 and v2.
// Max returns the larger one of v1 and v2.
func Max[T constraints.Ordered](v1, v2 T) T {
return expr.If(v1 > v2, v1, v2)
return expr.Ternary(v1 > v2, v1, v2)
}
// MinInt returns the smaller one of v1 and v2.
// Min returns the smaller one of v1 and v2.
func Min[T constraints.Ordered](v1, v2 T) T {
return expr.If(v1 < v2, v1, v2)
return expr.Ternary(v1 < v2, v1, v2)
}
func Abs1[T constraints.Signed](n T) T {
shift := 63
switch unsafe.Sizeof(n) {
case 1:
shift = 7
case 4:
shift = 31
}
y := n >> shift
return T((n ^ y) - y)
}
func abs(n int64) int64 {
func Abs(n int64) int64 {
y := n >> 63
return (n ^ y) - y
}

27
mathx/int_test.go Normal file
View File

@ -0,0 +1,27 @@
package mathx_test
import (
"github.com/charlienet/go-mixed/mathx"
"github.com/stretchr/testify/assert"
"testing"
)
func TestMin(t *testing.T) {
assert.Equal(t, 1, mathx.Min(1, 3))
assert.Equal(t, 2, mathx.Min(66, 2))
}
func TestMax(t *testing.T) {
assert.Equal(t, 3, mathx.Max(1, 3))
assert.Equal(t, 66, mathx.Max(66, 2))
}
func TestAbs(t *testing.T) {
assert.Equal(t, 23, mathx.Abs1(23))
assert.Equal(t, 23, mathx.Abs1(-23))
assert.Equal(t, 0, mathx.Abs1(0))
var u int8 = -127
var exp int8 = 127
assert.Equal(t, exp, mathx.Abs1(u))
}

54
redis/readme.md Normal file
View File

@ -0,0 +1,54 @@
# go-redis 连接选项
连接字符串
redis://<user>:<pass>@<hostname>:<port>/<db>
redis.ParseURL
```go
gClient = redis.NewClient(&redis.Options{
//连接信息
Network: "tcp", //网络类型tcp or unix默认tcp
Addr: "127.0.0.1:6379", //主机名+冒号+端口默认localhost:6379
Password: "", //密码
DB: 0, // redis数据库index
//连接池容量及闲置连接数量
PoolSize: 15, // 连接池最大socket连接数默认为4倍CPU数 4 * runtime.NumCPU
MinIdleConns: 10, //在启动阶段创建指定数量的Idle连接并长期维持idle状态的连接数不少于指定数量
//超时
DialTimeout: 5 * time.Second, //连接建立超时时间默认5秒。
ReadTimeout: 3 * time.Second, //读超时默认3秒 -1表示取消读超时
WriteTimeout: 3 * time.Second, //写超时,默认等于读超时
PoolTimeout: 4 * time.Second, //当所有连接都处在繁忙状态时,客户端等待可用连接的最大等待时长,默认为读超时+1秒。
//闲置连接检查包括IdleTimeoutMaxConnAge
IdleCheckFrequency: 60 * time.Second, //闲置连接检查的周期默认为1分钟-1表示不做周期性检查只在客户端获取连接时对闲置连接进行处理。
IdleTimeout: 5 * time.Minute, //闲置超时默认5分钟-1表示取消闲置超时检查
MaxConnAge: 0 * time.Second, //连接存活时长从创建开始计时超过指定时长则关闭连接默认为0即不关闭存活时长较长的连接
//命令执行失败时的重试策略
MaxRetries: 0, // 命令执行失败时最多重试多少次默认为0即不重试
MinRetryBackoff: 8 * time.Millisecond, //每次计算重试间隔时间的下限默认8毫秒-1表示取消间隔
MaxRetryBackoff: 512 * time.Millisecond, //每次计算重试间隔时间的上限默认512毫秒-1表示取消间隔
//可自定义连接函数
Dialer: func() (net.Conn, error) {
netDialer := &net.Dialer{
Timeout: 5 * time.Second,
KeepAlive: 5 * time.Minute,
}
return netDialer.Dial("tcp", "127.0.0.1:6379")
},
//钩子函数
OnConnect: func(conn *redis.Conn) error { //仅当客户端执行命令时需要从连接池获取连接时,如果连接池需要新建连接时则会调用此钩子函数
fmt.Printf("conn=%v\n", conn)
return nil
},
})
defer gClient.Close()
```

105
redis/redis.go Normal file
View File

@ -0,0 +1,105 @@
package redis
import (
"context"
"time"
"github.com/go-redis/redis/v8"
)
const (
defaultSeparator = ":"
blockingQueryTimeout = 5 * time.Second
readWriteTimeout = 2 * time.Second
defaultSlowThreshold = time.Millisecond * 100 // 慢查询
)
type Option func(r *Redis)
type Redis struct {
addr string // 服务器地址
prefix string // 键值前缀
separator string // 分隔符
}
func New(addr string, opts ...Option) *Redis {
r := &Redis{
addr: addr,
}
return r
}
func (s *Redis) Set(ctx context.Context, key, value string) error {
conn, err := s.getRedis()
if err != nil {
return err
}
return conn.Set(ctx, s.formatKey(key), value, 0).Err()
}
func (s *Redis) Get(ctx context.Context, key string) (string, error) {
conn, err := s.getRedis()
if err != nil {
return "", err
}
return conn.Get(ctx, s.formatKey(key)).Result()
}
func (s *Redis) GetSet(ctx context.Context, key, value string) (string, error) {
conn, err := s.getRedis()
if err != nil {
return "", err
}
val, err := conn.GetSet(ctx, s.formatKey(key), value).Result()
return val, err
}
func (s *Redis) Del(ctx context.Context, key ...string) (int, error) {
conn, err := s.getRedis()
if err != nil {
return 0, err
}
keys := s.formatKeys(key...)
v, err := conn.Del(ctx, keys...).Result()
if err != nil {
return 0, err
}
return int(v), err
}
func (s *Redis) getRedis() (redis.UniversalClient, error) {
client := redis.NewUniversalClient(&redis.UniversalOptions{
Addrs: []string{s.addr},
})
return client, nil
}
func (s *Redis) formatKeys(keys ...string) []string {
// If no prefix is configured, this parameter is returned
if s.prefix == "" {
return keys
}
ret := make([]string, 0, len(keys))
for _, k := range keys {
ret = append(ret, s.formatKey(k))
}
return ret
}
func (s *Redis) formatKey(key string) string {
if s.prefix == "" {
return key
}
return s.prefix + s.separator + key
}

86
redis/redis_test.go Normal file
View File

@ -0,0 +1,86 @@
package redis
import (
"context"
"log"
"testing"
"time"
"github.com/alicebob/miniredis/v2"
"github.com/stretchr/testify/assert"
)
func TestGetSet(t *testing.T) {
runOnRedis(t, func(client *Redis) {
ctx := context.Background()
val, err := client.GetSet(ctx, "hello", "world")
assert.NotNil(t, err)
assert.Equal(t, "", val)
val, err = client.Get(ctx, "hello")
assert.Nil(t, err)
assert.Equal(t, "world", val)
val, err = client.GetSet(ctx, "hello", "newworld")
assert.Nil(t, err)
assert.Equal(t, "world", val)
val, err = client.Get(ctx, "hello")
assert.Nil(t, err)
assert.Equal(t, "newworld", val)
ret, err := client.Del(ctx, "hello")
assert.Nil(t, err)
assert.Equal(t, 1, ret)
})
}
func TestRedis_SetGetDel(t *testing.T) {
runOnRedis(t, func(client *Redis) {
ctx := context.Background()
err := client.Set(ctx, "hello", "world")
assert.Nil(t, err)
val, err := client.Get(ctx, "hello")
assert.Nil(t, err)
assert.Equal(t, "world", val)
ret, err := client.Del(ctx, "hello")
assert.Nil(t, err)
assert.Equal(t, 1, ret)
})
}
func runOnRedis(t *testing.T, fn func(client *Redis)) {
redis, clean, err := CreateMiniRedis()
assert.Nil(t, err)
defer clean()
fn(redis)
}
func CreateMiniRedis() (r *Redis, clean func(), err error) {
mr, err := miniredis.Run()
if err != nil {
return nil, nil, err
}
addr := mr.Addr()
log.Println("mini redis run at:", addr)
return New(addr), func() {
ch := make(chan struct{})
go func() {
mr.Close()
close(ch)
}()
select {
case <-ch:
case <-time.After(time.Second):
}
}, nil
}

View File

@ -27,7 +27,7 @@ func NewHashSet[T constraints.Ordered](values ...T) *hash_set[T] {
return &set
}
func (s *hash_set[T]) WithSync() *hash_set[T] {
func (s *hash_set[T]) Sync() *hash_set[T] {
s.lock = locker.NewRWLocker()
return s
}
@ -99,6 +99,10 @@ func (s hash_set[T]) copyToSorted() Set[T] {
return orderd
}
func (s *hash_set[T]) Shrink() *hash_set[T] {
return s
}
func (s *hash_set[T]) Clone() *hash_set[T] {
set := NewHashSet[T]()
set.Add(s.ToSlice()...)

View File

@ -29,7 +29,7 @@ func TestContainsAll(t *testing.T) {
}
func TestContainsAny(t *testing.T) {
sets.NewHashSet("1", "2").Sync()
}
func TestMarshal(t *testing.T) {

View File

@ -47,7 +47,7 @@ func BenchmarkGetId(b *testing.B) {
func BenchmarkMutiGetId(b *testing.B) {
s := CreateSnowflake(11)
set := sets.NewHashSet[int64]().WithSync()
set := sets.NewHashSet[int64]().Sync()
b.RunParallel(func(p *testing.PB) {
for i := 0; p.Next(); i++ {
id := s.GetId()

View File

@ -19,7 +19,7 @@ func parseField(fi reflect.StructField, opt option) field {
return field{
name: fi.Name,
tagName: expr.If(isValidTag(name), name, expr.If(opt.NameConverter != nil, opt.NameConverter(fi.Name), fi.Name)),
tagName: expr.Ternary(isValidTag(name), name, expr.Ternary(opt.NameConverter != nil, opt.NameConverter(fi.Name), fi.Name)),
ignoreEmpty: opt.IgnoreEmpty || (opts.Contains("omitempty") && opt.Omitempty),
ignore: (name == "-" && opt.Ignore) || isSkipField(fi.Name, opt.SkipFields),
}