├── .github └── workflows │ └── maven.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── page_layout.txt ├── pom.xml └── src ├── main └── java │ └── ds │ └── bplus │ ├── bptree │ ├── BPlusConfiguration.java │ ├── BPlusTree.java │ ├── BPlusTreePerformanceCounter.java │ ├── DeleteResult.java │ ├── KeyValueWrapper.java │ ├── RangeResult.java │ ├── SearchResult.java │ ├── TreeInternalNode.java │ ├── TreeLeaf.java │ ├── TreeLookupOverflowNode.java │ ├── TreeNode.java │ ├── TreeNodeType.java │ └── TreeOverflow.java │ ├── fudger │ └── Main.java │ └── util │ ├── InvalidBTreeStateException.java │ ├── StandardInputRead.java │ ├── TestRunner.java │ ├── TrialsClass.java │ └── Utilities.java └── test └── java └── BPlusTreeTest.java /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Set up JDK 1.8 13 | uses: actions/setup-java@v1 14 | with: 15 | java-version: 11 16 | - name: Build with Maven 17 | run: mvn -B package --file pom.xml 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | 3 | # IntelliJ project files 4 | .idea/ 5 | *.iml 6 | out 7 | gen 8 | 9 | # Mobile Tools for Java (J2ME) 10 | .mtj.tmp/ 11 | 12 | # Output folder 13 | target/ 14 | 15 | # Package Files # 16 | *.jar 17 | *.war 18 | *.ear 19 | 20 | # Binary files 21 | *.bin 22 | 23 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 24 | hs_err_pid* 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - oraclejdk11 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![build](https://travis-ci.org/andylamp/BPlusTree.svg?branch=master) 2 | ![Maven](https://github.com/andylamp/BPlusTree/workflows/Java%20CI/badge.svg) 3 | 4 | 5 | # A Purely *On-Disk* Implementation of a B+ Tree 6 | 7 | 8 | After quite a few hours that included developing the thing as well as testing it here is 9 | a (fully) functional implementation of a B+ Tree data structure purely ***on the disk***. 10 | This was developed mostly for educational reasons that stemmed from the fact that I could not 11 | find a B+ Tree implementation that met the following: 12 | 13 | * Purely Disk based implementation 14 | * Used strict paging which the user could tune (256k, 1K, 4K etc) 15 | * Was a (Key, Value) storage not just for Keys (B-Tree) 16 | * Implemented **deleting** from the data structure 17 | * Supported duplicate entries (if required) 18 | * was adequately *tested* (so that I know it would work) 19 | 20 | I came about needing to implement a B+ Tree for a side project of mine... and I was really 21 | surprised to see that there were no implementations that met the above requirements; not only 22 | that but I was not able to find a reference code/pseudocode that had a working **delete** 23 | implementation and support for duplicate keys. To my dismay even my trusty (and beloved) 24 | CLRS didn't include an implementation of a B+ Tree but only for the B-Tree with no duplicates 25 | and **delete** pseudocode (well, one could say that they gave the steps...but that's 26 | not the point). 27 | 28 | It has to be noted that I *could* find some B+ Tree implementations that had delete and duplicate 29 | key support, mainly in open-source database projects. This unfortunately meant that these 30 | implementations were deeply integrated into their parent projects and as a result they were 31 | optimized for their particular use-cases. Finally the code bases where significantly larger 32 | (hence making the code reading/understanding much harder than it should be!). 33 | 34 | So I went about to implement mine (cool stuff, many hours of head scratching were involved!) 35 | while also putting effort in creating this "tuned" down version which mainly cuts down on 36 | features for simplicity, enhanced code clarity and clutter reduction. 37 | 38 | 39 | # Ease of use features 40 | 41 | 42 | As I said above this project was done mainly to create a working example of a B+ Tree data 43 | structure purely on the disk so the code is well commented (I think) and can be understood 44 | easily; that said... we have some "delicacies" that make working and testing this project 45 | a bit easier, which are outlined below 46 | 47 | * it uses maven, so most modern IDE's can import it without hassle... 48 | * it requires jdk v8 (for some parts, change them to have backwards support) 49 | * it uses a dedicated tester as well as JUnit tests 50 | * has an interactive menu that the user can individually perform the operations 51 | 52 | 53 | # Implementation details 54 | 55 | ## Insertions 56 | 57 | For insertions we use a modified version of the method provided by CLRS with modifications to 58 | support duplicate keys (which will be covered below). Complexity is not altered from the 59 | usual case and we require one pass down the tree as well. 60 | 61 | ## Searching 62 | 63 | This is assumed to be the most common operation on the B+ Tree; which support two modes of 64 | searching: 65 | 66 | * Singular Key searches 67 | * Range Queries 68 | 69 | Here two distinct functions are used to cover these two cases: 70 | 71 | * **searchKey** is used for singular value searches (unique or not) 72 | * **rangeSearch** is used for performing range queries 73 | 74 | Both of these methods require only one pass over the tree to find the results (if any). Additionally, since 75 | we store the keys in a sorted order we can exploit binary search to reduce the total node lookup time *significantly*. 76 | This is done along with a slight modification to the search algorithm to return the lower bound instead of failure. 77 | 78 | ## Deletes 79 | 80 | We again use one pass down the tree deletes but this is a bit more complex than the other 81 | two operations... it again requires only one pass to delete the key (if found) but as it 82 | descends down the tree it performs any redistribution/merging that needs to happen in 83 | order to keep our data structure valid. 84 | 85 | ## Handling of duplicate keys 86 | 87 | In order to avoid hurting the search performance functionality which is (assumed to be) 88 | the most common operation in a B+ Tree data structure the following scheme was 89 | implemented to support duplicate keys (at the cost of a bit more page reads). 90 | 91 | The tree only actually indexes singular keys but each key has an *overflow* pointer 92 | which leads to its *overflow page* that has all of the duplicate entries stored as a 93 | linked list. If needed, multiple trailing *overflow* pages per key are created to 94 | accommodate for these extra insertions should they exceed the capacity of one page. 95 | The downside is that we use a bit more space per page as well as reads in order to 96 | read these overflow pages. 97 | 98 | ## Page lookup table 99 | 100 | To avoid moving around things too much we keep each page into a *free page pool* that has 101 | all of the available pages so far; this in turn let's us create an index very fast 102 | without having to pay costly reads if we wanted to have a clustered tree (although 103 | we again use more space, usually). 104 | 105 | ## Payload sizes 106 | 107 | Due to inherent limitations of how B+ Tree is designed to work the payload contained 108 | within each entry must be of a specific size. This can be configured at initialization 109 | but cannot be changed after creation. 110 | 111 | This means that if the payload is _less_ than the entry size it will be padded with 112 | whitespaces to fill in the bucket whereas if it is _more_ it will be truncuated to 113 | the size of the bucket. 114 | 115 | # License 116 | 117 | This work, at its current version, is licensed under the Apache 2.0 license. 118 | 119 | # Final words 120 | 121 | Hopefully I'll create a GitHub page for this... where I explain my implementation a bit 122 | more but until then this will suffice! Oh and I really hope this implementation is clear 123 | and concise enough so that it can make the notions of B+ Trees crystal clear! 124 | -------------------------------------------------------------------------------- /page_layout.txt: -------------------------------------------------------------------------------- 1 | header: 2 | 1Kb total 3 | | 4 | -- header size -- 4 bytes 5 | -- page size -- 4 bytes 6 | -- entry size -- 4 bytes 7 | -- key size -- 4 bytes 8 | -- number of pages -- 8 bytes 9 | -- max page index -- 8 bytes 10 | -- root index -- 8 bytes 11 | -- next lookup page pointer -- 8 bytes 12 | -- pool of available index spots -- page size - 40 (7 variables);; 13 | | 14 | 15 | internal page: 16 | 1Kb total 17 | | 18 | -- node type -- 2 bytes 19 | -- current capacity -- 4 bytes 20 | 21 | -- ADD ONS SIZE: (2+4) = 6 bytes 22 | 23 | -- keys -- 8 bytes * max size 24 | -- pointers -- 8 bytes * max size 25 | | 26 | 27 | 28 | leaf page: 29 | 1Kb total 30 | | 31 | -- node type -- 2 bytes 32 | //!!-- has overflow -- 2 bytes 33 | -- next pointer -- 8 bytes 34 | -- prev pointer -- 8 bytes 35 | -- current capacity -- 4 bytes 36 | 37 | -- ADD ONS SIZE: (2+8+8+4) = 22 bytes 38 | 39 | -- keys -- 8 bytes * max size 40 | -- overflow pointers -- 8 bytes * max size 41 | -- satellite data -- variable * max size 42 | | 43 | 44 | overflow page: 45 | 1Kb total 46 | 47 | | 48 | -- node type -- 2 bytes 49 | -- next pointer -- 8 bytes 50 | -- prev pointer -- 8 bytes 51 | -- current capacity -- 4 bytes 52 | 53 | -- ADD ONS SIZE: (2+8+8+4) = 22 bytes 54 | 55 | // no need for keys, since we already know 56 | // the key to that overflow page 57 | satellite data -- variable * max size 58 | 59 | | 60 | 61 | lookup page overflow page: 62 | 63 | 1Kb total 64 | 65 | | 66 | -- node type -- 2 bytes 67 | -- next pointer -- 8 bytes 68 | -- current capacity -- 4 bytes 69 | 70 | -- ADD ONS SIZE: (2+8+4) = 14 bytes 71 | 72 | -- number of keys dependenet on page size 73 | exact number is floor(pagesize/keysize) 74 | 75 | | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | groupId 8 | ds 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 13 | 14 | junit 15 | junit 16 | 4.13.1 17 | 18 | 19 | 20 | 21 | 22 | 23 | org.apache.maven.plugins 24 | maven-compiler-plugin 25 | 3.3 26 | 27 | 1.8 28 | 1.8 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/main/java/ds/bplus/bptree/BPlusConfiguration.java: -------------------------------------------------------------------------------- 1 | package ds.bplus.bptree; 2 | 3 | /** 4 | * 5 | * Class that stores all of the configuration parameters for our B+ Tree. 6 | * 7 | * You can view a description on all of the parameters below... 8 | * 9 | */ 10 | @SuppressWarnings({"WeakerAccess", "unused"}) 11 | public class BPlusConfiguration { 12 | 13 | private int pageSize; // page size (in bytes) 14 | private int keySize; // key size (in bytes) 15 | private int entrySize; // entry size (in bytes) 16 | private int treeDegree; // tree degree (internal node degree) 17 | private int headerSize; // header size (in bytes) 18 | private int leafHeaderSize; // leaf node header size (in bytes) 19 | private int internalNodeHeaderSize; // internal node header size (in bytes) 20 | private int lookupOverflowHeaderSize; // lookup overflow page header size 21 | private int lookupOverflowPageDegree; // lookup overflow page degree 22 | private int leafNodeDegree; // leaf node degree 23 | private int overflowPageDegree; // overflow page degree 24 | private int lookupPageSize; // look up page size 25 | private int conditionThreshold; // iterations to perform conditioning 26 | 27 | /** 28 | * 29 | * Default constructor which initializes all 30 | * settings to the predefined defaults. 31 | * 32 | */ 33 | public BPlusConfiguration() { 34 | basicParams(1024, 8, 20); 35 | initializeCommon(pageSize, keySize, entrySize, 1000); 36 | } 37 | 38 | /** 39 | * Overloaded constructor allows page size adjustments 40 | * 41 | * @param pageSize page size (in bytes) 42 | */ 43 | public BPlusConfiguration(int pageSize) { 44 | basicParams(pageSize, 8, 20); 45 | initializeCommon(pageSize, keySize, entrySize, 1000); 46 | } 47 | 48 | /** 49 | * Overloaded constructor 50 | * 51 | * @param pageSize page size (default is 1024 bytes) 52 | * @param keySize key size (default is long [8 bytes]) 53 | * @param entrySize satellite data (default is 20 bytes) 54 | */ 55 | public BPlusConfiguration(int pageSize, int keySize, 56 | int entrySize) { 57 | basicParams(pageSize, keySize, entrySize); 58 | initializeCommon(pageSize, keySize, entrySize, 1000); 59 | } 60 | 61 | /** 62 | * Overloaded constructor 63 | * 64 | * @param pageSize page size (default is 1024 bytes) 65 | * @param keySize key size (default is long [8 bytes]) 66 | * @param entrySize satellite data (default is 20 bytes) 67 | * @param conditionThreshold threshold to perform file conditioning 68 | */ 69 | @SuppressWarnings("unused") 70 | public BPlusConfiguration(int pageSize, int keySize, 71 | int entrySize, int conditionThreshold) { 72 | basicParams(pageSize, keySize, entrySize); 73 | initializeCommon(pageSize, keySize, entrySize, conditionThreshold); 74 | } 75 | 76 | /** 77 | * Set up the basic parameters of the tree 78 | * 79 | * @param pageSize page size (default is 1024 bytes) 80 | * @param keySize key size (default is long [8 bytes]) 81 | * @param entrySize satellite data (default is 20 bytes) 82 | */ 83 | private void basicParams(int pageSize, int keySize, int entrySize) { 84 | this.pageSize = pageSize; // page size (in bytes) 85 | this.entrySize = entrySize; // entry size (in bytes) 86 | this.keySize = keySize; // key size (in bytes) 87 | } 88 | 89 | /** 90 | * Common method to initialize constructor parameters 91 | * 92 | * @param pageSize page size (default is 1024 bytes) 93 | * @param keySize key size (default is long [8 bytes]) 94 | * @param entrySize satellite data (default is 20 bytes) 95 | * @param conditionThreshold the number of iterations before file conditioning 96 | */ 97 | private void initializeCommon(int pageSize, int keySize, 98 | int entrySize, int conditionThreshold) { 99 | this.headerSize = // header size in bytes 100 | (Integer.SIZE * 4 + 4 * Long.SIZE) / 8; 101 | this.internalNodeHeaderSize = (Short.SIZE + Integer.SIZE) / 8; // 6 bytes 102 | this.leafHeaderSize = (Short.SIZE + 2 * Long.SIZE + Integer.SIZE) / 8; // 22 bytes 103 | this.lookupOverflowHeaderSize = 14; 104 | this.lookupPageSize = pageSize - headerSize; // lookup page size 105 | this.conditionThreshold = conditionThreshold; // iterations for conditioning 106 | // now calculate the tree degree 107 | this.treeDegree = calculateDegree(2*keySize, internalNodeHeaderSize); 108 | // leaf & overflow have the same header size. 109 | this.leafNodeDegree = calculateDegree((2*keySize)+entrySize, leafHeaderSize); 110 | this.overflowPageDegree = calculateDegree(entrySize, leafHeaderSize); 111 | this.lookupOverflowPageDegree = calculateDegree(keySize, 112 | lookupOverflowHeaderSize); 113 | checkDegreeValidity(); 114 | } 115 | 116 | /** 117 | * calculates the degree of a node (internal/leaf) 118 | * 119 | * @param elementSize the node element size (in bytes) 120 | * @param elementHeaderSize the node header size (in bytes) 121 | * @return the node degree 122 | */ 123 | private int calculateDegree(int elementSize, int elementHeaderSize) 124 | {return((int) (((pageSize-elementHeaderSize)/(2.0*elementSize))/*+0.5*/));} 125 | 126 | /** 127 | * 128 | * Little function that checks if we have any degree < 2 (which is not allowed) 129 | * 130 | */ 131 | private void checkDegreeValidity() { 132 | if (treeDegree < 2 || leafNodeDegree < 2 || 133 | overflowPageDegree < 2 || lookupOverflowPageDegree < 2) 134 | {throw new IllegalArgumentException("Can't have a degree < 2");} 135 | } 136 | 137 | public int getPageSize() 138 | {return pageSize;} 139 | 140 | public int getEntrySize() { 141 | return entrySize; 142 | } 143 | 144 | public int getFirstLookupPageElements() { 145 | return lookupPageSize / keySize; 146 | } 147 | 148 | public int getTreeDegree() 149 | {return treeDegree;} 150 | 151 | public int getOverflowPageDegree() 152 | {return(overflowPageDegree);} 153 | 154 | public int getMaxInternalNodeCapacity() 155 | {return((2*treeDegree) - 1);} 156 | 157 | public int getMaxLeafNodeCapacity() 158 | {return((2*leafNodeDegree) - 1);} 159 | 160 | public int getMaxOverflowNodeCapacity() { 161 | return ((2 * overflowPageDegree) - 1); 162 | } 163 | 164 | public int getMaxLookupPageOverflowCapacity() { 165 | return ((2 * lookupOverflowPageDegree) - 1); 166 | } 167 | 168 | public int getMinLeafNodeCapacity() 169 | {return(leafNodeDegree-1);} 170 | 171 | public int getMinInternalNodeCapacity() 172 | {return(treeDegree-1);} 173 | 174 | public int getKeySize() 175 | {return keySize;} 176 | 177 | public int getLeafNodeDegree() 178 | {return leafNodeDegree;} 179 | 180 | public int getLookupPageDegree() 181 | {return(pageSize/keySize);} 182 | 183 | public int getLookupPageSize() 184 | {return(lookupPageSize);} 185 | 186 | public long getLookupPageOffset() 187 | {return(pageSize-lookupPageSize);} 188 | 189 | public int getConditionThreshold() 190 | {return(conditionThreshold);} 191 | 192 | public void setConditionThreshold(int conditionThreshold) 193 | {this.conditionThreshold = conditionThreshold;} 194 | 195 | public int getHeaderSize() 196 | {return(headerSize);} 197 | 198 | public int getPageCountOffset() { 199 | return (headerSize - 16); 200 | } 201 | 202 | public int getLookupOverflowHeaderSize() { 203 | return (lookupOverflowHeaderSize); 204 | } 205 | 206 | public void printConfiguration() { 207 | System.out.println("\n\nPrinting B+ Tree configuration\n"); 208 | System.out.println("Page size: " + pageSize + " (in bytes)"); 209 | System.out.println("Key size: " + keySize + " (in bytes)"); 210 | System.out.println("Entry size: " + entrySize + " (in bytes)"); 211 | System.out.println("File header size: " + headerSize + " (in bytes)"); 212 | System.out.println("Lookup space size: " + getLookupPageSize() + 213 | " (in bytes)"); 214 | System.out.println("\nInternal Node Degree: " + 215 | getTreeDegree() + 216 | "\n\t Min cap: " + getMinInternalNodeCapacity() + 217 | "\n\t Max cap: " + getMaxInternalNodeCapacity() + 218 | "\n\t Total header bytes: " + internalNodeHeaderSize); 219 | 220 | System.out.println("\nLeaf Node Degree: " + 221 | getLeafNodeDegree() + 222 | "\n\t Min cap: " + getMinLeafNodeCapacity() + 223 | "\n\t Max cap: " + getMaxLeafNodeCapacity() + 224 | "\n\t Total header bytes: " + leafHeaderSize); 225 | 226 | System.out.println("\nOverflow page Degree: " + 227 | getOverflowPageDegree() + 228 | "\n\tExpected cap: " + getMaxOverflowNodeCapacity()); 229 | 230 | System.out.println("\nLookup page overflow Degree" + 231 | getOverflowPageDegree() + 232 | "\n\tExpected cap: " + getMaxInternalNodeCapacity()); 233 | } 234 | } -------------------------------------------------------------------------------- /src/main/java/ds/bplus/bptree/BPlusTreePerformanceCounter.java: -------------------------------------------------------------------------------- 1 | package ds.bplus.bptree; 2 | 3 | import ds.bplus.util.InvalidBTreeStateException; 4 | 5 | import java.io.IOException; 6 | 7 | @SuppressWarnings({"WeakerAccess", "unused"}) 8 | public class BPlusTreePerformanceCounter { 9 | private int totalNodeReads; 10 | private int totalInternalNodeReads; 11 | private int totalLeafNodeReads; 12 | private int totalOverflowReads; 13 | 14 | private int totalNodeWrites; 15 | private int totalInternalNodeWrites; 16 | private int totalLeafNodeWrites; 17 | private int totalOverflowWrites; 18 | 19 | 20 | private int totalInsertionReads; 21 | private int totalDeletionReads; 22 | private int totalSearchReads; 23 | private int totalRangeQueryReads; 24 | private int totalInsertionWrites; 25 | private int totalDeletionWrites; 26 | private int totalSearchWrites; 27 | private int totalRangeQueryWrites; 28 | 29 | private int pageReads; 30 | private int pageWrites; 31 | 32 | private int pageInternalReads; 33 | private int pageLeafReads; 34 | private int pageOverflowReads; 35 | 36 | private int pageInternalWrites; 37 | private int pageLeafWrites; 38 | private int pageOverflowWrites; 39 | 40 | private int totalInsertions; 41 | private int totalDeletions; 42 | private int totalSearches; 43 | private int totalRangeQueries; 44 | 45 | private int totalSplits; 46 | private int totalRootSplits; 47 | private int totalInternalNodeSplits; 48 | private int totalLeafSplits; 49 | 50 | 51 | private int totalPages; 52 | private int totalOverflowPages; 53 | private int totalInternalNodes; 54 | private int totalLeaves; 55 | 56 | private int totalInternalNodeDeletions; 57 | private int totalLeafNodeDeletions; 58 | private int totalOverflowPagesDeletions; 59 | 60 | private boolean trackIO; 61 | private BPlusTree bt = null; 62 | 63 | public BPlusTreePerformanceCounter(boolean trackIO) { 64 | this.trackIO = trackIO; 65 | resetAllMetrics(); 66 | } 67 | 68 | public void setTrackIO(boolean trackIO) { 69 | this.trackIO = trackIO; 70 | } 71 | 72 | void setBTree(BPlusTree bt) { 73 | this.bt = bt; 74 | } 75 | 76 | void incrementTotalPages() { 77 | if(trackIO) { 78 | totalPages++; 79 | } 80 | } 81 | 82 | void incrementTotalOverflowPages() { 83 | if(trackIO) { 84 | totalOverflowPages++; 85 | incrementTotalPages(); 86 | } 87 | } 88 | 89 | void incrementTotalInternalNodes() { 90 | if(trackIO) { 91 | totalInternalNodes++; 92 | incrementTotalPages(); 93 | } 94 | } 95 | 96 | void incrementTotalLeaves() { 97 | if(trackIO) { 98 | totalLeaves++; 99 | incrementTotalPages(); 100 | } 101 | } 102 | 103 | private void incrementTotalNodeReads() { 104 | if(trackIO) { 105 | totalNodeReads++; 106 | } 107 | } 108 | 109 | private void incrementTotalNodeWrites() { 110 | if(trackIO) { 111 | totalNodeWrites++; 112 | } 113 | } 114 | 115 | void incrementTotalInsertions() { 116 | if(trackIO) { 117 | totalInsertions++; 118 | } 119 | } 120 | 121 | private void incrementTotalDeletions() { 122 | if(trackIO) { 123 | totalDeletions++; 124 | } 125 | } 126 | 127 | void incrementTotalInternalNodeDeletions() { 128 | if(trackIO) { 129 | totalInternalNodeDeletions++; 130 | incrementTotalDeletions(); 131 | } 132 | } 133 | 134 | public void incrementTotalLeafNodeDeletions() { 135 | if(trackIO) { 136 | totalLeafNodeDeletions++; 137 | incrementTotalDeletions(); 138 | } 139 | } 140 | 141 | public void incrementTotalOverflowPageDeletions() { 142 | if(trackIO) { 143 | totalOverflowPagesDeletions++; 144 | incrementTotalDeletions(); 145 | } 146 | } 147 | 148 | void incrementTotalSearches() { 149 | if(trackIO) { 150 | totalSearches++; 151 | } 152 | } 153 | 154 | void incrementTotalRangeQueries() { 155 | if(trackIO) { 156 | totalRangeQueries++; 157 | } 158 | } 159 | 160 | private void incrementTotalSplits() { 161 | if(trackIO) { 162 | totalSplits++; 163 | } 164 | } 165 | 166 | void incrementRootSplits() { 167 | if(trackIO) { 168 | totalRootSplits++; 169 | incrementTotalSplits(); 170 | } 171 | } 172 | 173 | void incrementInternalNodeSplits() { 174 | if(trackIO) { 175 | totalInternalNodeSplits++; 176 | incrementTotalSplits(); 177 | } 178 | } 179 | 180 | void incrementTotalLeafSplits() { 181 | if(trackIO) { 182 | totalLeafSplits++; 183 | incrementTotalSplits(); 184 | } 185 | } 186 | 187 | private void incrementPageReads() { 188 | if(trackIO) { 189 | pageReads++; 190 | } 191 | } 192 | 193 | private void incrementPageWrites() { 194 | if(trackIO) { 195 | pageWrites++; 196 | } 197 | } 198 | 199 | public void startPageTracking() { 200 | setDefaults(); 201 | } 202 | 203 | private void setDefaults() { 204 | pageReads = 0; 205 | pageWrites = 0; 206 | pageInternalReads = 0; 207 | pageLeafReads = 0; 208 | pageOverflowReads = 0; 209 | 210 | pageInternalWrites = 0; 211 | pageLeafWrites = 0; 212 | pageOverflowWrites = 0; 213 | } 214 | 215 | private void resetIntermittentPageTracking() { 216 | setDefaults(); 217 | } 218 | 219 | public int getPageReads() { 220 | return(pageReads); 221 | } 222 | 223 | public int getPageWrites() { 224 | return(pageWrites); 225 | } 226 | 227 | public int getInterminentInternalPageReads() { 228 | return(pageInternalReads); 229 | } 230 | 231 | public int getInterminentLeafPageReads() { 232 | return(pageLeafReads); 233 | } 234 | 235 | public int getInterminentOverflowPageReads() { 236 | return(pageOverflowReads); 237 | } 238 | 239 | public int getInterminentInternalPageWrites() { 240 | return(pageInternalWrites); 241 | } 242 | 243 | public int getInterminentLeafPageWrites() { 244 | return(pageLeafWrites); 245 | } 246 | 247 | public int getInterminentOverflowPageWrites() { 248 | return(pageOverflowWrites); 249 | } 250 | 251 | public int[] deleteIO(long key, boolean unique, boolean verbose) 252 | throws IOException, InvalidBTreeStateException { 253 | startPageTracking(); 254 | DeleteResult r = bt.deleteKey(key, unique); 255 | //bt.searchKey(key, unique); 256 | if(verbose) { 257 | System.out.println("Key " + key + 258 | (r.isFound() ? " has been found" : " was not found")); 259 | if(r.isFound()) { 260 | System.out.println("Number of results returned: " + r.getValues().size()); 261 | } 262 | System.out.println("Total page reads for this deletion: " + getPageReads()); 263 | System.out.println("Total page writes for this deletion: " + getPageWrites()); 264 | System.out.println("\nBroken down statistics: "); 265 | System.out.println("\tInternal node (reads, writes): " + 266 | getInterminentInternalPageReads() + ", " + 267 | getInterminentInternalPageWrites()); 268 | System.out.println("\tLeaf node (reads, writes): " + 269 | getInterminentLeafPageReads() + ", " + 270 | getInterminentInternalPageWrites()); 271 | System.out.println("\tOverflow node (reads, writes): " + 272 | getInterminentOverflowPageReads() + ", " + 273 | getInterminentOverflowPageWrites()); 274 | } 275 | int res[] = new int[9]; 276 | res[0] = getPageReads(); 277 | res[1] = getPageWrites(); 278 | res[2] = getInterminentInternalPageReads(); 279 | res[3] = getInterminentInternalPageWrites(); 280 | res[4] = getInterminentLeafPageReads(); 281 | res[5] = getInterminentLeafPageWrites(); 282 | res[6] = getInterminentOverflowPageReads(); 283 | res[7] = getInterminentOverflowPageWrites(); 284 | res[8] = r.isFound() ? 1 : 0; 285 | totalDeletionReads += pageReads; 286 | totalDeletionWrites += pageWrites; 287 | 288 | resetIntermittentPageTracking(); 289 | return res; 290 | } 291 | 292 | public int[] searchIO(long key, boolean unique, boolean verbose) 293 | throws IOException, InvalidBTreeStateException { 294 | startPageTracking(); 295 | SearchResult r = bt.searchKey(key, unique); 296 | if(verbose) { 297 | System.out.println("Key " + key + 298 | (r.isFound() ? " has been found" : " was not found")); 299 | if(r.isFound()) { 300 | System.out.println("Number of results returned: " + r.getValues().size()); 301 | } 302 | System.out.println("Total page reads for this search: " + getPageReads()); 303 | System.out.println("Total page writes for this search: " + getPageWrites()); 304 | System.out.println("\nBroken down statistics: "); 305 | System.out.println("\tInternal node (reads, writes): " + 306 | getInterminentInternalPageReads() + ", " + 307 | getInterminentInternalPageWrites()); 308 | System.out.println("\tLeaf node (reads, writes): " + 309 | getInterminentLeafPageReads() + ", " + 310 | getInterminentInternalPageWrites()); 311 | System.out.println("\tOverflow node (reads, writes): " + 312 | getInterminentOverflowPageReads() + ", " + 313 | getInterminentOverflowPageWrites()); 314 | } 315 | int res[] = new int[9]; 316 | res[0] = getPageReads(); 317 | res[1] = getPageWrites(); 318 | res[2] = getInterminentInternalPageReads(); 319 | res[3] = getInterminentInternalPageWrites(); 320 | res[4] = getInterminentLeafPageReads(); 321 | res[5] = getInterminentLeafPageWrites(); 322 | res[6] = getInterminentOverflowPageReads(); 323 | res[7] = getInterminentOverflowPageWrites(); 324 | res[8] = r.isFound() ? 1 : 0; 325 | totalSearchReads += pageReads; 326 | totalSearchWrites += pageWrites; 327 | 328 | resetIntermittentPageTracking(); 329 | return res; 330 | } 331 | 332 | public int[] rangeIO(long minKey, long maxKey, 333 | boolean unique, boolean verbose) throws IOException, InvalidBTreeStateException { 334 | startPageTracking(); 335 | RangeResult rangeQRes = bt.rangeSearch(minKey, maxKey, unique); 336 | if(verbose) { 337 | System.out.println("Range Query returned: " + 338 | (rangeQRes.getQueryResult() != null ? 339 | (rangeQRes.getQueryResult().size()) : "0") + " results"); 340 | System.out.println("Total page reads for this search: " + getPageReads()); 341 | System.out.println("Total page writes for this search: " + getPageWrites()); 342 | System.out.println("\nBroken down statistics: "); 343 | System.out.println("\tInternal node (reads, writes): " + 344 | getInterminentInternalPageReads() + ", " + 345 | getInterminentInternalPageWrites()); 346 | System.out.println("\tLeaf node (reads, writes): " + 347 | getInterminentLeafPageReads() + ", " + 348 | getInterminentInternalPageWrites()); 349 | System.out.println("\tOverflow node (reads, writes): " + 350 | getInterminentOverflowPageReads() + ", " + 351 | getInterminentOverflowPageWrites()); 352 | } 353 | int res[] = new int[8]; 354 | res[0] = getPageReads(); 355 | res[1] = getPageWrites(); 356 | res[2] = getInterminentInternalPageReads(); 357 | res[3] = getInterminentInternalPageWrites(); 358 | res[4] = getInterminentLeafPageReads(); 359 | res[5] = getInterminentLeafPageWrites(); 360 | res[6] = getInterminentOverflowPageReads(); 361 | res[7] = getInterminentOverflowPageWrites(); 362 | totalRangeQueryReads += pageReads; 363 | totalRangeQueryWrites += pageWrites; 364 | 365 | resetIntermittentPageTracking(); 366 | return res; 367 | } 368 | 369 | public int[] insertIO(long key, String value, 370 | boolean unique, boolean verbose) 371 | throws IOException, InvalidBTreeStateException { 372 | startPageTracking(); 373 | bt.insertKey(key, value, unique); 374 | if(verbose) { 375 | System.out.println("Total page reads for this insertion: " + getPageReads()); 376 | System.out.println("Total page writes for this insertion: " + getPageWrites()); 377 | System.out.println("\nBroken down statistics: "); 378 | System.out.println("\tInternal node (reads, writes): " + 379 | getInterminentInternalPageReads() + ", " + 380 | getInterminentInternalPageWrites()); 381 | System.out.println("\tLeaf node (reads, writes): " + 382 | getInterminentLeafPageReads() + ", " + 383 | getInterminentInternalPageWrites()); 384 | System.out.println("\tOverflow node (reads, writes): " + 385 | getInterminentOverflowPageReads() + ", " + 386 | getInterminentOverflowPageWrites()); 387 | } 388 | int res[] = new int[8]; 389 | res[0] = getPageReads(); 390 | res[1] = getPageWrites(); 391 | res[2] = getInterminentInternalPageReads(); 392 | res[3] = getInterminentInternalPageWrites(); 393 | res[4] = getInterminentLeafPageReads(); 394 | res[5] = getInterminentLeafPageWrites(); 395 | res[6] = getInterminentOverflowPageReads(); 396 | res[7] = getInterminentOverflowPageWrites(); 397 | 398 | totalInsertionReads += pageReads; 399 | totalInsertionWrites += pageReads; 400 | 401 | resetIntermittentPageTracking(); 402 | return res; 403 | } 404 | 405 | public int getTotalIntermittentInsertionReads() { 406 | return(totalInsertionReads); 407 | } 408 | 409 | public int getTotalIntermittentInsertionWrites() { 410 | return(totalInsertionWrites); 411 | } 412 | 413 | public void incrementIntermittentInternalNodeReads() { 414 | if(trackIO) { 415 | pageInternalReads++; 416 | incrementPageReads(); 417 | } 418 | } 419 | 420 | private void incrementIntermittentLeafNodeReads() { 421 | if(trackIO) { 422 | pageLeafReads++; 423 | incrementPageReads(); 424 | } 425 | } 426 | 427 | private void incrementIntermittentOverflowPageReads() { 428 | if(trackIO) { 429 | pageOverflowReads++; 430 | incrementPageReads(); 431 | } 432 | } 433 | 434 | private void incrementIntermittentInternalNodeWrites() { 435 | if(trackIO) { 436 | pageInternalWrites++; 437 | incrementPageWrites(); 438 | } 439 | } 440 | 441 | private void incrementIntermittentLeafNodeWrites() { 442 | if(trackIO) { 443 | pageLeafWrites++; 444 | incrementPageWrites(); 445 | } 446 | } 447 | 448 | private void incrementIntermittentOverflowPageWrites() { 449 | if(trackIO) { 450 | pageOverflowWrites++; 451 | incrementPageWrites(); 452 | } 453 | } 454 | 455 | 456 | void incrementTotalInternalNodeReads() { 457 | if(trackIO) { 458 | totalInternalNodeReads++; 459 | incrementTotalNodeReads(); 460 | incrementIntermittentInternalNodeReads(); 461 | } 462 | } 463 | 464 | void incrementTotalLeafNodeReads() { 465 | if(trackIO) { 466 | totalLeafNodeReads++; 467 | incrementTotalNodeReads(); 468 | incrementIntermittentLeafNodeReads(); 469 | } 470 | } 471 | 472 | void incrementTotalOverflowReads() { 473 | if(trackIO) { 474 | totalOverflowReads++; 475 | incrementTotalNodeReads(); 476 | incrementIntermittentOverflowPageReads(); 477 | } 478 | } 479 | 480 | void incrementTotalInternalNodeWrites() { 481 | if(trackIO) { 482 | totalInternalNodeWrites++; 483 | incrementTotalNodeWrites(); 484 | incrementIntermittentInternalNodeWrites(); 485 | } 486 | } 487 | 488 | void incrementTotalLeafNodeWrites() { 489 | if(trackIO) { 490 | totalLeafNodeWrites++; 491 | incrementTotalNodeWrites(); 492 | incrementIntermittentLeafNodeWrites(); 493 | } 494 | } 495 | 496 | void incrementTotalOverflowNodeWrites() { 497 | if(trackIO) { 498 | totalOverflowWrites++; 499 | incrementTotalNodeWrites(); 500 | incrementIntermittentOverflowPageWrites(); 501 | } 502 | } 503 | 504 | private int totalOperationCount() { 505 | return(totalInsertions + totalSearches + 506 | totalRangeQueries + totalDeletions); 507 | } 508 | 509 | public void printTotalStatistics() { 510 | System.out.println("\n !! Printing total recorded statistics !!"); 511 | System.out.println("\nOperations break down"); 512 | System.out.println("\n\tTotal insertions: " + totalInsertions); 513 | System.out.println("\tTotal searches: " + totalSearches); 514 | System.out.println("\tTotal range queries: " + totalRangeQueries); 515 | System.out.println("\tTotal performed op count: " + totalOperationCount()); 516 | 517 | System.out.println("\nTotal I/O break down (this run only)"); 518 | System.out.println("\nTotal Read statistics"); 519 | System.out.println("\n\tTotal reads: " + totalNodeReads); 520 | System.out.println("\tTotal Internal node reads: " + totalInternalNodeReads); 521 | System.out.println("\tTotal Leaf node reads: " + totalLeafNodeReads); 522 | System.out.println("\tTotal Overflow node reads: " + totalOverflowReads); 523 | 524 | System.out.println("\nTotal Write statistics: "); 525 | System.out.println("\n\tTotal writes: " + totalNodeWrites); 526 | System.out.println("\tTotal Internal node writes: " + totalInternalNodeWrites); 527 | System.out.println("\tTotal Leaf node writes: " + totalLeafNodeWrites); 528 | System.out.println("\tTotal Overflow node writes: " + totalOverflowWrites); 529 | 530 | System.out.println("\nPage creation break down."); 531 | System.out.println("\n\tTotal pages created: " + totalPages); 532 | System.out.println("\tTotal Internal nodes created: " + totalInternalNodes); 533 | System.out.println("\tTotal Leaf nodes created: " + totalLeaves); 534 | System.out.println("\tTotal Overflow nodes created: " + totalOverflowPages); 535 | 536 | System.out.println("\nPage deletion break down."); 537 | System.out.println("\n\tTotal pages deleted: " + totalDeletions); 538 | System.out.println("\tTotal Internal nodes deleted: " + totalInternalNodeDeletions); 539 | System.out.println("\tTotal Leaf nodes deleted: " + totalLeafNodeDeletions); 540 | System.out.println("\tTotal Overflow pages deleted: " + totalOverflowPagesDeletions); 541 | 542 | System.out.println("\nPage split statistics"); 543 | System.out.println("\n\tTotal page splits: " + totalSplits); 544 | System.out.println("\tActual Root splits: " + totalRootSplits); 545 | System.out.println("\tInternal node splits: " + totalInternalNodeSplits); 546 | System.out.println("\tLeaf node splits: " + totalLeafSplits); 547 | } 548 | 549 | void resetAllMetrics() { 550 | totalPages = 0; 551 | totalInternalNodes = 0; 552 | totalLeaves = 0; 553 | totalOverflowPages = 0; 554 | 555 | totalNodeReads = 0; 556 | totalInternalNodeReads = 0; 557 | totalOverflowReads = 0; 558 | totalLeafNodeReads = 0; 559 | 560 | totalNodeWrites = 0; 561 | totalInternalNodeWrites = 0; 562 | totalLeafNodeWrites = 0; 563 | totalOverflowWrites = 0; 564 | 565 | totalInternalNodeDeletions = 0; 566 | totalLeafNodeDeletions = 0; 567 | totalOverflowPagesDeletions = 0; 568 | 569 | totalDeletions = 0; 570 | totalInsertions = 0; 571 | totalSearches = 0; 572 | totalRangeQueries = 0; 573 | 574 | totalSplits = 0; 575 | totalRootSplits = 0; 576 | totalInternalNodeSplits = 0; 577 | totalLeafSplits = 0; 578 | 579 | setDefaults(); 580 | 581 | totalSearchReads = 0; 582 | totalSearchWrites = 0; 583 | totalRangeQueryReads = 0; 584 | totalRangeQueryWrites = 0; 585 | totalInsertionReads = 0; 586 | totalInsertionWrites = 0; 587 | totalDeletionReads = 0; 588 | totalDeletionWrites = 0; 589 | } 590 | } 591 | -------------------------------------------------------------------------------- /src/main/java/ds/bplus/bptree/DeleteResult.java: -------------------------------------------------------------------------------- 1 | package ds.bplus.bptree; 2 | 3 | import java.util.LinkedList; 4 | 5 | /** 6 | * 7 | * Wrapper for deletions, stores the key as well as the 8 | * values deleted for that key (usually one or all). 9 | * 10 | */ 11 | @SuppressWarnings({"WeakerAccess", "unused"}) 12 | public class DeleteResult { 13 | private final long key; 14 | private final boolean found; 15 | private final LinkedList values; 16 | 17 | /** 18 | * Default constructor for single deletes 19 | * 20 | * @param key key that values are tied 21 | * @param value values deleted 22 | */ 23 | public DeleteResult(long key, String value) { 24 | this.key = key; 25 | if(value != null) { 26 | values = new LinkedList<>(); 27 | values.add(value); 28 | this.found = true; 29 | } else { 30 | this.values = null; 31 | this.found = false; 32 | } 33 | } 34 | 35 | /** 36 | * This is a more flexible constructor as we pass on 37 | * an already populated linked list. 38 | * 39 | * @param key key that values are tied 40 | * @param values already populated list of deleted values 41 | */ 42 | public DeleteResult(long key, LinkedList values) { 43 | this.key = key; 44 | this.values = values; 45 | this.found = !(values == null || values.isEmpty()); 46 | } 47 | 48 | // -- Getters -- 49 | 50 | public LinkedList getValues() 51 | {return(values);} 52 | 53 | public long getKey() 54 | {return(key);} 55 | 56 | public boolean isFound() 57 | {return(found);} 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/ds/bplus/bptree/KeyValueWrapper.java: -------------------------------------------------------------------------------- 1 | package ds.bplus.bptree; 2 | 3 | /** 4 | * Wrapper to conveniently return the (Key, Value) pair 5 | * without having to resort to "weird" solutions. 6 | */ 7 | @SuppressWarnings({"WeakerAccess", "unused"}) 8 | public class KeyValueWrapper { 9 | 10 | private final long key; // key 11 | private final String value; // value 12 | 13 | /** 14 | * This is the only constructor... as we only 15 | * need to set them 16 | * @param key the key of (K, V) pair 17 | * @param value the value of the (K, V) pair 18 | */ 19 | public KeyValueWrapper(long key, String value) { 20 | this.key = key; 21 | this.value = value; 22 | } 23 | 24 | public long getKey() { 25 | return key; 26 | } 27 | 28 | public String getValue() { 29 | return value; 30 | } 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/ds/bplus/bptree/RangeResult.java: -------------------------------------------------------------------------------- 1 | package ds.bplus.bptree; 2 | 3 | import java.util.LinkedList; 4 | 5 | /** 6 | * 7 | * This is a simple wrapper class for our range queries where 8 | * we pack in a linked list all the matching results for easy 9 | * access and manipulation. 10 | * 11 | */ 12 | @SuppressWarnings({"WeakerAccess", "unused"}) 13 | public class RangeResult { 14 | 15 | // our linked list 16 | private final LinkedList queryResult; 17 | 18 | /** 19 | * Constructor that instantiates basically our linked list 20 | */ 21 | public RangeResult() 22 | {this.queryResult = new LinkedList<>();} 23 | 24 | /** 25 | * Used to give us access to the actual list 26 | * 27 | * @return query result list reference 28 | */ 29 | public LinkedList getQueryResult() 30 | {return(queryResult);} 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/ds/bplus/bptree/SearchResult.java: -------------------------------------------------------------------------------- 1 | package ds.bplus.bptree; 2 | 3 | import java.util.LinkedList; 4 | 5 | /** 6 | * Wrapper to result the search results with ease, since we 7 | * need to store multiple information in our results which are: 8 | * 9 | * -- the leaf that the (K, V) might reside 10 | * -- the index where is the key that is less or matches our key 11 | * -- finally a convenient boolean flag to indicate the search result (T/F) 12 | * 13 | */ 14 | @SuppressWarnings({"WeakerAccess", "unused"}) 15 | public class SearchResult { 16 | 17 | private final int index; // index where first key is <= our requested key 18 | private final boolean found; // we found the requested key? 19 | private TreeLeaf leafLoc; // the leaf which our (K, V) might reside 20 | private LinkedList ovfValues; // linked list in the case of non-unique queries 21 | 22 | /** 23 | * Constructor for unique queries, hence feed it all the above information 24 | * 25 | * @param leaf the leaf which our (K, V) might reside 26 | * @param index index where first key is <= our requested key 27 | * @param found we found the requested key? 28 | */ 29 | public SearchResult(TreeLeaf leaf, int index, boolean found) { 30 | this.leafLoc = leaf; 31 | this.index = index; 32 | this.found = found; 33 | // add it only if we found it. 34 | if(found) { 35 | ovfValues = new LinkedList<>(); 36 | ovfValues.push((leafLoc.getValueAt(index))); 37 | } 38 | } 39 | 40 | /** 41 | * Constructor for returning all duplicates, we assume that the 42 | * list has already been populated inside the tree. 43 | * 44 | * @param leaf the leaf which our (K, V) might reside 45 | * @param index index where the first key is equal with our requested key 46 | * @param vals the linked list with the values 47 | */ 48 | public SearchResult(TreeLeaf leaf, int index, LinkedList vals) { 49 | this.leafLoc = leaf; 50 | this.index = index; 51 | this.found = true; 52 | this.ovfValues = vals; 53 | } 54 | 55 | // -- Just Setters and Getters 56 | 57 | public TreeLeaf getLeaf() 58 | {return(this.leafLoc);} 59 | 60 | public void setLeaf(TreeLeaf leaf) 61 | {this.leafLoc = leaf;} 62 | 63 | public LinkedList getValues() 64 | {return(ovfValues);} 65 | 66 | public long getKey() 67 | {return(leafLoc.getKeyAt(index));} 68 | 69 | public boolean isFound() 70 | {return(found);} 71 | 72 | public int getIndex() 73 | {return index;} 74 | 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/ds/bplus/bptree/TreeInternalNode.java: -------------------------------------------------------------------------------- 1 | package ds.bplus.bptree; 2 | 3 | import java.io.IOException; 4 | import java.io.RandomAccessFile; 5 | import java.util.LinkedList; 6 | 7 | 8 | /** 9 | * 10 | * Class for our Internal nodes 11 | * 12 | */ 13 | @SuppressWarnings({"WeakerAccess", "unused"}) 14 | class TreeInternalNode extends TreeNode { 15 | 16 | private final LinkedList pointerArray; // the pointer array 17 | 18 | /** 19 | * Create an internal node 20 | * 21 | * @param nodeType the node type parameter 22 | * @param pageIndex the index of the page 23 | */ 24 | TreeInternalNode(TreeNodeType nodeType, long pageIndex) { 25 | super(nodeType, pageIndex); 26 | pointerArray = new LinkedList<>(); 27 | } 28 | 29 | void removePointerAt(int index) 30 | {pointerArray.remove(index);} 31 | 32 | long getPointerAt(int index) { 33 | return((index < 0 || index >= pointerArray.size()) ? -1 : pointerArray.get(index));} 34 | 35 | long popPointer() 36 | {return(pointerArray.pop());} 37 | 38 | long removeLastPointer() 39 | {return(pointerArray.removeLast());} 40 | 41 | void addPointerAt(int index, long val) 42 | {pointerArray.add(index, val);} 43 | 44 | void addPointerLast(long val) 45 | {pointerArray.addLast(val);} 46 | 47 | void setPointerAt(int index, long val) 48 | {pointerArray.set(index, val);} 49 | 50 | int getPointerListSize() 51 | {return(pointerArray.size());} 52 | 53 | void pushToPointerArray(long val) 54 | {pointerArray.push(val);} 55 | 56 | 57 | /** 58 | * 59 | * Internal node structure is as follows: 60 | * 61 | * -- node type -- (2 bytes) 62 | * -- current capacity -- (4 bytes) 63 | * 64 | * -- Key -- (8 bytes max size) 65 | * 66 | * -- Pointers (8 bytes max size + 1) 67 | * 68 | * we go like: k1 -- p0 -- k2 -- p1 ... kn -- pn+1 69 | * 70 | * @param r pointer to *opened* B+ tree file 71 | * @throws IOException is thrown when an I/O exception is captured. 72 | */ 73 | @Override 74 | public void writeNode(RandomAccessFile r, BPlusConfiguration conf, 75 | BPlusTreePerformanceCounter bPerf) 76 | throws IOException { 77 | 78 | // update root index in the file 79 | if(this.isRoot()) { 80 | r.seek(conf.getHeaderSize()-8); 81 | r.writeLong(getPageIndex()); 82 | } 83 | 84 | // account for the header page as well. 85 | r.seek(getPageIndex()); 86 | 87 | // write the node type 88 | r.writeShort(getPageType()); 89 | 90 | // write current capacity 91 | r.writeInt(getCurrentCapacity()); 92 | 93 | // now write Key/Pointer pairs 94 | for(int i = 0; i < getCurrentCapacity(); i++) { 95 | r.writeLong(getKeyAt(i)); // Key 96 | r.writeLong(getPointerAt(i)); // Pointer 97 | } 98 | // final pointer. 99 | r.writeLong(getPointerAt(getCurrentCapacity())); 100 | 101 | // annoying correction 102 | if(r.length() < getPageIndex()+conf.getPageSize()) 103 | {r.setLength(getPageIndex()+conf.getPageSize());} 104 | 105 | bPerf.incrementTotalInternalNodeWrites(); 106 | } 107 | 108 | @Override 109 | public void printNode() { 110 | System.out.println("\nPrinting node of type: " + 111 | getNodeType().toString() + " with index: " + 112 | getPageIndex()); 113 | 114 | System.out.println("Current node capacity is: " + 115 | getCurrentCapacity()); 116 | 117 | System.out.println("\nPrinting stored Keys:"); 118 | for(Long i : keyArray) 119 | {System.out.print("\t" + i.toString() + " ");} 120 | System.out.println("\nPrinting stored Pointers"); 121 | for(Long i : pointerArray) 122 | {System.out.print(" " + i.toString() + " ");} 123 | System.out.println(); 124 | } 125 | 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/ds/bplus/bptree/TreeLeaf.java: -------------------------------------------------------------------------------- 1 | package ds.bplus.bptree; 2 | 3 | import ds.bplus.util.InvalidBTreeStateException; 4 | 5 | import java.io.IOException; 6 | import java.io.RandomAccessFile; 7 | import java.nio.charset.StandardCharsets; 8 | import java.util.LinkedList; 9 | 10 | /** 11 | * Class for our Tree leafs 12 | * 13 | */ 14 | @SuppressWarnings("unused") 15 | class TreeLeaf extends TreeNode { 16 | private long nextPagePointer; // pointer to next leaf in the list 17 | private long prevPagePointer; // pointer to prev leaf in the list 18 | private LinkedList valueList; // satellite data list 19 | private LinkedList overflowList; // overflow pointer list 20 | 21 | /** 22 | * Constructor for our Internal node 23 | * 24 | * @param nextPagePointer the next leaf pointer 25 | * @param prevPagePointer the previous leaf pointer 26 | * @param nodeType the node type 27 | * @param pageIndex the index of the page 28 | */ 29 | TreeLeaf(long nextPagePointer, long prevPagePointer, 30 | TreeNodeType nodeType, long pageIndex) { 31 | super(nodeType, pageIndex); 32 | if(nodeType == TreeNodeType.TREE_ROOT_LEAF && nextPagePointer > 0) 33 | {throw new IllegalArgumentException("Can't have leaf " + 34 | "root with non-null next pointer");} 35 | this.nextPagePointer = nextPagePointer; 36 | this.prevPagePointer = prevPagePointer; 37 | this.overflowList = new LinkedList<>(); 38 | this.valueList = new LinkedList<>(); 39 | } 40 | 41 | void addToOverflowList(int index, long value) 42 | {overflowList.add(index, value);} 43 | 44 | void addLastToOverflowList(long value) 45 | {overflowList.addLast(value);} 46 | 47 | void addLastToValueList(String value) 48 | {valueList.addLast(value);} 49 | 50 | long getOverflowPointerAt(int index) 51 | {return overflowList.get(index);} 52 | 53 | void pushToOverflowList(long overflowPointer) 54 | {overflowList.push(overflowPointer);} 55 | 56 | long popOverflowPointer() 57 | {return(overflowList.pop());} 58 | 59 | void setOverflowPointerAt(int index, long value) 60 | {overflowList.set(index, value);} 61 | 62 | long removeLastOverflowPointer() 63 | {return(overflowList.removeLast());} 64 | 65 | long getLastOverflowPointer() 66 | {return(overflowList.getLast());} 67 | 68 | void addToValueList(int index, String value) 69 | {valueList.add(index, value);} 70 | 71 | String getValueAt(int index) 72 | {return valueList.get(index);} 73 | 74 | void pushToValueList(String value) 75 | {valueList.push(value);} 76 | 77 | String popValue() 78 | {return valueList.pop();} 79 | 80 | String removeLastValue() 81 | {return valueList.removeLast();} 82 | 83 | long getNextPagePointer() 84 | {return(nextPagePointer);} 85 | 86 | void setNextPagePointer(long next) 87 | {nextPagePointer = next;} 88 | 89 | long getPrevPagePointer() 90 | {return prevPagePointer;} 91 | 92 | void setPrevPagePointer(long prevPagePointer) { 93 | this.prevPagePointer = prevPagePointer; 94 | } 95 | 96 | String removeEntryAt(int index, BPlusConfiguration conf) 97 | throws InvalidBTreeStateException { 98 | keyArray.remove(index); 99 | overflowList.remove(index); 100 | String s = valueList.remove(index); 101 | decrementCapacity(conf); 102 | return(s); 103 | } 104 | 105 | /** 106 | * 107 | * Leaf node write structure is as follows: 108 | * 109 | * -- node type -- (2 bytes) 110 | * -- next pointer -- (8 bytes) 111 | * -- prev pointer -- (8 bytes) 112 | * -- key/value pairs -- (max size * (key size + satellite size)) 113 | * 114 | * @param r pointer to *opened* B+ tree file 115 | * @param conf configuration parameter 116 | * @throws IOException is thrown when an I/O operation fails 117 | */ 118 | @Override 119 | public void writeNode(RandomAccessFile r, BPlusConfiguration conf, 120 | BPlusTreePerformanceCounter bPerf) 121 | throws IOException { 122 | 123 | // update root index in the file 124 | if(this.isRoot()) { 125 | r.seek(conf.getHeaderSize()-16L); 126 | r.writeLong(getPageIndex()); 127 | } 128 | 129 | // account for the header page as well. 130 | r.seek(getPageIndex()); 131 | 132 | // now write the node type 133 | r.writeShort(getPageType()); 134 | 135 | // write the next pointer 136 | r.writeLong(nextPagePointer); 137 | 138 | // write the prev pointer 139 | r.writeLong(prevPagePointer); 140 | 141 | // then write the current capacity 142 | r.writeInt(getCurrentCapacity()); 143 | 144 | // now write the Key/Value pairs 145 | for(int i = 0; i < getCurrentCapacity(); i++) { 146 | r.writeLong(getKeyAt(i)); 147 | r.writeLong(getOverflowPointerAt(i)); 148 | r.write(valueList.get(i).getBytes(StandardCharsets.UTF_8)); 149 | } 150 | 151 | // annoying correction 152 | if(r.length() < getPageIndex()+conf.getPageSize()) 153 | {r.setLength(getPageIndex()+conf.getPageSize());} 154 | 155 | bPerf.incrementTotalLeafNodeWrites(); 156 | } 157 | 158 | @Override 159 | public void printNode() { 160 | System.out.println("\nPrinting node of type: " + getNodeType().toString() + 161 | " with index: " + getPageIndex()); 162 | System.out.println("Current node capacity is: " + getCurrentCapacity()); 163 | 164 | System.out.println("Next pointer (index): " + getNextPagePointer()); 165 | System.out.println("Prev pointer (index): " + getPrevPagePointer()); 166 | 167 | System.out.println("\nPrinting stored (Key, Value, ovf) tuples:"); 168 | for(int i = 0; i < keyArray.size(); i++) { 169 | System.out.print(" (" + 170 | keyArray.get(i).toString() + ", " + 171 | valueList.get(i) + ", " + 172 | overflowList.get(i) + ") "); 173 | } 174 | System.out.println("\n"); 175 | } 176 | 177 | } 178 | -------------------------------------------------------------------------------- /src/main/java/ds/bplus/bptree/TreeLookupOverflowNode.java: -------------------------------------------------------------------------------- 1 | package ds.bplus.bptree; 2 | 3 | import java.io.IOException; 4 | import java.io.RandomAccessFile; 5 | 6 | @SuppressWarnings("unused") 7 | class TreeLookupOverflowNode extends TreeNode { 8 | 9 | private long next; // next pointer 10 | 11 | /** 12 | * Constructor which takes into the node type as well as the 13 | * page index 14 | * 15 | * @param pageIndex the page index in the file 16 | */ 17 | TreeLookupOverflowNode(long pageIndex, long nextPointer) { 18 | super(TreeNodeType.TREE_LOOKUP_OVERFLOW, pageIndex); 19 | this.next = nextPointer; 20 | } 21 | 22 | /** 23 | * Write a lookup page overflow to the page index; the node should 24 | * have the following structure: 25 | *

26 | * -- node type -- (2 bytes) 27 | * -- next pointer -- (8 bytes) 28 | * -- current capacity -- (4 bytes) 29 | *

30 | * -- page indexes (in place of keys) (8 bytes) 31 | * 32 | * @param r an *already* open pointer which points to our B+ Tree file 33 | * @param conf B+ Tree configuration 34 | * @param bPerf instance of performance counter class 35 | * @throws IOException is thrown when an I/O operation fails 36 | */ 37 | @Override 38 | public void writeNode(RandomAccessFile r, 39 | BPlusConfiguration conf, 40 | BPlusTreePerformanceCounter bPerf) 41 | throws IOException { 42 | 43 | // account for the header page as well 44 | r.seek(getPageIndex()); 45 | 46 | // write the node type 47 | r.writeShort(getPageType()); 48 | 49 | // write the next pointer 50 | r.writeLong(next); 51 | 52 | // write current capacity 53 | r.writeInt(getCurrentCapacity()); 54 | 55 | // now write the index values 56 | for (int i = 0; i < getCurrentCapacity(); i++) { 57 | r.writeLong(getKeyAt(i)); 58 | } 59 | 60 | } 61 | 62 | 63 | /** 64 | * Get the next pointer of the node 65 | * 66 | * @return the next pointer value 67 | */ 68 | long getNextPointer() { 69 | return next; 70 | } 71 | 72 | /** 73 | * Set the next pointer of the node 74 | * 75 | * @param nextPointer the new next pointer 76 | */ 77 | public void setNextPointer(long nextPointer) { 78 | this.next = nextPointer; 79 | } 80 | 81 | @Override 82 | public void printNode() { 83 | System.out.println("\nPrinting node of type: " + getNodeType().toString() + 84 | " with index: " + getPageIndex()); 85 | System.out.println("Current node capacity is: " + getCurrentCapacity()); 86 | 87 | System.out.println("\nPrinting tuples: \n"); 88 | for (Long key : keyArray) { 89 | System.out.print(key); 90 | } 91 | 92 | System.out.println("\n"); 93 | 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/ds/bplus/bptree/TreeNode.java: -------------------------------------------------------------------------------- 1 | package ds.bplus.bptree; 2 | 3 | import ds.bplus.util.InvalidBTreeStateException; 4 | 5 | import java.io.IOException; 6 | import java.io.RandomAccessFile; 7 | import java.util.InvalidPropertiesFormatException; 8 | import java.util.LinkedList; 9 | 10 | /** 11 | * 12 | * Class that describes all the common properties that 13 | * each of the node types have. 14 | * 15 | */ 16 | @SuppressWarnings("unused") 17 | abstract class TreeNode { 18 | final LinkedList keyArray; // key array 19 | private TreeNodeType nodeType; // actual node type 20 | private long pageIndex; // node page index 21 | private int currentCapacity; // current capacity 22 | private boolean beingDeleted; // deleted flag 23 | 24 | 25 | /** 26 | * Constructor which takes into the node type as well as the 27 | * page index 28 | * @param nodeType the actual node type 29 | * @param pageIndex the page index in the file 30 | */ 31 | TreeNode(TreeNodeType nodeType, long pageIndex) { 32 | this.nodeType = nodeType; // actual node type 33 | this.pageIndex = pageIndex; // node page index 34 | this.currentCapacity = 0; // current capacity 35 | this.keyArray = new LinkedList<>(); // instantiate the linked list 36 | this.beingDeleted = true; 37 | } 38 | 39 | /** 40 | * Check if the node is full (and needs splitting) 41 | * @param conf configuration to deduce which degree to use 42 | * 43 | * @return true is the node is full false if it's not. 44 | */ 45 | boolean isFull(BPlusConfiguration conf) { 46 | if(isLeaf()) { 47 | return(isOverflow() ? 48 | (conf.getMaxOverflowNodeCapacity() == currentCapacity) : 49 | (conf.getMaxLeafNodeCapacity() == currentCapacity));} 50 | else 51 | // internal 52 | {return(conf.getMaxInternalNodeCapacity() == currentCapacity);} 53 | } 54 | 55 | /** 56 | * Check if the node is underutilized and needs to be merged 57 | * 58 | * @param conf B+ Tree configuration reference 59 | * @return true is the node needs to be merged or false if it's not 60 | */ 61 | boolean isTimeToMerge(BPlusConfiguration conf) { 62 | // for roots (internal or leaf) return true only when empty 63 | if(isRoot()) 64 | {return(getCurrentCapacity() <= 1);} 65 | else if(isLeaf()) { 66 | // for overflow pages return true only if empty 67 | if (isOverflow()) 68 | {return (isEmpty());} 69 | // otherwise return based on degree 70 | else 71 | {return (conf.getMinLeafNodeCapacity() >= currentCapacity);} 72 | } else // internal 73 | { 74 | return (conf.getMinInternalNodeCapacity() >= currentCapacity); 75 | } 76 | } 77 | 78 | /** 79 | * Returns the current node capacity 80 | * 81 | * @return the newCap variable value. 82 | */ 83 | int getCurrentCapacity() { 84 | return (currentCapacity); 85 | } 86 | 87 | /** 88 | * Set the current capacity 89 | * 90 | * @param newCap replace node capacity with this argument. 91 | */ 92 | void setCurrentCapacity(int newCap) { 93 | currentCapacity = newCap; 94 | } 95 | 96 | /** 97 | * Increment the node capacity by one. 98 | * 99 | * @param conf configuration instance for validating the limits. 100 | * @throws InvalidBTreeStateException is thrown when the capacity limits are violated after incrementing. 101 | */ 102 | void incrementCapacity(BPlusConfiguration conf) throws InvalidBTreeStateException { 103 | currentCapacity++; 104 | validateNodeCapacityLimits(conf); 105 | } 106 | 107 | /** 108 | * Decrement the node capacity by one. 109 | * 110 | * @param conf configuration instance for validating the limits. 111 | * @throws InvalidBTreeStateException is thrown when the capacity limits are violated after decrementing. 112 | */ 113 | void decrementCapacity(BPlusConfiguration conf) 114 | throws InvalidBTreeStateException { 115 | currentCapacity--; 116 | validateNodeCapacityLimits(conf); 117 | } 118 | 119 | /** 120 | * Function that validates the node capacity invariants based on the current configuration instance. 121 | * 122 | * @param conf configuration instance for validating the limits. 123 | * @throws InvalidBTreeStateException is thrown when the capacity limits are violated upon checking. 124 | */ 125 | private void validateNodeCapacityLimits(BPlusConfiguration conf) 126 | throws InvalidBTreeStateException { 127 | 128 | if(isRoot()) { 129 | if(currentCapacity < 0) { 130 | throw new InvalidBTreeStateException("Cannot have less than zero elements"); 131 | } else if(isLeaf() && currentCapacity > conf.getMaxLeafNodeCapacity()) { 132 | throw new InvalidBTreeStateException("Exceeded leaf node " + 133 | "allowed capacity at root"); 134 | } else if(isInternalNode() && currentCapacity > conf.getMaxInternalNodeCapacity()) { 135 | throw new InvalidBTreeStateException("Exceeded internal node " + 136 | "allowed capacity at root"); 137 | } 138 | } else { 139 | if (isLookupPageOverflowNode()) { 140 | if (beingDeleted && currentCapacity < 0) { 141 | throw new InvalidBTreeStateException("Cannot have less than " + 142 | 0 + " elements in a lookup overflow node when deleting it"); 143 | } else if (currentCapacity > conf.getMaxLookupPageOverflowCapacity()) { 144 | throw new InvalidBTreeStateException("Exceeded lookup overflow node " + 145 | "allowed capacity (node)"); 146 | } 147 | } 148 | if(isOverflow()) { 149 | if(beingDeleted && currentCapacity < 0) { 150 | throw new InvalidBTreeStateException("Cannot have less than " + 151 | 0 + " elements in a overflow node when deleting it"); 152 | } 153 | else if(currentCapacity > conf.getMaxOverflowNodeCapacity()) { 154 | throw new InvalidBTreeStateException("Exceeded overflow node " + 155 | "allowed capacity (node)"); 156 | } 157 | } 158 | else if(isLeaf()) { 159 | if(beingDeleted && currentCapacity < 0) { 160 | throw new InvalidBTreeStateException("Cannot have less than " + 161 | 0 + " elements in a leaf node when deleting it"); 162 | } else if(!beingDeleted && currentCapacity < conf.getMinLeafNodeCapacity()) { 163 | throw new InvalidBTreeStateException("Cannot have less than " + 164 | conf.getMinLeafNodeCapacity() + " elements in a leaf node"); 165 | } 166 | else if(currentCapacity > conf.getMaxLeafNodeCapacity()) { 167 | throw new InvalidBTreeStateException("Exceeded leaf node " + 168 | "allowed capacity (node)"); 169 | } 170 | } else if(isInternalNode()) { 171 | if(beingDeleted && currentCapacity < 0) { 172 | throw new InvalidBTreeStateException("Cannot have less than " + 173 | 0 + " elements in an internal node"); 174 | } 175 | else if(!beingDeleted && currentCapacity < conf.getMinInternalNodeCapacity()) { 176 | throw new InvalidBTreeStateException("Cannot have less than " + 177 | conf.getMinInternalNodeCapacity() + 178 | " elements in an internal node"); 179 | } 180 | else if(currentCapacity > conf.getMaxInternalNodeCapacity()) { 181 | throw new InvalidBTreeStateException("Exceeded internal node " + 182 | "allowed capacity (node)"); 183 | } 184 | } 185 | } 186 | } 187 | 188 | /** 189 | * Being deleted flag 190 | * 191 | * @return true if the node is marked to be deleted, false otherwise. 192 | */ 193 | public boolean getBeingDeleted() { 194 | return beingDeleted; 195 | } 196 | 197 | /** 198 | * Set being deleted flag 199 | * 200 | * @param beingDeleted value to set the flag. 201 | */ 202 | void setBeingDeleted(boolean beingDeleted) { 203 | this.beingDeleted = beingDeleted; 204 | } 205 | 206 | /** 207 | * Check if the node is empty (and *definitely* needs merging) 208 | * 209 | * @return true if it is empty false if it's not. 210 | */ 211 | boolean isEmpty() 212 | {return(currentCapacity == 0);} 213 | 214 | /** 215 | * Check if the node in question is an overflow page 216 | * 217 | * @return true if the node is an overflow page, false if it's not 218 | */ 219 | boolean isOverflow() { 220 | return (nodeType == TreeNodeType.TREE_LEAF_OVERFLOW); 221 | } 222 | 223 | /** 224 | * Check if the node in question is a leaf (including root) 225 | * 226 | * @return true if the node is a leaf, false if it's not. 227 | */ 228 | boolean isLeaf() { 229 | return(nodeType == TreeNodeType.TREE_LEAF || 230 | nodeType == TreeNodeType.TREE_LEAF_OVERFLOW || 231 | nodeType == TreeNodeType.TREE_ROOT_LEAF); 232 | } 233 | 234 | /** 235 | * Check if the node in question is a tree root. 236 | * 237 | * @return true if it is a tree root, false if it's not. 238 | */ 239 | boolean isRoot() { 240 | return(nodeType == TreeNodeType.TREE_ROOT_INTERNAL || 241 | nodeType == TreeNodeType.TREE_ROOT_LEAF); 242 | } 243 | 244 | /** 245 | * Check if the node in question is an internal node (including root) 246 | * 247 | * @return true if the node is an internal node, false if it's not. 248 | */ 249 | boolean isInternalNode() { 250 | return(nodeType == TreeNodeType.TREE_INTERNAL_NODE || 251 | nodeType == TreeNodeType.TREE_ROOT_INTERNAL); 252 | } 253 | 254 | /** 255 | * Check if the node in question is a lookup page overflow node 256 | * 257 | * @return true if the node is a lookup page overflow node, false otherwise 258 | */ 259 | boolean isLookupPageOverflowNode() { 260 | return (nodeType == TreeNodeType.TREE_LOOKUP_OVERFLOW); 261 | } 262 | 263 | /** 264 | * Return the node type 265 | * 266 | * @return the current node type 267 | */ 268 | TreeNodeType getNodeType() { 269 | return (nodeType); 270 | } 271 | 272 | /** 273 | * Explicitly set the node type 274 | * 275 | * @param nodeType set the node type 276 | */ 277 | void setNodeType(TreeNodeType nodeType) { 278 | // check if we presently are a leaf 279 | if (isLeaf()) { 280 | this.nodeType = nodeType; 281 | if (isInternalNode()) { 282 | throw new IllegalArgumentException("Cannot convert Leaf to Internal Node"); 283 | } 284 | } 285 | // it must be an internal node 286 | else { 287 | this.nodeType = nodeType; 288 | if (isLeaf()) { 289 | throw new IllegalArgumentException("Cannot convert Internal Node to Leaf"); 290 | } 291 | } 292 | } 293 | 294 | /** 295 | * Get the specific key at position indicated by index 296 | * @param index the position to get the key 297 | * @return the key at position 298 | */ 299 | long getKeyAt(int index) 300 | {return(keyArray.get(index));} 301 | 302 | /** 303 | * Return the page index 304 | * 305 | * @return current page index 306 | */ 307 | long getPageIndex() 308 | {return pageIndex;} 309 | 310 | /** 311 | * Update the page index 312 | * 313 | * @param pageIndex new page index 314 | */ 315 | void setPageIndex(long pageIndex) 316 | {this.pageIndex = pageIndex;} 317 | 318 | /** 319 | * Set the key in the array at specific position 320 | * 321 | * @param index index to set the key 322 | * @param key key to set in position 323 | */ 324 | void setKeyArrayAt(int index, long key) 325 | {keyArray.set(index, key);} 326 | 327 | /** 328 | * Add key at index while shifting entries 329 | * pointed by index and after by one. 330 | * 331 | * @param index index to shift keys and add 332 | * @param key key to add in position 333 | */ 334 | void addToKeyArrayAt(int index, long key) 335 | {keyArray.add(index, key);} 336 | 337 | /** 338 | * Push a key to head of the array 339 | * 340 | * @param key key to push 341 | */ 342 | void pushToKeyArray(long key) 343 | {keyArray.push(key);} 344 | 345 | /** 346 | * Add a key to the last place of the array 347 | * 348 | * @param key key to add 349 | */ 350 | void addLastToKeyArray(long key) 351 | {keyArray.addLast(key);} 352 | 353 | /** 354 | * Get last element 355 | * 356 | * @return return the last key 357 | */ 358 | long getLastKey() 359 | {return keyArray.getLast();} 360 | 361 | /** 362 | * Get first key 363 | * 364 | * @return return the first key value 365 | */ 366 | long getFirstKey() 367 | {return keyArray.getFirst();} 368 | 369 | /** 370 | * Pop the key at the head of the array 371 | * 372 | * @return key that is in the head of the array 373 | */ 374 | long popKey() 375 | {return keyArray.pop();} 376 | 377 | /** 378 | * Remove and pop the last key of the array 379 | * 380 | * @return key that is in the last place of the array 381 | */ 382 | long removeLastKey() 383 | {return keyArray.removeLast();} 384 | 385 | /** 386 | * Remove and pop the key at specific position 387 | * 388 | * @param index index that points where to remvoe the key 389 | * @return removed key 390 | */ 391 | long removeKeyAt(int index) 392 | {return(keyArray.remove(index));} 393 | 394 | /** 395 | * Get the page type that maps the enumeration to numbers that are 396 | * easily stored in our file. 397 | * 398 | * @return the number representation of the node type 399 | * @throws InvalidPropertiesFormatException is thrown when the page type is not matched. 400 | */ 401 | short getPageType() 402 | throws InvalidPropertiesFormatException { 403 | switch(getNodeType()) { 404 | case TREE_LEAF: // LEAF 405 | {return(1);} 406 | 407 | case TREE_INTERNAL_NODE: // INTERNAL NODE 408 | {return(2);} 409 | 410 | case TREE_ROOT_INTERNAL: // INTERNAL NODE /w ROOT 411 | {return(3);} 412 | 413 | case TREE_ROOT_LEAF: // LEAF NODE /w ROOT 414 | {return(4);} 415 | 416 | case TREE_LEAF_OVERFLOW: // LEAF OVERFLOW NODE 417 | {return(5);} 418 | 419 | case TREE_LOOKUP_OVERFLOW: // TREE LOOKUP OVERFLOW 420 | { 421 | return (6); 422 | } 423 | 424 | default: { 425 | throw new InvalidPropertiesFormatException("Unknown " + 426 | "node value read; file possibly corrupt?"); 427 | } 428 | } 429 | } 430 | 431 | /** 432 | * Abstract method that all classes must implement that writes 433 | * each node type to a page slot. 434 | * 435 | * More details in each implementation. 436 | * 437 | * @param r an *already* open pointer which points to our B+ Tree file 438 | * @param conf B+ Tree configuration 439 | * @throws IOException is thrown when an I/O operation fails. 440 | */ 441 | public abstract void writeNode(RandomAccessFile r, BPlusConfiguration conf, 442 | BPlusTreePerformanceCounter bPerf) 443 | throws IOException; 444 | 445 | /** 446 | * 447 | * Each class must implement it's own printing method. 448 | * 449 | */ 450 | public abstract void printNode(); 451 | 452 | } 453 | -------------------------------------------------------------------------------- /src/main/java/ds/bplus/bptree/TreeNodeType.java: -------------------------------------------------------------------------------- 1 | package ds.bplus.bptree; 2 | 3 | enum TreeNodeType { 4 | TREE_LEAF, 5 | TREE_INTERNAL_NODE, 6 | TREE_ROOT_INTERNAL, 7 | TREE_ROOT_LEAF, 8 | TREE_LEAF_OVERFLOW, 9 | TREE_LOOKUP_OVERFLOW 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/ds/bplus/bptree/TreeOverflow.java: -------------------------------------------------------------------------------- 1 | package ds.bplus.bptree; 2 | 3 | import java.io.IOException; 4 | import java.io.RandomAccessFile; 5 | import java.nio.charset.StandardCharsets; 6 | import java.util.LinkedList; 7 | 8 | /** 9 | * Class that is responsible for handling the overflow blocks. 10 | * 11 | * Although it is derived from the TreeNode class we *don't* use 12 | * the key array at all (this could be improved but... well...) 13 | */ 14 | @SuppressWarnings("unused") 15 | class TreeOverflow extends TreeNode { 16 | 17 | 18 | private final LinkedList valueList; 19 | private long nextPagePointer; 20 | private long prevPagePointer; 21 | 22 | /** 23 | * Constructor which takes into the node type as well as the 24 | * page index 25 | * 26 | * @param nextPagePointer the next overflow pointer 27 | * @param prevPagePointer the previous leaf or overflow pointer 28 | * @param pageIndex the page index in the file 29 | */ 30 | TreeOverflow(long nextPagePointer, long prevPagePointer, 31 | long pageIndex) { 32 | super(TreeNodeType.TREE_LEAF_OVERFLOW, pageIndex); 33 | valueList = new LinkedList<>(); 34 | this.nextPagePointer = nextPagePointer; 35 | this.prevPagePointer = prevPagePointer; 36 | } 37 | 38 | void pushToValueList(String value) 39 | {valueList.push(value);} 40 | 41 | String removeLastValue() 42 | {return(valueList.removeLast());} 43 | 44 | void addToValueList(int index, String value) 45 | {valueList.add(index, value);} 46 | 47 | String getValueAt(int index) 48 | {return valueList.get(index);} 49 | 50 | long getNextPagePointer() 51 | {return(nextPagePointer);} 52 | 53 | void setNextPagePointer(long next) 54 | {nextPagePointer = next;} 55 | 56 | private long getPrevPagePointer() 57 | {return prevPagePointer;} 58 | 59 | void setPrevPagePointer(long prevPagePointer) 60 | {this.prevPagePointer = prevPagePointer;} 61 | 62 | 63 | /** 64 | * 65 | * Overflow node write structure is as follows: 66 | * 67 | * -- node type -- (2 bytes) 68 | * -- next pointer -- (8 bytes) 69 | * -- prev pointer -- (8 bytes) 70 | * -- values -- (max size * satellite size) 71 | * 72 | * @param r pointer to *opened* B+ tree file 73 | * @throws IOException is thrown when an I/O operation fails 74 | */ 75 | @Override 76 | public void writeNode(RandomAccessFile r, BPlusConfiguration conf, 77 | BPlusTreePerformanceCounter bPerf) 78 | throws IOException { 79 | // account for the header page as well. 80 | r.seek(getPageIndex()); 81 | 82 | // now write the node type 83 | r.writeShort(getPageType()); 84 | 85 | // write the next pointer 86 | r.writeLong(nextPagePointer); 87 | 88 | // write the prev pointer 89 | r.writeLong(prevPagePointer); 90 | 91 | // then write the current capacity 92 | r.writeInt(getCurrentCapacity()); 93 | 94 | // now write the values 95 | for(int i = 0; i < getCurrentCapacity(); i++) 96 | {r.write(valueList.get(i).getBytes(StandardCharsets.UTF_8));} 97 | 98 | // annoying correction 99 | if(r.length() < getPageIndex()+conf.getPageSize()) 100 | {r.setLength(getPageIndex()+conf.getPageSize());} 101 | 102 | bPerf.incrementTotalOverflowNodeWrites(); 103 | } 104 | 105 | @Override 106 | public void printNode() { 107 | System.out.println("\nPrinting node of type: " + getNodeType().toString() + 108 | " with index: " + getPageIndex()); 109 | System.out.println("Current node capacity is: " + getCurrentCapacity()); 110 | 111 | System.out.println("Next pointer (index): " + getNextPagePointer()); 112 | System.out.println("Prev pointer (index): " + getPrevPagePointer()); 113 | 114 | System.out.println("\nPrinting stored values:"); 115 | for(int i = 0; i < keyArray.size(); i++) { 116 | System.out.print(" " + valueList.get(i) + " "); 117 | } 118 | System.out.println("\n"); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/ds/bplus/fudger/Main.java: -------------------------------------------------------------------------------- 1 | package ds.bplus.fudger; 2 | 3 | import ds.bplus.bptree.BPlusConfiguration; 4 | import ds.bplus.bptree.BPlusTree; 5 | import ds.bplus.bptree.BPlusTreePerformanceCounter; 6 | import ds.bplus.util.InvalidBTreeStateException; 7 | import ds.bplus.util.TestRunner; 8 | 9 | import java.io.IOException; 10 | 11 | public class Main { 12 | 13 | public static void main(String[] args) 14 | throws IOException, InvalidBTreeStateException { 15 | boolean fastTrials = true; 16 | boolean recreateTree = true; 17 | BPlusConfiguration btconf = new BPlusConfiguration(); 18 | BPlusTreePerformanceCounter bPerf = new BPlusTreePerformanceCounter(true); 19 | BPlusTree bt = new BPlusTree(btconf, recreateTree ? "rw+" : "rw", bPerf); 20 | 21 | //int tlen = 20000; 22 | //long skey = 0; 23 | //long eKey = tlen; 24 | //String val = "1234567890"; 25 | //boolean unique = true; 26 | bt.printCurrentConfiguration(); 27 | // if(recreateTree) { 28 | // Utilities.sequentialAddToTree(skey, eKey, 29 | // val, unique, bt); 30 | // bPerf.printTotalStatistics(); 31 | // } 32 | 33 | if(fastTrials) 34 | {TestRunner.runDefaultTrialsFast(bPerf);} 35 | else 36 | {TestRunner.runBench(bPerf);} 37 | 38 | System.out.println("\n -- Total pages in the end: " + bt.getTotalTreePages()); 39 | // finally close it. 40 | bt.commitTree(); 41 | 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/ds/bplus/util/InvalidBTreeStateException.java: -------------------------------------------------------------------------------- 1 | package ds.bplus.util; 2 | 3 | /** 4 | * Just a wrapper to state exception. 5 | */ 6 | 7 | public class InvalidBTreeStateException extends Exception { 8 | private static final long serialVersionUID = 7295144377433447079L; 9 | public InvalidBTreeStateException(String m) 10 | {super(m);} 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/ds/bplus/util/StandardInputRead.java: -------------------------------------------------------------------------------- 1 | /* Courtesy of @garanest with edits by me. */ 2 | package ds.bplus.util; 3 | 4 | 5 | import java.io.BufferedReader; 6 | import java.io.IOException; 7 | import java.io.InputStreamReader; 8 | import java.text.DateFormat; 9 | import java.text.ParseException; 10 | import java.util.Date; 11 | import java.util.Locale; 12 | 13 | /** 14 | * It reads input from the standard input hiding from the user the usage 15 | * of java.io package classes 16 | */ 17 | @SuppressWarnings({"WeakerAccess", "unused"}) 18 | class StandardInputRead { 19 | /** The error that is return when reading positive integers from stdin*/ 20 | private final static int POS_ERROR = -1; 21 | 22 | /** The error that is return when reading negative integers from stdin*/ 23 | private final static int NEG_ERROR = 1; 24 | 25 | /** The basic reader*/ 26 | private final BufferedReader in; 27 | 28 | /** 29 | * Class constructor 30 | */ 31 | public StandardInputRead() { 32 | super(); 33 | in = new BufferedReader(new InputStreamReader(System.in)); 34 | } 35 | 36 | /** 37 | * It reads a string from standard inputand returns it as value. 38 | * In case of an error it returns null 39 | * 40 | * @param message The message that is apperad to the user asking for input 41 | */ 42 | public String readString(String message) { 43 | 44 | System.out.print(message); 45 | try { 46 | return in.readLine(); 47 | } 48 | catch (IOException e) { 49 | return null; 50 | } 51 | } 52 | 53 | /** 54 | * It reads an positive integer, zero included, from standard input 55 | * and returns it as value. In case of an error it returns -1 56 | * 57 | * @param message The message that is apperad to the user asking for input 58 | */ 59 | public int readPositiveInt(String message) { 60 | 61 | String str; 62 | int num; 63 | 64 | System.out.print(message); 65 | try { 66 | str = in.readLine(); 67 | num = Integer.parseInt(str); 68 | if (num < 0 ){ 69 | return POS_ERROR; 70 | } 71 | else { 72 | return num; 73 | } 74 | } 75 | catch (IOException e) { 76 | return POS_ERROR; 77 | } 78 | catch (NumberFormatException e1) { 79 | return POS_ERROR; 80 | } 81 | } 82 | 83 | /** 84 | * It reads an negative integer from standard input and returns it as value. 85 | * In case of an error it returns 1 86 | * 87 | * @param message The message that is apperad to the user asking for input 88 | */ 89 | public int readNegativeInt(String message) { 90 | 91 | String str; 92 | int num; 93 | 94 | System.out.print(message); 95 | try { 96 | str = in.readLine(); 97 | num = Integer.parseInt(str); 98 | if (num >= 0 ){ 99 | return NEG_ERROR; 100 | } 101 | else { 102 | return num; 103 | } 104 | } 105 | catch (IOException e) { 106 | return NEG_ERROR; 107 | } 108 | catch (NumberFormatException e1) { 109 | return NEG_ERROR; 110 | } 111 | } 112 | 113 | /** 114 | * It reads an positive float, zero included, from standard input 115 | * and returns it as value. In case of an error it returns -1.0 116 | * 117 | * @param message The message that is appeared to the user asking for input 118 | */ 119 | public float readPositiveFloat(String message) { 120 | 121 | String str; 122 | float num; 123 | 124 | System.out.print(message); 125 | try { 126 | str = in.readLine(); 127 | num = Float.parseFloat(str); 128 | if (num < 0 ){ 129 | return POS_ERROR; 130 | } 131 | else { 132 | return num; 133 | } 134 | } 135 | catch (IOException e) { 136 | return POS_ERROR; 137 | } 138 | catch (NumberFormatException e1) { 139 | return POS_ERROR; 140 | } 141 | } 142 | 143 | /** 144 | * It reads an negative float from standard input and returns it as value. 145 | * In case of an error it returns 1 146 | * 147 | * @param message The message that is appeared to the user asking for input 148 | */ 149 | public float readNegativeFloat(String message) { 150 | 151 | String str; 152 | float num; 153 | 154 | System.out.print(message); 155 | try { 156 | str = in.readLine(); 157 | num = Float.parseFloat(str); 158 | if (num >= 0 ){ 159 | return NEG_ERROR; 160 | } 161 | else { 162 | return num; 163 | } 164 | } 165 | catch (IOException e) { 166 | return NEG_ERROR; 167 | } 168 | catch (NumberFormatException e1) { 169 | return NEG_ERROR; 170 | } 171 | } 172 | 173 | /** 174 | * It reads an date in the form dd/mm/yyyy from standard input and 175 | * returns it as value.In case of an error it returns null 176 | * 177 | * @param message The message that is appeared to the user asking for input 178 | */ 179 | public Date readDate(String message) { 180 | 181 | String str; 182 | 183 | System.out.print(message); 184 | 185 | try { 186 | str = in.readLine(); 187 | Locale l = new Locale("el", "GR"); 188 | DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, l); 189 | return df.parse(str); 190 | } 191 | catch (IOException e) { 192 | return null; 193 | } 194 | catch (ParseException e1) { 195 | return null; 196 | } 197 | } 198 | 199 | /** 200 | * It reads an time in the form h:mm AM or PM from standard input and 201 | * returns it as value.In case of an error it returns null 202 | * Example of valid times: 8:30 AM, 2:00 PM, etc 203 | * 204 | * @param message The message that is apperad to the user asking for input 205 | */ 206 | public Date readTime(String message) { 207 | 208 | String str; 209 | 210 | System.out.print(message); 211 | 212 | try { 213 | str = in.readLine(); 214 | DateFormat df = DateFormat.getTimeInstance(DateFormat.SHORT); 215 | 216 | return df.parse(str); 217 | } 218 | catch (IOException e) { 219 | return null; 220 | } 221 | catch (ParseException e1) { 222 | return null; 223 | } 224 | } 225 | 226 | } 227 | 228 | -------------------------------------------------------------------------------- /src/main/java/ds/bplus/util/TestRunner.java: -------------------------------------------------------------------------------- 1 | package ds.bplus.util; 2 | 3 | import ds.bplus.bptree.BPlusTreePerformanceCounter; 4 | 5 | import java.io.IOException; 6 | 7 | /** 8 | * 9 | * Another wrapper class that makes running tests a bit easier. 10 | * 11 | */ 12 | @SuppressWarnings("unused") 13 | public class TestRunner { 14 | 15 | /** 16 | * Run the test interface 17 | * 18 | * @param bPerf performance class tied to a B+ Tree instance 19 | * @throws IOException is thrown when an I/O operation fails 20 | */ 21 | @SuppressWarnings("unused") 22 | public static void runBench(BPlusTreePerformanceCounter bPerf) 23 | throws IOException, InvalidBTreeStateException { 24 | StandardInputRead sin = new StandardInputRead(); 25 | int choice; 26 | 27 | while((choice = menuChoice(sin)) != 6) 28 | {handleChoices(choice, sin, bPerf);} 29 | } 30 | 31 | /** 32 | * Display menu choices and grab the user selection 33 | * 34 | * @param sin input class 35 | * @return a valid user option selection 36 | */ 37 | private static int menuChoice(StandardInputRead sin) { 38 | System.out.println("\nSelect from menu\n"); 39 | System.out.println("\t1) Run default trials"); 40 | System.out.println("\t2) Run insertion run"); 41 | System.out.println("\t3) Run deletion run"); 42 | System.out.println("\t4) Run search run"); 43 | System.out.println("\t5) Run range query run"); 44 | System.out.println("\t6) Exit\n"); 45 | int choice = sin.readPositiveInt("Enter your choice: "); 46 | while(!(choice > 0 && choice < 7)) 47 | {choice = sin.readPositiveInt("Wrong range, try again: ");} 48 | return(choice); 49 | } 50 | 51 | /** 52 | * Silently just run the default trials using the default values 53 | * 54 | * @param bPerf performance class tied to a B+ Tree instance 55 | * @throws IOException is thrown when an I/O operation fails 56 | * @throws InvalidBTreeStateException is thrown when there are inconsistencies in the blocks. 57 | */ 58 | @SuppressWarnings("unused") 59 | public static void runDefaultTrialsFast(BPlusTreePerformanceCounter bPerf) 60 | throws IOException, InvalidBTreeStateException { 61 | int trials = 4000; 62 | int vmin = 1; 63 | int vmax = 99999; 64 | //boolean verbose = false; 65 | //boolean unique = false; 66 | //String val = "asdfasdfas"; 67 | int qrange = 150; 68 | runDefaultTrials(trials, vmin, vmax, qrange, null, 69 | false, false, bPerf); 70 | } 71 | 72 | /** 73 | * Handle the selected user option 74 | * 75 | * @param choice the user choice 76 | * @param sin the input class 77 | * @param bPerf performance class ties to a B+ Tree instance 78 | * @throws IOException is thrown when an I/O operation fails 79 | * @throws InvalidBTreeStateException is thrown when there are inconsistencies in the blocks. 80 | */ 81 | private static void handleChoices(int choice, StandardInputRead sin, 82 | BPlusTreePerformanceCounter bPerf) 83 | throws IOException, InvalidBTreeStateException { 84 | //boolean unique = true; 85 | switch(choice) { 86 | case 1: { 87 | int trials = 2000; 88 | int vmin = 1; 89 | int vmax = 99999; 90 | //boolean verbose = false; 91 | //String val = "asdfasdfas"; 92 | int qrange = 150; 93 | runDefaultTrials(trials, vmin, vmax, qrange, null, 94 | false, false, bPerf); 95 | break; 96 | } 97 | case 2: { 98 | runInsertion(sin, bPerf); 99 | break; 100 | } 101 | case 3: { 102 | runDeletion(sin, bPerf); 103 | break; 104 | } 105 | case 4: { 106 | runSearch(sin, bPerf); 107 | break; 108 | } 109 | case 5: { 110 | runRangeQuery(sin, bPerf); 111 | break; 112 | } 113 | default: { 114 | System.out.println("Closing program."); 115 | break; 116 | } 117 | } 118 | } 119 | 120 | /** 121 | * Grab from user the unique flag. 122 | * 123 | * @param sin console input library. 124 | * @return the boolean user choice. 125 | */ 126 | private static boolean isUnique(StandardInputRead sin) { 127 | System.out.println("Want unique results?"); 128 | System.out.println("\t1) Yes"); 129 | System.out.println("\t2) No"); 130 | int choice = sin.readPositiveInt("Enter your choice: "); 131 | if(choice == 2) { 132 | return(false); 133 | } else if(choice == 1) { 134 | return(true); 135 | } else { 136 | System.out.println("Wrong choice, using default (Yes)"); 137 | return(true); 138 | } 139 | } 140 | 141 | /** 142 | * Run insertion 143 | * 144 | * @param sin input class 145 | * @param bPerf performance class tied to a B+ Tree instance 146 | * @throws IOException is thrown when an I/O operation fails 147 | * @throws InvalidBTreeStateException is thrown when there are inconsistencies in the blocks. 148 | */ 149 | private static void runInsertion(StandardInputRead sin, 150 | BPlusTreePerformanceCounter bPerf) 151 | throws IOException, InvalidBTreeStateException { 152 | boolean unique = isUnique(sin); 153 | String val = "1234567890"; // default value 154 | int key; 155 | // get a key to insert 156 | while((key = sin.readPositiveInt("Enter a valid key: ")) == -1) 157 | {System.out.println("Wrong key... try again");} 158 | // all are verbose 159 | bPerf.insertIO(key, val, unique, true); 160 | } 161 | 162 | 163 | /** 164 | * Run deletion 165 | * 166 | * @param sin input class 167 | * @param bPerf performance class tied to a B+ Tree instance 168 | * @throws IOException is thrown when an I/O operation fails 169 | * @throws InvalidBTreeStateException is thrown when there are inconsistencies in the blocks. 170 | */ 171 | private static void runDeletion(StandardInputRead sin, 172 | BPlusTreePerformanceCounter bPerf) 173 | throws IOException, InvalidBTreeStateException { 174 | boolean unique = isUnique(sin); 175 | int key; 176 | // get a key to insert 177 | while((key = sin.readPositiveInt("Enter a valid key: ")) == -1) 178 | {System.out.println("Wrong key... try again");} 179 | // all are verbose 180 | bPerf.deleteIO(key, unique, true); 181 | //bPerf.insertIO(key, val, unique, true); 182 | } 183 | 184 | 185 | /** 186 | * Run a search instance 187 | * 188 | * @param sin input class 189 | * @param bPerf performance class tied to a B+ Tree instance 190 | * @throws IOException is thrown when an I/O operation fails 191 | * @throws InvalidBTreeStateException is thrown when there are inconsistencies in the blocks. 192 | */ 193 | private static void runSearch(StandardInputRead sin, 194 | BPlusTreePerformanceCounter bPerf) 195 | throws IOException, InvalidBTreeStateException { 196 | boolean unique = isUnique(sin); 197 | int key; 198 | // get a key to insert 199 | while((key = sin.readPositiveInt("Enter a valid key: ")) == -1) 200 | {System.out.println("Wrong key... try again");} 201 | // all are verbose 202 | bPerf.searchIO(key, unique, true); 203 | } 204 | 205 | /** 206 | * Run a range query instance 207 | * 208 | * @param sin input class 209 | * @param bPerf performance class tied to a B+ Tree instance 210 | * @throws IOException is thrown when an I/O operation fails 211 | * @throws InvalidBTreeStateException is thrown when there are inconsistencies in the blocks. 212 | */ 213 | private static void runRangeQuery(StandardInputRead sin, 214 | BPlusTreePerformanceCounter bPerf) 215 | throws IOException, InvalidBTreeStateException { 216 | boolean unique = isUnique(sin); 217 | int minKey; 218 | int maxKey; 219 | // get a key to insert 220 | while((minKey = sin.readPositiveInt("Enter a valid min key: ")) == -1) 221 | {System.out.println("Wrong key... try again");} 222 | while((maxKey = sin.readPositiveInt("Enter a valid max key: ")) == -1) 223 | {System.out.println("Wrong key... try again");} 224 | 225 | if(maxKey < minKey) 226 | {System.out.println("Can't proceed maxKey < minKey"); return;} 227 | 228 | // all are verbose 229 | bPerf.rangeIO(minKey, maxKey, unique, true); 230 | } 231 | 232 | /** 233 | * Run default trial set 234 | * 235 | * @param trials number of trials to run 236 | * @param vmin min key value 237 | * @param vmax max key value 238 | * @param qrange range of range queries 239 | * @param val value of tied to the key (the same is used) 240 | * @param unique allow duplicates? 241 | * @param verbose verbose results? 242 | * @param bPerf performance class tied to a B+ Tree instance 243 | * @throws IOException is thrown when an I/O operation fails 244 | * @throws InvalidBTreeStateException is thrown when there are inconsistencies in the blocks. 245 | */ 246 | private static void runDefaultTrials(int trials, int vmin, int vmax, int qrange, 247 | String val, boolean unique, boolean verbose, 248 | BPlusTreePerformanceCounter bPerf) 249 | throws IOException, InvalidBTreeStateException { 250 | TrialsClass.runInsertTrial(trials, vmin, vmax, val, unique, bPerf, verbose); 251 | TrialsClass.runSearchTrial(trials, vmin, vmax, unique, bPerf, verbose); 252 | TrialsClass.runDeletionTrials(trials, vmin, vmax, unique, bPerf, verbose); 253 | TrialsClass.runRangeQueryTrial(trials, vmin, vmax, qrange, unique, bPerf, verbose); 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/main/java/ds/bplus/util/TrialsClass.java: -------------------------------------------------------------------------------- 1 | package ds.bplus.util; 2 | 3 | import ds.bplus.bptree.BPlusTreePerformanceCounter; 4 | 5 | import java.io.IOException; 6 | 7 | /** 8 | * 9 | * Wrapper class to run trials for a specific functionality 10 | * 11 | */ 12 | @SuppressWarnings("unused") 13 | class TrialsClass { 14 | 15 | /** 16 | * Run a search trial 17 | * 18 | * @param trials the number of trials to run 19 | * @param rmin the min key value 20 | * @param rmax the max key value 21 | * @param unique want unique results? 22 | * @param bPerf performance class tied to a B+ Tree instance 23 | * @param verbose verbose results? 24 | * @throws IOException is thrown when an I/O operation fails 25 | * @throws InvalidBTreeStateException is thrown when there are inconsistencies in the blocks. 26 | */ 27 | static void runSearchTrial(int trials, int rmin, int rmax, boolean unique, 28 | BPlusTreePerformanceCounter bPerf, 29 | boolean verbose) 30 | throws IOException, InvalidBTreeStateException { 31 | 32 | int pageReads = 0; 33 | int pageWrites = 0; 34 | //int found = 0; 35 | int stats[]; 36 | 37 | // trial loop 38 | for(int i = 0; i < trials; i++) { 39 | stats = bPerf.searchIO(Utilities.randInt(rmin, rmax), unique, verbose); 40 | pageReads += stats[0]; 41 | pageWrites += stats[1]; 42 | } 43 | 44 | System.out.println("\nPerformed " + trials + " search trials"); 45 | System.out.println("\n\tTotal page reads: " + pageReads); 46 | System.out.println("\tTotal page writes: " + pageWrites); 47 | System.out.println("\tAverage (reads, writes): " + (pageReads/(1.0*trials)) 48 | + ", " + (pageWrites/(1.0*trials))); 49 | } 50 | 51 | /** 52 | * Run a insertion trial 53 | * 54 | * @param trials the number of trials to run 55 | * @param rmin the min key value 56 | * @param rmax the max key value 57 | * @param value value to tie with the inserted key 58 | * @param unique allow duplicate insertions? 59 | * @param bPerf performance class tied to a B+ Tree instance 60 | * @param verbose verbose results? 61 | * @throws IOException is thrown when an I/O operation fails 62 | * @throws InvalidBTreeStateException is thrown when there are inconsistencies in the blocks. 63 | */ 64 | static void runInsertTrial(int trials, int rmin, int rmax, 65 | String value, boolean unique, 66 | BPlusTreePerformanceCounter bPerf, boolean verbose) 67 | throws IOException, InvalidBTreeStateException { 68 | int pageReads = 0; 69 | int pageWrites = 0; 70 | Long key; 71 | int stats[]; 72 | 73 | // trial loop 74 | for(int i = 0; i < trials; i++) { 75 | key = (long) Utilities.randInt(rmin, rmax); 76 | stats = bPerf.insertIO(key, 77 | value == null ? key.toString() : value, unique, verbose); 78 | pageReads += stats[0]; 79 | pageWrites += stats[1]; 80 | } 81 | 82 | System.out.println("\nPerformed " + trials + " insertion trials"); 83 | System.out.println("\n\tTotal page reads: " + pageReads); 84 | System.out.println("\tTotal page writes: " + pageWrites); 85 | System.out.println("\tAverage (reads, writes): " + (pageReads/(1.0*trials)) 86 | + ", " + (pageWrites/(1.0*trials))); 87 | } 88 | 89 | /** 90 | * Run a range query trial 91 | * 92 | * @param trials the number of trials to run 93 | * @param rmin the min key value 94 | * @param rmax the max key value 95 | * @param range value to tie with the inserted key 96 | * @param unique allow duplicate insertions? 97 | * @param bPerf performance class tied to a B+ Tree instance 98 | * @param verbose verbose results? 99 | * @throws IOException is thrown when an I/O operation fails 100 | * @throws InvalidBTreeStateException is thrown when there are inconsistencies in the blocks. 101 | */ 102 | static void runRangeQueryTrial(int trials, int rmin, int rmax, int range, 103 | boolean unique, BPlusTreePerformanceCounter bPerf, 104 | boolean verbose) throws IOException, InvalidBTreeStateException { 105 | int pageReads = 0; 106 | int pageWrites = 0; 107 | int stats[]; 108 | int rtmax = rmax - range; 109 | int arange = Utilities.randInt(rmin, rtmax); 110 | 111 | // trial loop 112 | for(int i = 0; i < trials; i++) { 113 | stats = bPerf.rangeIO(arange, arange+range, unique, verbose); 114 | pageReads += stats[0]; 115 | pageWrites += stats[1]; 116 | } 117 | 118 | System.out.println("\nPerformed " + trials + " Range Query trials"); 119 | System.out.println("\n\tTotal page reads: " + pageReads); 120 | System.out.println("\tTotal page writes: " + pageWrites); 121 | System.out.println("\tAverage (reads, writes): " + (pageReads/(1.0*trials)) 122 | + ", " + (pageWrites/(1.0*trials))); 123 | } 124 | 125 | /** 126 | * Run a deletion trial 127 | * 128 | * @param trials number of trials to run 129 | * @param rmin the min key value 130 | * @param rmax the max key value 131 | * @param unique delete the *first* found or *all* found? 132 | * @param bPerf performance class tied to a B+ Tree instance 133 | * @param verbose verbose results? 134 | * @throws IOException is thrown when an I/O operation fails 135 | * @throws InvalidBTreeStateException is thrown when there are inconsistencies in the blocks. 136 | */ 137 | static void runDeletionTrials(int trials, int rmin, int rmax, boolean unique, 138 | BPlusTreePerformanceCounter bPerf, 139 | boolean verbose) 140 | throws IOException, InvalidBTreeStateException { 141 | int pageReads = 0; 142 | int pageWrites = 0; 143 | int stats[]; 144 | 145 | // trial loop 146 | for(int i = 0; i < trials; i++) { 147 | stats = bPerf.deleteIO(Utilities.randInt(rmin, rmax), unique, verbose); 148 | pageReads += stats[0]; 149 | pageWrites += stats[1]; 150 | } 151 | 152 | System.out.println("\nPerformed " + trials + " deletion trials"); 153 | System.out.println("\n\tTotal page reads: " + pageReads); 154 | System.out.println("\tTotal page writes: " + pageWrites); 155 | System.out.println("\tAverage (reads, writes): " + (pageReads/(1.0*trials)) 156 | + ", " + (pageWrites/(1.0*trials))); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/ds/bplus/util/Utilities.java: -------------------------------------------------------------------------------- 1 | package ds.bplus.util; 2 | 3 | import ds.bplus.bptree.BPlusTree; 4 | 5 | import java.io.*; 6 | import java.util.Collections; 7 | import java.util.LinkedList; 8 | import java.util.Random; 9 | 10 | public class Utilities { 11 | 12 | private static final Random rand = new Random(); 13 | 14 | /** 15 | * Returns a pseudo-random number between min and max, inclusive. 16 | * The difference between min and max can be at most 17 | * Integer.MAX_VALUE - 1. 18 | * 19 | * @param min Minimum value 20 | * @param max Maximum value. Must be greater than min. 21 | * @return Integer between min and max, inclusive. 22 | * @see Random#nextInt(int) 23 | */ 24 | 25 | static int randInt(int min, int max) { 26 | // nextInt is normally exclusive of the top value, 27 | // so add 1 to make it inclusive 28 | return rand.nextInt((max - min) + 1) + min; 29 | } 30 | 31 | /** 32 | * Helper to add stuff to the tree 33 | * @param from key to start 34 | * @param to key to end 35 | * @param val value to tie with the keys 36 | * @param unique allow duplicates? 37 | * @param bt B+ Tree instance 38 | * @throws IOException is thrown when an I/O operation fails 39 | */ 40 | public static void sequentialAddToTree(long from, long to, String val, 41 | boolean unique, BPlusTree bt) 42 | throws IOException, InvalidBTreeStateException { 43 | long div = (to - from) / 10; 44 | for(long i = from; i < to; i++) { 45 | if (i % div == 0) { 46 | System.out.println("Currently at: " + ((double) i / to) * 100 + " %"); 47 | } 48 | bt.insertKey(i, val, unique); 49 | } 50 | System.out.println("Done!\n"); 51 | } 52 | 53 | /** 54 | * Add a random sequence of numbers in the tree using unique 55 | * or discrete values for the key. 56 | * 57 | * @param from starting range (>= 0) 58 | * @param to ending range 59 | * @param unique use unique values flag 60 | * @param bt tree instance to add the values 61 | * @return the list of the values in reverse order of insertion 62 | * @throws IOException is thrown when an I/O operation fails 63 | * @throws InvalidBTreeStateException is thrown when there are inconsistencies in the blocks. 64 | */ 65 | public static LinkedList fuzzyAddToTree(int from, int to, 66 | boolean unique, BPlusTree bt) 67 | throws IOException, InvalidBTreeStateException { 68 | 69 | if(from < 0 || to < from) 70 | {throw new IllegalArgumentException("range must > 0 and from > to");} 71 | 72 | LinkedList l = new LinkedList<>(); 73 | if(!unique) { 74 | for(long i = from; i < to; i++) { 75 | l.push((long) randInt(from, to)); 76 | bt.insertKey(l.peekFirst(), l.peekFirst().toString(), false); 77 | } 78 | //writeObjectToFile(l, "lfileex.ser"); 79 | } else { 80 | //throw new InvalidBTreeStateException("Not yet implemented"); 81 | for(long i = from; i < to; i++) 82 | {l.add(i);} 83 | 84 | // randomize 85 | Collections.shuffle(l); 86 | 87 | // add them 88 | for (Long key : l) 89 | {bt.insertKey(key, key.toString(), true);} 90 | 91 | } 92 | 93 | return(l); 94 | } 95 | 96 | /** 97 | * Add values to a B+ Tree from a file 98 | * 99 | * @param filename file to load 100 | * @param unique unique values? 101 | * @param bt tree to add the values 102 | * @return the list of the values in order of insertion 103 | * @throws IOException is thrown when an I/O operation fails 104 | * @throws InvalidBTreeStateException is thrown when there are inconsistencies in the blocks. 105 | * @throws ClassNotFoundException is thrown when the reflection is not able to find the correct class. 106 | */ 107 | @SuppressWarnings("unused") 108 | public static LinkedList addToTreeFromList(String filename, boolean unique, 109 | BPlusTree bt) 110 | throws IOException, InvalidBTreeStateException, ClassNotFoundException { 111 | 112 | LinkedList l = loadListFromFile(filename); 113 | for (Long key : l) 114 | {bt.insertKey(key, key.toString(), unique);} 115 | return(l); 116 | } 117 | 118 | /** 119 | * Write object to file (used for testing certain key-sequences) 120 | * 121 | * @param obj Linked list to write 122 | * @param filename filename to dump the object 123 | * @throws IOException is thrown when an I/O operation fails 124 | */ 125 | @SuppressWarnings("unused") 126 | public static void writeObjectToFile(LinkedList obj, 127 | String filename) throws IOException { 128 | System.out.println("Writing object to: " + filename); 129 | FileOutputStream fout = new FileOutputStream(filename); 130 | ObjectOutputStream foutStream = new ObjectOutputStream(fout); 131 | foutStream.writeObject(obj); 132 | foutStream.close(); 133 | System.out.println("Writing complete to file: " + filename); 134 | } 135 | 136 | /** 137 | * Load linked list object from file (used for testing certain key-sequences) 138 | * 139 | * @param filename file to load the object from 140 | * @return the object itself. 141 | * @throws IOException is thrown when an I/O operation fails 142 | * @throws ClassNotFoundException is thrown when the reflection is not able to find the correct class. 143 | */ 144 | private static LinkedList loadListFromFile(String filename) 145 | throws IOException, ClassNotFoundException { 146 | System.out.println("Loading LinkedList object from file: " + filename); 147 | FileInputStream fin = new FileInputStream(filename); 148 | ObjectInputStream finStream = new ObjectInputStream(fin); 149 | @SuppressWarnings("unchecked") 150 | LinkedList l = (LinkedList)finStream.readObject(); 151 | finStream.close(); 152 | return l; 153 | } 154 | 155 | 156 | /** 157 | * This is a pseudo random number generator for unique 158 | * discreet values using quadratic prime residues. 159 | * 160 | * Taken from @preshing 161 | * 162 | */ 163 | @SuppressWarnings("unused") 164 | public static class randQPR { 165 | static final long prime = 4294967291L; 166 | private final long inter_index; 167 | private long index; 168 | 169 | public randQPR(long seed, long seedOffset) { 170 | index = permQPR(permQPR(seed) + 0x682f0161L); 171 | inter_index = permQPR(permQPR(seedOffset) + 0x46790905L); 172 | } 173 | 174 | long permQPR(long x) { 175 | if (x >= prime) { 176 | return x; 177 | } 178 | long residue = (x * x) % prime; 179 | return (x <= prime / 2 ? residue : prime - residue); 180 | } 181 | 182 | public long next() { 183 | return (permQPR(permQPR(index++) + inter_index) ^ 0x5bf03635L); 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/test/java/BPlusTreeTest.java: -------------------------------------------------------------------------------- 1 | import ds.bplus.bptree.BPlusConfiguration; 2 | import ds.bplus.bptree.BPlusTree; 3 | import ds.bplus.bptree.BPlusTreePerformanceCounter; 4 | import ds.bplus.util.Utilities; 5 | import org.junit.After; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | 9 | import java.util.LinkedList; 10 | 11 | /** 12 | * BPlusTree Tester. 13 | * 14 | * @since

Jul 28, 2015
15 | * @version 1.0 16 | */ 17 | public class BPlusTreeTest { 18 | private String satelliteValue; 19 | private boolean uniqueEntries; 20 | private boolean verboseResults; 21 | private int startKey; 22 | private int endKey; 23 | private int totalKeys; 24 | private boolean recreateTree; 25 | 26 | private BPlusConfiguration btConf256; 27 | private BPlusConfiguration btConf1024; 28 | private BPlusConfiguration btConf2048; 29 | 30 | private BPlusTreePerformanceCounter bPerf256; 31 | private BPlusTreePerformanceCounter bPerf1024; 32 | private BPlusTreePerformanceCounter bPerf2048; 33 | 34 | private BPlusTree bt256; 35 | private BPlusTree bt1024; 36 | private BPlusTree bt2048; 37 | 38 | @Before 39 | public void before() throws Exception { 40 | System.out.println("Before test"); 41 | startKey = 0; 42 | endKey = 10000; 43 | totalKeys = endKey - startKey; 44 | satelliteValue = " "; 45 | } 46 | 47 | @After 48 | public void after() throws Exception { 49 | //System.out.println("After test"); 50 | bt256.commitTree(); 51 | bt1024.commitTree(); 52 | bt2048.commitTree(); 53 | } 54 | 55 | /** 56 | * 57 | * This test loads up sequentially a massive key list 58 | * (10^5) onto trees of the following degrees: 59 | * 60 | * - Page sizes: 256, 1024 (1Kb), 2048 (2Kb) 61 | * 62 | * with the following (Key, Value) settings: 63 | * 64 | * - Satellite data size: 20 Bytes each entry 65 | * - Key size: 8 bytes 66 | * 67 | * @throws Exception is thrown when an error is catch'ed in any of the operations performed. 68 | */ 69 | @Test 70 | public void testMassSequentialInsertions() throws Exception { 71 | uniqueEntries = true; 72 | verboseResults = false; 73 | recreateTree = true; 74 | 75 | // initialize the configuration 76 | btConf256 = new BPlusConfiguration(256); 77 | btConf1024 = new BPlusConfiguration(1024); 78 | btConf2048 = new BPlusConfiguration(2048); 79 | 80 | // set up the the counters for each tree 81 | bPerf256 = new BPlusTreePerformanceCounter(true); 82 | bPerf1024 = new BPlusTreePerformanceCounter(true); 83 | bPerf2048 = new BPlusTreePerformanceCounter(true); 84 | 85 | // finally setup the tree instances 86 | bt256 = new BPlusTree(btConf256, recreateTree ? "rw+" : "rw", 87 | "tree256.bin", bPerf256); 88 | bt1024 = new BPlusTree(btConf1024, recreateTree ? "rw+" : "rw", 89 | "tree1024.bin", bPerf1024); 90 | bt2048 = new BPlusTree(btConf2048, recreateTree ? "rw+" : "rw", 91 | "tree2048.bin", bPerf2048); 92 | 93 | // now set up the insertions 94 | Utilities.sequentialAddToTree(startKey, endKey, 95 | satelliteValue, uniqueEntries, bt256); 96 | 97 | Utilities.sequentialAddToTree(startKey, endKey, 98 | satelliteValue, uniqueEntries, bt1024); 99 | 100 | Utilities.sequentialAddToTree(startKey, endKey, 101 | satelliteValue, uniqueEntries, bt2048); 102 | 103 | // now search 104 | int found_cnt256 = 0; 105 | int found_cnt1024 = 0; 106 | int found_cnt2048 = 0; 107 | 108 | int[] res256, res1024, res2048; 109 | for(int i = startKey; i < endKey; i++) { 110 | res256 = bPerf256.searchIO(i, uniqueEntries, verboseResults); 111 | res1024 = bPerf1024.searchIO(i, uniqueEntries, verboseResults); 112 | res2048 = bPerf2048.searchIO(i, uniqueEntries, verboseResults); 113 | 114 | if(res256[8] == 1) {found_cnt256++;} 115 | if(res1024[8] == 1) {found_cnt1024++;} 116 | if(res2048[8] == 1) {found_cnt2048++;} 117 | } 118 | 119 | // check result numbers 120 | if(found_cnt256 != totalKeys) 121 | {throw new Exception("BTree with page size: 256 failed to find all keys");} 122 | 123 | if(found_cnt1024 != totalKeys) 124 | {throw new Exception("BTree with page size: 1024 failed to find all keys");} 125 | 126 | if(found_cnt2048 != totalKeys) 127 | {throw new Exception("BTree with page size: 2048 failed to find all keys");} 128 | } 129 | 130 | /** 131 | * 132 | * This test loads up sequentially a massive key list 133 | * (10^5) onto trees of the following degrees: 134 | * 135 | * - Page sizes: 256, 1024 (1Kb), 2048 (2Kb) 136 | * 137 | * with the following (Key, Value) settings: 138 | * 139 | * - Satellite data size: 20 Bytes each entry 140 | * - Key size: 8 bytes 141 | * 142 | * In the end they are deleted as well. 143 | * @throws Exception is thrown when an error is catch'ed in any of the operations performed. 144 | */ 145 | @Test 146 | public void testMassSequentialInsertionsWithDelete() throws Exception { 147 | uniqueEntries = true; 148 | verboseResults = false; 149 | recreateTree = true; 150 | 151 | // initialize the configuration 152 | btConf256 = new BPlusConfiguration(256); 153 | btConf1024 = new BPlusConfiguration(1024); 154 | btConf2048 = new BPlusConfiguration(2048); 155 | 156 | // set up the the counters for each tree 157 | bPerf256 = new BPlusTreePerformanceCounter(true); 158 | bPerf1024 = new BPlusTreePerformanceCounter(true); 159 | bPerf2048 = new BPlusTreePerformanceCounter(true); 160 | 161 | // finally setup the tree instances 162 | bt256 = new BPlusTree(btConf256, recreateTree ? "rw+" : "rw", 163 | "tree256.bin", bPerf256); 164 | bt1024 = new BPlusTree(btConf1024, recreateTree ? "rw+" : "rw", 165 | "tree1024.bin", bPerf1024); 166 | bt2048 = new BPlusTree(btConf2048, recreateTree ? "rw+" : "rw", 167 | "tree2048.bin", bPerf2048); 168 | 169 | // now set up the insertions 170 | Utilities.sequentialAddToTree(startKey, endKey, 171 | satelliteValue, uniqueEntries, bt256); 172 | 173 | Utilities.sequentialAddToTree(startKey, endKey, 174 | satelliteValue, uniqueEntries, bt1024); 175 | 176 | Utilities.sequentialAddToTree(startKey, endKey, 177 | satelliteValue, uniqueEntries, bt2048); 178 | 179 | // now search 180 | int found_cnt256 = 0; 181 | int found_cnt1024 = 0; 182 | int found_cnt2048 = 0; 183 | 184 | int[] res256, res1024, res2048; 185 | for(int i = startKey; i < endKey; i++) { 186 | res256 = bPerf256.deleteIO(i, uniqueEntries, verboseResults); 187 | res1024 = bPerf1024.deleteIO(i, uniqueEntries, verboseResults); 188 | res2048 = bPerf2048.deleteIO(i, uniqueEntries, verboseResults); 189 | 190 | //bt256.commitLookupPage(); 191 | //bt1024.commitLookupPage(); 192 | //bt2048.commitLookupPage(); 193 | if (res256[8] == 1) { 194 | found_cnt256++; 195 | } 196 | if (res1024[8] == 1) { 197 | found_cnt1024++; 198 | } 199 | if (res2048[8] == 1) { 200 | found_cnt2048++; 201 | } 202 | } 203 | 204 | // check result numbers 205 | if(found_cnt256 != totalKeys) 206 | {throw new Exception("BTree with page size: 256 failed to find all keys");} 207 | 208 | if(found_cnt1024 != totalKeys) 209 | {throw new Exception("BTree with page size: 1024 failed to find all keys");} 210 | 211 | if(found_cnt2048 != totalKeys) 212 | {throw new Exception("BTree with page size: 2048 failed to find all keys");} 213 | } 214 | 215 | /** 216 | * This test loads up a massive unique key list in 217 | * random order (10^5) onto trees of the following degrees: 218 | * 219 | * - Page sizes: 256, 1024 (1Kb), 2048 (2Kb) 220 | * 221 | * with the following (Key, Value) settings: 222 | * 223 | * - Satellite data size: 20 224 | * @throws Exception is thrown when an error is catch'ed in any of the operations performed. 225 | */ 226 | @Test 227 | public void testMassRandomUniqueInsertions() throws Exception { 228 | uniqueEntries = true; 229 | verboseResults = false; 230 | recreateTree = true; 231 | 232 | LinkedList bt256val, bt1024val, bt2048val; 233 | 234 | 235 | // initialize the configuration 236 | btConf256 = new BPlusConfiguration(256); 237 | btConf1024 = new BPlusConfiguration(1024); 238 | btConf2048 = new BPlusConfiguration(2048); 239 | 240 | // set up the the counters for each tree 241 | bPerf256 = new BPlusTreePerformanceCounter(true); 242 | bPerf1024 = new BPlusTreePerformanceCounter(true); 243 | bPerf2048 = new BPlusTreePerformanceCounter(true); 244 | 245 | // finally setup the tree instances 246 | bt256 = new BPlusTree(btConf256, recreateTree ? "rw+" : "rw", 247 | "tree256.bin", bPerf256); 248 | bt1024 = new BPlusTree(btConf1024, recreateTree ? "rw+" : "rw", 249 | "tree1024.bin", bPerf1024); 250 | bt2048 = new BPlusTree(btConf1024, recreateTree ? "rw+" : "rw", 251 | "tree2048.bin", bPerf2048); 252 | 253 | // randomly add non-unique insertions 254 | bt256val = Utilities.fuzzyAddToTree(startKey, endKey, 255 | uniqueEntries, bt256); 256 | 257 | bt1024val = Utilities.fuzzyAddToTree(startKey, endKey, 258 | uniqueEntries, bt1024); 259 | bt2048val = Utilities.fuzzyAddToTree(startKey, endKey, 260 | uniqueEntries, bt2048); 261 | 262 | // now search 263 | int found_cnt256 = 0; 264 | int found_cnt1024 = 0; 265 | int found_cnt2048 = 0; 266 | 267 | int[] res256, res1024, res2048; 268 | 269 | System.out.println("\n--> Dataset size: " + endKey + "\n"); 270 | for(int i = startKey; i < endKey; i++) { 271 | res256 = bPerf256.searchIO(bt256val.pop(), uniqueEntries, 272 | verboseResults); 273 | res1024 = bPerf1024.searchIO(bt1024val.pop(), uniqueEntries, 274 | verboseResults); 275 | res2048 = bPerf2048.searchIO(bt2048val.pop(), uniqueEntries, 276 | verboseResults); 277 | 278 | if(res256[8] == 1) {found_cnt256++;} 279 | if(res1024[8] == 1) {found_cnt1024++;} 280 | if(res2048[8] == 1) {found_cnt2048++;} 281 | } 282 | 283 | System.out.println("Total pages for bt256 in the end: " + 284 | bt256.getTotalTreePages()); 285 | System.out.println("Total pages for bt1024 in the end: " + 286 | bt1024.getTotalTreePages()); 287 | System.out.println("Total pages for bt2048 in the end: " + 288 | bt2048.getTotalTreePages()); 289 | 290 | // check result numbers 291 | if(found_cnt256 != totalKeys) 292 | {throw new Exception("BTree with page size: 256 failed to find all keys");} 293 | 294 | if(found_cnt1024 != totalKeys) 295 | {throw new Exception("BTree with page size: 1024 failed to find all keys");} 296 | 297 | if(found_cnt2048 != totalKeys) 298 | {throw new Exception("BTree with page size: 2048 failed to find all keys");} 299 | } 300 | 301 | /** 302 | * This test loads up a massive non-unique key list in 303 | * random order (10^5) onto trees of the following degrees: 304 | * 305 | * - Page sizes: 256, 1024 (1Kb), 2048 (2Kb) 306 | * 307 | * with the following (Key, Value) settings: 308 | * 309 | * - Satellite data size: 20 Bytes each entry 310 | * - Key size: 8 bytes 311 | * 312 | * After insertion each of the keys are searched. 313 | * @throws Exception is thrown when an error is catch'ed in any of the operations performed. 314 | */ 315 | @Test 316 | public void testMassRandomInsertionsWithSearch() throws Exception { 317 | uniqueEntries = false; 318 | verboseResults = false; 319 | recreateTree = true; 320 | 321 | LinkedList bt256val, bt1024val, bt2048val; 322 | 323 | 324 | // initialize the configuration 325 | btConf256 = new BPlusConfiguration(256); 326 | btConf1024 = new BPlusConfiguration(1024); 327 | btConf2048 = new BPlusConfiguration(2048); 328 | 329 | // set up the the counters for each tree 330 | bPerf256 = new BPlusTreePerformanceCounter(true); 331 | bPerf1024 = new BPlusTreePerformanceCounter(true); 332 | bPerf2048 = new BPlusTreePerformanceCounter(true); 333 | 334 | // finally setup the tree instances 335 | bt256 = new BPlusTree(btConf256, recreateTree ? "rw+" : "rw", 336 | "tree256.bin", bPerf256); 337 | bt1024 = new BPlusTree(btConf1024, recreateTree ? "rw+" : "rw", 338 | "tree1024.bin", bPerf1024); 339 | bt2048 = new BPlusTree(btConf1024, recreateTree ? "rw+" : "rw", 340 | "tree2048.bin", bPerf2048); 341 | 342 | // randomly add non-unique insertions 343 | bt256val = Utilities.fuzzyAddToTree(startKey, endKey, 344 | uniqueEntries, bt256); 345 | 346 | 347 | //bt256val = Utilities.addToTreeFromList("list.ser", satelliteValue, uniqueEntries, bt256); 348 | 349 | //bt1024val = Utilities.addToTreeFromList("del.ser", satelliteValue, uniqueEntries, bt1024); 350 | //bt1024val = Utilities.addToTreeFromList("delsmall.ser", satelliteValue, uniqueEntries, bt1024); 351 | //bt1024val = Utilities.addToTreeFromList("lfile.ser", satelliteValue, uniqueEntries, bt1024); 352 | //bt1024val = Utilities.addToTreeFromList("lfileex.ser", satelliteValue, uniqueEntries, bt1024); 353 | 354 | bt1024val = Utilities.fuzzyAddToTree(startKey, endKey, 355 | uniqueEntries, bt1024); 356 | bt2048val = Utilities.fuzzyAddToTree(startKey, endKey, 357 | uniqueEntries, bt2048); 358 | 359 | // now search 360 | int found_cnt256 = 0; 361 | int found_cnt1024 = 0; 362 | int found_cnt2048 = 0; 363 | 364 | int[] res256, res1024, res2048; 365 | //Utilities.writeObjectToFile(bt1024val, "delsmall.ser"); 366 | //bPerf1024.searchIO(2, false, false); 367 | //bPerf1024.searchIO(0, false, false); 368 | //endKey = bt1024val.size(); 369 | 370 | 371 | System.out.println("\n--> Dataset size: " + endKey + "\n"); 372 | for(int i = startKey; i < endKey; i++) { 373 | res256 = bPerf256.searchIO(bt256val.pop(), uniqueEntries, 374 | verboseResults); 375 | res1024 = bPerf1024.searchIO(bt1024val.pop(), uniqueEntries, 376 | verboseResults); 377 | res2048 = bPerf2048.searchIO(bt2048val.pop(), uniqueEntries, 378 | verboseResults); 379 | 380 | if(res256[8] == 1) {found_cnt256++;} 381 | if(res1024[8] == 1) {found_cnt1024++;} 382 | if(res2048[8] == 1) {found_cnt2048++;} 383 | } 384 | 385 | System.out.println("Total pages for bt256 in the end: " + 386 | bt256.getTotalTreePages()); 387 | System.out.println("Total pages for bt1024 in the end: " + 388 | bt1024.getTotalTreePages()); 389 | System.out.println("Total pages for bt2048 in the end: " + 390 | bt2048.getTotalTreePages()); 391 | 392 | // check result numbers 393 | if(found_cnt256 != totalKeys) 394 | {throw new Exception("BTree with page size: 256 failed to find all keys");} 395 | 396 | if(found_cnt1024 != totalKeys) 397 | {throw new Exception("BTree with page size: 1024 failed to find all keys");} 398 | 399 | if(found_cnt2048 != totalKeys) 400 | {throw new Exception("BTree with page size: 2048 failed to find all keys");} 401 | } 402 | 403 | /** 404 | * This test loads up a massive non-unique key list in 405 | * random order (10^5) onto trees of the following degrees: 406 | * 407 | * - Page sizes: 256, 1024 (1Kb), 2048 (2Kb) 408 | * 409 | * with the following (Key, Value) settings: 410 | * 411 | * - Satellite data size: 20 Bytes each entry 412 | * - Key size: 8 bytes 413 | * 414 | * After insertion they are deleted in the same order as they were 415 | * put in. 416 | * 417 | * @throws Exception is thrown when an error is catch'ed in any of the operations performed. 418 | */ 419 | @Test 420 | public void testMassRandomInsertionsWithDelete() throws Exception { 421 | uniqueEntries = false; 422 | verboseResults = false; 423 | recreateTree = true; 424 | 425 | LinkedList bt256val, bt1024val, bt2048val; 426 | 427 | 428 | // initialize the configuration 429 | btConf256 = new BPlusConfiguration(256); 430 | btConf1024 = new BPlusConfiguration(1024); 431 | btConf2048 = new BPlusConfiguration(2048); 432 | 433 | // set up the the counters for each tree 434 | bPerf256 = new BPlusTreePerformanceCounter(true); 435 | bPerf1024 = new BPlusTreePerformanceCounter(true); 436 | bPerf2048 = new BPlusTreePerformanceCounter(true); 437 | 438 | // finally setup the tree instances 439 | bt256 = new BPlusTree(btConf256, recreateTree ? "rw+" : "rw", 440 | "tree256.bin", bPerf256); 441 | bt1024 = new BPlusTree(btConf1024, recreateTree ? "rw+" : "rw", 442 | "tree1024.bin", bPerf1024); 443 | bt2048 = new BPlusTree(btConf2048, recreateTree ? "rw+" : "rw", 444 | "tree2048.bin", bPerf2048); 445 | 446 | // randomly add non-unique insertions 447 | bt256val = Utilities.fuzzyAddToTree(startKey, endKey, 448 | uniqueEntries, bt256); 449 | 450 | bt1024val = Utilities.fuzzyAddToTree(startKey, endKey, 451 | uniqueEntries, bt1024); 452 | bt2048val = Utilities.fuzzyAddToTree(startKey, endKey, 453 | uniqueEntries, bt2048); 454 | 455 | // our counters 456 | int found_cnt256 = 0; 457 | int found_cnt1024 = 0; 458 | int found_cnt2048 = 0; 459 | 460 | int[] res256, res1024, res2048; 461 | 462 | //System.out.println("\n--> Dataset size: " + endKey + "\n"); 463 | 464 | for(int i = startKey; i < endKey; i++) { 465 | res256 = bPerf256.deleteIO(bt256val.pop(), true, 466 | verboseResults); 467 | res1024 = bPerf1024.deleteIO(bt1024val.pop(), true, 468 | verboseResults); 469 | res2048 = bPerf2048.deleteIO(bt2048val.pop(), true, 470 | verboseResults); 471 | 472 | if(res256[8] == 1) {found_cnt256++;} 473 | if(res1024[8] == 1) {found_cnt1024++;} 474 | if(res2048[8] == 1) {found_cnt2048++;} 475 | } 476 | 477 | System.out.println("Total pages for bt256 in the end: " + bt256.getTotalTreePages()); 478 | System.out.println("Total pages for bt1024 in the end: " + bt1024.getTotalTreePages()); 479 | System.out.println("Total pages for bt2048 in the end: " + bt2048.getTotalTreePages()); 480 | 481 | // check result numbers 482 | if(found_cnt256 != totalKeys) 483 | {throw new Exception("BTree with page size: 256 failed to delete all keys");} 484 | 485 | if(found_cnt1024 != totalKeys) 486 | {throw new Exception("BTree with page size: 1024 failed to delete all keys");} 487 | 488 | if(found_cnt2048 != totalKeys) 489 | {throw new Exception("BTree with page size: 2048 failed to delete all keys");} 490 | 491 | } 492 | 493 | } 494 | --------------------------------------------------------------------------------