Skip to content

Commit 21e91c1

Browse files
author
Steve Powell
committed
Added IntBitSetAllocator.java and IntAllocatorTests tests it.
1 parent 4da741e commit 21e91c1

File tree

2 files changed

+235
-13
lines changed

2 files changed

+235
-13
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// The contents of this file are subject to the Mozilla Public License
2+
// Version 1.1 (the "License"); you may not use this file except in
3+
// compliance with the License. You may obtain a copy of the License
4+
// at http://www.mozilla.org/MPL/
5+
//
6+
// Software distributed under the License is distributed on an "AS IS"
7+
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
8+
// the License for the specific language governing rights and
9+
// limitations under the License.
10+
//
11+
// The Original Code is RabbitMQ.
12+
//
13+
// The Initial Developer of the Original Code is VMware, Inc.
14+
// Copyright (c) 2007-2011 VMware, Inc. All rights reserved.
15+
//
16+
17+
package com.rabbitmq.utility;
18+
19+
import java.util.BitSet;
20+
21+
/**
22+
* A class for allocating integers from a given range that uses a
23+
* {@link BitSet} representation of the free integers.
24+
*
25+
* <p/><strong>Concurrent Semantics:</strong><br />
26+
* This class is <b><i>not</i></b> thread safe.
27+
*
28+
* <p/><b>Implementation notes:</b>
29+
* <br/>This was originally an ordered chain of non-overlapping Intervals,
30+
* together with a fixed size array cache for freed integers.
31+
* <br/>{@link #reserve()} was expensive in this scheme, whereas in the
32+
* present implementation it is O(1), as is {@link #free()}.
33+
* <br/>Although {@link #allocate()} is slightly slower than O(1) and in the
34+
* worst case could be O(N), the use of the {@link #lastIndex} field
35+
* for starting the next scan for free integers means this is negligible.
36+
* <br/>The data representation overhead is O(N) where N is the size of the
37+
* allocation range. One <code>long</code> is used for every 64 integers in the
38+
* range.
39+
* <br/>Very little Object creation and destruction occurs in use.
40+
*/
41+
public class IntBitSetAllocator {
42+
43+
private final int loRange; // the integer bit 0 represents
44+
private final int hiRange; // the integer(+1) the highest bit represents
45+
private final int numberOfBits; // relevant in freeSet
46+
private int lastIndex = 0; // for searching for FREE integers
47+
/** A bit is SET in freeSet if the corresponding integer is FREE
48+
* <br/>A bit is UNSET in freeSet if the corresponding integer is ALLOCATED
49+
*/
50+
private final BitSet freeSet;
51+
52+
/**
53+
* Creates an IntBitSetAllocator allocating integer IDs within the
54+
* inclusive range [<code>bottom</code>, <code>top</code>].
55+
* @param bottom lower end of range
56+
* @param top upper end of range (inclusive)
57+
*/
58+
public IntBitSetAllocator(int bottom, int top) {
59+
this.loRange = bottom;
60+
this.hiRange = top + 1;
61+
this.numberOfBits = hiRange - loRange;
62+
this.freeSet = new BitSet(this.numberOfBits);
63+
this.freeSet.set(0, this.numberOfBits); // All integers FREE initially
64+
}
65+
66+
/**
67+
* Allocate an unallocated integer from the range, or return -1 if no
68+
* more integers are available.
69+
* @return the allocated integer, or -1
70+
*/
71+
public int allocate() {
72+
int setIndex = this.freeSet.nextSetBit(this.lastIndex);
73+
if (setIndex<0) { // means none found in trailing part
74+
setIndex = this.freeSet.nextSetBit(0);
75+
}
76+
if (setIndex<0) return -1;
77+
this.lastIndex = setIndex;
78+
this.freeSet.clear(setIndex);
79+
return setIndex + this.loRange;
80+
}
81+
/**
82+
* Make the provided integer available for allocation again. This operation
83+
* runs in O(1) time.
84+
* <br/>No error checking is performed, so if you double free or free an
85+
* integer that was not originally allocated the results are undefined.
86+
* @param reservation the previously allocated integer to free
87+
*/
88+
public void free(int reservation) {
89+
this.freeSet.set(reservation - this.loRange);
90+
}
91+
92+
/**
93+
* Attempt to reserve the provided ID as if it had been allocated. Returns
94+
* true if it is available, false otherwise.
95+
* <br/>
96+
* This operation runs in O(1) time.
97+
* @param reservation the integer to be allocated, if possible
98+
* @return <code><b>true</b></code> if allocated, <code><b>false</b></code>
99+
* if already allocated
100+
*/
101+
public boolean reserve(int reservation) {
102+
int index = reservation - this.loRange;
103+
if (this.freeSet.get(index)) { // FREE
104+
this.freeSet.clear(index);
105+
return true;
106+
} else {
107+
return false;
108+
}
109+
}
110+
111+
@Override
112+
public String toString() {
113+
StringBuilder sb
114+
= new StringBuilder("IntBitSetAllocator{allocated = [");
115+
116+
int firstClearBit = this.freeSet.nextClearBit(0);
117+
if (firstClearBit < this.numberOfBits) {
118+
int firstSetAfterThat = this.freeSet.nextSetBit(firstClearBit+1);
119+
if (firstSetAfterThat < 0)
120+
firstSetAfterThat = this.numberOfBits;
121+
122+
stringInterval(sb, firstClearBit, firstSetAfterThat);
123+
for (int i = this.freeSet.nextClearBit(firstSetAfterThat+1);
124+
i < this.numberOfBits;
125+
i = this.freeSet.nextClearBit(i+1)) {
126+
int nextSet = this.freeSet.nextSetBit(i);
127+
if (nextSet<0) nextSet = this.numberOfBits;
128+
stringInterval(sb.append(", "), i, nextSet);
129+
i = nextSet;
130+
}
131+
}
132+
sb.append("]}");
133+
return sb.toString();
134+
}
135+
private void stringInterval(StringBuilder sb, int i1, int i2) {
136+
sb.append(i1 + this.loRange);
137+
if (i1+1 != i2) {
138+
sb.append("..").append(i2-1 + this.loRange);
139+
}
140+
}
141+
}

test/src/com/rabbitmq/utility/IntAllocatorTests.java

Lines changed: 94 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,65 +28,146 @@ public class IntAllocatorTests extends TestCase {
2828
private static final int TEST_ITERATIONS = 50000;
2929
private static final int HI_RANGE = 100000;
3030
private static final int LO_RANGE = 100;
31-
private IntAllocator intAllocator = new IntAllocator(LO_RANGE, HI_RANGE);
31+
private IntAllocator iAll = new IntAllocator(LO_RANGE, HI_RANGE);
3232

33-
private Random rand = new Random();
33+
private Random rand = new Random(70608L);
3434

3535
public void testReserveAndFree() throws Exception {
3636
Set<Integer> set = new HashSet<Integer>();
3737
for (int i = 0; i < TEST_ITERATIONS; ++i) {
38-
int trial = getTrial();
38+
int trial = getTrial(rand);
3939
if (set.contains(trial)) {
40-
intAllocator.free(trial);
40+
iAll.free(trial);
4141
set.remove(trial);
4242
} else {
4343
assertTrue("Did not reserve free integer " + trial,
44-
intAllocator.reserve(trial));
44+
iAll.reserve(trial));
4545
set.add(trial);
4646
}
4747
}
4848

4949
for (int trial : set) {
5050
assertFalse("Integer " + trial + " not allocated!",
51-
intAllocator.reserve(trial));
51+
iAll.reserve(trial));
5252
}
5353
}
5454

5555
public void testAllocateAndFree() throws Exception {
5656
Set<Integer> set = new HashSet<Integer>();
5757
for (int i=0; i < TEST_ITERATIONS; ++i) {
58-
if (getBool()) {
59-
int trial = intAllocator.allocate();
58+
if (getBool(rand)) {
59+
int trial = iAll.allocate();
6060
assertFalse("Already allocated " + trial, set.contains(trial));
6161
set.add(trial);
6262
} else {
6363
if (!set.isEmpty()) {
6464
int trial = extract(set);
6565
assertFalse("Allocator agreed to reserve " + trial,
66-
intAllocator.reserve(trial));
67-
intAllocator.free(trial);
66+
iAll.reserve(trial));
67+
iAll.free(trial);
6868
}
6969
}
7070
}
7171

7272
for (int trial : set) {
7373
assertFalse("Integer " + trial + " should be allocated!",
74-
intAllocator.reserve(trial));
74+
iAll.reserve(trial));
7575
}
7676
}
7777

78+
public void testReserveAndFreeBS() throws Exception {
79+
IntBitSetAllocator ibs = new IntBitSetAllocator(LO_RANGE, HI_RANGE);
80+
Set<Integer> set = new HashSet<Integer>();
81+
for (int i = 0; i < TEST_ITERATIONS; ++i) {
82+
int trial = getTrial(rand);
83+
if (set.contains(trial)) {
84+
ibs.free(trial);
85+
set.remove(trial);
86+
} else {
87+
assertTrue("Did not reserve free integer " + trial,
88+
ibs.reserve(trial));
89+
set.add(trial);
90+
}
91+
}
92+
93+
for (int trial : set) {
94+
assertFalse("Integer " + trial + " not allocated!",
95+
ibs.reserve(trial));
96+
}
97+
}
98+
99+
public void testAllocateAndFreeBS() throws Exception {
100+
IntBitSetAllocator ibs = new IntBitSetAllocator(LO_RANGE, HI_RANGE);
101+
Set<Integer> set = new HashSet<Integer>();
102+
for (int i=0; i < TEST_ITERATIONS; ++i) {
103+
if (getBool(rand)) {
104+
int trial = ibs.allocate();
105+
assertFalse("Already allocated " + trial, set.contains(trial));
106+
set.add(trial);
107+
} else {
108+
if (!set.isEmpty()) {
109+
int trial = extract(set);
110+
assertFalse("Allocator agreed to reserve " + trial,
111+
ibs.reserve(trial));
112+
ibs.free(trial);
113+
}
114+
}
115+
}
116+
117+
for (int trial : set) {
118+
assertFalse("Integer " + trial + " should be allocated!",
119+
ibs.reserve(trial));
120+
}
121+
}
122+
123+
public void testToStringBS() throws Exception {
124+
IntBitSetAllocator ibs = new IntBitSetAllocator(LO_RANGE, HI_RANGE);
125+
assertEquals("IntBitSetAllocator{allocated = []}", ibs.toString());
126+
ibs.allocate();
127+
assertEquals("IntBitSetAllocator{allocated = [100]}", ibs.toString());
128+
for(int i = 200; i<211; i=i+4) {
129+
ibs.reserve(i);
130+
ibs.reserve(i+1);
131+
ibs.reserve(i+2);
132+
}
133+
assertEquals("IntBitSetAllocator{allocated = "
134+
+ "[100, 200..202, 204..206, 208..210]}"
135+
, ibs.toString());
136+
}
137+
138+
public void testToString() throws Exception {
139+
assertEquals("IntAllocator{intervals = [100..100000], unsorted = []}"
140+
, iAll.toString());
141+
iAll.allocate();
142+
assertEquals("IntAllocator{intervals = [101..100000], unsorted = []}"
143+
, iAll.toString());
144+
iAll.free(100);
145+
assertEquals("IntAllocator{intervals = [101..100000]"
146+
+ ", unsorted = [100]}"
147+
, iAll.toString());
148+
for(int i = 200; i<211; i=i+4) {
149+
iAll.reserve(i);
150+
iAll.reserve(i+1);
151+
iAll.reserve(i+2);
152+
}
153+
assertEquals("IntAllocator{intervals = "
154+
+ "[100..199, 203..203, 207..207, 211..100000]"
155+
+ ", unsorted = []}"
156+
, iAll.toString());
157+
}
158+
78159
private static int extract(Set<Integer> set) {
79160
Iterator<Integer> iter = set.iterator();
80161
int trial = iter.next();
81162
iter.remove();
82163
return trial;
83164
}
84165

85-
private int getTrial() {
166+
private static int getTrial(Random rand) {
86167
return rand.nextInt(HI_RANGE-LO_RANGE+1) + LO_RANGE;
87168
}
88169

89-
private boolean getBool() {
170+
private static boolean getBool(Random rand) {
90171
return rand.nextBoolean();
91172
}
92173
}

0 commit comments

Comments
 (0)