Йордан обнови решението на 16.11.2014 18:35 (преди над 3 години)
+package main
+
+import (
+ "errors"
+ "reflect"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+)
+
+type DeleteSignal struct{}
+
+type HashedValue struct {
+ value interface{}
+ expire time.Duration
+ timeExpires time.Time
+ ch chan DeleteSignal
+}
+
+// Create new HashedValue
+func NewHashedValue(value interface{}, expire time.Duration) *HashedValue {
+ hv := new(HashedValue)
+ hv.value = value
+ hv.expire = expire
+ hv.ch = make(chan DeleteSignal)
+ hv.timeExpires = time.Now().Add(expire)
+ return hv
+}
+
+type ExpireMap struct {
+ hash map[string]*HashedValue
+ expiredKeys []string
+ ch chan string
+ chEKIn chan string
+ chEKOut chan string
+ mutex *sync.Mutex
+}
+
+// Create new ExpireMap
+func NewExpireMap() *ExpireMap {
+ em := new(ExpireMap)
+ em.hash = make(map[string]*HashedValue)
+ em.ch = make(chan string)
+ em.chEKIn = make(chan string)
+ em.chEKOut = make(chan string)
+
+ go func() {
+ for {
+ if len(em.expiredKeys) > 0 {
+ select {
+ case em.chEKOut <- em.expiredKeys[0]:
+ em.expiredKeys = em.expiredKeys[1:]
+ case key := <-em.chEKIn:
+ em.expiredKeys = append(em.expiredKeys, key)
+ }
+ } else {
+ key := <-em.chEKIn
+ em.expiredKeys = append(em.expiredKeys, key)
+ }
+ }
+ }()
+ em.mutex = new(sync.Mutex)
+ return em
+}
+
+// Set a record in Expire map
+func (em *ExpireMap) Set(key string, value interface{}, expire time.Duration) {
+ hv := NewHashedValue(value, expire)
+ em.hash[key] = hv
+
+ // run a goroute to delete the record after expiring
+ go func(key string, duration time.Duration, ch chan DeleteSignal) {
+
+ defer em.mutex.Unlock()
+
+ select {
+ case <-ch:
+ em.mutex.Lock()
+ delete(em.hash, key)
+
+ case <-time.After(duration):
+ em.mutex.Lock()
+ go func(expiredKey string) {
+ em.chEKIn <- expiredKey
+ }(key)
+
+ delete(em.hash, key)
+
+ }
+
+ }(key, expire, hv.ch)
+}
+
+// get a HashedValue.value by a key from the ExpireMap
+func (em *ExpireMap) Get(key string) (interface{}, bool) {
+ if hashed, ok := em.hash[key]; ok {
+
+ em.mutex.Lock()
+
+ defer em.mutex.Unlock()
+
+ return hashed.value, true
+ }
+ return nil, false
+}
+
+// get an integer HashedValue.value by a key from the ExpireMap
+func (em *ExpireMap) GetInt(key string) (int, bool) {
+ if hashed, ok := em.hash[key]; ok && reflect.ValueOf(hashed.value).Type().Name() == "int" {
+
+ em.mutex.Lock()
+
+ defer em.mutex.Unlock()
+
+ return hashed.value.(int), true
+ }
+ return 0, false
+}
+
+// get a float64 HashedValue.value by a key from the ExpireMap
+func (em *ExpireMap) GetFloat64(key string) (float64, bool) {
+ if hashed, ok := em.hash[key]; ok && reflect.ValueOf(hashed.value).Type().Name() == "float64" {
+
+ em.mutex.Lock()
+
+ defer em.mutex.Unlock()
+
+ return hashed.value.(float64), true
+ }
+ return 0.0, false
+}
+
+// get a string HashedValue.value by a key from the ExpireMap
+func (em *ExpireMap) GetString(key string) (string, bool) {
+ if hashed, ok := em.hash[key]; ok && reflect.ValueOf(hashed.value).Type().Name() == "string" {
+ em.mutex.Lock()
+ defer em.mutex.Unlock()
+
+ return hashed.value.(string), true
+ }
+ return "", false
+}
+
+// get a string HashedValue.value by a key from the ExpireMap
+func (em *ExpireMap) GetBool(key string) (bool, bool) {
+ if hashed, ok := em.hash[key]; ok && reflect.ValueOf(hashed.value).Type().Name() == "bool" {
+
+ em.mutex.Lock()
+
+ defer em.mutex.Unlock()
+
+ return hashed.value.(bool), true
+ }
+ return false, false
+}
+
+// get the time when the HashedValue.value will expire
+func (em *ExpireMap) Expires(key string) (time.Time, bool) {
+ if hashed, ok := em.hash[key]; ok {
+
+ em.mutex.Lock()
+
+ defer em.mutex.Unlock()
+
+ return hashed.timeExpires, true
+ }
+ return time.Date(1, time.January, 01, 00, 0, 0, 0, time.UTC), false
+}
+
+// deletes the value in the ExpireMap with the given key
+func (em *ExpireMap) Delete(key string) {
+ if hashed, ok := em.hash[key]; ok {
+ hashed.ch <- DeleteSignal{}
+ }
+}
+
+// checks in the key is in the hash
+func (em *ExpireMap) Contains(key string) bool {
+ em.mutex.Lock()
+ defer em.mutex.Unlock()
+ _, ok := em.hash[key]
+
+ return ok
+}
+
+// size of the hash
+func (em *ExpireMap) Size() int {
+ return len(em.hash)
+}
+
+// makes the string on position key in ExpireMap with CAPS
+func (em *ExpireMap) ToUpper(key string) error {
+ if hashed, ok := em.hash[key]; ok && reflect.ValueOf(hashed.value).Type().Name() == "string" {
+
+ em.mutex.Lock()
+
+ defer func() {
+ em.mutex.Unlock()
+ }()
+
+ em.hash[key].value = strings.ToUpper(hashed.value.(string))
+ return nil
+ }
+ return errors.New("Not a string or key expired")
+}
+
+// makes the string on position key in ExpireMap without CAPS
+func (em *ExpireMap) ToLower(key string) error {
+ if hashed, ok := em.hash[key]; ok && reflect.ValueOf(hashed.value).Type().Name() == "string" {
+
+ em.mutex.Lock()
+
+ defer func() {
+ em.mutex.Unlock()
+ }()
+
+ em.hash[key].value = strings.ToLower(hashed.value.(string))
+ return nil
+ }
+ return errors.New("Not a string or key expired")
+}
+
+// returns expired keys
+func (em *ExpireMap) ExpiredChan() <-chan string {
+ return em.chEKOut
+}
+
+// cleans all records in the hash
+func (em *ExpireMap) Cleanup() {
+ for key, _ := range em.hash {
+ delete(em.hash, key)
+ }
+}
+
+// destructor of the ExpireMap
+func (em *ExpireMap) Destroy() {
+
+ for _, value := range em.hash {
+
+ value.ch <- DeleteSignal{}
+
+ }
+ close(em.ch)
+
+}
+
+// increments values like 42 or "42" by 1
+func (em *ExpireMap) Increment(key string) error {
+ if hashed, ok := em.hash[key]; ok {
+
+ em.mutex.Lock()
+
+ defer func() {
+ em.mutex.Unlock()
+ }()
+
+ switch reflect.ValueOf(hashed.value).Type().Name() {
+ case "int":
+ em.hash[key].value = hashed.value.(int) + 1
+ return nil
+ break
+ case "string":
+ number, err := strconv.Atoi(hashed.value.(string))
+ if err != nil {
+ return err
+ }
+ number++
+ em.hash[key].value = strconv.Itoa(number)
+ return nil
+ break
+ default:
+ return errors.New("Cannot increment. Not a string or int")
+ }
+ }
+ return errors.New("Key not found")
+}
+
+// decrements values like 42 or "42" by 1
+func (em *ExpireMap) Decrement(key string) error {
+ if hashed, ok := em.hash[key]; ok {
+
+ em.mutex.Lock()
+
+ defer func() {
+ em.mutex.Unlock()
+ }()
+
+ switch reflect.ValueOf(hashed.value).Type().Name() {
+ case "int":
+ em.hash[key].value = hashed.value.(int) - 1
+ return nil
+ break
+ case "string":
+ number, err := strconv.Atoi(hashed.value.(string))
+ if err != nil {
+ return err
+ }
+ number--
+ em.hash[key].value = strconv.Itoa(number)
+ return nil
+ break
+ default:
+ errors.New("Cannot decrement. Not a string or int")
+ }
+ }
+ return errors.New("Key not found")
+}
Така първо с добро - харесва ми.
Второ има такова нещо като type switch - reflect не ти трябва :)
И после :
Какво става ако кажа
em.Set("key", 2, time.Millisecond)
em.Set("key", 2, 1 * time.Second)
Cleanup-а ти има лекия недостатък че не премахва горутини и съответно (приемерно) пращаш сигнали че е expired-нал ключ който всъщност е бил Cleanup-нат.
Destroy-а ти има още неща за затваряне.
Харесва ми буферирането ти на изтекли ключове, но ние НЕ го искаме и имаме тестове които фейлват заради това - не знам дали ще ги задържим и дали ще ти отнемаме точки, но го махни докато оправяш другите неща за всеки случай (аз поне не смятам че трябва да го правиш - макар че е готино :)).