code: protected int findBucket(
        final boolean allowNewBucket,
        final int buckets,
        final ByteBuffer targetTableBuffer,
        final ByteBuffer keyBuffer,
        final int keyHash
    )
    {
      // startBucket will never be negative since keyHash is always positive (see Groupers.hash)
      final int startBucket = keyHash % buckets;
      int bucket = startBucket;
  
      outer:
      while (true) {
        final int bucketOffset = bucket * bucketSizeWithHash;
  
        if ((targetTableBuffer.get(bucketOffset) & 0x80) == 0) {
          // Found unused bucket before finding our key
          return allowNewBucket ? bucket : -1;
        }
  
        for (int i = bucketOffset + HASH_SIZE, j = keyBuffer.position(); j < keyBuffer.position() + keySize; i++, j++) {
          if (targetTableBuffer.get(i) != keyBuffer.get(j)) {
            bucket += 1;
            if (bucket == buckets) {
              bucket = 0;
            }
  
            if (bucket == startBucket) {
              // Came back around to the start without finding a free slot, that was a long trip!
              // Should never happen unless buckets == regrowthThreshold.
              return -1;
            }
  
            continue outer;
          }
        }
  
        // Found our key in a used bucket
        return bucket;
      }
    }
docs: Finds the bucket into which we should insert a key.
  
  @param keyBuffer key, must have exactly keySize bytes remaining. Will not be modified.
  @param targetTableBuffer Need selectable buffer, since when resizing hash table,
  findBucket() is used on the newly allocated table buffer
  
  @return bucket index for this key, or -1 if no bucket is available due to being full