|
24 | 24 | // LLC. Portions created by Rabbit Technologies Ltd are Copyright
|
25 | 25 | // (C) 2007-2010 Rabbit Technologies Ltd.
|
26 | 26 | //
|
27 |
| -// All Rights Reserved. |
| 27 | +// All Rights Reserved. |
28 | 28 | //
|
29 |
| -// Contributor(s): ______________________________________. |
| 29 | +// Contributor(s): ______________________________________. |
30 | 30 | //
|
31 | 31 | package com.rabbitmq.utility;
|
32 | 32 |
|
33 |
| -import java.util.*; |
| 33 | +import java.util.Arrays; |
34 | 34 |
|
35 | 35 | /**
|
36 |
| - * A class for allocating integer IDs in a given range. |
37 |
| - */ |
| 36 | + * A class for allocating integer IDs in a given range. |
| 37 | + */ |
38 | 38 | public class IntAllocator{
|
39 | 39 |
|
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; } |
55 | 63 | }
|
56 | 64 |
|
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; |
87 | 115 | }
|
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); |
102 | 130 | }
|
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; |
127 | 145 | }
|
128 |
| - return true; |
129 |
| - } |
130 | 146 | }
|
131 | 147 |
|
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(); |
156 | 160 | }
|
157 |
| - } |
158 |
| - |
159 |
| - it.add(interval); |
160 |
| - i++; |
| 161 | + unsorted[unsortedCount++] = id; |
161 | 162 | }
|
162 | 163 |
|
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; |
195 | 198 | }
|
196 |
| - builder.append("]"); |
197 | 199 |
|
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; |
202 | 205 | }
|
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("]"); |
204 | 227 |
|
205 | 228 |
|
206 |
| - builder.append("}"); |
207 |
| - return builder.toString(); |
208 |
| - } |
| 229 | + builder.append("}"); |
| 230 | + return builder.toString(); |
| 231 | + } |
209 | 232 | }
|
0 commit comments