Skip to content

Commit ea57628

Browse files
author
Matthew Sackman
committed
Merging bug 22334 onto default
2 parents 835822c + 262667c commit ea57628

File tree

2 files changed

+202
-161
lines changed

2 files changed

+202
-161
lines changed

src/com/rabbitmq/utility/IntAllocator.java

Lines changed: 182 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -24,186 +24,209 @@
2424
// LLC. Portions created by Rabbit Technologies Ltd are Copyright
2525
// (C) 2007-2010 Rabbit Technologies Ltd.
2626
//
27-
// All Rights Reserved.
27+
// All Rights Reserved.
2828
//
29-
// Contributor(s): ______________________________________.
29+
// Contributor(s): ______________________________________.
3030
//
3131
package com.rabbitmq.utility;
3232

33-
import java.util.*;
33+
import java.util.Arrays;
3434

3535
/**
36-
* A class for allocating integer IDs in a given range.
37-
*/
36+
* A class for allocating integer IDs in a given range.
37+
*/
3838
public class IntAllocator{
3939

40-
// Invariant: Sorted in order of first element. Non-overlapping, non-adjacent.
41-
// This could really use being a balanced binary tree. However for normal usages
42-
// it doesn't actually matter.
43-
private LinkedList<Interval> intervals = new LinkedList<Interval>();
44-
45-
private final int[] unsorted;
46-
private int unsortedCount = 0;
47-
48-
/**
49-
* A class representing an inclusive interval from start to end.
50-
*/
51-
private static class Interval{
52-
Interval(int start, int end){
53-
this.start = start;
54-
this.end = end;
40+
// Invariant: Sorted in order of first element.
41+
// Invariant: Intervals are non-overlapping, non-adjacent.
42+
// This could really use being a balanced binary tree. However for normal
43+
// usages it doesn't actually matter.
44+
private IntervalList base;
45+
46+
private final int[] unsorted;
47+
private int unsortedCount = 0;
48+
49+
/**
50+
* A class representing an inclusive interval from start to end.
51+
*/
52+
private static class IntervalList{
53+
IntervalList(int start, int end){
54+
this.start = start;
55+
this.end = end;
56+
}
57+
58+
int start;
59+
int end;
60+
IntervalList next;
61+
62+
int length(){ return end - start + 1; }
5563
}
5664

57-
int start;
58-
int end;
59-
60-
int length(){ return end - start + 1; }
61-
}
62-
63-
/**
64-
* Creates an IntAllocator allocating integer IDs within the inclusive range [start, end]
65-
*/
66-
public IntAllocator(int start, int end){
67-
if(start > end) throw new IllegalArgumentException("illegal range [" + start +", " + end + "]");
68-
69-
// Fairly arbitrary heuristic for a good size for the unsorted set.
70-
unsorted = new int[Math.max(32, (int)Math.sqrt(end - start))];
71-
intervals.add(new Interval(start, end));
72-
}
73-
74-
/**
75-
* Allocate a fresh integer from the range, or return -1 if no more integers
76-
* are available. This operation is guaranteed to run in O(1)
77-
*/
78-
public int allocate(){
79-
if(unsortedCount > 0){
80-
return unsorted[--unsortedCount];
81-
} else if (!intervals.isEmpty()) {
82-
Interval first = intervals.getFirst();
83-
if(first.length() == 1) intervals.removeFirst();
84-
return first.start++;
85-
} else {
86-
return -1;
65+
/** Destructively merge two IntervalLists.
66+
* Invariant: None of the Intervals in the two lists may overlap
67+
* intervals in this list.
68+
*/
69+
public static IntervalList merge(IntervalList x, IntervalList y){
70+
if(x == null) return y;
71+
if(y == null) return x;
72+
73+
if(x.end > y.start) return merge(y, x);
74+
75+
// We now have x, y non-null and x.End < y.Start.
76+
if(y.start == x.end + 1){
77+
// The two intervals adjoin. Merge them into one and then
78+
// merge the tails.
79+
x.end = y.end;
80+
x.next = merge(x.next, y.next);
81+
return x;
82+
}
83+
84+
// y belongs in the tail of x.
85+
86+
x.next = merge(y, x.next);
87+
return x;
88+
}
89+
90+
91+
public static IntervalList fromArray(int[] xs, int length){
92+
Arrays.sort(xs, 0, length);
93+
94+
IntervalList result = null;
95+
IntervalList current = null;
96+
97+
int i = 0;
98+
while(i < length){
99+
int start = i;
100+
while((i < length - 1) && (xs[i + 1] == xs[i] + 1))
101+
i++;
102+
103+
IntervalList interval = new IntervalList(xs[start], xs[i]);
104+
105+
if(result == null){
106+
result = interval;
107+
current = interval;
108+
} else {
109+
current.next = interval;
110+
current = interval;
111+
}
112+
i++;
113+
}
114+
return result;
87115
}
88-
}
89-
90-
/**
91-
* Make the provided integer available for allocation again. This operation
92-
* runs in amortized O(sqrt(range size)) time: About every sqrt(range size)
93-
* operations will take O(range_size + number of intervals) to complete and
94-
* the rest run in constant time.
95-
*
96-
* No error checking is performed, so if you double free or free an integer
97-
* that was not originally allocated the results are undefined. Sorry.
98-
*/
99-
public void free(int id){
100-
if(unsortedCount >= unsorted.length){
101-
flush();
116+
117+
118+
/**
119+
* Creates an IntAllocator allocating integer IDs within the inclusive range
120+
* [start, end]
121+
*/
122+
public IntAllocator(int start, int end){
123+
if(start > end)
124+
throw new IllegalArgumentException("illegal range [" + start +
125+
", " + end + "]");
126+
127+
// Fairly arbitrary heuristic for a good size for the unsorted set.
128+
unsorted = new int[Math.max(32, (int)Math.sqrt(end - start))];
129+
base = new IntervalList(start, end);
102130
}
103-
unsorted[unsortedCount++] = id;
104-
}
105-
106-
/**
107-
* Attempt to reserve the provided ID as if it had been allocated. Returns true
108-
* if it is available, false otherwise.
109-
*
110-
* This operation runs in O(id) in the worst case scenario, though it can usually
111-
* be expected to perform better than that unless a great deal of fragmentation
112-
* has occurred.
113-
*/
114-
public boolean reserve(int id){
115-
flush();
116-
ListIterator<Interval> it = intervals.listIterator();
117-
118-
while(it.hasNext()){
119-
Interval i = it.next();
120-
if(i.start <= id && id <= i.end){
121-
if(i.length() == 1) it.remove();
122-
else if(i.start == id) i.start++;
123-
else if(i.end == id) i.end--;
124-
else {
125-
it.add(new Interval(id + 1, i.end));
126-
i.end = id - 1;
131+
132+
/**
133+
* Allocate a fresh integer from the range, or return -1 if no more integers
134+
* are available. This operation is guaranteed to run in O(1)
135+
*/
136+
public int allocate(){
137+
if(unsortedCount > 0){
138+
return unsorted[--unsortedCount];
139+
} else if (base != null){
140+
IntervalList source = base;
141+
if(base.length() == 1) base = base.next;
142+
return source.start++;
143+
} else {
144+
return -1;
127145
}
128-
return true;
129-
}
130146
}
131147

132-
return false;
133-
}
134-
135-
private void flush(){
136-
if(unsortedCount == 0) return;
137-
138-
Arrays.sort(unsorted);
139-
140-
ListIterator<Interval> it = intervals.listIterator();
141-
142-
int i = 0;
143-
while(i < unsortedCount){
144-
int start = i;
145-
while((i < unsortedCount - 1) && (unsorted[i + 1] == unsorted[i] + 1))
146-
i++;
147-
148-
Interval interval = new Interval(start, i);
149-
150-
// Scan to an appropriate point in the list to insert this interval
151-
// this may well be the end
152-
while(it.hasNext()){
153-
if(it.next().start > interval.end){
154-
it.previous();
155-
break;
148+
/**
149+
* Make the provided integer available for allocation again. This operation
150+
* runs in amortized O(sqrt(range size)) time: About every sqrt(range size)
151+
* operations will take O(range_size + number of intervals) to complete and
152+
* the rest run in constant time.
153+
*
154+
* No error checking is performed, so if you double free or free an integer
155+
* that was not originally allocated the results are undefined. Sorry.
156+
*/
157+
public void free(int id){
158+
if(unsortedCount >= unsorted.length){
159+
flush();
156160
}
157-
}
158-
159-
it.add(interval);
160-
i++;
161+
unsorted[unsortedCount++] = id;
161162
}
162163

163-
normalize();
164-
unsortedCount = 0;
165-
}
166-
167-
private void normalize(){
168-
if(intervals.isEmpty()) return;
169-
Iterator<Interval> it = intervals.iterator();
170-
171-
Interval trailing, leading;
172-
leading = it.next();
173-
while(it.hasNext()){
174-
trailing = leading;
175-
leading = it.next();
176-
177-
if(leading.start == trailing.end + 1) {
178-
it.remove();
179-
trailing.end = leading.end;
180-
}
181-
}
182-
}
183-
184-
@Override public String toString(){
185-
StringBuilder builder = new StringBuilder();
186-
187-
builder.append("IntAllocator{");
188-
189-
builder.append("intervals = [");
190-
Iterator<Interval> it = intervals.iterator();
191-
while(it.hasNext()){
192-
Interval i = it.next();
193-
builder.append(i.start).append("..").append(i.end);
194-
if(it.hasNext()) builder.append(", ");
164+
/**
165+
* Attempt to reserve the provided ID as if it had been allocated. Returns
166+
* true if it is available, false otherwise.
167+
*
168+
* This operation runs in O(id) in the worst case scenario, though it can
169+
* usually be expected to perform better than that unless a great deal of
170+
* fragmentation has occurred.
171+
*/
172+
public boolean reserve(int id){
173+
flush();
174+
175+
IntervalList current = base;
176+
177+
while(current != null && current.end < id){
178+
current = current.next;
179+
}
180+
181+
if(current == null) return false;
182+
if(current.start > id) return false;
183+
184+
if(current.end == id)
185+
current.end--;
186+
else if(current.start == id)
187+
current.start++;
188+
else {
189+
// The ID is in the middle of this interval.
190+
// We need to split the interval into two.
191+
IntervalList rest = new IntervalList(id + 1, current.end);
192+
current.end = id - 1;
193+
rest.next = current.next;
194+
current.next = rest;
195+
}
196+
197+
return true;
195198
}
196-
builder.append("]");
197199

198-
builder.append(", unsorted = [");
199-
for(int i = 0; i < unsortedCount; i++){
200-
builder.append(unsorted[i]);
201-
if( i < unsortedCount - 1) builder.append(", ");
200+
public void flush(){
201+
if(unsortedCount == 0) return;
202+
203+
base = merge(base, fromArray(unsorted, unsortedCount));
204+
unsortedCount = 0;
202205
}
203-
builder.append("]");
206+
207+
@Override public String toString(){
208+
StringBuilder builder = new StringBuilder();
209+
210+
builder.append("IntAllocator{");
211+
212+
builder.append("intervals = [");
213+
IntervalList it = base;
214+
while(it != null){
215+
builder.append(it.start).append("..").append(it.end);
216+
if(it.next != null) builder.append(", ");
217+
it = it.next;
218+
}
219+
builder.append("]");
220+
221+
builder.append(", unsorted = [");
222+
for(int i = 0; i < unsortedCount; i++){
223+
builder.append(unsorted[i]);
224+
if( i < unsortedCount - 1) builder.append(", ");
225+
}
226+
builder.append("]");
204227

205228

206-
builder.append("}");
207-
return builder.toString();
208-
}
229+
builder.append("}");
230+
return builder.toString();
231+
}
209232
}

0 commit comments

Comments
 (0)