From 2babd13b8c75e56a4b4a066fa1472c39f4f64439 Mon Sep 17 00:00:00 2001 From: ZZZank <47418975+ZZZank@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:39:17 +0000 Subject: [PATCH 1/5] replace BitSet with IntOpenHashSet --- .../api2/storage/SimpleDatabase.java | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/main/java/betterquesting/api2/storage/SimpleDatabase.java b/src/main/java/betterquesting/api2/storage/SimpleDatabase.java index 1de0070ed..068afaca9 100644 --- a/src/main/java/betterquesting/api2/storage/SimpleDatabase.java +++ b/src/main/java/betterquesting/api2/storage/SimpleDatabase.java @@ -1,38 +1,41 @@ package betterquesting.api2.storage; -import java.util.BitSet; -import java.util.Collections; -import java.util.List; -import java.util.TreeMap; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; public class SimpleDatabase extends AbstractDatabase { - private final BitSet idMap = new BitSet(); + private final IntOpenHashSet ids = new IntOpenHashSet(); @Override public synchronized int nextID() { - return idMap.nextClearBit(0); + for (int i = 0; i < Integer.MAX_VALUE ; i++) { + if (!ids.contains(i)) { + return i; + } + } + throw new IllegalStateException(String.format("All integer id from 0 to %s have been consumed", Integer.MAX_VALUE)); } @Override public synchronized DBEntry add(int id, T value) { DBEntry result = super.add(id, value); // Don't add when an exception is thrown - idMap.set(id); + ids.add(id); return result; } @Override public synchronized boolean removeID(int key) { boolean result = super.removeID(key); - if (result) idMap.clear(key); + if (result) { + ids.remove(key); + } return result; } @Override public synchronized void reset() { super.reset(); - idMap.clear(); + ids.clear(); } - } From e8917f88b4bf77e08f7ef028bfde1ed74a11616e Mon Sep 17 00:00:00 2001 From: ZZZank <47418975+ZZZank@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:43:13 +0000 Subject: [PATCH 2/5] simple trick to reduce the amount of computation of `nextID()` --- .../java/betterquesting/api2/storage/SimpleDatabase.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/betterquesting/api2/storage/SimpleDatabase.java b/src/main/java/betterquesting/api2/storage/SimpleDatabase.java index 068afaca9..86a011fa1 100644 --- a/src/main/java/betterquesting/api2/storage/SimpleDatabase.java +++ b/src/main/java/betterquesting/api2/storage/SimpleDatabase.java @@ -5,11 +5,13 @@ public class SimpleDatabase extends AbstractDatabase { private final IntOpenHashSet ids = new IntOpenHashSet(); + private int lowerBound = 0; @Override public synchronized int nextID() { - for (int i = 0; i < Integer.MAX_VALUE ; i++) { + for (int i = lowerBound; i < Integer.MAX_VALUE ; i++) { if (!ids.contains(i)) { + lowerBound = i + 1; return i; } } @@ -21,6 +23,7 @@ public synchronized DBEntry add(int id, T value) { DBEntry result = super.add(id, value); // Don't add when an exception is thrown ids.add(id); + // lowerBound = id; //no, lowerBound will not be refreshed here, but delayed to next `nextID()` call return result; } @@ -29,6 +32,7 @@ public synchronized boolean removeID(int key) { boolean result = super.removeID(key); if (result) { ids.remove(key); + lowerBound = Math.min(key, lowerBound); } return result; } From 6c921d5b6640f44faf8814d10ce5b24d9c8a43ee Mon Sep 17 00:00:00 2001 From: ZZZank <47418975+ZZZank@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:02:44 +0000 Subject: [PATCH 3/5] forgot to add generated id to used ids --- .../java/betterquesting/api2/storage/SimpleDatabase.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/betterquesting/api2/storage/SimpleDatabase.java b/src/main/java/betterquesting/api2/storage/SimpleDatabase.java index 86a011fa1..41a20945e 100644 --- a/src/main/java/betterquesting/api2/storage/SimpleDatabase.java +++ b/src/main/java/betterquesting/api2/storage/SimpleDatabase.java @@ -5,17 +5,24 @@ public class SimpleDatabase extends AbstractDatabase { private final IntOpenHashSet ids = new IntOpenHashSet(); + /** + * the "smallest acceptable" id, any id smaller than this is considered as used and can + * be skipped when searching for new id, this condition can be maintained by: + * - refresh lowerBound after new id is generated: {@link #nextID()} + * - refresh lowerBound after one id is removed: {@link #removeID(int)} + */ private int lowerBound = 0; @Override public synchronized int nextID() { for (int i = lowerBound; i < Integer.MAX_VALUE ; i++) { if (!ids.contains(i)) { + ids.add(i); lowerBound = i + 1; return i; } } - throw new IllegalStateException(String.format("All integer id from 0 to %s have been consumed", Integer.MAX_VALUE)); + throw new IllegalStateException(String.format("All integer id from 0 to %s have been consumed, it's abnormal", Integer.MAX_VALUE)); } @Override From a2a5cb2be24e244c594aab1f6a400b8055395327 Mon Sep 17 00:00:00 2001 From: ZZZank <47418975+ZZZank@users.noreply.github.com> Date: Sun, 27 Oct 2024 14:12:03 +0000 Subject: [PATCH 4/5] add `SequentialDataBase`, ideally all `SimpleDatabase` usage should be redirected to this new class --- .../api2/storage/SequentialDataBase.java | 52 +++++++++++++++++++ .../api2/storage/SimpleDatabase.java | 52 ++----------------- 2 files changed, 56 insertions(+), 48 deletions(-) create mode 100644 src/main/java/betterquesting/api2/storage/SequentialDataBase.java diff --git a/src/main/java/betterquesting/api2/storage/SequentialDataBase.java b/src/main/java/betterquesting/api2/storage/SequentialDataBase.java new file mode 100644 index 000000000..3126ff6ed --- /dev/null +++ b/src/main/java/betterquesting/api2/storage/SequentialDataBase.java @@ -0,0 +1,52 @@ +package betterquesting.api2.storage; + +import java.util.BitSet; + +/** + * a database implementation specialized for ids that're sequential, dense and not randomly generated + *

+ * see {@link RandomIndexDatabase} for database specialized in handling randomly generated ids + */ +public class SequentialDataBase extends AbstractDatabase { + + private final BitSet ids = new BitSet(); + /** + * the "smallest acceptable" id, any id smaller than this is considered as used and can + * be skipped when searching for new id, this condition can be maintained by: + * - refresh lowerBound after new id is generated: {@link #nextID()} + * - refresh lowerBound after one id is removed: {@link #removeID(int)} + */ + private int lowerBound = 0; + + @Override + public synchronized int nextID() { + int next = ids.nextSetBit(lowerBound); + lowerBound = next + 1; + return next; + } + + @Override + public synchronized DBEntry add(int id, T value) { + DBEntry result = super.add(id, value); + // Don't add when an exception is thrown + ids.set(id); + // lowerBound = id; //no, lowerBound will not be refreshed here, delay to next `nextID()` call instead + return result; + } + + @Override + public synchronized boolean removeID(int key) { + boolean result = super.removeID(key); + if (result) { + ids.clear(key); + lowerBound = Math.min(key, lowerBound); + } + return result; + } + + @Override + public synchronized void reset() { + super.reset(); + ids.clear(); + } +} diff --git a/src/main/java/betterquesting/api2/storage/SimpleDatabase.java b/src/main/java/betterquesting/api2/storage/SimpleDatabase.java index 41a20945e..a16b3aac2 100644 --- a/src/main/java/betterquesting/api2/storage/SimpleDatabase.java +++ b/src/main/java/betterquesting/api2/storage/SimpleDatabase.java @@ -1,52 +1,8 @@ package betterquesting.api2.storage; -import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +/** + * @see SequentialDataBase + */ +public class SimpleDatabase extends SequentialDataBase { -public class SimpleDatabase extends AbstractDatabase { - - private final IntOpenHashSet ids = new IntOpenHashSet(); - /** - * the "smallest acceptable" id, any id smaller than this is considered as used and can - * be skipped when searching for new id, this condition can be maintained by: - * - refresh lowerBound after new id is generated: {@link #nextID()} - * - refresh lowerBound after one id is removed: {@link #removeID(int)} - */ - private int lowerBound = 0; - - @Override - public synchronized int nextID() { - for (int i = lowerBound; i < Integer.MAX_VALUE ; i++) { - if (!ids.contains(i)) { - ids.add(i); - lowerBound = i + 1; - return i; - } - } - throw new IllegalStateException(String.format("All integer id from 0 to %s have been consumed, it's abnormal", Integer.MAX_VALUE)); - } - - @Override - public synchronized DBEntry add(int id, T value) { - DBEntry result = super.add(id, value); - // Don't add when an exception is thrown - ids.add(id); - // lowerBound = id; //no, lowerBound will not be refreshed here, but delayed to next `nextID()` call - return result; - } - - @Override - public synchronized boolean removeID(int key) { - boolean result = super.removeID(key); - if (result) { - ids.remove(key); - lowerBound = Math.min(key, lowerBound); - } - return result; - } - - @Override - public synchronized void reset() { - super.reset(); - ids.clear(); - } } From 7ef539421feffa7b44b815d5fced082323771fba Mon Sep 17 00:00:00 2001 From: ZZZank <47418975+ZZZank@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:09:33 +0000 Subject: [PATCH 5/5] look for the first false, not true --- .../java/betterquesting/api2/storage/SequentialDataBase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/betterquesting/api2/storage/SequentialDataBase.java b/src/main/java/betterquesting/api2/storage/SequentialDataBase.java index 3126ff6ed..5d6811355 100644 --- a/src/main/java/betterquesting/api2/storage/SequentialDataBase.java +++ b/src/main/java/betterquesting/api2/storage/SequentialDataBase.java @@ -20,7 +20,7 @@ public class SequentialDataBase extends AbstractDatabase { @Override public synchronized int nextID() { - int next = ids.nextSetBit(lowerBound); + int next = ids.nextClearBit(lowerBound); lowerBound = next + 1; return next; }