diff --git a/java/com/google/flatbuffers/FlatBufferBuilder.java b/java/com/google/flatbuffers/FlatBufferBuilder.java index e5e3967ac..939e1b0cd 100644 --- a/java/com/google/flatbuffers/FlatBufferBuilder.java +++ b/java/com/google/flatbuffers/FlatBufferBuilder.java @@ -22,6 +22,9 @@ import java.io.IOException; import java.io.InputStream; import java.nio.*; import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.lang.Integer; /// @file /// @addtogroup flatbuffers_java_api @@ -33,20 +36,21 @@ import java.util.Arrays; */ public class FlatBufferBuilder { /// @cond FLATBUFFERS_INTERNAL - ByteBuffer bb; // Where we construct the FlatBuffer. - int space; // Remaining space in the ByteBuffer. - int minalign = 1; // Minimum alignment encountered so far. - int[] vtable = null; // The vtable for the current table. - int vtable_in_use = 0; // The amount of fields we're actually using. - boolean nested = false; // Whether we are currently serializing a table. - boolean finished = false; // Whether the buffer is finished. - int object_start; // Starting offset of the current struct/table. - int[] vtables = new int[16]; // List of offsets of all vtables. - int num_vtables = 0; // Number of entries in `vtables` in use. - int vector_num_elems = 0; // For the current vector being built. - boolean force_defaults = false; // False omits default values from the serialized data. - ByteBufferFactory bb_factory; // Factory for allocating the internal buffer - final Utf8 utf8; // UTF-8 encoder to use + ByteBuffer bb; // Where we construct the FlatBuffer. + int space; // Remaining space in the ByteBuffer. + int minalign = 1; // Minimum alignment encountered so far. + int[] vtable = null; // The vtable for the current table. + int vtable_in_use = 0; // The amount of fields we're actually using. + boolean nested = false; // Whether we are currently serializing a table. + boolean finished = false; // Whether the buffer is finished. + int object_start; // Starting offset of the current struct/table. + int[] vtables = new int[16]; // List of offsets of all vtables. + int num_vtables = 0; // Number of entries in `vtables` in use. + int vector_num_elems = 0; // For the current vector being built. + boolean force_defaults = false; // False omits default values from the serialized data. + ByteBufferFactory bb_factory; // Factory for allocating the internal buffer + final Utf8 utf8; // UTF-8 encoder to use + Map string_pool; // map used to cache shared strings. /// @endcond /** @@ -147,6 +151,9 @@ public class FlatBufferBuilder { object_start = 0; num_vtables = 0; vector_num_elems = 0; + if (string_pool != null) { + string_pool.clear(); + } return this; } @@ -224,6 +231,9 @@ public class FlatBufferBuilder { object_start = 0; num_vtables = 0; vector_num_elems = 0; + if (string_pool != null) { + string_pool.clear(); + } } /** @@ -527,6 +537,37 @@ public class FlatBufferBuilder { return createVectorOfTables(offsets); } + /** + * Encode the String `s` in the buffer using UTF-8. If a String with + * this exact contents has already been serialized using this method, + * instead simply returns the offset of the existing String. + * + * Usage of the method will incur into additional allocations, + * so it is advisable to use it only when it is known upfront that + * your message will have several repeated strings. + * + * @param s The String to encode. + * @return The offset in the buffer where the encoded String starts. + */ + public int createSharedString(String s) { + + if (string_pool == null) { + string_pool = new HashMap<>(); + int offset = createString(s); + string_pool.put(s, offset); + return offset; + + } + + Integer offset = string_pool.get(s); + + if(offset == null) { + offset = createString(s); + string_pool.put(s, offset); + } + return offset; + } + /** * Encode the string `s` in the buffer using UTF-8. If {@code s} is * already a {@link CharBuffer}, this method is allocation free. diff --git a/tests/JavaTest.java b/tests/JavaTest.java index 39c773345..fef099ffd 100644 --- a/tests/JavaTest.java +++ b/tests/JavaTest.java @@ -23,7 +23,9 @@ import java.io.*; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.CharBuffer; import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -101,6 +103,8 @@ class JavaTest { TestVectorOfBytes(); + TestSharedStringPool(); + System.out.println("FlatBuffers test: completed successfully"); } @@ -1216,6 +1220,15 @@ class JavaTest { TestEq(monsterObject8.inventoryLength(), 2048); } + static void TestSharedStringPool() { + FlatBufferBuilder fb = new FlatBufferBuilder(1); + String testString = "My string"; + int offset = fb.createSharedString(testString); + for (int i=0; i< 10; i++) { + TestEq(offset, fb.createSharedString(testString)); + } + } + static void TestEq(T a, T b) { if (!a.equals(b)) { System.out.println("" + a.getClass().getName() + " " + b.getClass().getName()); diff --git a/tests/KotlinTest.kt b/tests/KotlinTest.kt index 2d64a3867..538ab466b 100644 --- a/tests/KotlinTest.kt +++ b/tests/KotlinTest.kt @@ -76,6 +76,8 @@ class KotlinTest { TestVectorOfUnions() + TestSharedStringPool() + println("FlatBuffers test: completed successfully") } @@ -456,5 +458,14 @@ class KotlinTest { assert((movie.characters(Attacker(), 0) as Attacker).swordAttackDamage == swordAttackDamage) } -} + + fun TestSharedStringPool() { + val fb = FlatBufferBuilder(1); + val testString = "My string"; + val offset = fb.createSharedString(testString); + for (i in 0..10) { + assert(offset == fb.createSharedString(testString)); + } + } } +}