diff --git a/src/com/esotericsoftware/kryo/io/ByteBufferInput.java b/src/com/esotericsoftware/kryo/io/ByteBufferInput.java index cec6d4db6..1db08a005 100644 --- a/src/com/esotericsoftware/kryo/io/ByteBufferInput.java +++ b/src/com/esotericsoftware/kryo/io/ByteBufferInput.java @@ -352,6 +352,30 @@ public void readBytes (byte[] bytes, int offset, int count) throws KryoException } } + public int readInt (int count) { + if (count < 0 || count > 4) throw new IllegalArgumentException("count must be >= 0 and <= 4: " + count); + require(count); + position += count; + ByteBuffer byteBuffer = this.byteBuffer; + switch (count) { + case 1: + return byteBuffer.get(); + case 2: + return byteBuffer.get() << 8 + | byteBuffer.get() & 0xFF; + case 3: + return byteBuffer.get() << 16 + | (byteBuffer.get() & 0xFF) << 8 + | byteBuffer.get() & 0xFF; + case 4: + return byteBuffer.get() << 24 + | (byteBuffer.get() & 0xFF) << 16 + | (byteBuffer.get() & 0xFF) << 8 + | byteBuffer.get() & 0xFF; + } + throw new IllegalStateException(); // impossible + } + // int: public int readInt () throws KryoException { diff --git a/src/com/esotericsoftware/kryo/io/ByteBufferOutput.java b/src/com/esotericsoftware/kryo/io/ByteBufferOutput.java index 4061f16db..07bdc9d30 100644 --- a/src/com/esotericsoftware/kryo/io/ByteBufferOutput.java +++ b/src/com/esotericsoftware/kryo/io/ByteBufferOutput.java @@ -276,6 +276,33 @@ public void writeBytes (byte[] bytes, int offset, int count) throws KryoExceptio } } + public void writeInt (int bytes, int count) { + if (count < 0 || count > 4) throw new IllegalArgumentException("count must be >= 0 and <= 4: " + count); + require(count); + position += count; + ByteBuffer byteBuffer = this.byteBuffer; + switch (count) { + case 1: + byteBuffer.put((byte)bytes); + break; + case 2: + byteBuffer.put((byte)(bytes >> 8)); + byteBuffer.put((byte)bytes); + break; + case 3: + byteBuffer.put((byte)(bytes >> 16)); + byteBuffer.put((byte)(bytes >> 8)); + byteBuffer.put((byte)bytes); + break; + case 4: + byteBuffer.put((byte)(bytes >> 24)); + byteBuffer.put((byte)(bytes >> 16)); + byteBuffer.put((byte)(bytes >> 8)); + byteBuffer.put((byte)bytes); + break; + } + } + // int: public void writeInt (int value) throws KryoException { diff --git a/src/com/esotericsoftware/kryo/io/Input.java b/src/com/esotericsoftware/kryo/io/Input.java index 78c1d284c..dd72768fe 100644 --- a/src/com/esotericsoftware/kryo/io/Input.java +++ b/src/com/esotericsoftware/kryo/io/Input.java @@ -373,6 +373,44 @@ public void readBytes (byte[] bytes, int offset, int count) throws KryoException } } + /** Reads count bytes and returns them as int, the last byte read will be the lowest byte in the int. */ + public int readInt (int count) { + if (count < 0 || count > 4) throw new IllegalArgumentException("count must be >= 0 and <= 4: " + count); + require(count); + int p = position; + position = p + count; + switch (count) { + case 1: + return buffer[p]; + case 2: + return buffer[p] << 8 + | buffer[p+1] & 0xFF; + case 3: + return buffer[p] << 16 + | (buffer[p+1] & 0xFF) << 8 + | buffer[p+2] & 0xFF; + case 4: + return buffer[p] << 24 + | (buffer[p+1] & 0xFF) << 16 + | (buffer[p+2] & 0xFF) << 8 + | buffer[p+3] & 0xFF; + } + throw new IllegalStateException(); // impossible + } + + /** Reads count bytes and returns them as long, the last byte read will be the lowest byte in the long. */ + public long readLong (int count) { + if (count < 0 || count > 8) throw new IllegalArgumentException("count must be >= 0 and <= 8: " + count); + if (count <= 4) { + return readInt(count); + } else { + require(count); + long highBytes = ((long) readInt(count - 4)) << 32; + long lowBytes = ((long) readInt(4)) & (1L << 32) - 1; + return highBytes | lowBytes; + } + } + // int: /** Reads a 4 byte int. */ diff --git a/src/com/esotericsoftware/kryo/io/Output.java b/src/com/esotericsoftware/kryo/io/Output.java index b1d351c69..dc7e6ac4a 100644 --- a/src/com/esotericsoftware/kryo/io/Output.java +++ b/src/com/esotericsoftware/kryo/io/Output.java @@ -20,7 +20,6 @@ package com.esotericsoftware.kryo.io; import com.esotericsoftware.kryo.KryoException; -import com.esotericsoftware.kryo.io.KryoBufferOverflowException; import com.esotericsoftware.kryo.util.Pool.Poolable; import com.esotericsoftware.kryo.util.Util; @@ -274,6 +273,48 @@ public void writeBytes (byte[] bytes, int offset, int count) throws KryoExceptio } } + /** Writes count bytes from long, the last byte written is the lowest byte from the long. + * Note the number of bytes is not written. */ + public void writeInt (int bytes, int count) { + if (count < 0 || count > 4) throw new IllegalArgumentException("count must be >= 0 and <= 4: " + count); + require(count); + int p = position; + position = p + count; + switch (count) { + case 1: + buffer[p] = (byte)bytes; + break; + case 2: + buffer[p] = (byte)(bytes >> 8); + buffer[p+1] = (byte)bytes; + break; + case 3: + buffer[p] = (byte)(bytes >> 16); + buffer[p+1] = (byte)(bytes >> 8); + buffer[p+2] = (byte)bytes; + break; + case 4: + buffer[p] = (byte)(bytes >> 24); + buffer[p+1] = (byte)(bytes >> 16); + buffer[p+2] = (byte)(bytes >> 8); + buffer[p+3] = (byte)bytes; + break; + } + } + + /** Writes count bytes from long, the last byte written is the lowest byte from the long. + * Note the number of bytes is not written. */ + public void writeLong (long bytes, int count) { + if (count < 0 || count > 8) throw new IllegalArgumentException("count must be >= 0 and <= 8: " + count); + if (count <= 4) { + writeInt((int) bytes, count); + } else { + require(count); + writeInt((int) (bytes >> 32), count - 4); + writeInt((int) bytes, 4); + } + } + // int: /** Writes a 4 byte int. */ diff --git a/src/com/esotericsoftware/kryo/serializers/DefaultSerializers.java b/src/com/esotericsoftware/kryo/serializers/DefaultSerializers.java index 32e5cffa8..e442f10cc 100644 --- a/src/com/esotericsoftware/kryo/serializers/DefaultSerializers.java +++ b/src/com/esotericsoftware/kryo/serializers/DefaultSerializers.java @@ -21,6 +21,7 @@ import static com.esotericsoftware.kryo.Kryo.*; import static com.esotericsoftware.kryo.util.Util.*; +import static java.lang.Long.numberOfLeadingZeros; import com.esotericsoftware.kryo.Kryo; import com.esotericsoftware.kryo.KryoException; @@ -282,27 +283,15 @@ public void write (Kryo kryo, Output output, BigDecimal object) { } // compatible with writing unscaled value represented as BigInteger's bytes - private static void writeUnscaledLong(Output output, long unscaledLong) { - if (unscaledLong >>> 7 == 0) { // optimize for tiny values - output.writeVarInt(2, true); - output.writeByte((byte) unscaledLong); - } else { - byte[] bytes = new byte[8]; - int pos = 8; - do { - bytes[--pos] = (byte) (unscaledLong & 0xFF); - unscaledLong >>= 8; - } while (unscaledLong != 0 && unscaledLong != -1); // out of bits - - if (((bytes[pos] ^ unscaledLong) & 0x80) != 0) { - // sign bit didn't fit in previous byte, need to add another byte - bytes[--pos] = (byte) unscaledLong; - } + private static void writeUnscaledLong (Output output, long unscaledLong) { + int insignificantBits = unscaledLong >= 0 + ? numberOfLeadingZeros(unscaledLong) + : numberOfLeadingZeros(~unscaledLong); + int significantBits = (64 - insignificantBits) + 1; // one more bit is for the sign + int length = (significantBits + (8 - 1)) >> 3; // how many bytes are needed (rounded up) - int length = 8 - pos; - output.writeVarInt(length + 1, true); - output.writeBytes(bytes, pos, length); - } + output.writeByte(length + 1); + output.writeLong(unscaledLong, length); } public BigDecimal read (Kryo kryo, Input input, Class type) { @@ -313,15 +302,11 @@ public BigDecimal read (Kryo kryo, Input input, Class type if (length == NULL) return null; length--; - byte[] bytes = input.readBytes(length); if (length > 8) { + byte[] bytes = input.readBytes(length); unscaledBig = new BigInteger(bytes); } else { - unscaledLong = bytes[0]; - for (int i = 1; i < bytes.length; i++) { - unscaledLong <<= 8; - unscaledLong |= (bytes[i] & 0xFF); - } + unscaledLong = input.readLong(length); } int scale = input.readInt(false); diff --git a/test/com/esotericsoftware/kryo/io/InputOutputTest.java b/test/com/esotericsoftware/kryo/io/InputOutputTest.java index 5cb29e261..5b48be080 100644 --- a/test/com/esotericsoftware/kryo/io/InputOutputTest.java +++ b/test/com/esotericsoftware/kryo/io/InputOutputTest.java @@ -348,6 +348,25 @@ private void runIntTest (Output write) throws IOException { write.writeInt(-268435455); write.writeInt(-134217728); write.writeInt(-268435456); + write.writeInt(0, 1); + write.writeInt(63, 1); + write.writeInt(64, 1); + write.writeInt(127, 1); + write.writeInt(128, 2); + write.writeInt(8192, 2); + write.writeInt(16384, 2); + write.writeInt(2097151, 3); + write.writeInt(1048575, 3); + write.writeInt(134217727, 4); + write.writeInt(268435455, 4); + write.writeInt(134217728, 4); + write.writeInt(268435456, 4); + write.writeInt(-2097151, 3); + write.writeInt(-1048575, 3); + write.writeInt(-134217727, 4); + write.writeInt(-268435455, 4); + write.writeInt(-134217728, 4); + write.writeInt(-268435456, 4); assertEquals(1, write.writeVarInt(0, true)); assertEquals(1, write.writeVarInt(0, false)); assertEquals(1, write.writeVarInt(63, true)); @@ -417,6 +436,25 @@ private void runIntTest (Output write) throws IOException { assertEquals(-268435455, read.readInt()); assertEquals(-134217728, read.readInt()); assertEquals(-268435456, read.readInt()); + assertEquals(0, read.readInt(1)); + assertEquals(63, read.readInt(1)); + assertEquals(64, read.readInt(1)); + assertEquals(127, read.readInt(1)); + assertEquals(128, read.readInt(2)); + assertEquals(8192, read.readInt(2)); + assertEquals(16384, read.readInt(2)); + assertEquals(2097151, read.readInt(3)); + assertEquals(1048575, read.readInt(3)); + assertEquals(134217727, read.readInt(4)); + assertEquals(268435455, read.readInt(4)); + assertEquals(134217728, read.readInt(4)); + assertEquals(268435456, read.readInt(4)); + assertEquals(-2097151, read.readInt(3)); + assertEquals(-1048575, read.readInt(3)); + assertEquals(-134217727, read.readInt(4)); + assertEquals(-268435455, read.readInt(4)); + assertEquals(-134217728, read.readInt(4)); + assertEquals(-268435456, read.readInt(4)); assertTrue(read.canReadVarInt()); assertTrue(read.canReadVarInt()); assertTrue(read.canReadVarInt()); @@ -477,11 +515,15 @@ private void runIntTest (Output write) throws IOException { write.writeInt(value); write.writeVarInt(value, true); write.writeVarInt(value, false); + int numOfBytes = (i % 4) + 1; + write.writeInt(value, numOfBytes); read.setBuffer(write.toBytes()); assertEquals(value, read.readInt()); assertEquals(value, read.readVarInt(true)); assertEquals(value, read.readVarInt(false)); + int numOfBytesMask = numOfBytes == 4 ? -1 : (1 << numOfBytes * 8) - 1; + assertEquals(value & numOfBytesMask, read.readInt(numOfBytes) & numOfBytesMask); } } @@ -511,6 +553,25 @@ private void runLongTest (Output write) throws IOException { write.writeLong(-268435455); write.writeLong(-134217728); write.writeLong(-268435456); + write.writeLong(0, 1); + write.writeLong(63, 1); + write.writeLong(64, 1); + write.writeLong(127, 1); + write.writeLong(128, 2); + write.writeLong(8192, 2); + write.writeLong(16384, 2); + write.writeLong(2097151, 3); + write.writeLong(1048575, 3); + write.writeLong(134217727, 4); + write.writeLong(268435455, 4); + write.writeLong(134217728, 4); + write.writeLong(268435456, 4); + write.writeLong(-2097151, 3); + write.writeLong(-1048575, 3); + write.writeLong(-134217727, 4); + write.writeLong(-268435455, 4); + write.writeLong(-134217728, 4); + write.writeLong(-268435456, 4); assertEquals(1, write.writeVarLong(0, true)); assertEquals(1, write.writeVarLong(0, false)); assertEquals(1, write.writeVarLong(63, true)); @@ -574,6 +635,25 @@ private void runLongTest (Output write) throws IOException { assertEquals(-268435455, read.readLong()); assertEquals(-134217728, read.readLong()); assertEquals(-268435456, read.readLong()); + assertEquals(0, read.readLong(1)); + assertEquals(63, read.readLong(1)); + assertEquals(64, read.readLong(1)); + assertEquals(127, read.readLong(1)); + assertEquals(128, read.readLong(2)); + assertEquals(8192, read.readLong(2)); + assertEquals(16384, read.readLong(2)); + assertEquals(2097151, read.readLong(3)); + assertEquals(1048575, read.readLong(3)); + assertEquals(134217727, read.readLong(4)); + assertEquals(268435455, read.readLong(4)); + assertEquals(134217728, read.readLong(4)); + assertEquals(268435456, read.readLong(4)); + assertEquals(-2097151, read.readLong(3)); + assertEquals(-1048575, read.readLong(3)); + assertEquals(-134217727, read.readLong(4)); + assertEquals(-268435455, read.readLong(4)); + assertEquals(-134217728, read.readLong(4)); + assertEquals(-268435456, read.readLong(4)); assertEquals(0, read.readVarLong(true)); assertEquals(0, read.readVarLong(false)); assertEquals(63, read.readVarLong(true)); @@ -624,11 +704,15 @@ private void runLongTest (Output write) throws IOException { write.writeLong(value); write.writeVarLong(value, true); write.writeVarLong(value, false); + int numOfBytes = (i % 8) + 1; + write.writeLong(value, numOfBytes); read.setBuffer(write.toBytes()); assertEquals(value, read.readLong()); assertEquals(value, read.readVarLong(true)); assertEquals(value, read.readVarLong(false)); + long numOfBytesMask = numOfBytes == 8 ? -1 : (1L << numOfBytes * 8) - 1; + assertEquals(value & numOfBytesMask, read.readLong(numOfBytes) & numOfBytesMask); } }