github.com/fufuok/balancer@v1.0.0/internal/doublejump/doublejump.go (about) 1 // Package doublejump provides a revamped Google's jump consistent hash. 2 package doublejump 3 4 import ( 5 "math/rand" 6 7 "github.com/fufuok/balancer/internal/go-jump" 8 ) 9 10 type looseHolder struct { 11 a []interface{} 12 m map[interface{}]int 13 f []int 14 } 15 16 func (l *looseHolder) add(obj interface{}) { 17 if _, ok := l.m[obj]; ok { 18 return 19 } 20 21 if nf := len(l.f); nf == 0 { 22 l.a = append(l.a, obj) 23 l.m[obj] = len(l.a) - 1 24 } else { 25 idx := l.f[nf-1] 26 l.f = l.f[:nf-1] 27 l.a[idx] = obj 28 l.m[obj] = idx 29 } 30 } 31 32 func (l *looseHolder) remove(obj interface{}) { 33 if idx, ok := l.m[obj]; ok { 34 l.f = append(l.f, idx) 35 l.a[idx] = nil 36 delete(l.m, obj) 37 } 38 } 39 40 func (l *looseHolder) get(key uint64) interface{} { 41 na := len(l.a) 42 if na == 0 { 43 return nil 44 } 45 46 h := jump.Hash(key, na) 47 return l.a[h] 48 } 49 50 func (l *looseHolder) shrink() { 51 if len(l.f) == 0 { 52 return 53 } 54 55 var a []interface{} 56 for _, obj := range l.a { 57 if obj != nil { 58 a = append(a, obj) 59 l.m[obj] = len(a) - 1 60 } 61 } 62 l.a = a 63 l.f = nil 64 } 65 66 type compactHolder struct { 67 a []interface{} 68 m map[interface{}]int 69 } 70 71 func (c *compactHolder) add(obj interface{}) { 72 if _, ok := c.m[obj]; ok { 73 return 74 } 75 76 c.a = append(c.a, obj) 77 c.m[obj] = len(c.a) - 1 78 } 79 80 func (c *compactHolder) shrink(a []interface{}) { 81 for i, obj := range a { 82 c.a[i] = obj 83 c.m[obj] = i 84 } 85 } 86 87 func (c *compactHolder) remove(obj interface{}) { 88 if idx, ok := c.m[obj]; ok { 89 n := len(c.a) 90 c.a[idx] = c.a[n-1] 91 c.m[c.a[idx]] = idx 92 c.a[n-1] = nil 93 c.a = c.a[:n-1] 94 delete(c.m, obj) 95 } 96 } 97 98 func (c *compactHolder) get(key uint64) interface{} { 99 na := len(c.a) 100 if na == 0 { 101 return nil 102 } 103 104 h := jump.Hash(key*0xc6a4a7935bd1e995, na) 105 return c.a[h] 106 } 107 108 // Hash is a revamped Google's jump consistent hash. It overcomes the shortcoming of the 109 // original implementation - not being able to remove nodes. 110 type Hash struct { 111 loose looseHolder 112 compact compactHolder 113 } 114 115 // NewHash creates a new doublejump hash instance, which does NOT threadsafe. 116 func NewHash() *Hash { 117 hash := &Hash{} 118 hash.loose.m = make(map[interface{}]int) 119 hash.compact.m = make(map[interface{}]int) 120 return hash 121 } 122 123 // Add adds an object to the hash. 124 func (h *Hash) Add(obj interface{}) { 125 if obj == nil { 126 return 127 } 128 129 h.loose.add(obj) 130 h.compact.add(obj) 131 } 132 133 // Remove removes an object from the hash. 134 func (h *Hash) Remove(obj interface{}) { 135 if obj == nil { 136 return 137 } 138 139 h.loose.remove(obj) 140 h.compact.remove(obj) 141 } 142 143 // Len returns the number of objects in the hash. 144 func (h *Hash) Len() int { 145 return len(h.compact.a) 146 } 147 148 // LooseLen returns the size of the inner loose object holder. 149 func (h *Hash) LooseLen() int { 150 return len(h.loose.a) 151 } 152 153 // Shrink removes all empty slots from the hash. 154 func (h *Hash) Shrink() { 155 h.loose.shrink() 156 h.compact.shrink(h.loose.a) 157 } 158 159 // Get returns an object according to the key provided. 160 func (h *Hash) Get(key uint64) interface{} { 161 obj := h.loose.get(key) 162 switch obj { 163 case nil: 164 return h.compact.get(key) 165 default: 166 return obj 167 } 168 } 169 170 // All returns all the objects in this Hash. 171 func (h *Hash) All() []interface{} { 172 n := len(h.compact.a) 173 if n == 0 { 174 return nil 175 } 176 all := make([]interface{}, n) 177 copy(all, h.compact.a) 178 return all 179 } 180 181 // Random returns a random object. 182 func (h *Hash) Random() interface{} { 183 if n := len(h.compact.a); n > 0 { 184 idx := rand.Intn(n) 185 return h.compact.a[idx] 186 } 187 return nil 188 }