├── .gitignore
├── README.md
├── pom.xml
└── src
├── main
└── java
│ └── com
│ └── github
│ └── ssedano
│ └── hash
│ └── JumpConsistentHash.java
└── test
└── java
└── com
└── github
└── ssedano
└── hash
└── JumpConsistentHashTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | .classpath
2 | .project
3 | target/
4 | .settings
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Jump Consistent Hash
2 |
3 | A Java implementation of the jump consistent hash from Lamping and Veach.
4 |
5 | Paper: [http://arxiv.org/ftp/arxiv/papers/1406/1406.2294.pdf](http://arxiv.org/ftp/arxiv/papers/1406/1406.2294.pdf)
6 |
7 | ## Abstract
8 |
9 | Extracted from the paper
10 |
11 | > We present jump consistent hash, a fast, minimal memory, consistent hash algorithm that can
12 | be expressed in about 5 lines of code. In comparison to the algorithm of Karger et al., jump
13 | consistent hash requires no storage, is faster, and does a better job of evenly dividing the key
14 | space among the buckets and of evenly dividing the workload when the number of buckets
15 | changes. Its main limitation is that the buckets must be numbered sequentially, which makes it
16 | more suitable for data storage applications than for distributed web caching.
17 |
18 | # Usage
19 |
20 | Add the dependency to your pom.xml
21 |
22 |
24 | * The C++ implementation they provide is as follows: 25 | * 26 | *
27 | * {@code 28 | * int32_t JumpConsistentHash(uint64_t key, int32_t num_buckets) { 29 | * int64_t b = -1, j = 0; 30 | * while (j < num_buckets) { 31 | * b = j; 32 | * key = key * 2862933555777941757ULL + 1; 33 | * j = (b + 1) * (double(1LL << 31) / double((key >> 33) + 1)); 34 | * } 35 | * return b; 36 | * }} 37 | *38 | * 39 | * @author Serafin Sedano 40 | */ 41 | public final class JumpConsistentHash { 42 | private static final long UNSIGNED_MASK = 0x7fffffffffffffffL; 43 | 44 | private static final long JUMP = 1L << 31; 45 | 46 | private static final long CONSTANT = Long 47 | .parseUnsignedLong("2862933555777941757"); 48 | 49 | private JumpConsistentHash() { 50 | throw new AssertionError( 51 | "No com.github.ssedano.hash.JumpConsistentHash instances for you!"); 52 | } 53 | 54 | /** 55 | * Accepts "a 64-bit key and the number of buckets. It outputs a number in 56 | * the range [0, buckets].". This implementation uses as a key the 57 | * {@link Object#hashCode()} of the supplied argument. 58 | * 59 | * @param o 60 | * object to store 61 | * @param buckets 62 | * number of available buckets 63 | * @return the hash of the object
o
64 | */
65 | public static int jumpConsistentHash(final Object o, final int buckets) {
66 | return jumpConsistentHash(o.hashCode(), buckets);
67 | }
68 |
69 | /**
70 | * Accepts "a 64-bit key and the number of buckets. It outputs a number in
71 | * the range [0, buckets]."
72 | *
73 | * @param key
74 | * key to store
75 | * @param buckets
76 | * number of available buckets
77 | * @return the hash of the key
78 | * @throws IllegalArgumentException
79 | * if buckets is less than 0
80 | */
81 | public static int jumpConsistentHash(final long key, final int buckets) {
82 | checkBuckets(buckets);
83 | long k = key;
84 | long b = -1;
85 | long j = 0;
86 |
87 | while (j < buckets) {
88 | b = j;
89 | k = k * CONSTANT + 1L;
90 |
91 | j = (long) ((b + 1L) * (JUMP / toDouble((k >>> 33) + 1L)));
92 | }
93 | return (int) b;
94 | }
95 |
96 | private static void checkBuckets(final int buckets) {
97 | if (buckets < 0) {
98 | throw new IllegalArgumentException("Buckets cannot be less than 0");
99 | }
100 | }
101 |
102 | private static double toDouble(final long n) {
103 | double d = n & UNSIGNED_MASK;
104 | if (n < 0) {
105 | d += 0x1.0p63;
106 | }
107 | return d;
108 | }
109 |
110 | }
111 |
--------------------------------------------------------------------------------
/src/test/java/com/github/ssedano/hash/JumpConsistentHashTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2014 ssedano
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 |
16 | */
17 | package com.github.ssedano.hash;
18 |
19 | import static org.junit.Assert.assertEquals;
20 | import static org.junit.Assert.assertTrue;
21 |
22 | import java.util.IntSummaryStatistics;
23 | import java.util.Map;
24 | import java.util.TreeMap;
25 | import java.util.concurrent.ThreadLocalRandom;
26 |
27 | import org.junit.Test;
28 |
29 | import com.github.ssedano.hash.JumpConsistentHash;
30 |
31 | /**
32 | * @author Serafin Sedano
33 | */
34 | public class JumpConsistentHashTest {
35 |
36 | @Test
37 | public void hashInRange() {
38 | int bucket = JumpConsistentHash.jumpConsistentHash(ThreadLocalRandom
39 | .current().nextLong(), 10);
40 | assertTrue("Expected bucket in range [0, 10) but was " + bucket,
41 | bucket > -1 && bucket < 10);
42 | }
43 |
44 | @Test(expected = IllegalArgumentException.class)
45 | public void negativeBucketsEqualsMinusOne() {
46 | JumpConsistentHash.jumpConsistentHash(1L, -1);
47 | }
48 |
49 | @Test
50 | public void hashInRangeForObject() {
51 | int bucket = JumpConsistentHash.jumpConsistentHash(new Object(), 10);
52 | assertTrue("Expected bucket in range [0, 10) but was " + bucket,
53 | bucket > -1 && bucket < 10);
54 | }
55 |
56 | @Test(expected = IllegalArgumentException.class)
57 | public void negativeBucketsEqualsMinusOnForObjecte() {
58 | JumpConsistentHash.jumpConsistentHash(new Object(), -1);
59 | }
60 |
61 | @Test
62 | public void assignsToSameBucket() {
63 | int bucket1 = JumpConsistentHash.jumpConsistentHash(1L, 10);
64 | int bucket2 = JumpConsistentHash.jumpConsistentHash(1L, 11);
65 |
66 | assertEquals(bucket1, bucket2);
67 | }
68 |
69 | @Test
70 | public void assignsObjectToSameBucket() {
71 | int bucket1 = JumpConsistentHash.jumpConsistentHash("object", 10);
72 | int bucket2 = JumpConsistentHash.jumpConsistentHash("object", 11);
73 |
74 | assertEquals(bucket1, bucket2);
75 | }
76 |
77 | /**
78 | * Paper states a standar error of 0.00000000764 and (0.99999998,
79 | * 1.00000002) 99% confidence interval.
80 | *
81 | */
82 | @Test
83 | public void keyDistribution() {
84 | Map