Skip to content

Commit 2049481

Browse files
Edarkevbabanin
andauthored
Optimize ObjectID sort and encoding/decoding (#1582)
JAVA-5388 Co-authored-by: Viacheslav Babanin <[email protected]>
1 parent 53da758 commit 2049481

File tree

2 files changed

+165
-134
lines changed

2 files changed

+165
-134
lines changed

Diff for: bson/src/main/org/bson/types/ObjectId.java

+50-128
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,18 @@
1616

1717
package org.bson.types;
1818

19+
import static org.bson.assertions.Assertions.isTrueArgument;
20+
import static org.bson.assertions.Assertions.notNull;
21+
1922
import java.io.InvalidObjectException;
2023
import java.io.ObjectInputStream;
2124
import java.io.Serializable;
2225
import java.nio.ByteBuffer;
26+
import java.nio.ByteOrder;
2327
import java.security.SecureRandom;
2428
import java.util.Date;
2529
import java.util.concurrent.atomic.AtomicInteger;
2630

27-
import static org.bson.assertions.Assertions.isTrueArgument;
28-
import static org.bson.assertions.Assertions.notNull;
29-
3031
/**
3132
* <p>A globally unique identifier for objects.</p>
3233
*
@@ -53,9 +54,8 @@ public final class ObjectId implements Comparable<ObjectId>, Serializable {
5354
private static final int OBJECT_ID_LENGTH = 12;
5455
private static final int LOW_ORDER_THREE_BYTES = 0x00ffffff;
5556

56-
// Use primitives to represent the 5-byte random value.
57-
private static final int RANDOM_VALUE1;
58-
private static final short RANDOM_VALUE2;
57+
// Use upper bytes of a long to represent the 5-byte random value.
58+
private static final long RANDOM_VALUE;
5959

6060
private static final AtomicInteger NEXT_COUNTER;
6161

@@ -67,18 +67,12 @@ public final class ObjectId implements Comparable<ObjectId>, Serializable {
6767
* The timestamp
6868
*/
6969
private final int timestamp;
70+
7071
/**
71-
* The counter.
72-
*/
73-
private final int counter;
74-
/**
75-
* the first four bits of randomness.
76-
*/
77-
private final int randomValue1;
78-
/**
79-
* The last two bits of randomness.
72+
* The final 8 bytes of the ObjectID are 5 bytes probabilistically unique to the machine and
73+
* process, followed by a 3 byte incrementing counter initialized to a random value.
8074
*/
81-
private final short randomValue2;
75+
private final long nonce;
8276

8377
/**
8478
* Gets a new object id.
@@ -101,7 +95,7 @@ public static ObjectId get() {
10195
* @since 4.1
10296
*/
10397
public static ObjectId getSmallestWithDate(final Date date) {
104-
return new ObjectId(dateToTimestampSeconds(date), 0, (short) 0, 0, false);
98+
return new ObjectId(dateToTimestampSeconds(date), 0L);
10599
}
106100

107101
/**
@@ -152,7 +146,7 @@ public ObjectId() {
152146
* @param date the date
153147
*/
154148
public ObjectId(final Date date) {
155-
this(dateToTimestampSeconds(date), NEXT_COUNTER.getAndIncrement() & LOW_ORDER_THREE_BYTES, false);
149+
this(dateToTimestampSeconds(date), RANDOM_VALUE | (NEXT_COUNTER.getAndIncrement() & LOW_ORDER_THREE_BYTES));
156150
}
157151

158152
/**
@@ -163,7 +157,7 @@ public ObjectId(final Date date) {
163157
* @throws IllegalArgumentException if the high order byte of counter is not zero
164158
*/
165159
public ObjectId(final Date date, final int counter) {
166-
this(dateToTimestampSeconds(date), counter, true);
160+
this(dateToTimestampSeconds(date), getNonceFromUntrustedCounter(counter));
167161
}
168162

169163
/**
@@ -174,25 +168,19 @@ public ObjectId(final Date date, final int counter) {
174168
* @throws IllegalArgumentException if the high order byte of counter is not zero
175169
*/
176170
public ObjectId(final int timestamp, final int counter) {
177-
this(timestamp, counter, true);
171+
this(timestamp, getNonceFromUntrustedCounter(counter));
178172
}
179173

180-
private ObjectId(final int timestamp, final int counter, final boolean checkCounter) {
181-
this(timestamp, RANDOM_VALUE1, RANDOM_VALUE2, counter, checkCounter);
174+
private ObjectId(final int timestamp, final long nonce) {
175+
this.timestamp = timestamp;
176+
this.nonce = nonce;
182177
}
183178

184-
private ObjectId(final int timestamp, final int randomValue1, final short randomValue2, final int counter,
185-
final boolean checkCounter) {
186-
if ((randomValue1 & 0xff000000) != 0) {
187-
throw new IllegalArgumentException("The random value must be between 0 and 16777215 (it must fit in three bytes).");
188-
}
189-
if (checkCounter && ((counter & 0xff000000) != 0)) {
179+
private static long getNonceFromUntrustedCounter(final int counter) {
180+
if ((counter & 0xff000000) != 0) {
190181
throw new IllegalArgumentException("The counter must be between 0 and 16777215 (it must fit in three bytes).");
191182
}
192-
this.timestamp = timestamp;
193-
this.counter = counter & LOW_ORDER_THREE_BYTES;
194-
this.randomValue1 = randomValue1;
195-
this.randomValue2 = randomValue2;
183+
return RANDOM_VALUE | counter;
196184
}
197185

198186
/**
@@ -226,12 +214,14 @@ public ObjectId(final ByteBuffer buffer) {
226214
notNull("buffer", buffer);
227215
isTrueArgument("buffer.remaining() >=12", buffer.remaining() >= OBJECT_ID_LENGTH);
228216

229-
// Note: Cannot use ByteBuffer.getInt because it depends on tbe buffer's byte order
230-
// and ObjectId's are always in big-endian order.
231-
timestamp = makeInt(buffer.get(), buffer.get(), buffer.get(), buffer.get());
232-
randomValue1 = makeInt((byte) 0, buffer.get(), buffer.get(), buffer.get());
233-
randomValue2 = makeShort(buffer.get(), buffer.get());
234-
counter = makeInt((byte) 0, buffer.get(), buffer.get(), buffer.get());
217+
ByteOrder originalOrder = buffer.order();
218+
try {
219+
buffer.order(ByteOrder.BIG_ENDIAN);
220+
this.timestamp = buffer.getInt();
221+
this.nonce = buffer.getLong();
222+
} finally {
223+
buffer.order(originalOrder);
224+
}
235225
}
236226

237227
/**
@@ -240,9 +230,11 @@ public ObjectId(final ByteBuffer buffer) {
240230
* @return the byte array
241231
*/
242232
public byte[] toByteArray() {
243-
ByteBuffer buffer = ByteBuffer.allocate(OBJECT_ID_LENGTH);
244-
putToByteBuffer(buffer);
245-
return buffer.array(); // using .allocate ensures there is a backing array that can be returned
233+
// using .allocate ensures there is a backing array that can be returned
234+
return ByteBuffer.allocate(OBJECT_ID_LENGTH)
235+
.putInt(this.timestamp)
236+
.putLong(this.nonce)
237+
.array();
246238
}
247239

248240
/**
@@ -257,18 +249,14 @@ public void putToByteBuffer(final ByteBuffer buffer) {
257249
notNull("buffer", buffer);
258250
isTrueArgument("buffer.remaining() >=12", buffer.remaining() >= OBJECT_ID_LENGTH);
259251

260-
buffer.put(int3(timestamp));
261-
buffer.put(int2(timestamp));
262-
buffer.put(int1(timestamp));
263-
buffer.put(int0(timestamp));
264-
buffer.put(int2(randomValue1));
265-
buffer.put(int1(randomValue1));
266-
buffer.put(int0(randomValue1));
267-
buffer.put(short1(randomValue2));
268-
buffer.put(short0(randomValue2));
269-
buffer.put(int2(counter));
270-
buffer.put(int1(counter));
271-
buffer.put(int0(counter));
252+
ByteOrder originalOrder = buffer.order();
253+
try {
254+
buffer.order(ByteOrder.BIG_ENDIAN);
255+
buffer.putInt(this.timestamp);
256+
buffer.putLong(this.nonce);
257+
} finally {
258+
buffer.order(originalOrder);
259+
}
272260
}
273261

274262
/**
@@ -313,49 +301,26 @@ public boolean equals(final Object o) {
313301
return false;
314302
}
315303

316-
ObjectId objectId = (ObjectId) o;
317-
318-
if (counter != objectId.counter) {
319-
return false;
320-
}
321-
if (timestamp != objectId.timestamp) {
322-
return false;
323-
}
324-
325-
if (randomValue1 != objectId.randomValue1) {
304+
ObjectId other = (ObjectId) o;
305+
if (timestamp != other.timestamp) {
326306
return false;
327307
}
328-
329-
if (randomValue2 != objectId.randomValue2) {
330-
return false;
331-
}
332-
333-
return true;
308+
return nonce == other.nonce;
334309
}
335310

336311
@Override
337312
public int hashCode() {
338-
int result = timestamp;
339-
result = 31 * result + counter;
340-
result = 31 * result + randomValue1;
341-
result = 31 * result + randomValue2;
342-
return result;
313+
return 31 * timestamp + Long.hashCode(nonce);
343314
}
344315

345316
@Override
346317
public int compareTo(final ObjectId other) {
347-
if (other == null) {
348-
throw new NullPointerException();
318+
int cmp = Integer.compareUnsigned(this.timestamp, other.timestamp);
319+
if (cmp != 0) {
320+
return cmp;
349321
}
350322

351-
byte[] byteArray = toByteArray();
352-
byte[] otherByteArray = other.toByteArray();
353-
for (int i = 0; i < OBJECT_ID_LENGTH; i++) {
354-
if (byteArray[i] != otherByteArray[i]) {
355-
return ((byteArray[i] & 0xff) < (otherByteArray[i] & 0xff)) ? -1 : 1;
356-
}
357-
}
358-
return 0;
323+
return Long.compareUnsigned(nonce, other.nonce);
359324
}
360325

361326
@Override
@@ -407,8 +372,7 @@ private Object readResolve() {
407372
static {
408373
try {
409374
SecureRandom secureRandom = new SecureRandom();
410-
RANDOM_VALUE1 = secureRandom.nextInt(0x01000000);
411-
RANDOM_VALUE2 = (short) secureRandom.nextInt(0x00008000);
375+
RANDOM_VALUE = secureRandom.nextLong() & ~LOW_ORDER_THREE_BYTES;
412376
NEXT_COUNTER = new AtomicInteger(secureRandom.nextInt());
413377
} catch (Exception e) {
414378
throw new RuntimeException(e);
@@ -443,46 +407,4 @@ private static int hexCharToInt(final char c) {
443407
private static int dateToTimestampSeconds(final Date time) {
444408
return (int) (time.getTime() / 1000);
445409
}
446-
447-
// Big-Endian helpers, in this class because all other BSON numbers are little-endian
448-
449-
private static int makeInt(final byte b3, final byte b2, final byte b1, final byte b0) {
450-
// CHECKSTYLE:OFF
451-
return (((b3) << 24) |
452-
((b2 & 0xff) << 16) |
453-
((b1 & 0xff) << 8) |
454-
((b0 & 0xff)));
455-
// CHECKSTYLE:ON
456-
}
457-
458-
private static short makeShort(final byte b1, final byte b0) {
459-
// CHECKSTYLE:OFF
460-
return (short) (((b1 & 0xff) << 8) | ((b0 & 0xff)));
461-
// CHECKSTYLE:ON
462-
}
463-
464-
private static byte int3(final int x) {
465-
return (byte) (x >> 24);
466-
}
467-
468-
private static byte int2(final int x) {
469-
return (byte) (x >> 16);
470-
}
471-
472-
private static byte int1(final int x) {
473-
return (byte) (x >> 8);
474-
}
475-
476-
private static byte int0(final int x) {
477-
return (byte) (x);
478-
}
479-
480-
private static byte short1(final short x) {
481-
return (byte) (x >> 8);
482-
}
483-
484-
private static byte short0(final short x) {
485-
return (byte) (x);
486-
}
487-
488410
}

0 commit comments

Comments
 (0)