github.com/sunvim/utils@v0.1.0/stringsbank/stringsbank.go (about) 1 /* 2 Package stringbank allows you to hold large numbers of strings without bothering the 3 garbage collector. For small strings storage is reduced as the lengths are encoded compactly. 4 */ 5 package stringbank 6 7 import ( 8 "math/bits" 9 "unsafe" 10 ) 11 12 const stringbankSize = 1 << 18 // about 250k as a power of 2 13 14 var packageBank Stringbank 15 16 // Index is returned by Save and can be converted into the saved string by calling it's String() method 17 type Index int 18 19 func (i Index) String() string { 20 return packageBank.Get(int(i)) 21 } 22 23 // Save stores the string in the default package-level Stringbank, and returns an Index which can be converted back to the original string by calling its String() method 24 func Save(val string) Index { 25 return Index(packageBank.Save(val)) 26 } 27 28 // Stringbank is a place to put strings that never need to be deleted. Saving a string into the Stringbank 29 // returns an integer offset for the string, so the string can be stored and referenced without bothering the 30 // garbage collector. The offset can be exchanged for the original string via a call to Get 31 type Stringbank struct { 32 current []byte 33 allocations [][]byte 34 } 35 36 // Size returns the approximate number of bytes in the string bank. The estimate includes currently unused and 37 // wasted space 38 func (s *Stringbank) Size() int { 39 return len(s.allocations) * stringbankSize 40 } 41 42 // Get converts an index to the original string 43 func (s *Stringbank) Get(index int) string { 44 // read the length and string from the data 45 data := s.allocations[index/stringbankSize] 46 offset := index % stringbankSize 47 l, llen := readLength(data[offset:]) 48 49 b := data[offset+llen : offset+llen+l] 50 return *(*string)(unsafe.Pointer(&b)) 51 } 52 53 // Save copies a string into the Stringbank, and returns the index of the string in the bank 54 func (s *Stringbank) Save(tocopy string) int { 55 l := len(tocopy) 56 offset, buf := s.reserve(l + spaceForLength(l)) 57 // Write the length 58 start := writeLength(l, buf) 59 60 // Write the data 61 copy(buf[start:], tocopy) 62 return offset 63 } 64 65 // reserve finds a contiguous space of length l that can be used for writing data 66 func (s *Stringbank) reserve(l int) (index int, data []byte) { 67 if len(s.current)+l > cap(s.current) { 68 s.current = make([]byte, 0, stringbankSize) 69 s.allocations = append(s.allocations, s.current[0:stringbankSize]) 70 } 71 offset := len(s.current) 72 s.current = s.current[:offset+l] 73 return (len(s.allocations)-1)*stringbankSize + offset, s.current[offset:] 74 } 75 76 func spaceForLength(len int) int { 77 // 7 bits => 1 byte 78 // 8 bits => 2 byte 79 // 1 80 bits := bits.Len(uint(len)) 81 return (bits + 6) / 7 82 } 83 84 func writeLength(len int, buf []byte) int { 85 // Want to write the length in a compact manner, with the assumption that short lengths 86 // are much more common 87 remainder := len 88 var i int 89 for i = 0; remainder != 0; i++ { 90 val := byte(remainder & 0x7F) 91 remainder = remainder >> 7 92 if remainder != 0 { 93 val |= 0x80 94 } 95 buf[i] = val 96 } 97 return i 98 } 99 100 func readLength(buf []byte) (int, int) { 101 total := 0 102 for i := uint(0); ; i++ { 103 val := buf[i] 104 total += int(val&0x7F) << (7 * i) 105 if val&0x80 == 0 { 106 return total, int(i + 1) 107 } 108 } 109 }