func (cache *Cache) get(key string) (*Entry, bool) {
	entry, ok := cache.entries[key]
	return entry, ok
}
get retrieves an entry using the key passed as parameter, but unlike Get, it doesn't update the access time or
move the position of the entry to the head
package gocache
import (
	"errors"
	"reflect"
	"sync"
	"time"
)
var (
	Debug = false
)
const (
	NoMaxSize = 0
	NoMaxMemoryUsage = 0
	DefaultMaxSize = 100000
	NoExpiration = -1
	Kilobyte = 1024
	Megabyte = 1024 * Kilobyte
	Gigabyte = 1024 * Megabyte
)
var (
	ErrKeyDoesNotExist       = errors.New("key does not exist")          
	ErrKeyHasNoExpiration    = errors.New("key has no expiration")       
	ErrJanitorAlreadyRunning = errors.New("janitor is already running")  
)
type Cache struct {
	maxSize int
	maxMemoryUsage int
	evictionPolicy EvictionPolicy
	stats *Statistics
	entries map[string]*Entry
	mutex sync.RWMutex
	head *Entry
	tail *Entry
	stopJanitor chan bool
	memoryUsage int
	forceNilInterfaceOnNilPointer bool
}
func (cache *Cache) MaxSize() int {
	return cache.maxSize
}
func (cache *Cache) MaxMemoryUsage() int {
	return cache.maxMemoryUsage
}
func (cache *Cache) EvictionPolicy() EvictionPolicy {
	return cache.evictionPolicy
}
func (cache *Cache) Stats() Statistics {
	cache.mutex.RLock()
	stats := Statistics{
		EvictedKeys: cache.stats.EvictedKeys,
		ExpiredKeys: cache.stats.ExpiredKeys,
		Hits:        cache.stats.Hits,
		Misses:      cache.stats.Misses,
	}
	cache.mutex.RUnlock()
	return stats
}
func (cache *Cache) MemoryUsage() int {
	return cache.memoryUsage
}
func (cache *Cache) WithMaxSize(maxSize int) *Cache {
	if maxSize < 0 {
		maxSize = NoMaxSize
	}
	if maxSize != NoMaxSize && cache.Count() == 0 {
		cache.entries = make(map[string]*Entry, maxSize)
	}
	cache.maxSize = maxSize
	return cache
}
func (cache *Cache) WithMaxMemoryUsage(maxMemoryUsageInBytes int) *Cache {
	if maxMemoryUsageInBytes < 0 {
		maxMemoryUsageInBytes = NoMaxMemoryUsage
	}
	cache.maxMemoryUsage = maxMemoryUsageInBytes
	return cache
}
func (cache *Cache) WithEvictionPolicy(policy EvictionPolicy) *Cache {
	cache.evictionPolicy = policy
	return cache
}
func (cache *Cache) WithForceNilInterfaceOnNilPointer(forceNilInterfaceOnNilPointer bool) *Cache {
	cache.forceNilInterfaceOnNilPointer = forceNilInterfaceOnNilPointer
	return cache
}
func NewCache() *Cache {
	return &Cache{
		maxSize:                       DefaultMaxSize,
		evictionPolicy:                FirstInFirstOut,
		stats:                         &Statistics{},
		entries:                       make(map[string]*Entry),
		mutex:                         sync.RWMutex{},
		stopJanitor:                   nil,
		forceNilInterfaceOnNilPointer: true,
	}
}
func (cache *Cache) Set(key string, value interface{}) {
	cache.SetWithTTL(key, value, NoExpiration)
}
func (cache *Cache) SetWithTTL(key string, value interface{}, ttl time.Duration) {
	if cache.forceNilInterfaceOnNilPointer {
		if value != nil && (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) {
			value = nil
		}
	}
	cache.mutex.Lock()
	entry, ok := cache.get(key)
	if !ok {
		if ttl != NoExpiration && ttl < 1 {
			cache.mutex.Unlock()
			return
		}
		entry = &Entry{
			Key:               key,
			Value:             value,
			RelevantTimestamp: time.Now(),
			next:              cache.head,
		}
		if cache.head == nil {
			cache.tail = entry
		} else {
			cache.head.previous = entry
		}
		cache.head = entry
		cache.entries[key] = entry
		if cache.maxMemoryUsage != NoMaxMemoryUsage {
			cache.memoryUsage += entry.SizeInBytes()
		}
	} else {
		if ttl != NoExpiration && ttl < 1 {
			cache.delete(key)
			cache.mutex.Unlock()
			return
		}
		if cache.maxMemoryUsage != NoMaxMemoryUsage {
			cache.memoryUsage -= entry.SizeInBytes()
		}
		entry.Value = value
		entry.RelevantTimestamp = time.Now()
		if cache.maxMemoryUsage != NoMaxMemoryUsage {
			cache.memoryUsage += entry.SizeInBytes()
		}
		cache.moveExistingEntryToHead(entry)
	}
	if ttl != NoExpiration {
		entry.Expiration = time.Now().Add(ttl).UnixNano()
	} else {
		entry.Expiration = NoExpiration
	}
	if cache.maxSize == NoMaxSize && cache.maxMemoryUsage == NoMaxMemoryUsage {
		cache.mutex.Unlock()
		return
	}
	if cache.maxSize != NoMaxSize && len(cache.entries) > cache.maxSize {
		cache.evict()
	}
	if cache.maxMemoryUsage != NoMaxMemoryUsage && cache.memoryUsage > cache.maxMemoryUsage {
		for cache.memoryUsage > cache.maxMemoryUsage && len(cache.entries) > 0 {
			cache.evict()
		}
	}
	cache.mutex.Unlock()
}
func (cache *Cache) SetAll(entries map[string]interface{}) {
	for key, value := range entries {
		cache.SetWithTTL(key, value, NoExpiration)
	}
}
func (cache *Cache) Get(key string) (interface{}, bool) {
	cache.mutex.Lock()
	entry, ok := cache.get(key)
	if !ok {
		cache.mutex.Unlock()
		cache.stats.Misses++
		return nil, false
	}
	if entry.Expired() {
		cache.stats.ExpiredKeys++
		cache.delete(key)
		cache.mutex.Unlock()
		return nil, false
	}
	cache.stats.Hits++
	if cache.evictionPolicy == LeastRecentlyUsed {
		entry.Accessed()
		if cache.head == entry {
			cache.mutex.Unlock()
			return entry.Value, true
		}
		cache.moveExistingEntryToHead(entry)
	}
	cache.mutex.Unlock()
	return entry.Value, true
}
func (cache *Cache) GetValue(key string) interface{} {
	value, _ := cache.Get(key)
	return value
}
func (cache *Cache) GetByKeys(keys []string) map[string]interface{} {
	entries := make(map[string]interface{})
	for _, key := range keys {
		entries[key], _ = cache.Get(key)
	}
	return entries
}
func (cache *Cache) GetAll() map[string]interface{} {
	entries := make(map[string]interface{})
	cache.mutex.Lock()
	for key, entry := range cache.entries {
		if entry.Expired() {
			cache.delete(key)
			continue
		}
		entries[key] = entry.Value
	}
	cache.stats.Hits += uint64(len(entries))
	cache.mutex.Unlock()
	return entries
}
func (cache *Cache) GetKeysByPattern(pattern string, limit int) []string {
	var matchingKeys []string
	cache.mutex.Lock()
	for key, value := range cache.entries {
		if value.Expired() {
			continue
		}
		if MatchPattern(pattern, key) {
			matchingKeys = append(matchingKeys, key)
			if limit > 0 && len(matchingKeys) >= limit {
				break
			}
		}
	}
	cache.mutex.Unlock()
	return matchingKeys
}
func (cache *Cache) Delete(key string) bool {
	cache.mutex.Lock()
	ok := cache.delete(key)
	cache.mutex.Unlock()
	return ok
}
func (cache *Cache) DeleteAll(keys []string) int {
	numberOfKeysDeleted := 0
	cache.mutex.Lock()
	for _, key := range keys {
		if cache.delete(key) {
			numberOfKeysDeleted++
		}
	}
	cache.mutex.Unlock()
	return numberOfKeysDeleted
}
func (cache *Cache) Count() int {
	cache.mutex.RLock()
	count := len(cache.entries)
	cache.mutex.RUnlock()
	return count
}
func (cache *Cache) Clear() {
	cache.mutex.Lock()
	cache.entries = make(map[string]*Entry)
	cache.memoryUsage = 0
	cache.head = nil
	cache.tail = nil
	cache.mutex.Unlock()
}
func (cache *Cache) TTL(key string) (time.Duration, error) {
	cache.mutex.RLock()
	entry, ok := cache.get(key)
	cache.mutex.RUnlock()
	if !ok {
		return 0, ErrKeyDoesNotExist
	}
	if entry.Expiration == NoExpiration {
		return 0, ErrKeyHasNoExpiration
	}
	timeUntilExpiration := time.Until(time.Unix(0, entry.Expiration))
	if timeUntilExpiration < 0 {
		return 0, ErrKeyDoesNotExist
	}
	return timeUntilExpiration, nil
}
func (cache *Cache) Expire(key string, ttl time.Duration) bool {
	entry, ok := cache.get(key)
	if !ok || entry.Expired() {
		return false
	}
	if ttl != NoExpiration {
		entry.Expiration = time.Now().Add(ttl).UnixNano()
	} else {
		entry.Expiration = NoExpiration
	}
	return true
}