diff --git a/java/src/main/java/net/razorvine/pickle/Pickler.java b/java/src/main/java/net/razorvine/pickle/Pickler.java index d205e50..5b463be 100644 --- a/java/src/main/java/net/razorvine/pickle/Pickler.java +++ b/java/src/main/java/net/razorvine/pickle/Pickler.java @@ -82,6 +82,12 @@ public Memo(Object obj, int index) { */ protected boolean useMemo=true; + /** + * When memoizing, compare objects by value. This saves pickle size, but can slow down pickling. + * Alo, it should only be used if the object graph is immutable. Unused if useMemo is false. + */ + protected boolean valueCompare=true; + /** * The memoization cache. */ @@ -100,7 +106,17 @@ public Pickler() { * If you use a memo table, you can only pickle objects that are hashable. */ public Pickler(boolean useMemo) { + this(useMemo, false); + } + + /** + * Create a Pickler. Also specify if it is to compare objects by value. + * If you compare objects by value, the object graph might be altered, + * as different instances with the same value will be unified. + */ + public Pickler(boolean useMemo, boolean valueCompare) { this.useMemo=useMemo; + this.valueCompare=valueCompare; } /** @@ -184,15 +200,15 @@ public void save(Object o) throws PickleException, IOException { * Write the object to the memo table and output a memo write opcode * Only works for hashable objects */ - protected void writeMemo( Object obj ) throws IOException - { + protected void writeMemo( Object obj ) throws IOException + { if(!this.useMemo) return; - int idHash = System.identityHashCode(obj); - if(!memo.containsKey(idHash)) + int hash = valueCompare ? obj.hashCode() : System.identityHashCode(obj); + if(!memo.containsKey(hash)) { int memo_index = memo.size(); - memo.put(idHash, new Memo(obj, memo_index)); + memo.put(hash, new Memo(obj, memo_index)); if(memo_index<=0xFF) { out.write(Opcodes.BINPUT); @@ -213,10 +229,10 @@ protected void writeMemo( Object obj ) throws IOException private boolean lookupMemo(Class objectType, Object obj) throws IOException { if(!this.useMemo) return false; - int idHash = System.identityHashCode(obj); if(!objectType.isPrimitive()) { - if(memo.containsKey(idHash) && memo.get(idHash).obj == obj) { // same object - int memo_index = memo.get(idHash).index; + int hash = valueCompare ? obj.hashCode() : System.identityHashCode(obj); + if(memo.containsKey(hash) && (valueCompare ? memo.get(hash).obj.equals(obj) : memo.get(hash).obj == obj)) { // same object or value + int memo_index = memo.get(hash).index; if(memo_index <= 0xff) { out.write(Opcodes.BINGET); out.write((byte) memo_index); diff --git a/java/src/test/java/net/razorvine/examples/ValueCompareExample.java b/java/src/test/java/net/razorvine/examples/ValueCompareExample.java new file mode 100644 index 0000000..051e19f --- /dev/null +++ b/java/src/test/java/net/razorvine/examples/ValueCompareExample.java @@ -0,0 +1,54 @@ +package net.razorvine.examples; + +import java.util.*; +import java.io.IOException; + +import net.razorvine.pickle.Pickler; +import net.razorvine.pickle.Unpickler; + +public class ValueCompareExample { + + public static void main(String[] args) throws IOException { + + Random random = new Random(1337); + List values = new ArrayList(); + for (int i = 0; i < 100000; ++i) { + values.add(("This is a string with a number in it: " + random.nextInt(100)) + // .intern() // You could also see what happens when the strings are interned + ); + } + + long t0 = System.nanoTime(); + byte[] noValueCompare = new Pickler(true, false).dumps(values); + long t1 = System.nanoTime(); + byte[] withValueCompare = new Pickler(true, true).dumps(values); + long t2 = System.nanoTime(); + + System.out.println("Pickle size without: " + noValueCompare.length + "B, with: " + withValueCompare.length + "B" + + " (" + (int) ((1 - withValueCompare.length / (double) noValueCompare.length) * 100.0) + "% smaller)"); + + Unpickler unpickler = new Unpickler(); + + long t3 = System.nanoTime(); + List without = (List) unpickler.loads(noValueCompare); + long t4 = System.nanoTime(); + List with = (List) unpickler.loads(withValueCompare); + long t5 = System.nanoTime(); + + System.out.println("Whole List equality: ==: " + (without == with) + ", .equals(): " + (without.equals(with))); + + System.out.println( + "Two equal elements equality without valueCompare: ==: " + (without.get(107) == without.get(259)) + + ", .equals(): " + (without.get(107).equals(without.get(259)))); + System.out.println("Two equal elements equality with valueCompare: ==: " + (with.get(107) == with.get(259)) + + ", .equals(): " + (with.get(107).equals(with.get(259)))); + + System.out.println("Pickling without valueCompare took " + (t1 - t0) / 1000000.0 + "ms"); + System.out.println("Pickling with valueCompare took " + (t2 - t1) / 1000000.0 + "ms"); + + System.out.println("Unpickling without valueCompare took " + (t4 - t3) / 1000000.0 + "ms"); + System.out.println("Unpickling with valueCompare took " + (t5 - t4) / 1000000.0 + "ms"); + + } + +}