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  }