├── .circleci └── config.yml ├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── LICENSE ├── README.md ├── mvnw ├── mvnw.cmd ├── nbactions.xml ├── pom.xml └── src ├── main └── java │ └── com │ └── github │ └── coderodde │ └── util │ ├── Finger.java │ ├── FingerList.java │ ├── IndexedLinkedList.java │ ├── Node.java │ └── benchmark │ ├── LinkedListBenchmark.java │ ├── LinkedListBenchmark2.java │ └── LinkedListBenchmarkRunner.java └── test └── java └── com └── github └── coderodde └── util ├── FingerListTest.java └── IndexedLinkedListTest.java /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Use the latest 2.1 version of CircleCI pipeline process engine. 2 | # See: https://circleci.com/docs/2.0/configuration-reference 3 | version: 2.1 4 | orbs: 5 | codecov: codecov/codecov@3.3.0 6 | workflows: 7 | upload-to-codecov: 8 | jobs: 9 | - checkout 10 | - codecov/upload 11 | 12 | #jobs: 13 | # build: 14 | # docker: 15 | # - image: circleci/clojure:openjdk-18-lein-2.9.8-buster-node-browsers-legacy 16 | # steps: 17 | # - checkout 18 | # - run : mvn test 19 | # - store_artifacts: 20 | # path: target -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /nbproject/ 2 | /target/ 3 | nb-configuration.xml 4 | performance_tables.html 5 | .idea 6 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderodde/IndexedLinkedList/0fc4e573ca5b0c720a34f824ff2292d79fe5e183/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.4/apache-maven-3.9.4-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021, 2022 Rodion "coderodde" Efremov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CircleCI](https://dl.circleci.com/status-badge/img/gh/coderodde/IndexedLinkedList/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/coderodde/IndexedLinkedList/tree/main) 2 | [![codecov](https://codecov.io/gh/coderodde/IndexedLinkedList/branch/main/graph/badge.svg)](https://codecov.io/gh/coderodde/IndexedLinkedList) 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.coderodde/IndexedLinkedList.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22io.github.coderodde%22%20AND%20a:%22IndexedLinkedList%22) 5 | [![javadoc](https://javadoc.io/badge2/io.github.coderodde/IndexedLinkedList/javadoc.svg)](https://javadoc.io/doc/io.github.coderodde/IndexedLinkedList) 6 | 7 | # IndexedLinkedList - an indexed, heuristic doubly-linked list in Java 8 | 9 | This repository maintains the implementation of a linked list data structure that runs single-element operations in Θ(sqrt(n)) time under mild assumptions. 10 | 11 | The blog post explaining the (simple, high school level) math behind this data structure may be found behind [this link](http://coderodde.github.io/weblog/#meill). 12 | 13 | Our `IndexedLinkedList` exhibits performance faster than [Apache Commons Collections4 `org.apache.commons.collections4.list.TreeList.java`](https://github.com/apache/commons-collections/blob/master/src/main/java/org/apache/commons/collections4/list/TreeList.java) (which runs all the single-element operations in O(log(N)) time due to the AVL-tree algorithm), while (apart from having ceil(sqrt(N)) fingers, each consisting from a reference and an `int` value) having smaller memory footprint: for each node, our list maintains 3 references; each node in the `TreeList` consists of 3 references, 2 `int` values and 2 `boolean` values. 14 | 15 | ## Running time comparison 16 | 17 | | Operation | ArrayList | LinkedList | IndexedLinkedList | TreeList | 18 | | ---------------- | -------------- | -------------- | ------------------------- | ------------------------ | 19 | | `add(int)` | ***O(n)*** | ***O(n)*** | ***O(sqrt(n))*** | ***O(log n)*** | 20 | | `addFirst` | ***O(n)*** | ***O(1)*** | ***O(sqrt(n))*** | ***O(log n)*** | 21 | | `addLast` | ***O(1)*** | ***O(1)*** | ***O(1)*** | ***O(log n)*** | 22 | | `get` | ***O(1)*** | ***O(n)*** | ***O(sqrt(n))*** | ***O(log n)*** | 23 | | `remove(int)` | ***O(n)*** | ***O(n)*** | ***O(sqrt(n))*** | ***O(log n)*** | 24 | | `removeFirst` | ***O(n)*** | ***O(1)*** | ***O(sqrt(n))*** | ***O(log n)*** | 25 | | `removeLast` | ***O(1)*** | ***O(1)*** | ***O(1)*** | ***O(log n)*** | 26 | | `remove(Object)` | ***O(n)*** | ***O(n)*** | ***O(n)*** | ***O(n)*** | 27 | | `setAll` | ***O(n)*** | ***O(n)*** | ***O(n)*** | ***O(n)*** | 28 | | `prependAll` | ***O(m + n)*** | ***O(m)*** | ***O(m + sqrt(n))*** | ***O(m log n)*** | 29 | | `appendAll` | ***O(m)*** | ***O(m)*** | ***O(m)*** | ***O(m + log n)*** | 30 | | `insertAll` | ***O(m + n)*** | ***O(m + n)*** | ***O(m + sqrt(n))*** | ***O(m log n)*** | 31 | | `removeAll` | ***O(nf)*** | ***O(nf)*** | ***O(nf + n * sqrt(n))*** | ***O(nf + n * log(n))*** | | 32 | 33 | Above, ***n*** is the current size of a list, ***m*** is the size of a newly added collection, and ***f*** is the cost of consulting whether an element is contained in a filter collectoin. 34 | 35 | ## Benchmark output 36 | On a PC with a quad-core CPU with base speed 1,99 GHz and 256 kB L1 cache, 1 MB L2 cache and 8 MB L3 cache, the benchmark gives typically the following results: 37 | 38 | ### Benchmark with JMH 39 | 40 | #### Small size data 41 | 42 | | Operation | ms | 43 | |----------------------------------------------------|------------| 44 | | profileArrayListAddAtIndex | 0,074 | 45 | | profileCursorableLinkedListAddAtIndex | 0,185 | 46 | | profileJavaLinkedListAddAtIndex | 0,174 | 47 | | profileNodeCachingLinkedListAddAtIndex | 0,181 | 48 | | profileRoddeListAddAtIndex | 0,116 | 49 | | profileTreeListAddAtIndex | 0,153 | 50 | | | | 51 | | profileArrayListAddCollection | 0,127 | 52 | | profileCursorableLinkedListAddCollection | 0,151 | 53 | | profileJavaLinkedListAddCollection | 0,140 | 54 | | profileNodeCachingLinkedListAddCollection | 0,223 | 55 | | profileRoddeListAddCollection | 0,133 | 56 | | profileTreeListAddCollection | 0,413 | 57 | | | | 58 | | profileArrayListAddCollectionAtIndex | 0,375 | 59 | | profileCursorableLinkedListAddCollectionAtIndex | 2,989 | 60 | | profileJavaLinkedListAddCollectionAtIndex | 2,899 | 61 | | profileNodeCachingLinkedListAddCollectionAtIndex | 3,079 | 62 | | profileRoddeListAddCollectionAtIndex | 0,327 | 63 | | profileTreeListAddCollectionAtIndex | 1,944 | 64 | | | | 65 | | profileArrayListAddFirst | 0,074 | 66 | | profileCursorableLinkedListAddFirst | 0,008 | 67 | | profileJavaLinkedListAddFirst | 0,008 | 68 | | profileNodeCachingLinkedListAddFirst | 0,011 | 69 | | profileRoddeListAddFirst | 0,046 | 70 | | profileTreeListAddFirst | 0,086 | 71 | | | | 72 | | profileArrayListAddLast | 0,006 | 73 | | profileCursorableLinkedListAddLast | 0,007 | 74 | | profileJavaLinkedListAddLast | 0,006 | 75 | | profileNodeCachingLinkedListAddLast | 0,015 | 76 | | profileRoddeListAddLast | 0,010 | 77 | | profileTreeListAddLast | 0,082 | 78 | | | | 79 | | profileArrayListGet | 0,015 | 80 | | profileCursorableLinkedListGet | 3,738 | 81 | | profileJavaLinkedListGet | 3,721 | 82 | | profileNodeCachingLinkedListGet | 3,758 | 83 | | profileRoddeListGet | 0,144 | 84 | | profileTreeListGet | 0,117 | 85 | | | | 86 | | profileArrayListRemoveAll | 0,178 | 87 | | profileCursorableLinkedListRemoveAll | 0,388 | 88 | | profileJavaLinkedListRemoveAll | 19,543 | 89 | | profileNodeCachingLinkedListRemoveAll | 0,377 | 90 | | profileRoddeListRemoveAll | 0,360 | 91 | | profileTreeListRemoveAll | 56,304 | 92 | | | | 93 | | profileArrayListRemoveAtIndex | 1,986 | 94 | | profileCursorableLinkedListRemoveAtIndex | 24,609 | 95 | | profileJavaLinkedListRemoveAtIndex | 25,117 | 96 | | profileNodeCachingLinkedListRemoveAtIndex | 28,070 | 97 | | profileRoddeListRemoveAtIndex | 5,798 | 98 | | profileTreeListRemoveAtIndex | 2,978 | 99 | | | | 100 | | profileArrayListRemoveFirst | 2,753 | 101 | | profileCursorableLinkedListRemoveFirst | 0,955 | 102 | | profileJavaLinkedListRemoveFirst | 0,139 | 103 | | profileNodeCachingLinkedListRemoveFirst | 0,346 | 104 | | profileRoddeListRemoveFirst | 1,176 | 105 | | profileTreeListRemoveFirst | 1,060 | 106 | | | | 107 | | profileArrayListRemoveLast | 0,031 | 108 | | profileCursorableLinkedListRemoveLast | 0,266 | 109 | | profileJavaLinkedListRemoveLast | 0,136 | 110 | | profileNodeCachingLinkedListRemoveLast | 0,379 | 111 | | profileRoddeListRemoveLast | 0,204 | 112 | | profileTreeListRemoveLast | 1,348 | 113 | | | | 114 | | profileArrayListRemoveObject | 4,929 | 115 | | profileCursorableLinkedListRemoveObject | 29,639 | 116 | | profileJavaLinkedListRemoveObject | 10,105 | 117 | | profileNodeCachingLinkedListRemoveObject | 11,481 | 118 | | profileRoddeListRemoveObject | 21,035 | 119 | | profileTreeListRemoveObject | 30,990 | 120 | | | | 121 | | profileArrayListRemoveRange | 0,051 | 122 | | profileCursorableLinkedListRemoveRange | 11,476 | 123 | | profileJavaLinkedListRemoveRange | 0,540 | 124 | | profileNodeCachingLinkedListRemoveRange | 1,819 | 125 | | profileRoddeListRemoveRange | 0,574 | 126 | | profileTreeListRemoveRange | 7,156 | 127 | | | | 128 | | profileArrayListSortRange | 1,412 | 129 | | profileCursorableLinkedListSortRange | 8,641 | 130 | | profileJavaLinkedListSortRange | 1,547 | 131 | | profileNodeCachingLinkedListSortRange | 1,864 | 132 | | profileRoddeListSortRange | 1,488 | 133 | | profileTreeListSortRange | 1,792 | 134 | | | | 135 | | Total of ArrayList | 12,012 | 136 | | Total of JavaLinkedList | 64,074 | 137 | | Total of RoddeList | 31,410 | 138 | | Total of TreeList | 104,422 | 139 | | Total of NodeCachingLinkedList | 51,604 | 140 | | Total of CursorableLinkedList | 83,052 | 141 | 142 | #### Medium size data 143 | 144 | | Operation | ms | 145 | |----------------------------------------------------|------------| 146 | | profileArrayListAddAtIndex | 0,397 | 147 | | profileCursorableLinkedListAddAtIndex | 2,668 | 148 | | profileJavaLinkedListAddAtIndex | 2,653 | 149 | | profileNodeCachingLinkedListAddAtIndex | 2,705 | 150 | | profileRoddeListAddAtIndex | 0,432 | 151 | | profileTreeListAddAtIndex | 0,549 | 152 | | | | 153 | | profileArrayListAddCollection | 0,393 | 154 | | profileCursorableLinkedListAddCollection | 0,425 | 155 | | profileJavaLinkedListAddCollection | 0,439 | 156 | | profileNodeCachingLinkedListAddCollection | 0,554 | 157 | | profileRoddeListAddCollection | 0,405 | 158 | | profileTreeListAddCollection | 1,314 | 159 | | | | 160 | | profileArrayListAddCollectionAtIndex | 2,108 | 161 | | profileCursorableLinkedListAddCollectionAtIndex | 37,327 | 162 | | profileJavaLinkedListAddCollectionAtIndex | 35,409 | 163 | | profileNodeCachingLinkedListAddCollectionAtIndex | 36,833 | 164 | | profileRoddeListAddCollectionAtIndex | 1,328 | 165 | | profileTreeListAddCollectionAtIndex | 6,087 | 166 | | | | 167 | | profileArrayListAddFirst | 0,404 | 168 | | profileCursorableLinkedListAddFirst | 0,024 | 169 | | profileJavaLinkedListAddFirst | 0,020 | 170 | | profileNodeCachingLinkedListAddFirst | 0,049 | 171 | | profileRoddeListAddFirst | 0,140 | 172 | | profileTreeListAddFirst | 0,294 | 173 | | | | 174 | | profileArrayListAddLast | 0,016 | 175 | | profileCursorableLinkedListAddLast | 0,020 | 176 | | profileJavaLinkedListAddLast | 0,019 | 177 | | profileNodeCachingLinkedListAddLast | 0,045 | 178 | | profileRoddeListAddLast | 0,032 | 179 | | profileTreeListAddLast | 0,274 | 180 | | | | 181 | | profileArrayListGet | 0,056 | 182 | | profileCursorableLinkedListGet | 35,485 | 183 | | profileJavaLinkedListGet | 46,542 | 184 | | profileNodeCachingLinkedListGet | 36,150 | 185 | | profileRoddeListGet | 0,679 | 186 | | profileTreeListGet | 0,501 | 187 | | | | 188 | | profileArrayListRemoveAll | 0,631 | 189 | | profileCursorableLinkedListRemoveAll | 1,259 | 190 | | profileJavaLinkedListRemoveAll | 222,924 | 191 | | profileNodeCachingLinkedListRemoveAll | 1,138 | 192 | | profileRoddeListRemoveAll | 1,250 | 193 | | profileTreeListRemoveAll | 701,820 | 194 | | | | 195 | | profileArrayListRemoveAtIndex | 17,447 | 196 | | profileCursorableLinkedListRemoveAtIndex | 258,963 | 197 | | profileJavaLinkedListRemoveAtIndex | 264,451 | 198 | | profileNodeCachingLinkedListRemoveAtIndex | 326,732 | 199 | | profileRoddeListRemoveAtIndex | 49,221 | 200 | | profileTreeListRemoveAtIndex | 10,607 | 201 | | | | 202 | | profileArrayListRemoveFirst | 29,851 | 203 | | profileCursorableLinkedListRemoveFirst | 3,229 | 204 | | profileJavaLinkedListRemoveFirst | 0,643 | 205 | | profileNodeCachingLinkedListRemoveFirst | 0,975 | 206 | | profileRoddeListRemoveFirst | 5,066 | 207 | | profileTreeListRemoveFirst | 3,738 | 208 | | | | 209 | | profileArrayListRemoveLast | 0,089 | 210 | | profileCursorableLinkedListRemoveLast | 0,791 | 211 | | profileJavaLinkedListRemoveLast | 0,637 | 212 | | profileNodeCachingLinkedListRemoveLast | 1,141 | 213 | | profileRoddeListRemoveLast | 0,572 | 214 | | profileTreeListRemoveLast | 5,143 | 215 | | | | 216 | | profileArrayListRemoveObject | 46,302 | 217 | | profileCursorableLinkedListRemoveObject | 270,996 | 218 | | profileJavaLinkedListRemoveObject | 93,453 | 219 | | profileNodeCachingLinkedListRemoveObject | 106,020 | 220 | | profileRoddeListRemoveObject | 168,869 | 221 | | profileTreeListRemoveObject | 299,543 | 222 | | | | 223 | | profileArrayListRemoveRange | 0,097 | 224 | | profileCursorableLinkedListRemoveRange | 20,298 | 225 | | profileJavaLinkedListRemoveRange | 1,186 | 226 | | profileNodeCachingLinkedListRemoveRange | 3,278 | 227 | | profileRoddeListRemoveRange | 1,155 | 228 | | profileTreeListRemoveRange | 14,247 | 229 | | | | 230 | | profileArrayListSortRange | 8,417 | 231 | | profileCursorableLinkedListSortRange | 18,045 | 232 | | profileJavaLinkedListSortRange | 9,346 | 233 | | profileNodeCachingLinkedListSortRange | 10,747 | 234 | | profileRoddeListSortRange | 9,196 | 235 | | profileTreeListSortRange | 10,697 | 236 | | | | 237 | | Total of ArrayList | 106,209 | 238 | | Total of JavaLinkedList | 677,722 | 239 | | Total of RoddeList | 238,345 | 240 | | Total of TreeList | 1054,816 | 241 | | Total of NodeCachingLinkedList | 526,367 | 242 | | Total of CursorableLinkedList | 649,529 | 243 | 244 | 245 | #### Larger size data 246 | 247 | 248 | | Operation | ms | 249 | |----------------------------------------------------|------------| 250 | | profileArrayListAddAtIndex | 0,663 | 251 | | profileCursorableLinkedListAddAtIndex | 8,430 | 252 | | profileJavaLinkedListAddAtIndex | 8,413 | 253 | | profileNodeCachingLinkedListAddAtIndex | 8,527 | 254 | | profileRoddeListAddAtIndex | 0,867 | 255 | | profileTreeListAddAtIndex | 1,035 | 256 | | | | 257 | | profileArrayListAddCollection | 0,619 | 258 | | profileCursorableLinkedListAddCollection | 0,749 | 259 | | profileJavaLinkedListAddCollection | 0,671 | 260 | | profileNodeCachingLinkedListAddCollection | 1,115 | 261 | | profileRoddeListAddCollection | 0,675 | 262 | | profileTreeListAddCollection | 2,452 | 263 | | | | 264 | | profileArrayListAddCollectionAtIndex | 5,163 | 265 | | profileCursorableLinkedListAddCollectionAtIndex | 103,718 | 266 | | profileJavaLinkedListAddCollectionAtIndex | 100,904 | 267 | | profileNodeCachingLinkedListAddCollectionAtIndex | 108,378 | 268 | | profileRoddeListAddCollectionAtIndex | 2,540 | 269 | | profileTreeListAddCollectionAtIndex | 10,566 | 270 | | | | 271 | | profileArrayListAddFirst | 1,382 | 272 | | profileCursorableLinkedListAddFirst | 0,040 | 273 | | profileJavaLinkedListAddFirst | 0,044 | 274 | | profileNodeCachingLinkedListAddFirst | 0,058 | 275 | | profileRoddeListAddFirst | 0,268 | 276 | | profileTreeListAddFirst | 0,532 | 277 | | | | 278 | | profileArrayListAddLast | 0,024 | 279 | | profileCursorableLinkedListAddLast | 0,035 | 280 | | profileJavaLinkedListAddLast | 0,040 | 281 | | profileNodeCachingLinkedListAddLast | 0,067 | 282 | | profileRoddeListAddLast | 0,052 | 283 | | profileTreeListAddLast | 0,498 | 284 | | | | 285 | | profileArrayListGet | 0,096 | 286 | | profileCursorableLinkedListGet | 101,479 | 287 | | profileJavaLinkedListGet | 101,086 | 288 | | profileNodeCachingLinkedListGet | 102,238 | 289 | | profileRoddeListGet | 1,564 | 290 | | profileTreeListGet | 0,846 | 291 | | | | 292 | | profileArrayListRemoveAll | 1,041 | 293 | | profileCursorableLinkedListRemoveAll | 2,103 | 294 | | profileJavaLinkedListRemoveAll | 497,436 | 295 | | profileNodeCachingLinkedListRemoveAll | 2,278 | 296 | | profileRoddeListRemoveAll | 2,277 | 297 | | profileTreeListRemoveAll | 1699,747 | 298 | | | | 299 | | profileArrayListRemoveAtIndex | 49,006 | 300 | | profileCursorableLinkedListRemoveAtIndex | 755,302 | 301 | | profileJavaLinkedListRemoveAtIndex | 790,056 | 302 | | profileNodeCachingLinkedListRemoveAtIndex | 932,956 | 303 | | profileRoddeListRemoveAtIndex | 155,181 | 304 | | profileTreeListRemoveAtIndex | 17,946 | 305 | | | | 306 | | profileArrayListRemoveFirst | 80,048 | 307 | | profileCursorableLinkedListRemoveFirst | 5,888 | 308 | | profileJavaLinkedListRemoveFirst | 1,055 | 309 | | profileNodeCachingLinkedListRemoveFirst | 1,868 | 310 | | profileRoddeListRemoveFirst | 10,886 | 311 | | profileTreeListRemoveFirst | 6,530 | 312 | | | | 313 | | profileArrayListRemoveLast | 0,152 | 314 | | profileCursorableLinkedListRemoveLast | 1,367 | 315 | | profileJavaLinkedListRemoveLast | 1,104 | 316 | | profileNodeCachingLinkedListRemoveLast | 2,086 | 317 | | profileRoddeListRemoveLast | 1,216 | 318 | | profileTreeListRemoveLast | 8,458 | 319 | | | | 320 | | profileArrayListRemoveObject | 135,200 | 321 | | profileCursorableLinkedListRemoveObject | 768,759 | 322 | | profileJavaLinkedListRemoveObject | 258,027 | 323 | | profileNodeCachingLinkedListRemoveObject | 325,939 | 324 | | profileRoddeListRemoveObject | 529,313 | 325 | | profileTreeListRemoveObject | 856,094 | 326 | | | | 327 | | profileArrayListRemoveRange | 0,166 | 328 | | profileCursorableLinkedListRemoveRange | 18,914 | 329 | | profileJavaLinkedListRemoveRange | 1,922 | 330 | | profileNodeCachingLinkedListRemoveRange | 5,676 | 331 | | profileRoddeListRemoveRange | 1,889 | 332 | | profileTreeListRemoveRange | 22,228 | 333 | | | | 334 | | profileArrayListSortRange | 28,928 | 335 | | profileCursorableLinkedListSortRange | 52,450 | 336 | | profileJavaLinkedListSortRange | 32,352 | 337 | | profileNodeCachingLinkedListSortRange | 36,016 | 338 | | profileRoddeListSortRange | 31,444 | 339 | | profileTreeListSortRange | 36,604 | 340 | | | | 341 | | Total of ArrayList | 302,487 | 342 | | Total of JavaLinkedList | 1793,110 | 343 | | Total of RoddeList | 738,172 | 344 | | Total of TreeList | 2663,536 | 345 | | Total of NodeCachingLinkedList | 1527,201 | 346 | | Total of CursorableLinkedList | 1819,234 | 347 | 348 | ### Benchmark without JMH 349 | 350 | ``` 351 | <<< Benchmark seed = 1654425745819 >>> 352 | 353 | <<< Flags >>> 354 | runSubListClear: true 355 | runRemoveAll : true 356 | runSort : true 357 | 358 | === WARMUP RUN === 359 | com.github.coderodde.util.IndexedLinkedList.addFirst in (ms): 53 360 | java.util.LinkedList.addFirst in (ms): 11 361 | java.util.ArrayList.addFirst in (ms): 617 362 | org.apache.commons.collections4.list.TreeList.addFirst in (ms): 36 363 | 364 | com.github.coderodde.util.IndexedLinkedList.addLast in (ms): 0 365 | java.util.LinkedList.addLast in (ms): 32 366 | java.util.ArrayList.addLast in (ms): 18 367 | org.apache.commons.collections4.list.TreeList.addLast in (ms): 41 368 | 369 | com.github.coderodde.util.IndexedLinkedList.add(int, E) in (ms): 29 370 | java.util.LinkedList.add(int, E) in (ms): 1873 371 | java.util.ArrayList.add(int, E) in (ms): 115 372 | org.apache.commons.collections4.list.TreeList.add(int, E) in (ms): 28 373 | 374 | com.github.coderodde.util.IndexedLinkedList.addAll(Collection) in (ms): 9 375 | java.util.LinkedList.addAll(Collection) in (ms): 28 376 | java.util.ArrayList.addAll(Collection) in (ms): 4 377 | org.apache.commons.collections4.list.TreeList.addAll(Collection) in (ms): 31 378 | 379 | com.github.coderodde.util.IndexedLinkedList.addAll(int, Collection) in (ms): 38 380 | java.util.LinkedList.addAll(int, Collection) in (ms): 1546 381 | java.util.ArrayList.addAll(int, Collection) in (ms): 92 382 | org.apache.commons.collections4.list.TreeList.addAll(int, Collection) in (ms): 16 383 | 384 | com.github.coderodde.util.IndexedLinkedList.get(int) in (ms): 10 385 | java.util.LinkedList.get(int) in (ms): 2133 386 | java.util.ArrayList.get(int) in (ms): 1 387 | org.apache.commons.collections4.list.TreeList.get(int) in (ms): 3 388 | 389 | com.github.coderodde.util.IndexedLinkedList.removeFirst() in (ms): 29 390 | java.util.LinkedList.removeFirst() in (ms): 1 391 | java.util.ArrayList.removeFirst() in (ms): 243 392 | org.apache.commons.collections4.list.TreeList.removeFirst() in (ms): 3 393 | 394 | com.github.coderodde.util.IndexedLinkedList.removeLast() in (ms): 0 395 | java.util.LinkedList.removeLast() in (ms): 0 396 | java.util.ArrayList.removeLast() in (ms): 0 397 | org.apache.commons.collections4.list.TreeList.removeLast() in (ms): 0 398 | 399 | com.github.coderodde.util.IndexedLinkedList.remove(int) in (ms): 42 400 | java.util.LinkedList.remove(int) in (ms): 4067 401 | java.util.ArrayList.remove(int) in (ms): 216 402 | org.apache.commons.collections4.list.TreeList.remove(int) in (ms): 11 403 | 404 | com.github.coderodde.util.IndexedLinkedList.remove(Object) in (ms): 109 405 | java.util.LinkedList.remove(Object) in (ms): 56 406 | java.util.ArrayList.remove(Object) in (ms): 281 407 | org.apache.commons.collections4.list.TreeList.remove(Object) in (ms): 139 408 | 409 | com.github.coderodde.util.IndexedLinkedList.iterator().add() in (ms): 25 410 | java.util.LinkedList.iterator().add() in (ms): 3 411 | java.util.ArrayList.iterator().add() in (ms): 826 412 | org.apache.commons.collections4.list.TreeList.iterator().add() in (ms): 45 413 | 414 | com.github.coderodde.util.IndexedLinkedList.iterator().remove() in (ms): 6 415 | java.util.LinkedList.iterator().remove() in (ms): 15 416 | java.util.ArrayList.iterator().remove() in (ms): 925 417 | org.apache.commons.collections4.list.TreeList.iterator().remove() in (ms): 40 418 | 419 | com.github.coderodde.util.IndexedLinkedList.stream() in (ms): 26 420 | java.util.LinkedList.stream() in (ms): 10 421 | java.util.ArrayList.stream() in (ms): 10 422 | org.apache.commons.collections4.list.TreeList.stream() in (ms): 98 423 | 424 | com.github.coderodde.util.IndexedLinkedList.stream().parallel() in (ms): 59 425 | java.util.LinkedList.stream().parallel() in (ms): 55 426 | java.util.ArrayList.stream().parallel() in (ms): 17 427 | org.apache.commons.collections4.list.TreeList.stream().parallel() in (ms): 205 428 | 429 | com.github.coderodde.util.IndexedLinkedList.removeAll() in (ms): 2 430 | java.util.LinkedList.removeAll() in (ms): 126 431 | java.util.ArrayList.removeAll() in (ms): 2 432 | org.apache.commons.collections4.list.TreeList.removeAll() in (ms): 304 433 | 434 | com.github.coderodde.util.IndexedLinkedList.subList(...).clear() in (ms): 10 435 | java.util.LinkedList.subList(...).clear() in (ms): 39 436 | java.util.ArrayList.subList(...).clear() in (ms): 5 437 | org.apache.commons.collections4.list.TreeList.subList(...).clear() in (ms): 224 438 | 439 | IndexedLinkedList.subList().sort() in (ms): 409 440 | LinkedList.subList().sort() in (ms): 325 441 | ArrayList.subList().sort() in (ms): 271 442 | TreeList.subList().sort() in (ms): 329 443 | 444 | --- Total time elapsed --- 445 | com.github.coderodde.util.IndexedLinkedList in (ms): 856 446 | java.util.LinkedList in (ms): 10320 447 | java.util.ArrayList in (ms): 3643 448 | org.apache.commons.collections4.list.TreeList in (ms): 1553 449 | 450 | === BENCHMARK RUN === 451 | com.github.coderodde.util.IndexedLinkedList.addFirst in (ms): 23 452 | java.util.LinkedList.addFirst in (ms): 3 453 | java.util.ArrayList.addFirst in (ms): 541 454 | org.apache.commons.collections4.list.TreeList.addFirst in (ms): 16 455 | 456 | com.github.coderodde.util.IndexedLinkedList.addLast in (ms): 8 457 | java.util.LinkedList.addLast in (ms): 2 458 | java.util.ArrayList.addLast in (ms): 157 459 | org.apache.commons.collections4.list.TreeList.addLast in (ms): 38 460 | 461 | com.github.coderodde.util.IndexedLinkedList.add(int, E) in (ms): 22 462 | java.util.LinkedList.add(int, E) in (ms): 1977 463 | java.util.ArrayList.add(int, E) in (ms): 112 464 | org.apache.commons.collections4.list.TreeList.add(int, E) in (ms): 7 465 | 466 | com.github.coderodde.util.IndexedLinkedList.addAll(Collection) in (ms): 12 467 | java.util.LinkedList.addAll(Collection) in (ms): 4 468 | java.util.ArrayList.addAll(Collection) in (ms): 4 469 | org.apache.commons.collections4.list.TreeList.addAll(Collection) in (ms): 7 470 | 471 | com.github.coderodde.util.IndexedLinkedList.addAll(int, Collection) in (ms): 11 472 | java.util.LinkedList.addAll(int, Collection) in (ms): 2084 473 | java.util.ArrayList.addAll(int, Collection) in (ms): 91 474 | org.apache.commons.collections4.list.TreeList.addAll(int, Collection) in (ms): 14 475 | 476 | com.github.coderodde.util.IndexedLinkedList.get(int) in (ms): 10 477 | java.util.LinkedList.get(int) in (ms): 2408 478 | java.util.ArrayList.get(int) in (ms): 0 479 | org.apache.commons.collections4.list.TreeList.get(int) in (ms): 1 480 | 481 | com.github.coderodde.util.IndexedLinkedList.removeFirst() in (ms): 8 482 | java.util.LinkedList.removeFirst() in (ms): 0 483 | java.util.ArrayList.removeFirst() in (ms): 210 484 | org.apache.commons.collections4.list.TreeList.removeFirst() in (ms): 11 485 | 486 | com.github.coderodde.util.IndexedLinkedList.removeLast() in (ms): 1 487 | java.util.LinkedList.removeLast() in (ms): 1 488 | java.util.ArrayList.removeLast() in (ms): 0 489 | org.apache.commons.collections4.list.TreeList.removeLast() in (ms): 0 490 | 491 | com.github.coderodde.util.IndexedLinkedList.remove(int) in (ms): 23 492 | java.util.LinkedList.remove(int) in (ms): 4684 493 | java.util.ArrayList.remove(int) in (ms): 202 494 | org.apache.commons.collections4.list.TreeList.remove(int) in (ms): 10 495 | 496 | com.github.coderodde.util.IndexedLinkedList.remove(Object) in (ms): 53 497 | java.util.LinkedList.remove(Object) in (ms): 38 498 | java.util.ArrayList.remove(Object) in (ms): 245 499 | org.apache.commons.collections4.list.TreeList.remove(Object) in (ms): 87 500 | 501 | com.github.coderodde.util.IndexedLinkedList.iterator().add() in (ms): 19 502 | java.util.LinkedList.iterator().add() in (ms): 10 503 | java.util.ArrayList.iterator().add() in (ms): 828 504 | org.apache.commons.collections4.list.TreeList.iterator().add() in (ms): 18 505 | 506 | com.github.coderodde.util.IndexedLinkedList.iterator().remove() in (ms): 10 507 | java.util.LinkedList.iterator().remove() in (ms): 16 508 | java.util.ArrayList.iterator().remove() in (ms): 1265 509 | org.apache.commons.collections4.list.TreeList.iterator().remove() in (ms): 43 510 | 511 | com.github.coderodde.util.IndexedLinkedList.stream() in (ms): 1 512 | java.util.LinkedList.stream() in (ms): 20 513 | java.util.ArrayList.stream() in (ms): 16 514 | org.apache.commons.collections4.list.TreeList.stream() in (ms): 15 515 | 516 | com.github.coderodde.util.IndexedLinkedList.stream().parallel() in (ms): 3 517 | java.util.LinkedList.stream().parallel() in (ms): 110 518 | java.util.ArrayList.stream().parallel() in (ms): 3 519 | org.apache.commons.collections4.list.TreeList.stream().parallel() in (ms): 206 520 | 521 | com.github.coderodde.util.IndexedLinkedList.removeAll() in (ms): 0 522 | java.util.LinkedList.removeAll() in (ms): 103 523 | java.util.ArrayList.removeAll() in (ms): 1 524 | org.apache.commons.collections4.list.TreeList.removeAll() in (ms): 256 525 | 526 | com.github.coderodde.util.IndexedLinkedList.subList(...).clear() in (ms): 4 527 | java.util.LinkedList.subList(...).clear() in (ms): 35 528 | java.util.ArrayList.subList(...).clear() in (ms): 2 529 | org.apache.commons.collections4.list.TreeList.subList(...).clear() in (ms): 235 530 | 531 | IndexedLinkedList.subList().sort() in (ms): 315 532 | LinkedList.subList().sort() in (ms): 321 533 | ArrayList.subList().sort() in (ms): 299 534 | TreeList.subList().sort() in (ms): 302 535 | 536 | --- Total time elapsed --- 537 | com.github.coderodde.util.IndexedLinkedList in (ms): 523 538 | java.util.LinkedList in (ms): 11816 539 | java.util.ArrayList in (ms): 3976 540 | org.apache.commons.collections4.list.TreeList in (ms): 1266 541 | ``` 542 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.2.0 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | # e.g. to debug Maven itself, use 32 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | # ---------------------------------------------------------------------------- 35 | 36 | if [ -z "$MAVEN_SKIP_RC" ] ; then 37 | 38 | if [ -f /usr/local/etc/mavenrc ] ; then 39 | . /usr/local/etc/mavenrc 40 | fi 41 | 42 | if [ -f /etc/mavenrc ] ; then 43 | . /etc/mavenrc 44 | fi 45 | 46 | if [ -f "$HOME/.mavenrc" ] ; then 47 | . "$HOME/.mavenrc" 48 | fi 49 | 50 | fi 51 | 52 | # OS specific support. $var _must_ be set to either true or false. 53 | cygwin=false; 54 | darwin=false; 55 | mingw=false 56 | case "$(uname)" in 57 | CYGWIN*) cygwin=true ;; 58 | MINGW*) mingw=true;; 59 | Darwin*) darwin=true 60 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 61 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 62 | if [ -z "$JAVA_HOME" ]; then 63 | if [ -x "/usr/libexec/java_home" ]; then 64 | JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME 65 | else 66 | JAVA_HOME="/Library/Java/Home"; export JAVA_HOME 67 | fi 68 | fi 69 | ;; 70 | esac 71 | 72 | if [ -z "$JAVA_HOME" ] ; then 73 | if [ -r /etc/gentoo-release ] ; then 74 | JAVA_HOME=$(java-config --jre-home) 75 | fi 76 | fi 77 | 78 | # For Cygwin, ensure paths are in UNIX format before anything is touched 79 | if $cygwin ; then 80 | [ -n "$JAVA_HOME" ] && 81 | JAVA_HOME=$(cygpath --unix "$JAVA_HOME") 82 | [ -n "$CLASSPATH" ] && 83 | CLASSPATH=$(cygpath --path --unix "$CLASSPATH") 84 | fi 85 | 86 | # For Mingw, ensure paths are in UNIX format before anything is touched 87 | if $mingw ; then 88 | [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && 89 | JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" 90 | fi 91 | 92 | if [ -z "$JAVA_HOME" ]; then 93 | javaExecutable="$(which javac)" 94 | if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then 95 | # readlink(1) is not available as standard on Solaris 10. 96 | readLink=$(which readlink) 97 | if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then 98 | if $darwin ; then 99 | javaHome="$(dirname "\"$javaExecutable\"")" 100 | javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" 101 | else 102 | javaExecutable="$(readlink -f "\"$javaExecutable\"")" 103 | fi 104 | javaHome="$(dirname "\"$javaExecutable\"")" 105 | javaHome=$(expr "$javaHome" : '\(.*\)/bin') 106 | JAVA_HOME="$javaHome" 107 | export JAVA_HOME 108 | fi 109 | fi 110 | fi 111 | 112 | if [ -z "$JAVACMD" ] ; then 113 | if [ -n "$JAVA_HOME" ] ; then 114 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 115 | # IBM's JDK on AIX uses strange locations for the executables 116 | JAVACMD="$JAVA_HOME/jre/sh/java" 117 | else 118 | JAVACMD="$JAVA_HOME/bin/java" 119 | fi 120 | else 121 | JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" 122 | fi 123 | fi 124 | 125 | if [ ! -x "$JAVACMD" ] ; then 126 | echo "Error: JAVA_HOME is not defined correctly." >&2 127 | echo " We cannot execute $JAVACMD" >&2 128 | exit 1 129 | fi 130 | 131 | if [ -z "$JAVA_HOME" ] ; then 132 | echo "Warning: JAVA_HOME environment variable is not set." 133 | fi 134 | 135 | # traverses directory structure from process work directory to filesystem root 136 | # first directory with .mvn subdirectory is considered project base directory 137 | find_maven_basedir() { 138 | if [ -z "$1" ] 139 | then 140 | echo "Path not specified to find_maven_basedir" 141 | return 1 142 | fi 143 | 144 | basedir="$1" 145 | wdir="$1" 146 | while [ "$wdir" != '/' ] ; do 147 | if [ -d "$wdir"/.mvn ] ; then 148 | basedir=$wdir 149 | break 150 | fi 151 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 152 | if [ -d "${wdir}" ]; then 153 | wdir=$(cd "$wdir/.." || exit 1; pwd) 154 | fi 155 | # end of workaround 156 | done 157 | printf '%s' "$(cd "$basedir" || exit 1; pwd)" 158 | } 159 | 160 | # concatenates all lines of a file 161 | concat_lines() { 162 | if [ -f "$1" ]; then 163 | # Remove \r in case we run on Windows within Git Bash 164 | # and check out the repository with auto CRLF management 165 | # enabled. Otherwise, we may read lines that are delimited with 166 | # \r\n and produce $'-Xarg\r' rather than -Xarg due to word 167 | # splitting rules. 168 | tr -s '\r\n' ' ' < "$1" 169 | fi 170 | } 171 | 172 | log() { 173 | if [ "$MVNW_VERBOSE" = true ]; then 174 | printf '%s\n' "$1" 175 | fi 176 | } 177 | 178 | BASE_DIR=$(find_maven_basedir "$(dirname "$0")") 179 | if [ -z "$BASE_DIR" ]; then 180 | exit 1; 181 | fi 182 | 183 | MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR 184 | log "$MAVEN_PROJECTBASEDIR" 185 | 186 | ########################################################################################## 187 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 188 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 189 | ########################################################################################## 190 | wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" 191 | if [ -r "$wrapperJarPath" ]; then 192 | log "Found $wrapperJarPath" 193 | else 194 | log "Couldn't find $wrapperJarPath, downloading it ..." 195 | 196 | if [ -n "$MVNW_REPOURL" ]; then 197 | wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 198 | else 199 | wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 200 | fi 201 | while IFS="=" read -r key value; do 202 | # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) 203 | safeValue=$(echo "$value" | tr -d '\r') 204 | case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; 205 | esac 206 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 207 | log "Downloading from: $wrapperUrl" 208 | 209 | if $cygwin; then 210 | wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") 211 | fi 212 | 213 | if command -v wget > /dev/null; then 214 | log "Found wget ... using wget" 215 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" 216 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 217 | wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 218 | else 219 | wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 220 | fi 221 | elif command -v curl > /dev/null; then 222 | log "Found curl ... using curl" 223 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" 224 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 225 | curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 226 | else 227 | curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 228 | fi 229 | else 230 | log "Falling back to using Java to download" 231 | javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" 232 | javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" 233 | # For Cygwin, switch paths to Windows format before running javac 234 | if $cygwin; then 235 | javaSource=$(cygpath --path --windows "$javaSource") 236 | javaClass=$(cygpath --path --windows "$javaClass") 237 | fi 238 | if [ -e "$javaSource" ]; then 239 | if [ ! -e "$javaClass" ]; then 240 | log " - Compiling MavenWrapperDownloader.java ..." 241 | ("$JAVA_HOME/bin/javac" "$javaSource") 242 | fi 243 | if [ -e "$javaClass" ]; then 244 | log " - Running MavenWrapperDownloader.java ..." 245 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" 246 | fi 247 | fi 248 | fi 249 | fi 250 | ########################################################################################## 251 | # End of extension 252 | ########################################################################################## 253 | 254 | # If specified, validate the SHA-256 sum of the Maven wrapper jar file 255 | wrapperSha256Sum="" 256 | while IFS="=" read -r key value; do 257 | case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; 258 | esac 259 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 260 | if [ -n "$wrapperSha256Sum" ]; then 261 | wrapperSha256Result=false 262 | if command -v sha256sum > /dev/null; then 263 | if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then 264 | wrapperSha256Result=true 265 | fi 266 | elif command -v shasum > /dev/null; then 267 | if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then 268 | wrapperSha256Result=true 269 | fi 270 | else 271 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." 272 | echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." 273 | exit 1 274 | fi 275 | if [ $wrapperSha256Result = false ]; then 276 | echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 277 | echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 278 | echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 279 | exit 1 280 | fi 281 | fi 282 | 283 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 284 | 285 | # For Cygwin, switch paths to Windows format before running java 286 | if $cygwin; then 287 | [ -n "$JAVA_HOME" ] && 288 | JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") 289 | [ -n "$CLASSPATH" ] && 290 | CLASSPATH=$(cygpath --path --windows "$CLASSPATH") 291 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 292 | MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") 293 | fi 294 | 295 | # Provide a "standardized" way to retrieve the CLI args that will 296 | # work with both Windows and non-Windows executions. 297 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" 298 | export MAVEN_CMD_LINE_ARGS 299 | 300 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 301 | 302 | # shellcheck disable=SC2086 # safe args 303 | exec "$JAVACMD" \ 304 | $MAVEN_OPTS \ 305 | $MAVEN_DEBUG_OPTS \ 306 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 307 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 308 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 309 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Apache Maven Wrapper startup batch script, version 3.2.0 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 28 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 29 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 30 | @REM e.g. to debug Maven itself, use 31 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 32 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 33 | @REM ---------------------------------------------------------------------------- 34 | 35 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 36 | @echo off 37 | @REM set title of command window 38 | title %0 39 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 40 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 41 | 42 | @REM set %HOME% to equivalent of $HOME 43 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 44 | 45 | @REM Execute a user defined script before this one 46 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 47 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 48 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 49 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 50 | :skipRcPre 51 | 52 | @setlocal 53 | 54 | set ERROR_CODE=0 55 | 56 | @REM To isolate internal variables from possible post scripts, we use another setlocal 57 | @setlocal 58 | 59 | @REM ==== START VALIDATION ==== 60 | if not "%JAVA_HOME%" == "" goto OkJHome 61 | 62 | echo. 63 | echo Error: JAVA_HOME not found in your environment. >&2 64 | echo Please set the JAVA_HOME variable in your environment to match the >&2 65 | echo location of your Java installation. >&2 66 | echo. 67 | goto error 68 | 69 | :OkJHome 70 | if exist "%JAVA_HOME%\bin\java.exe" goto init 71 | 72 | echo. 73 | echo Error: JAVA_HOME is set to an invalid directory. >&2 74 | echo JAVA_HOME = "%JAVA_HOME%" >&2 75 | echo Please set the JAVA_HOME variable in your environment to match the >&2 76 | echo location of your Java installation. >&2 77 | echo. 78 | goto error 79 | 80 | @REM ==== END VALIDATION ==== 81 | 82 | :init 83 | 84 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 85 | @REM Fallback to current working directory if not found. 86 | 87 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 88 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 89 | 90 | set EXEC_DIR=%CD% 91 | set WDIR=%EXEC_DIR% 92 | :findBaseDir 93 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 94 | cd .. 95 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 96 | set WDIR=%CD% 97 | goto findBaseDir 98 | 99 | :baseDirFound 100 | set MAVEN_PROJECTBASEDIR=%WDIR% 101 | cd "%EXEC_DIR%" 102 | goto endDetectBaseDir 103 | 104 | :baseDirNotFound 105 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 106 | cd "%EXEC_DIR%" 107 | 108 | :endDetectBaseDir 109 | 110 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 111 | 112 | @setlocal EnableExtensions EnableDelayedExpansion 113 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 114 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 115 | 116 | :endReadAdditionalConfig 117 | 118 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 123 | 124 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 125 | IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | if "%MVNW_VERBOSE%" == "true" ( 132 | echo Found %WRAPPER_JAR% 133 | ) 134 | ) else ( 135 | if not "%MVNW_REPOURL%" == "" ( 136 | SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 137 | ) 138 | if "%MVNW_VERBOSE%" == "true" ( 139 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 140 | echo Downloading from: %WRAPPER_URL% 141 | ) 142 | 143 | powershell -Command "&{"^ 144 | "$webclient = new-object System.Net.WebClient;"^ 145 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 146 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 147 | "}"^ 148 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ 149 | "}" 150 | if "%MVNW_VERBOSE%" == "true" ( 151 | echo Finished downloading %WRAPPER_JAR% 152 | ) 153 | ) 154 | @REM End of extension 155 | 156 | @REM If specified, validate the SHA-256 sum of the Maven wrapper jar file 157 | SET WRAPPER_SHA_256_SUM="" 158 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 159 | IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B 160 | ) 161 | IF NOT %WRAPPER_SHA_256_SUM%=="" ( 162 | powershell -Command "&{"^ 163 | "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ 164 | "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ 165 | " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ 166 | " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ 167 | " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ 168 | " exit 1;"^ 169 | "}"^ 170 | "}" 171 | if ERRORLEVEL 1 goto error 172 | ) 173 | 174 | @REM Provide a "standardized" way to retrieve the CLI args that will 175 | @REM work with both Windows and non-Windows executions. 176 | set MAVEN_CMD_LINE_ARGS=%* 177 | 178 | %MAVEN_JAVA_EXE% ^ 179 | %JVM_CONFIG_MAVEN_PROPS% ^ 180 | %MAVEN_OPTS% ^ 181 | %MAVEN_DEBUG_OPTS% ^ 182 | -classpath %WRAPPER_JAR% ^ 183 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 184 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 185 | if ERRORLEVEL 1 goto error 186 | goto end 187 | 188 | :error 189 | set ERROR_CODE=1 190 | 191 | :end 192 | @endlocal & set ERROR_CODE=%ERROR_CODE% 193 | 194 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 195 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 196 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 197 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 198 | :skipRcPost 199 | 200 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 201 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 202 | 203 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 204 | 205 | cmd /C exit /B %ERROR_CODE% 206 | -------------------------------------------------------------------------------- /nbactions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | run 5 | 6 | jar 7 | 8 | 9 | process-classes 10 | org.codehaus.mojo:exec-maven-plugin:3.0.0:exec 11 | 12 | 13 | ${exec.vmArgs} -classpath %classpath ${exec.mainClass} ${exec.appArgs} 14 | java 15 | com.github.coderodde.util.benchmark.LinkedListBenchmark2 16 | -Xms1000m -Xmx1000m 17 | --all 18 | 19 | 20 | 21 | debug 22 | 23 | jar 24 | 25 | 26 | process-classes 27 | org.codehaus.mojo:exec-maven-plugin:3.0.0:exec 28 | 29 | 30 | ${exec.vmArgs} -classpath %classpath ${exec.mainClass} ${exec.appArgs} 31 | java 32 | true 33 | com.github.coderodde.util.benchmark.LinkedListBenchmark2 34 | <<<<<<< HEAD 35 | -Xms1000m -Xmx1000m -agentlib:jdwp=transport=dt_socket,server=n,address=${jpda.address} 36 | ======= 37 | -Xms3000m -Xmx3000m -agentlib:jdwp=transport=dt_socket,server=n,address=${jpda.address} 38 | >>>>>>> e5fa429ccefb73592677fea194382d9468d6346e 39 | --all 40 | 41 | 42 | 43 | profile 44 | 45 | jar 46 | 47 | 48 | process-classes 49 | org.codehaus.mojo:exec-maven-plugin:3.0.0:exec 50 | 51 | 52 | -Xms1000m -Xmx1000m 53 | ${exec.vmArgs} -classpath %classpath ${exec.mainClass} ${exec.appArgs} 54 | com.github.coderodde.util.benchmark.LinkedListBenchmark2 55 | java 56 | --all 57 | 58 | 59 | 60 | CUSTOM-Coverage 61 | Coverage 62 | 63 | cobertura:cobertura 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | fi.helsinki 5 | IndexedLinkedList 6 | 1.618033988 7 | jar 8 | 9 | IndexedLinkedList 10 | An efficient, heuristic, indexeded linked-list for versatile usage. 11 | https://github.com/coderodde/IndexedLinkedList 12 | 13 | 14 | UTF-8 15 | 8 16 | 8 17 | 18 | 19 | 20 | 21 | MIT License 22 | http://www.opensource.org/licenses/mit-license.php 23 | 24 | 25 | 26 | 27 | 28 | Rodion Efremov 29 | rodion.efremov@helsinki.fi 30 | University of Helsinki, Department of Computer Science 31 | github.com/coderodde 32 | 33 | 34 | 35 | 36 | scm:git:git://github.com/coderodde/IndexedLinkedList.git 37 | scm:git:ssh://github.com:coderodde/IndexedLinkedList.git 38 | http://github.com/coderodde/IndexedLinkedList 39 | 40 | 41 | 42 | 43 | org.apache.logging.log4j 44 | log4j-api 45 | 2.7 46 | 47 | 48 | 49 | org.apache.logging.log4j 50 | log4j-core 51 | 2.7 52 | 53 | 54 | 55 | org.apache.logging.log4j 56 | log4j-slf4j-impl 57 | 2.7 58 | 59 | 60 | 61 | net.openhft 62 | affinity 63 | 3.23.3 64 | 65 | 66 | 67 | junit 68 | junit 69 | 4.13.2 70 | test 71 | 72 | 73 | 74 | org.hamcrest 75 | hamcrest-core 76 | 1.3 77 | test 78 | 79 | 80 | 81 | org.apache.commons 82 | commons-collections4 83 | 4.4 84 | 85 | 86 | 87 | 88 | 89 | ossrh 90 | https://oss.sonatype.org/content/repositories/snapshots 91 | 92 | 93 | ossrh 94 | https://oss.sonatype.org/service/local/staging/deploy/maven2 95 | 96 | 97 | 98 | 99 | 115 | 142 | 143 | 144 | org.apache.maven.plugins 145 | maven-surefire-plugin 146 | 3.1.2 147 | 148 | 149 | 150 | org.apache.maven.surefire 151 | surefire-junit4 152 | 3.1.2 153 | 154 | 155 | 156 | 157 | 158 | org.apache.maven.plugins 159 | maven-compiler-plugin 160 | 3.11.0 161 | 162 | 163 | -g 164 | 165 | 8 166 | 8 167 | true 168 | lines,vars,source 169 | 170 | 171 | 172 | 173 | org.apache.maven.plugins 174 | maven-source-plugin 175 | 3.3.0 176 | 177 | 178 | attach-sources 179 | 180 | jar-no-fork 181 | 182 | 183 | 184 | 185 | 186 | 187 | org.apache.maven.plugins 188 | maven-javadoc-plugin 189 | 3.6.3 190 | 191 | private 192 | 193 | 194 | 195 | 196 | 197 | com.github.coderodde.util.benchmark 198 | 199 | 200 | 201 | attach-javadocs 202 | 203 | jar 204 | 205 | 206 | 207 | 208 | 209 | 210 | org.jacoco 211 | jacoco-maven-plugin 212 | 0.8.10 213 | 214 | 215 | 216 | 217 | prepare-agent 218 | 219 | 220 | 221 | 222 | jacoco-report 223 | test 224 | 225 | report 226 | 227 | 228 | 229 | 230 | 231 | 232 | **/com/github/coderodde/util/benchmark/LinkedListBenchmarkRunner.class 233 | **/com/github/coderodde/util/benchmark/LinkedListBenchmark.class 234 | **/com/github/coderodde/util/benchmark/LinkedListBenchmark$BenchmarkChoice.class 235 | **/com/github/coderodde/util/Finger.class 236 | **/com/github/coderodde/util/Node.class 237 | 238 | 239 | 240 | 241 | 242 | org.apache.maven.plugins 243 | maven-dependency-plugin 244 | 3.6.0 245 | 246 | 247 | copy-dependencies 248 | prepare-package 249 | 250 | copy-dependencies 251 | 252 | 253 | 254 | ${project.build.directory}/libs 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | org.apache.maven.plugins 263 | maven-jar-plugin 264 | 3.3.0 265 | 266 | 267 | 268 | true 269 | libs/ 270 | 271 | com.github.coderodde.util.benchmark.LinkedListBenchmark2 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | -------------------------------------------------------------------------------- /src/main/java/com/github/coderodde/util/Finger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2024 rodio. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.coderodde.util; 25 | 26 | import java.util.Objects; 27 | 28 | /** 29 | * This static inner class implements the actual finger. 30 | * 31 | * @param the type of the list's satellite data. 32 | */ 33 | final class Finger { 34 | 35 | /** 36 | * The pointed to {@link Node}. 37 | */ 38 | Node node; 39 | 40 | /** 41 | * The index at which the {@code node} appears in the list. 42 | */ 43 | int index; 44 | 45 | /** 46 | * Constructs a new {@link Finger}. 47 | * 48 | * @param node the pointed node. 49 | * @param index the index of {@code node} in the actual list. 50 | */ 51 | Finger(Node node, int index) { 52 | this.node = node; 53 | this.index = index; 54 | } 55 | 56 | /** 57 | * Copy constructs this finger. 58 | * 59 | * @param finger the finger whose state to copy. 60 | */ 61 | Finger(Finger finger) { 62 | this.node = finger.node; 63 | this.index = finger.index; 64 | } 65 | 66 | /** 67 | * Returns the index of this finger. Used for research. 68 | * 69 | * @return the index of this finger. 70 | */ 71 | public int getIndex() { 72 | return index; 73 | } 74 | 75 | @Override 76 | public boolean equals(Object o) { 77 | if (o == null) { 78 | return false; 79 | } 80 | 81 | if (o == this) { 82 | return true; 83 | } 84 | 85 | if (!o.getClass().equals(this.getClass())) { 86 | return false; 87 | } 88 | 89 | final Finger other = (Finger) o; 90 | 91 | return Objects.equals(index, other.index) 92 | && Objects.equals(node, other.node); 93 | } 94 | 95 | /** 96 | * Returns the textual representation of this finger. 97 | * 98 | * @return the textual representation. 99 | */ 100 | @Override 101 | public String toString() { 102 | return "[Finger; index = " + index 103 | + ", item = " + ((node == null) ? "null" : node.item) 104 | + "]"; 105 | } 106 | 107 | /** 108 | * Moves this finger {@code steps} position to the left. 109 | * 110 | * @param steps the number of steps to rewind. 111 | */ 112 | void rewindLeft(int steps) { 113 | for (int i = 0; i < steps; i++) { 114 | node = node.prev; 115 | } 116 | 117 | index -= steps; 118 | } 119 | 120 | /** 121 | * Moves this finger {@code steps} position to the right. 122 | * 123 | * @param steps the number of steps to rewind. 124 | */ 125 | void rewindRight(int steps) { 126 | for (int i = 0; i < steps; i++) { 127 | node = node.next; 128 | } 129 | 130 | index += steps; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/com/github/coderodde/util/FingerList.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2025 Rodion Efremov 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.coderodde.util; 25 | 26 | import java.util.Arrays; 27 | import java.util.Objects; 28 | 29 | /** 30 | * This inner class implements the finger list data structure for managing list 31 | * fingers. 32 | * 33 | * @param the list node item type. 34 | */ 35 | final class FingerList { 36 | 37 | /** 38 | * The owner indexed linked list. 39 | */ 40 | private final IndexedLinkedList list; 41 | 42 | /** 43 | * This is also the minimum capacity. 44 | */ 45 | private static final int INITIAL_CAPACITY = 8; 46 | 47 | /** 48 | * The actual list storage array. 49 | */ 50 | Finger[] fingerArray = new Finger[INITIAL_CAPACITY]; 51 | 52 | /** 53 | * Constructs this finger list setting it to empty. 54 | * 55 | * @param list the owner list. 56 | */ 57 | FingerList(IndexedLinkedList list) { 58 | this.list = list; 59 | this.fingerArray[0] = new Finger<>(null, 0); 60 | } 61 | 62 | @Override 63 | public boolean equals(Object o) { 64 | if (o == null) { 65 | return false; 66 | } 67 | 68 | if (o == this) { 69 | return true; 70 | } 71 | 72 | if (!o.getClass().equals(this.getClass())) { 73 | return false; 74 | } 75 | 76 | final FingerList other = (FingerList) o; 77 | 78 | if (size != other.size) { 79 | return false; 80 | } 81 | 82 | for (int i = 0; i < size; i++) { 83 | if (!Objects.equals(fingerArray[i], other.fingerArray[i])) { 84 | return false; 85 | } 86 | } 87 | 88 | return true; 89 | } 90 | 91 | @Override 92 | public String toString() { 93 | StringBuilder sb = new StringBuilder().append("["); 94 | boolean first = true; 95 | 96 | for (int i = 0; i != size; i++) { 97 | if (first) { 98 | first = false; 99 | } else { 100 | sb.append(", "); 101 | } 102 | 103 | sb.append(fingerArray[i].toString()); 104 | } 105 | 106 | return sb.append("]").toString(); 107 | } 108 | 109 | /** 110 | * The number of fingers stored in the list. This field does not count the 111 | * end-of-list sentinel finger {@code F} for which {@code F.index = size}. 112 | */ 113 | int size; 114 | 115 | /** 116 | * Adjusts the finger list after removing the first finger. 117 | */ 118 | void adjustOnRemoveFirst() { 119 | int lastPrefixIndex = Integer.MAX_VALUE; 120 | 121 | for (int i = 0; i < size; ++i) { 122 | Finger finger = fingerArray[i]; 123 | 124 | if (finger.index != i) { 125 | lastPrefixIndex = i; 126 | break; 127 | } else { 128 | finger.node = finger.node.next; 129 | } 130 | } 131 | 132 | shiftFingerIndicesToLeftOnceAll(lastPrefixIndex); 133 | } 134 | 135 | /** 136 | * Appends the input finger to the tail of the finger list. 137 | * 138 | * @param finger the finger to append. 139 | */ 140 | void appendFinger(Finger finger) { 141 | size++; 142 | enlargeFingerArrayIfNeeded(size + 1); 143 | fingerArray[size] = fingerArray[size - 1]; 144 | fingerArray[size - 1] = finger; 145 | fingerArray[size].index = list.size; 146 | } 147 | 148 | /** 149 | * Pushes {@code numberOfFingersToMoveToPrefix} fingers to the prefix with 150 | * {@code numberOfFingersInPrefix} fingers. 151 | * 152 | * @param fromIndex the index of the leftmost element to 153 | * remove. 154 | * @param numberOfFingersInPrefix the number of fingers already in the 155 | * prefix. 156 | * @param numberOfFingersToMoveToPrefix the number of fingers we need to 157 | * move to the prefix. 158 | */ 159 | void arrangePrefix(int fromIndex, 160 | int numberOfFingersInPrefix, 161 | int numberOfFingersToMoveToPrefix) { 162 | 163 | makeRoomAtPrefix(fromIndex, 164 | numberOfFingersInPrefix, 165 | numberOfFingersToMoveToPrefix); 166 | 167 | pushCoveredFingersToPrefix(fromIndex, 168 | numberOfFingersInPrefix, 169 | numberOfFingersToMoveToPrefix); 170 | } 171 | 172 | void arrangeSuffix(int toIndex, 173 | int toFingerIndex, 174 | int numberOfSuffixFingers, 175 | int numberOfFingetsToPush) { 176 | 177 | makeRoomAtSuffix(toIndex, 178 | toFingerIndex, 179 | numberOfSuffixFingers, 180 | numberOfFingetsToPush); 181 | 182 | pushCoveredFingersToSuffix(toIndex, 183 | numberOfSuffixFingers, 184 | numberOfFingetsToPush); 185 | } 186 | 187 | /** 188 | * Clears entirely this finger list. Only the end-of-finger-list finger 189 | * remains in the finger list. Not {@code private} since is used in the unit 190 | * tests. 191 | */ 192 | void clear() { 193 | Arrays.fill(fingerArray, 0, size, null); 194 | fingerArray = new Finger[INITIAL_CAPACITY]; 195 | fingerArray[0] = new Finger<>(null, 0); 196 | size = 0; 197 | } 198 | 199 | /** 200 | * Contracts the finger array, if possible. The {@code nextSize} defines the 201 | * requested finger array size not counting the end-of-finger-list sentinel 202 | * finger. 203 | * 204 | * @param nextSize the requested size not counting the end-of-finger-list 205 | * sentinel finger. 206 | */ 207 | void contractFingerArrayIfNeeded(int nextSize) { 208 | // Can we contract at least once? 209 | if ((nextSize + 1) * 4 < fingerArray.length 210 | && fingerArray.length > INITIAL_CAPACITY) { 211 | 212 | int nextCapacity = fingerArray.length / 2; 213 | 214 | // Good, we can. But can we keep on splitting in half the 215 | // capacity any further? 216 | while (nextCapacity >= 2 * (nextSize + 1) 217 | && nextCapacity > INITIAL_CAPACITY) { 218 | // Yes, we can do it as well. 219 | nextCapacity /= 2; 220 | } 221 | 222 | fingerArray = Arrays.copyOf(fingerArray, nextCapacity); 223 | } 224 | } 225 | 226 | /** 227 | * Enlarges the finger array so that it can accommodate 228 | * {@code requestedSize} fingers. 229 | * 230 | * @param requestedSize the requested size, including the end-of-finger-list 231 | * sentinel finger. 232 | */ 233 | private void enlargeFingerArrayIfNeeded(int requestedSize) { 234 | int nextCapacity = fingerArray.length; 235 | 236 | while (requestedSize > nextCapacity) { 237 | nextCapacity *= 2; 238 | } 239 | 240 | if (nextCapacity != fingerArray.length) { 241 | fingerArray = Arrays.copyOf(fingerArray, nextCapacity); 242 | } 243 | } 244 | 245 | /** 246 | * Returns {@code index}th finger. 247 | * 248 | * @param index the index of the target finger. 249 | * @return the {@code index}th finger. 250 | */ 251 | Finger getFinger(int index) { 252 | return fingerArray[index]; 253 | } 254 | 255 | /** 256 | * Returns the index of the finger that is closest to the 257 | * {@code elementIndex}th list element. 258 | * 259 | * @param elementIndex the target element index. 260 | * @return the index of the finger that is closest to the 261 | * {@code elementIndex}th element. 262 | */ 263 | int getClosestFingerIndex(int elementIndex) { 264 | return normalize(getFingerIndexImpl(elementIndex), elementIndex); 265 | } 266 | 267 | /** 268 | * Returns the finger index {@code i}, such that 269 | * {@code fingerArray[i].index} is no less than {@code elementIndex}, and 270 | * {@code fingerArray[i].index} is closest to {@code elementIndex}. This 271 | * algorithm is translated from 272 | * C++ 273 | * lower_bound algorithm. 274 | * 275 | * @param elementIndex the target element index. 276 | * @return the index of the finger {@code f}, for which 277 | * {@code elementIndex <= f.index} and {@code f} is the leftmost such 278 | * finger. 279 | */ 280 | int getFingerIndexImpl(int elementIndex) { 281 | int count = size + 1; // + 1 for the end sentinel. 282 | int idx = 0; 283 | 284 | while (count > 0) { 285 | int it = idx; 286 | int step = count / 2; 287 | it += step; 288 | 289 | if (fingerArray[it].index < elementIndex) { 290 | idx = ++it; 291 | count -= step + 1; 292 | } else { 293 | count = step; 294 | } 295 | } 296 | 297 | return idx; 298 | } 299 | 300 | /** 301 | * Access the {@code index}th node without modifying the fingers unlike 302 | * {@link #getNode(int)}. 303 | * 304 | * @param index the index of the desired node. 305 | * 306 | * @return the {@code index}th node. 307 | */ 308 | Node getNodeNoFingersFix(int index) { 309 | Finger finger = fingerArray[getClosestFingerIndex(index)]; 310 | int steps = finger.index - index; 311 | Node node = finger.node; 312 | 313 | if (steps > 0) { 314 | for (int i = 0; i < steps; i++) { 315 | node = node.prev; 316 | } 317 | } else { 318 | for (int i = 0; i < -steps; i++) { 319 | node = node.next; 320 | } 321 | } 322 | 323 | return node; 324 | } 325 | 326 | /** 327 | * Returns the {@code i}th node of this linked list. The closest finger is 328 | * updated to point to the returned node. 329 | * 330 | * @param elementIndex the element index. 331 | * @return the {@code index}th node in the linked list. 332 | */ 333 | Node getNode(int elementIndex) { 334 | if (size < 3) { 335 | // We need at least 3 fingers to do the actual trick: 336 | return list.getNodeSequentially(elementIndex); 337 | } 338 | 339 | int fingerIndex = getFingerIndexImpl(elementIndex); 340 | 341 | if (fingerIndex == 0) { 342 | // There is no required preceding finger: 343 | return getPrefixNode(elementIndex); 344 | } 345 | 346 | if (fingerIndex >= size - 1) { 347 | return getSuffixNode(elementIndex); 348 | } 349 | 350 | Finger a = fingerArray[fingerIndex - 1]; 351 | Finger b = fingerArray[fingerIndex]; 352 | Finger c = fingerArray[fingerIndex + 1]; 353 | 354 | int diff = c.index - a.index; 355 | int step = diff / 2; 356 | int saveBIndex = b.index; 357 | int nextBIndex = a.index + step; 358 | 359 | b.index = nextBIndex; 360 | 361 | // Rewind the finger b node: 362 | if (saveBIndex < nextBIndex) { 363 | for (int i = 0; i != nextBIndex - saveBIndex; i++) { 364 | b.node = b.node.next; 365 | } 366 | } else { 367 | // Here, 'saveBIndex >= nextBIndex': 368 | for (int i = 0; i != saveBIndex - nextBIndex; i++) { 369 | b.node = b.node.prev; 370 | } 371 | } 372 | 373 | // Go fetch the correct node: 374 | if (elementIndex < nextBIndex) { 375 | // Here, the desired element is between a and b: 376 | int leftDistance = elementIndex - a.index; 377 | int rightDistance = b.index - elementIndex; 378 | 379 | if (leftDistance < rightDistance) { 380 | Node node = a.node; 381 | 382 | for (int i = 0; i != leftDistance; i++) { 383 | node = node.next; 384 | } 385 | 386 | return node; 387 | } else { 388 | Node node = b.node; 389 | // TODO: Replace saveBIndex - elementIndex with rightDistance? 390 | for (int i = 0; i != rightDistance; i++) { 391 | node = node.prev; 392 | } 393 | 394 | return node; 395 | } 396 | } else { 397 | // Here, the desired element is between c and b: 398 | int leftDistance = elementIndex - b.index; 399 | int rightDistance = c.index - elementIndex; 400 | 401 | if (leftDistance < rightDistance) { 402 | Node node = b.node; 403 | 404 | for (int i = 0; i != leftDistance; i++) { 405 | node = node.next; 406 | } 407 | 408 | return node; 409 | } else { 410 | Node node = c.node; 411 | 412 | for (int i = 0; i != rightDistance; i++) { 413 | node = node.prev; 414 | } 415 | 416 | return node; 417 | } 418 | } 419 | } 420 | 421 | /** 422 | * Normalizes the first finger and returns the {@code elementIndex}th node. 423 | * 424 | * @param elementIndex the index of the desired element. 425 | * 426 | * @return the node corresponding to the {@code elementIndex}th position. 427 | */ 428 | private Node getPrefixNode(int elementIndex) { 429 | Finger a = fingerArray[0]; 430 | Finger b = fingerArray[1]; 431 | Node aNode = a.node; 432 | 433 | // Put a between b and the beginning of the list: 434 | int nextAIndex = b.index / 2; 435 | int saveAIndex = a.index; 436 | 437 | a.index = nextAIndex; 438 | 439 | if (saveAIndex < nextAIndex) { 440 | // Here, we need to rewind to the right: 441 | for (int i = saveAIndex; i < nextAIndex; i++) { 442 | aNode = aNode.next; 443 | } 444 | } else { 445 | // Once here, 'saveAIndex >= nextAIndex'. 446 | // We need to rewind to the left: 447 | for (int i = nextAIndex; i < saveAIndex; i++) { 448 | aNode = aNode.prev; 449 | } 450 | } 451 | 452 | a.node = aNode; 453 | 454 | // Go get the proper node: 455 | if (elementIndex < nextAIndex) { 456 | // Here, the desired element is between the head of the list and 457 | // the very first figner: 458 | int leftDistance = elementIndex; 459 | int rightDistance = nextAIndex - elementIndex; 460 | 461 | if (leftDistance < rightDistance) { 462 | Node node = (Node) list.head; 463 | 464 | for (int i = 0; i != elementIndex; i++) { 465 | node = node.next; 466 | } 467 | 468 | return node; 469 | } else { 470 | Node node = aNode; 471 | 472 | for (int i = 0; i != rightDistance; i++) { 473 | node = node.prev; 474 | } 475 | 476 | return node; 477 | } 478 | } else { 479 | return aNode; 480 | } 481 | } 482 | 483 | /** 484 | * Returns the {@code elementIndex}th node and normalizes the last finger. 485 | * 486 | * @param elementIndex the index of the desired element. 487 | * 488 | * @return the {@code elementIndex}th node. 489 | */ 490 | private Node getSuffixNode(int elementIndex) { 491 | Finger a = fingerArray[size - 2]; 492 | Finger b = fingerArray[size - 1]; 493 | Node bNode = b.node; 494 | 495 | int saveBIndex = b.index; 496 | int nextBIndex = (a.index + list.size) / 2; 497 | 498 | b.index = nextBIndex; 499 | 500 | // Rewind the finger 'b' to between 'a' and tail: 501 | if (saveBIndex < nextBIndex) { 502 | int distance = nextBIndex - saveBIndex; 503 | 504 | for (int i = 0; i != distance; i++) { 505 | bNode = bNode.next; 506 | } 507 | } else { 508 | // Here, 'nextBIndex <= saveBIndex': 509 | int distance = saveBIndex - nextBIndex; 510 | 511 | for (int i = 0; i != distance; i++) { 512 | bNode = bNode.prev; 513 | } 514 | } 515 | 516 | b.node = bNode; 517 | 518 | // Go get the proper node: 519 | if (elementIndex < nextBIndex) { 520 | // Here, the desired element node is between 'a' and 'b': 521 | int leftDistance = elementIndex - a.index; 522 | int rightDistance = nextBIndex - elementIndex; 523 | 524 | if (leftDistance < rightDistance) { 525 | Node node = a.node; 526 | 527 | for (int i = 0; i != leftDistance; i++) { 528 | node = node.next; 529 | } 530 | 531 | return node; 532 | } else { 533 | Node node = b.node; 534 | 535 | for (int i = 0; i != rightDistance; i++) { 536 | node = node.prev; 537 | } 538 | 539 | return node; 540 | } 541 | } else { 542 | // Here, the desired element node is between 'b' and the tail 543 | // node of the list: 544 | int leftDistance = elementIndex - nextBIndex; 545 | int rightDistance = list.size - elementIndex - 1; 546 | 547 | if (leftDistance < rightDistance) { 548 | // Once here, rewind the node reference from bNode to the 549 | // right: 550 | Node node = bNode; 551 | 552 | for (int i = 0; i != leftDistance; i++) { 553 | node = node.next; 554 | } 555 | 556 | return node; 557 | } else { 558 | // Once here, rewind the node reference from tail to the 559 | // left: 560 | Node node = list.tail; 561 | 562 | for (int i = 0; i != rightDistance; i++) { 563 | node = node.prev; 564 | } 565 | 566 | return node; 567 | } 568 | } 569 | } 570 | 571 | /** 572 | * Inserts the input finger into the finger list such that the entire finger 573 | * list is sorted by indices. 574 | * 575 | * @param finger the finger to insert. 576 | */ 577 | void insertFingerAndShiftOnceToRight(Finger finger) { 578 | enlargeFingerArrayIfNeeded(size + 2); 579 | int beforeFingerIndex = getFingerIndexImpl(finger.index); 580 | System.arraycopy( 581 | fingerArray, 582 | beforeFingerIndex, 583 | fingerArray, 584 | beforeFingerIndex + 1, 585 | size + 1 - beforeFingerIndex); 586 | 587 | ++size; 588 | 589 | // Shift fingerArray[beforeFingerIndex + 1 ... size] one position to 590 | // the right (towards larger index values): 591 | shiftFingerIndicesToRightOnce(beforeFingerIndex + 1); 592 | fingerArray[beforeFingerIndex] = finger; 593 | } 594 | 595 | /** 596 | * Make sure we can insert {@code roomSize} fingers starting from 597 | * {@code fingerIndex}, shifting all the fingers starting from 598 | * {@code numberOfNodes} to the right. 599 | * 600 | * @param fingerIndex the finger index of the first finger in the shifted 601 | * finger slice. 602 | * @param roomSize the number of free spots requested. 603 | * @param numberOfNodes the shift amount of the moved fingers. 604 | */ 605 | void makeRoomAtIndex(int fingerIndex, 606 | int roomSize, 607 | int numberOfNodes) { 608 | 609 | shiftFingerIndicesToRight(fingerIndex, numberOfNodes); 610 | size += roomSize; 611 | enlargeFingerArrayIfNeeded(size + 1); // +1 for the end of list 612 | // sentinel. 613 | System.arraycopy(fingerArray, 614 | fingerIndex, 615 | fingerArray, 616 | fingerIndex + roomSize, 617 | size - roomSize - fingerIndex + 1); 618 | } 619 | 620 | void makeRoomAtPrefix(int fromIndex, 621 | int numberOfFingersInPrefix, 622 | int numberOfFingersToMoveToPrefix) { 623 | 624 | if (numberOfFingersToMoveToPrefix <= 0) { 625 | // System.out.println("no: " + numberOfFingersToMoveToPrefix); 626 | // return; 627 | } 628 | 629 | if (numberOfFingersInPrefix == 0) { 630 | // Here, no fingers in the prefix to move. 631 | return; 632 | } 633 | 634 | int targetFingerIndex = numberOfFingersInPrefix - 1; 635 | int freeFingerSpotsSoFar = fromIndex 636 | - getFinger(targetFingerIndex).index 637 | - 1; 638 | 639 | if (freeFingerSpotsSoFar >= numberOfFingersToMoveToPrefix) { 640 | return; 641 | } 642 | 643 | for (; targetFingerIndex > 0; targetFingerIndex--) { 644 | Finger finger1 = getFinger(targetFingerIndex - 1); 645 | Finger finger2 = getFinger(targetFingerIndex); 646 | 647 | int distance = finger2.index 648 | - finger1.index 649 | - 1; 650 | 651 | freeFingerSpotsSoFar += distance; 652 | 653 | if (freeFingerSpotsSoFar >= numberOfFingersToMoveToPrefix) { 654 | break; 655 | } 656 | } 657 | 658 | if (freeFingerSpotsSoFar < numberOfFingersToMoveToPrefix) { 659 | // Once here, we need to move the leftmost prefix finger to the 660 | // left. 661 | int index = fromIndex 662 | - numberOfFingersInPrefix 663 | - numberOfFingersToMoveToPrefix; 664 | 665 | Node node = getNodeNoFingersFix(index); 666 | 667 | for (int i = 0; i < numberOfFingersInPrefix; i++) { 668 | Finger finger = getFinger(i); 669 | finger.index = index++; 670 | finger.node = node; 671 | node = node.next; 672 | } 673 | } else { 674 | Finger startFinger = getFinger(targetFingerIndex - 1); 675 | int index = startFinger.index; 676 | Node node = startFinger.node; 677 | 678 | for (int i = targetFingerIndex; i < numberOfFingersInPrefix; i++) { 679 | Finger finger = getFinger(i); 680 | node = node.next; 681 | finger.node = node; 682 | finger.index = ++index; 683 | } 684 | } 685 | } 686 | 687 | void makeRoomAtSuffix(int toIndex, 688 | int toFingerIndex, 689 | int numberOfFingersInSuffix, 690 | int numberOfFingersToMoveToSuffix) { 691 | 692 | if (numberOfFingersInSuffix == 0) { 693 | // Here, no fingers in the suffix to move. 694 | return; 695 | } 696 | 697 | int targetFingerIndex = size - numberOfFingersInSuffix; 698 | int freeFingerSpotsSoFar = getFinger(targetFingerIndex).index 699 | - toIndex; 700 | 701 | if (freeFingerSpotsSoFar >= numberOfFingersToMoveToSuffix) { 702 | return; 703 | } 704 | 705 | for (; targetFingerIndex < size - 1; targetFingerIndex++) { 706 | Finger finger1 = getFinger(targetFingerIndex); 707 | Finger finger2 = getFinger(targetFingerIndex + 1); 708 | 709 | int distance = finger2.index 710 | - finger1.index 711 | - 1; 712 | 713 | freeFingerSpotsSoFar += distance; 714 | 715 | if (freeFingerSpotsSoFar >= numberOfFingersToMoveToSuffix) { 716 | break; 717 | } 718 | } 719 | 720 | if (freeFingerSpotsSoFar < numberOfFingersToMoveToSuffix) { 721 | // Once here, we need to move the rightmost suffix finger to the 722 | // right. 723 | int index = list.size 724 | - numberOfFingersInSuffix; 725 | 726 | Node node = getNodeNoFingersFix(index); 727 | 728 | for (int i = 0; i < numberOfFingersInSuffix; i++) { 729 | Finger finger = 730 | getFinger(size - numberOfFingersInSuffix + i); 731 | 732 | finger.index = index++; 733 | finger.node = node; 734 | node = node.next; 735 | } 736 | } else { 737 | Finger startFinger = getFinger(targetFingerIndex + 1); 738 | int index = startFinger.index - 1; 739 | Node node = startFinger.node.prev; 740 | 741 | // TODO: Debug, please! 742 | for (int i = targetFingerIndex; 743 | i >= toFingerIndex; 744 | i--) { 745 | Finger finger = getFinger(i); 746 | finger.index = index--; 747 | finger.node = node; 748 | node = node.prev; 749 | } 750 | } 751 | } 752 | 753 | /** 754 | * Makes sure that the returned finger index {@code i} points to the closest 755 | * finger in the finger array. 756 | * 757 | * @param fingerIndex the finger index. 758 | * @param elementIndex the element index. 759 | * 760 | * @return the index of the finger that is closest to the 761 | * {@code elementIndex}th element. 762 | */ 763 | private int normalize(int fingerIndex, int elementIndex) { 764 | if (fingerIndex == 0) { 765 | // Since we cannot point to '-1'th finger, return 0: 766 | return 0; 767 | } 768 | 769 | if (fingerIndex == size) { 770 | // Don't go outside of 'size - 1': 771 | return size - 1; 772 | } 773 | 774 | Finger finger1 = fingerArray[fingerIndex - 1]; 775 | Finger finger2 = fingerArray[fingerIndex]; 776 | 777 | int distance1 = elementIndex - finger1.index; 778 | int distance2 = finger2.index - elementIndex; 779 | 780 | // Return the closest finger index: 781 | return distance1 < distance2 ? fingerIndex - 1 : fingerIndex; 782 | } 783 | 784 | /** 785 | * Creates a finger for the input node {@code node} and inserts it at the 786 | * head of the finger array. 787 | * 788 | * @param node the target node. 789 | */ 790 | void prependFingerForNode(Node node) { 791 | Finger finger = new Finger<>(node, 0); 792 | enlargeFingerArrayIfNeeded(size + 2); 793 | shiftFingerIndicesToRightOnce(0); 794 | System.arraycopy(fingerArray, 0, fingerArray, 1, size + 1); 795 | fingerArray[0] = finger; 796 | size++; 797 | } 798 | 799 | /** 800 | * Pushes {@code numberOfFingersToPush} to the finger prefix. 801 | * 802 | * @param fromIndex the starting index of the range to delete. 803 | * @param numberOfPrefixFingers the number of fingers in the prefix. 804 | * @param numberOfFingersToPush the number of fingers to move to the prefix. 805 | */ 806 | void pushCoveredFingersToPrefix(int fromIndex, 807 | int numberOfPrefixFingers, 808 | int numberOfFingersToPush) { 809 | if (numberOfPrefixFingers == 0) { 810 | int index = fromIndex - 1; 811 | Node node = getNodeNoFingersFix(index); 812 | 813 | for (int i = numberOfFingersToPush - 1; i >= 0; i--) { 814 | Finger finger = getFinger(i); 815 | finger.index = index--; 816 | finger.node = node; 817 | node = node.prev; 818 | } 819 | } else { 820 | Finger rightmostPrefixFinger = 821 | getFinger(numberOfPrefixFingers - 1); 822 | 823 | int index = rightmostPrefixFinger.index + 1; 824 | Node node = rightmostPrefixFinger.node.next; 825 | 826 | for (int i = numberOfPrefixFingers; 827 | i < numberOfPrefixFingers + numberOfFingersToPush; 828 | i++) { 829 | 830 | Finger finger = getFinger(i); 831 | finger.index = index++; 832 | finger.node = node; 833 | node = node.next; 834 | } 835 | } 836 | } 837 | 838 | void pushCoveredFingersToSuffix(int toIndex, 839 | int numberOfSuffixFingers, 840 | int numberOfFingersToPush) { 841 | if (numberOfSuffixFingers == 0) { 842 | int index = toIndex; 843 | Node node = getNodeNoFingersFix(index); 844 | 845 | for (int i = 0; i < numberOfFingersToPush; i++) { 846 | Finger finger = getFinger(size - numberOfFingersToPush + i); 847 | finger.index = index++; 848 | finger.node = node; 849 | node = node.next; 850 | } 851 | } else { 852 | Finger leftmostSuffixFinger = 853 | getFinger(size - numberOfSuffixFingers); 854 | 855 | int index = leftmostSuffixFinger.index; 856 | Node node = leftmostSuffixFinger.node; 857 | 858 | // TODO: Check this bound! 859 | for (int i = 0; i < numberOfFingersToPush; i++) { 860 | Finger finger = 861 | getFinger(size - numberOfSuffixFingers - 1 - i); 862 | 863 | node = node.prev; 864 | finger.node = node; 865 | finger.index = --index; 866 | } 867 | } 868 | } 869 | 870 | /** 871 | * Removes the last finger residing right before the end-of-finger-list 872 | * sentinel finger. 873 | */ 874 | void removeFinger() { 875 | contractFingerArrayIfNeeded(--size); 876 | fingerArray[size] = fingerArray[size + 1]; 877 | fingerArray[size + 1] = null; 878 | fingerArray[size].index = list.size; 879 | } 880 | 881 | void removeSingleNode(int fromIndex) { 882 | 883 | } 884 | 885 | // TODO: Debug me, please! 886 | void removeFingersOnDeleteRange(int fromFingerIndex, 887 | int numberOfFingersToRemove, 888 | int removalRangeLength) { 889 | 890 | if (numberOfFingersToRemove != 0) { 891 | 892 | System.arraycopy( 893 | fingerArray, 894 | fromFingerIndex 895 | + list.numberOfCoveringFingersToPrefix 896 | + numberOfFingersToRemove, 897 | fingerArray, 898 | fromFingerIndex + list.numberOfCoveringFingersToPrefix, 899 | size 900 | - fromFingerIndex 901 | - numberOfFingersToRemove 902 | - list.numberOfCoveringFingersToPrefix 903 | + 1); 904 | 905 | Arrays.fill(fingerArray, 906 | size - numberOfFingersToRemove + 1, 907 | size + 1, 908 | null); 909 | 910 | this.size -= numberOfFingersToRemove; 911 | shiftFingerIndicesToLeft( 912 | fromFingerIndex + list.numberOfCoveringFingersToPrefix, 913 | removalRangeLength); 914 | } else { 915 | shiftFingerIndicesToLeft( 916 | fromFingerIndex + list.numberOfCoveringFingersToPrefix, 917 | removalRangeLength); 918 | } 919 | 920 | // shiftFingerIndicesToLeft( 921 | // fromFingerIndex + list.numberOfCoveringFingersToPrefix, 922 | // removalRangeLength); 923 | 924 | this.list.size -= removalRangeLength; 925 | } 926 | 927 | /** 928 | * Sets the finger {@code finger} to the finger array at index 929 | * {@code index}. 930 | * 931 | * @param index the index of the finger list component. 932 | * @param finger the target finger to set. 933 | */ 934 | void setFinger(int index, Finger finger) { 935 | fingerArray[index] = finger; 936 | } 937 | 938 | /** 939 | * Sets all the leftmost {@code indices.length} fingers to the specified 940 | * indices. 941 | * 942 | * @param indices the target indices. 943 | */ 944 | void setFingerIndices(int... indices) { 945 | Arrays.sort(indices); 946 | int fingerIndex = 0; 947 | 948 | for (final int index : indices) { 949 | final Finger finger = fingerArray[fingerIndex++]; 950 | finger.index = index; 951 | finger.node = getNodeSequentially(index); 952 | } 953 | } 954 | 955 | /** 956 | * Accesses the {@code index}th node sequentially without using fingers and 957 | * modifying the fingers. 958 | * 959 | * @param index the index of the desired node. 960 | * 961 | * @return {@code index} node. 962 | */ 963 | private Node getNodeSequentially(final int index) { 964 | return list.getNodeSequentially(index); 965 | } 966 | 967 | /** 968 | * Moves all the fingers in range {@code [startFingerIndex, size]} 969 | * {@code shiftLength} positions to the left (towards smaller indices). 970 | * 971 | * @param startFingerIndex the index of the leftmost finger to shift. 972 | * @param shiftLength the length of the shift operation. 973 | */ 974 | void shiftFingerIndicesToLeft(int startFingerIndex, int shiftLength) { 975 | for (int i = startFingerIndex; i <= size; ++i) { 976 | fingerArray[i].index -= shiftLength; 977 | } 978 | } 979 | 980 | /** 981 | * Moves all the fingers in range {@code [startFingerIndex, size]} one 982 | * position to the left (towards smaller indices). 983 | * 984 | * @param startFingerIndex the index of the leftmost finger to shift. 985 | */ 986 | void shiftFingerIndicesToLeftOnceAll(int startFingerIndex) { 987 | for (int i = startFingerIndex; i <= size; ++i) { 988 | fingerArray[i].index--; 989 | } 990 | } 991 | 992 | /** 993 | * Moves all the fingers in range {@code [startFingerIndex, size]} 994 | * {@code shiftLength} positions to the right (towards larger indices). 995 | * 996 | * @param startIndex the index of the leftmost finger to shift. 997 | * @param shiftLength the length of the shift operation. 998 | */ 999 | void shiftFingerIndicesToRight(int startIndex, int shiftLength) { 1000 | for (int i = startIndex; i <= size; ++i) { 1001 | fingerArray[i].index += shiftLength; 1002 | } 1003 | } 1004 | 1005 | /** 1006 | * Moves all the fingers in range {@code [startFingerIndex, size]} one 1007 | * position to the right (towards larger indices). 1008 | * 1009 | * @param startIndex the index of the leftmost finger to shift. 1010 | */ 1011 | void shiftFingerIndicesToRightOnce(int startIndex) { 1012 | shiftFingerIndicesToRight(startIndex, 1); 1013 | } 1014 | 1015 | /** 1016 | * Returns the number of fingers in this finger list not counting the 1017 | * end-of-finger-list finger. 1018 | * 1019 | * @return the number of fingers in this finger list. 1020 | */ 1021 | int size() { 1022 | return size; 1023 | } 1024 | } 1025 | -------------------------------------------------------------------------------- /src/main/java/com/github/coderodde/util/Node.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2024 rodio. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.coderodde.util; 25 | 26 | import java.util.Objects; 27 | 28 | /** 29 | * This static inner class implements the actual linked list node. 30 | * 31 | * @param the type of the satellite data. 32 | */ 33 | final class Node { 34 | 35 | /** 36 | * The actual satellite datum. 37 | */ 38 | E item; 39 | 40 | /** 41 | * The previous node or {@code null} if this {@link Node} is the head of the 42 | * list. 43 | */ 44 | Node prev; 45 | 46 | /** 47 | * The next node or {@code null} if this {@link Node} is the tail of the 48 | * list. 49 | */ 50 | Node next; 51 | 52 | /** 53 | * Constructs a new {@link Node} object. 54 | * 55 | * @param item the satellite datum of the newly created {@link Node}. 56 | */ 57 | Node(E item) { 58 | this.item = item; 59 | } 60 | 61 | /** 62 | * Returns {@code true} if and only if the input object is another node with 63 | * the same item. 64 | * 65 | * @param o the object to test. 66 | * 67 | * @return {@code true} iff the two nodes are the same. 68 | */ 69 | @Override 70 | public boolean equals(Object o) { 71 | if (o == null) { 72 | return false; 73 | } 74 | 75 | if (o == this) { 76 | return true; 77 | } 78 | 79 | if (!getClass().equals(o.getClass())) { 80 | return false; 81 | } 82 | 83 | final Node other = (Node) o; 84 | return Objects.equals(this.item, other.item); 85 | } 86 | 87 | /** 88 | * Returns the textual representation of this {@link Node}. 89 | * 90 | * @return the textual representation. 91 | */ 92 | @Override 93 | public String toString() { 94 | return "[Node; item = " + item + "]"; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/github/coderodde/util/benchmark/LinkedListBenchmark.java: -------------------------------------------------------------------------------- 1 | package com.github.coderodde.util.benchmark; 2 | 3 | import com.github.coderodde.util.IndexedLinkedList; 4 | import java.util.ArrayList; 5 | import java.util.Collection; 6 | import java.util.Collections; 7 | import java.util.Deque; 8 | import java.util.HashSet; 9 | import java.util.Iterator; 10 | import java.util.LinkedList; 11 | import java.util.List; 12 | import java.util.ListIterator; 13 | import java.util.Random; 14 | import java.util.stream.Collectors; 15 | import org.apache.commons.collections4.list.TreeList; 16 | 17 | final class LinkedListBenchmark { 18 | 19 | private static final int ADD_FIRST_OPERATIONS = 100_000; 20 | private static final int ADD_LAST_OPERATIONS = 100_000; 21 | private static final int ADD_AT_OPERATIONS = 10_000; 22 | private static final int ADD_COLLECTION_AT_OPERATIONS = 4_000; 23 | private static final int ADD_LAST_COLLECTION_OPERATIONS = 10_000; 24 | private static final int REMOVE_VIA_INDEX_OPERATIONS = 10_000; 25 | private static final int REMOVE_OBJECT_OPERATIONS = 5_000; 26 | private static final int GET_OPERATIONS = 5_000; 27 | private static final int REMOVE_FIRST_OPERATIONS = 5_000; 28 | 29 | private static final int MAXIMUM_COLLECTION_SIZE = 20; 30 | 31 | private static final int MAXIMUM_INTEGER = 1_000; 32 | 33 | private final long seed; 34 | 35 | private Random randomJavaUtilLinkedList; 36 | private Random randomJavaUtilArrayList; 37 | private Random randomRoddeList; 38 | private Random randomTreeList; 39 | 40 | private IndexedLinkedList roddeList = new IndexedLinkedList<>(); 41 | private LinkedList linkedList = new LinkedList<>(); 42 | private ArrayList arrayList = new ArrayList<>(); 43 | private TreeList treeList = new TreeList<>(); 44 | 45 | private long totalMillisRoddeList = 0L; 46 | private long totalMillisLinkedList = 0L; 47 | private long totalMillisArrayList = 0L; 48 | private long totalMillisTreeList = 0L; 49 | 50 | private final boolean runSubListClear; 51 | private final boolean runRemoveAll; 52 | private final boolean runSort; 53 | 54 | private final List[] getLists = new ArrayList[5]; 55 | 56 | LinkedListBenchmark(long seed, 57 | boolean runSubListClear, 58 | boolean runRemoveAll, 59 | boolean runSort) { 60 | Thread.currentThread().setPriority(Thread.MAX_PRIORITY); 61 | 62 | this.seed = seed; 63 | this.runSubListClear = runSubListClear; 64 | this.runRemoveAll = runRemoveAll; 65 | this.runSort = runSort; 66 | 67 | for (int i = 0; i < getLists.length; i++) { 68 | getLists[i] = new ArrayList<>(GET_OPERATIONS); 69 | } 70 | } 71 | 72 | void warmup() { 73 | profile(BenchmarkChoice.WARMUP); 74 | } 75 | 76 | void benchmark() { 77 | profile(BenchmarkChoice.BENCHMARK); 78 | } 79 | 80 | private static Integer getRandomInteger(Random random) { 81 | return random.nextInt(MAXIMUM_INTEGER + 1); 82 | } 83 | 84 | private static List createRandomCollection(Random random) { 85 | int size = 1 + random.nextInt(MAXIMUM_COLLECTION_SIZE); 86 | 87 | List list = new ArrayList<>(size); 88 | 89 | for (int i = 0; i < size; i++) { 90 | list.add(getRandomInteger(random)); 91 | } 92 | 93 | return list; 94 | } 95 | 96 | private enum BenchmarkChoice { WARMUP, BENCHMARK } 97 | 98 | private void initRandomGenerators() { 99 | randomJavaUtilLinkedList = new Random(seed); 100 | randomJavaUtilArrayList = new Random(seed); 101 | randomRoddeList = new Random(seed); 102 | randomTreeList = new Random(seed); 103 | } 104 | 105 | private void listsEqual() { 106 | listsEqual(roddeList, 107 | linkedList, 108 | arrayList, 109 | treeList); 110 | } 111 | 112 | private static void listsEqual(List... lists) { 113 | if (lists.length < 2) { 114 | throw new IllegalArgumentException("lists.length < 2"); 115 | } 116 | 117 | for (int i = 0; i < lists.length - 1; i++) { 118 | if (lists[i].size() != lists[lists.length - 1].size()) { 119 | throw new IllegalStateException("Different size"); 120 | } 121 | 122 | Iterator iterator1 = lists[i].iterator(); 123 | Iterator iterator2 = lists[lists.length - 1].iterator(); 124 | 125 | int elementIndex = 0; 126 | 127 | while (iterator1.hasNext() && iterator2.hasNext()) { 128 | Integer integer1 = iterator1.next(); 129 | Integer integer2 = iterator2.next(); 130 | 131 | if (!integer1.equals(integer2)) { 132 | throw new IllegalStateException( 133 | "Data mismatch: " + integer1 + " vs. " + 134 | integer2 + " at list " + i + 135 | ", element index: " + elementIndex); 136 | } 137 | 138 | elementIndex++; 139 | } 140 | 141 | if (iterator1.hasNext() || iterator2.hasNext()) { 142 | throw new IllegalStateException("Bad iterators"); 143 | } 144 | } 145 | } 146 | 147 | private void profile(BenchmarkChoice benchmarkChoice) { 148 | 149 | printTitle(benchmarkChoice); 150 | initRandomGenerators(); 151 | 152 | profileAddFirst(); 153 | profileAddLast(); 154 | profileAddViaIndex(); 155 | profileAppendCollection(); 156 | profileAddCollection(); 157 | profileGet(); 158 | profileRemoveFirst(); 159 | profileRemoveLast(); 160 | profileRemoveViaIndex(); 161 | profileRemoveObject(); 162 | profileListIteratorAddition(); 163 | profileListIteratorRemoval(); 164 | profileStream(); 165 | profileParallelStream(); 166 | 167 | if (runRemoveAll) { 168 | profileRemoveAll(); 169 | } 170 | 171 | if (runSubListClear) { 172 | profileSubListClear(); 173 | } 174 | 175 | if (runSort) { 176 | profileSort(); 177 | } 178 | 179 | printTotalDurations(); 180 | 181 | resetLists(); 182 | zeroTimeDurationCounters(); 183 | 184 | System.gc(); 185 | } 186 | 187 | private void zeroTimeDurationCounters() { 188 | totalMillisArrayList = 0; 189 | totalMillisLinkedList = 0; 190 | totalMillisRoddeList = 0; 191 | totalMillisTreeList = 0; 192 | } 193 | 194 | private void resetLists() { 195 | roddeList = new IndexedLinkedList<>(); 196 | linkedList = new java.util.LinkedList<>(); 197 | arrayList = new ArrayList<>(); 198 | treeList = new TreeList<>(); 199 | } 200 | 201 | private void profileAddFirst() { 202 | profileAddFirstRoddeListV2(); 203 | profileAddFirstLinkedList(); 204 | profileAddFirstArrayList(); 205 | profileAddFirstTreeList(); 206 | 207 | listsEqual(); 208 | System.out.println(); 209 | } 210 | 211 | private void profileAddLast() { 212 | profileAddLastRoddeListV2(); 213 | profileAddLastLinkedList(); 214 | profileAddLastArrayList(); 215 | profileAddLastTreeList(); 216 | 217 | listsEqual(); 218 | System.out.println(); 219 | } 220 | 221 | private void profileAddViaIndex() { 222 | profileAddIndexRoddeListV2(); 223 | profileAddIndexLinkedList(); 224 | profileAddIndexArrayList(); 225 | profileAddIndexTreeList(); 226 | 227 | listsEqual(); 228 | System.out.println(); 229 | } 230 | 231 | private void profileAddCollection() { 232 | profileAddCollectionRoddeListV2(); 233 | profileAddCollectionLinkedList(); 234 | profileAddCollectionArrayList(); 235 | profileAddCollectionTreeList(); 236 | 237 | listsEqual(); 238 | System.out.println(); 239 | } 240 | 241 | private void profileGet() { 242 | profileGetRoddeListV2(); 243 | profileGetLinkedList(); 244 | profileGetArrayList(); 245 | profileGetTreeList(); 246 | 247 | listsEqual(); 248 | 249 | System.out.println(); 250 | } 251 | 252 | private void profileRemoveFirst() { 253 | profileRemoveFirstRoddeListV2(); 254 | profileRemoveFirstLinkedList(); 255 | profileRemoveFirstArrayList(); 256 | profileRemoveFirstTreeList(); 257 | 258 | listsEqual(); 259 | System.out.println(); 260 | } 261 | 262 | private void profileRemoveLast() { 263 | profileRemoveLastRoddeList(); 264 | profileRemoveLastLinkedList(); 265 | profileRemoveLastArrayList(); 266 | profileRemoveLastTreeList(); 267 | 268 | listsEqual(); 269 | System.out.println(); 270 | } 271 | 272 | private void profileAppendCollection() { 273 | profileAppendCollectionRoddeListV2(); 274 | profileAppendCollectionLinkedList(); 275 | profileAppendCollectionArrayList(); 276 | profileAppendCollectionTreeList(); 277 | 278 | listsEqual(); 279 | System.out.println(); 280 | } 281 | 282 | private void profileRemoveViaIndex() { 283 | profileRemoveViaIndexRoddeListV2(); 284 | profileRemoveViaIndexLinkedList(); 285 | profileRemoveViaIndexArrayList(); 286 | profileRemoveViaIndexTreeList(); 287 | 288 | listsEqual(); 289 | System.out.println(); 290 | } 291 | 292 | private void profileRemoveObject() { 293 | profileRemoveObjectRoddeListV2(); 294 | profileRemoveObjectLinkedList(); 295 | profileRemoveObjectArrayList(); 296 | profileRemoveObjectTreeList(); 297 | 298 | listsEqual(); 299 | System.out.println(); 300 | } 301 | 302 | private void profileListIteratorAddition() { 303 | profileListIteratorAdditionRoddeListV2(); 304 | profileListIteratorAdditionLinkedList(); 305 | profileListIteratorAdditionArrayList(); 306 | profileListIteratorAdditionTreeList(); 307 | 308 | listsEqual(); 309 | System.out.println(); 310 | } 311 | 312 | private void profileListIteratorRemoval() { 313 | profileListIteratorRemovalRoddeListV2(); 314 | profileListIteratorRemovalLinkedList(); 315 | profileListIteratorRemovalArrayList(); 316 | profileListIteratorRemovalTreeList(); 317 | 318 | listsEqual(); 319 | System.out.println(); 320 | } 321 | 322 | private void profileStream() { 323 | profileStreamRoddeListV2(); 324 | profileStreamLinkedList(); 325 | profileStreamArrayList(); 326 | profileStreamTreeList(); 327 | 328 | listsEqual(); 329 | System.out.println(); 330 | } 331 | 332 | private void profileParallelStream() { 333 | profileParallelStreamRoddeList(); 334 | profileParallelStreamLinkedList(); 335 | profileParallelStreamArrayList(); 336 | profileParallelStreamTreeList(); 337 | 338 | Collections.sort(treeList); 339 | Collections.sort(roddeList); 340 | Collections.sort(arrayList); 341 | Collections.sort(linkedList); 342 | 343 | listsEqual(); 344 | System.out.println(); 345 | } 346 | 347 | private void profileRemoveAll() { 348 | roddeList.clear(); 349 | linkedList.clear(); 350 | arrayList.clear(); 351 | treeList.clear(); 352 | 353 | roddeList.addAll(getIntegerArray(10_000)); 354 | linkedList.addAll(roddeList); 355 | arrayList.addAll(roddeList); 356 | treeList.addAll(roddeList); 357 | 358 | Collection toRemove = new HashSet<>(); 359 | Random random = new Random(seed + 1); 360 | 361 | for (int i = 0; i < 5_000; ++i) { 362 | int value = random.nextInt(5_000); 363 | toRemove.add(value); 364 | } 365 | 366 | profileRemoveAllRoddeList (toRemove); 367 | profileRemoveAllLinkedList (toRemove); 368 | profileRemoveAllArrayList (toRemove); 369 | profileRemoveAllTreeList (toRemove); 370 | 371 | listsEqual(); 372 | System.out.println(); 373 | } 374 | 375 | private static List getIntegerArray(int size) { 376 | List integers = new ArrayList<>(size); 377 | 378 | for (int i = 0; i < size; ++i) { 379 | integers.add(Integer.valueOf(size % 900_000)); 380 | } 381 | 382 | Collections.shuffle(integers); 383 | return integers; 384 | } 385 | 386 | private void profileSubListClear() { 387 | roddeList.clear(); 388 | arrayList.clear(); 389 | linkedList.clear(); 390 | treeList.clear(); 391 | 392 | roddeList.addAll(getIntegerArray(1_000_000)); 393 | arrayList.addAll(roddeList); 394 | linkedList.addAll(roddeList); 395 | treeList.addAll(roddeList); 396 | 397 | profileSubListClearRoddeList(); 398 | profileSubListClearLinkedList(); 399 | profileSubListClearArrayList(); 400 | profileSubListClearTreeList(); 401 | 402 | listsEqual(); 403 | System.out.println(); 404 | } 405 | 406 | private void profileSort() { 407 | roddeList.clear(); 408 | arrayList.clear(); 409 | linkedList.clear(); 410 | treeList.clear(); 411 | 412 | Random random = new Random(seed + 1); 413 | 414 | for (int i = 0; i < 500_000; ++i) { 415 | Integer value = random.nextInt((i % 460_000) + 1); 416 | roddeList.add(value); 417 | arrayList.add(value); 418 | linkedList.add(value); 419 | treeList.add(value); 420 | } 421 | 422 | Collections.shuffle(roddeList, randomRoddeList); 423 | Collections.shuffle(arrayList, randomJavaUtilArrayList); 424 | Collections.shuffle(linkedList, randomJavaUtilLinkedList); 425 | Collections.shuffle(treeList, randomTreeList); 426 | 427 | profileSortRoddeList(); 428 | profileSortLinkedList(); 429 | profileSortArrayList(); 430 | profileSortTreeList(); 431 | 432 | listsEqual(); 433 | System.out.println(); 434 | } 435 | 436 | private void printTotalDurations() { 437 | System.out.println("--- Total time elapsed ---"); 438 | 439 | System.out.println( 440 | roddeList.getClass().getName() + 441 | " in (ms): " + 442 | totalMillisRoddeList); 443 | 444 | System.out.println( 445 | linkedList.getClass().getName() + 446 | " in (ms): " + 447 | totalMillisLinkedList); 448 | 449 | System.out.println( 450 | arrayList.getClass().getName() + 451 | " in (ms): " + 452 | totalMillisArrayList); 453 | 454 | System.out.println( 455 | treeList.getClass().getName() + 456 | " in (ms): " + 457 | totalMillisTreeList); 458 | } 459 | 460 | private long profileAddFirst( 461 | List list, 462 | int operations, 463 | Random random) { 464 | 465 | long startMillis = System.currentTimeMillis(); 466 | 467 | for (int i = 0; i < operations; i++) { 468 | list.add(0, getRandomInteger(random)); 469 | } 470 | 471 | long endMillis = System.currentTimeMillis(); 472 | long durationMillis = endMillis - startMillis; 473 | 474 | System.out.println( 475 | list.getClass().getName() + 476 | ".addFirst in (ms): " + 477 | durationMillis); 478 | 479 | return durationMillis; 480 | } 481 | 482 | private long profileAddLast( 483 | List list, 484 | int operations, 485 | Random random) { 486 | 487 | long startMillis = System.currentTimeMillis(); 488 | 489 | for (int i = 0; i < operations; i++) { 490 | list.add(getRandomInteger(random)); 491 | } 492 | 493 | long endMillis = System.currentTimeMillis(); 494 | long durationMillis = endMillis - startMillis; 495 | 496 | System.out.println( 497 | list.getClass().getName() + 498 | ".addLast in (ms): " + 499 | durationMillis); 500 | 501 | return durationMillis; 502 | } 503 | 504 | private long profileAddIndex( 505 | List list, 506 | int operations, 507 | Random random) { 508 | 509 | long startMillis = System.currentTimeMillis(); 510 | 511 | for (int i = 0; i < operations; i++) { 512 | int index = random.nextInt(list.size()); 513 | Integer value = getRandomInteger(random); 514 | list.add(index, value); 515 | } 516 | 517 | long endMillis = System.currentTimeMillis(); 518 | long durationMillis = endMillis - startMillis; 519 | 520 | System.out.println( 521 | list.getClass().getName() + 522 | ".add(int, E) in (ms): " + 523 | durationMillis); 524 | 525 | return durationMillis; 526 | } 527 | 528 | private long profileAddCollection( 529 | List list, 530 | int operations, 531 | Random random) { 532 | 533 | long startMillis = System.currentTimeMillis(); 534 | 535 | for (int i = 0; i < operations; i++) { 536 | List collection = createRandomCollection(random); 537 | int index = random.nextInt(list.size()); 538 | list.addAll(index, collection); 539 | } 540 | 541 | long endMillis = System.currentTimeMillis(); 542 | long durationMillis = endMillis - startMillis; 543 | 544 | System.out.println( 545 | list.getClass().getName() + 546 | ".addAll(int, Collection) in (ms): " + 547 | durationMillis); 548 | 549 | return durationMillis; 550 | } 551 | 552 | private long profileAppendCollection( 553 | List list, 554 | int operations, 555 | Random random) { 556 | 557 | long startMillis = System.currentTimeMillis(); 558 | 559 | for (int i = 0; i < operations; i++) { 560 | List collection = createRandomCollection(random); 561 | list.addAll(collection); 562 | } 563 | 564 | long endMillis = System.currentTimeMillis(); 565 | long durationMillis = endMillis - startMillis; 566 | 567 | System.out.println( 568 | list.getClass().getName() + 569 | ".addAll(Collection) in (ms): " + 570 | durationMillis); 571 | 572 | return durationMillis; 573 | } 574 | 575 | private long profileGet(List list, int operations, Random random) { 576 | long startMillis = System.currentTimeMillis(); 577 | 578 | for (int i = 0; i < operations; i++) { 579 | list.get(random.nextInt(list.size())); 580 | } 581 | 582 | long endMillis = System.currentTimeMillis(); 583 | long durationMillis = endMillis - startMillis; 584 | 585 | System.out.println( 586 | list.getClass().getName() + ".get(int) in (ms): " + 587 | durationMillis); 588 | 589 | return durationMillis; 590 | } 591 | 592 | private long profileRemoveFirst(List list) { 593 | long startMillis = System.currentTimeMillis(); 594 | 595 | if (!list.getClass().equals(Deque.class)) { 596 | for (int i = 0; i < REMOVE_FIRST_OPERATIONS; i++) { 597 | list.remove(0); 598 | } 599 | } else { 600 | for (int i = 0; i < REMOVE_FIRST_OPERATIONS; i++) { 601 | ((Deque) list).removeFirst(); 602 | } 603 | } 604 | 605 | 606 | long endMillis = System.currentTimeMillis(); 607 | long durationMillis = endMillis - startMillis; 608 | 609 | System.out.println( 610 | list.getClass().getName() + ".removeFirst() in (ms): " + 611 | durationMillis); 612 | 613 | return durationMillis; 614 | } 615 | 616 | private long profileRemoveLast(List list) { 617 | long startMillis; 618 | long endMillis; 619 | 620 | if (list instanceof Deque) { 621 | startMillis = System.currentTimeMillis(); 622 | 623 | for (int i = 0; i < REMOVE_FIRST_OPERATIONS; ++i) { 624 | ((Deque) list).removeLast(); 625 | } 626 | 627 | endMillis = System.currentTimeMillis(); 628 | } else { 629 | startMillis = System.currentTimeMillis(); 630 | 631 | for (int i = 0; i < REMOVE_FIRST_OPERATIONS; ++i) { 632 | list.remove(list.size() - 1); 633 | } 634 | 635 | endMillis = System.currentTimeMillis(); 636 | } 637 | 638 | long durationMillis = endMillis - startMillis; 639 | 640 | System.out.println( 641 | list.getClass().getName() + ".removeLast() in (ms): " + 642 | durationMillis); 643 | 644 | return durationMillis; 645 | } 646 | 647 | private long profileRemoveViaIndex( 648 | List list, 649 | int operations, 650 | Random random) { 651 | 652 | long startMillis = System.currentTimeMillis(); 653 | 654 | for (int i = 0; i < operations; i++) { 655 | list.remove(random.nextInt(list.size())); 656 | } 657 | 658 | long endMillis = System.currentTimeMillis(); 659 | long durationMillis = endMillis - startMillis; 660 | 661 | System.out.println( 662 | list.getClass().getName() + 663 | ".remove(int) in (ms): " + 664 | durationMillis); 665 | 666 | return durationMillis; 667 | } 668 | 669 | private long profileRemoveObject( 670 | List list, 671 | int operations, 672 | Random random) { 673 | 674 | long startMillis = System.currentTimeMillis(); 675 | 676 | for (int i = 0; i < operations; i++) { 677 | list.remove(Integer.valueOf(getRandomInteger(random))); 678 | } 679 | 680 | long endMillis = System.currentTimeMillis(); 681 | long durationMillis = endMillis - startMillis; 682 | 683 | System.out.println( 684 | list.getClass().getName() + 685 | ".remove(Object) in (ms): " + 686 | durationMillis); 687 | 688 | return durationMillis; 689 | } 690 | 691 | private long profileListIteratorRemoval(List list) { 692 | long startMillis = System.currentTimeMillis(); 693 | Iterator iterator = list.iterator(); 694 | int counter = 0; 695 | 696 | while (iterator.hasNext()) { 697 | iterator.next(); 698 | 699 | // Remove every 2nd element: 700 | if (counter % 10 == 0) { 701 | try { 702 | iterator.remove(); 703 | } catch (AssertionError ae) { 704 | System.err.println(ae.getMessage()); 705 | System.exit(1); 706 | } 707 | } 708 | 709 | counter++; 710 | } 711 | 712 | long endMillis = System.currentTimeMillis(); 713 | long durationMillis = endMillis - startMillis; 714 | 715 | System.out.println( 716 | list.getClass().getName() + 717 | ".iterator().remove() in (ms): " + 718 | durationMillis); 719 | 720 | return durationMillis; 721 | } 722 | 723 | private long profileListIteratorAddition( 724 | List list, Random random) { 725 | 726 | long startMillis = System.currentTimeMillis(); 727 | ListIterator iterator = list.listIterator(1); 728 | int counter = 0; 729 | 730 | while (iterator.hasNext()) { 731 | iterator.next(); 732 | 733 | // Remove every 2nd element: 734 | if (counter % 10 == 0) { 735 | try { 736 | Integer integer = Integer.valueOf(random.nextInt(10_000)); 737 | iterator.add(integer); 738 | } catch (AssertionError ae) { 739 | System.err.println(ae.getMessage()); 740 | System.exit(1); 741 | } 742 | } 743 | 744 | counter++; 745 | } 746 | 747 | long endMillis = System.currentTimeMillis(); 748 | long durationMillis = endMillis - startMillis; 749 | 750 | System.out.println( 751 | list.getClass().getName() + 752 | ".iterator().add() in (ms): " + 753 | durationMillis); 754 | 755 | return durationMillis; 756 | } 757 | 758 | private long profileStream(List list) { 759 | long startMillis = System.currentTimeMillis(); 760 | 761 | List newList = 762 | list.stream().map(x -> 2 * x).collect(Collectors.toList()); 763 | 764 | long endMillis = System.currentTimeMillis(); 765 | long durationMillis = endMillis - startMillis; 766 | 767 | list.clear(); 768 | list.addAll(newList); 769 | 770 | System.out.println( 771 | list.getClass().getName() + 772 | ".stream() in (ms): " + durationMillis); 773 | 774 | return durationMillis; 775 | } 776 | 777 | private long profileParallelStream(List list) { 778 | long startMillis = System.currentTimeMillis(); 779 | 780 | List newList = list.stream() 781 | .parallel() 782 | .map(x -> 2 * x) 783 | .collect(Collectors.toList()); 784 | 785 | long endMillis = System.currentTimeMillis(); 786 | long durationMillis = endMillis - startMillis; 787 | 788 | list.clear(); 789 | list.addAll(newList); 790 | 791 | System.out.println( 792 | list.getClass().getName() + 793 | ".stream().parallel() in (ms): " + durationMillis); 794 | 795 | return durationMillis; 796 | } 797 | 798 | private long profileRemoveAll(List list, 799 | Collection toRemove) { 800 | long startMillis; 801 | long endMillis; 802 | 803 | if (list instanceof TreeList || list instanceof LinkedList) { 804 | startMillis = System.currentTimeMillis(); 805 | 806 | for (Integer i : toRemove) { 807 | list.remove(i); 808 | } 809 | 810 | endMillis = System.currentTimeMillis(); 811 | } else { 812 | startMillis = System.currentTimeMillis(); 813 | list.removeAll(toRemove); 814 | endMillis = System.currentTimeMillis(); 815 | } 816 | 817 | long durationMillis = endMillis - startMillis; 818 | 819 | System.out.println( 820 | list.getClass().getName() + 821 | ".removeAll() in (ms): " + durationMillis); 822 | 823 | return durationMillis; 824 | } 825 | 826 | private long profileSubListClear(List list) { 827 | int fromIndex = list.size() / 2; 828 | int toIndex = list.size() / 2 + 1; 829 | 830 | long startMillis = System.currentTimeMillis(); 831 | 832 | // Clear short range: 833 | list.subList(fromIndex, toIndex).clear(); 834 | 835 | fromIndex = 10; 836 | toIndex = list.size() - 9; 837 | 838 | // Clear long range: 839 | list.subList(fromIndex, toIndex).clear(); 840 | 841 | long endMillis = System.currentTimeMillis(); 842 | 843 | long durationMillis = endMillis - startMillis; 844 | 845 | System.out.println( 846 | list.getClass().getName() + 847 | ".subList(...).clear() in (ms): " + 848 | durationMillis); 849 | 850 | return durationMillis; 851 | } 852 | 853 | private long profileSort(List list) { 854 | long startMillis = System.currentTimeMillis(); 855 | 856 | list.subList(10, list.size() - 10).sort(Integer::compare); 857 | 858 | long endMillis = System.currentTimeMillis(); 859 | long durationMillis = endMillis - startMillis; 860 | 861 | System.out.println( 862 | list.getClass() 863 | .getSimpleName() 864 | + ".subList().sort() in (ms): " 865 | + durationMillis); 866 | 867 | return durationMillis; 868 | } 869 | 870 | private void profileAddFirstRoddeListV2() { 871 | totalMillisRoddeList += 872 | profileAddFirst( 873 | roddeList, 874 | ADD_FIRST_OPERATIONS, 875 | randomRoddeList); 876 | } 877 | 878 | private void profileAddFirstLinkedList() { 879 | totalMillisLinkedList += 880 | profileAddFirst(linkedList, 881 | ADD_FIRST_OPERATIONS, 882 | randomJavaUtilLinkedList); 883 | } 884 | 885 | private void profileAddFirstArrayList() { 886 | totalMillisArrayList += 887 | profileAddFirst(arrayList, 888 | ADD_FIRST_OPERATIONS, 889 | randomJavaUtilArrayList); 890 | } 891 | 892 | private void profileAddFirstTreeList() { 893 | totalMillisTreeList += 894 | profileAddFirst(treeList, 895 | ADD_FIRST_OPERATIONS, 896 | randomTreeList); 897 | } 898 | 899 | private void profileAddLastRoddeListV2() { 900 | totalMillisRoddeList += 901 | profileAddLast(roddeList, ADD_LAST_OPERATIONS, randomRoddeList); 902 | } 903 | 904 | private void profileAddLastLinkedList() { 905 | totalMillisLinkedList += 906 | profileAddLast( 907 | linkedList, 908 | ADD_LAST_OPERATIONS, 909 | randomJavaUtilLinkedList); 910 | } 911 | 912 | private void profileAddLastArrayList() { 913 | totalMillisArrayList += 914 | profileAddLast(arrayList, 915 | ADD_LAST_OPERATIONS, 916 | randomJavaUtilArrayList); 917 | } 918 | 919 | private void profileAddLastTreeList() { 920 | totalMillisTreeList += 921 | profileAddLast(treeList, 922 | ADD_LAST_OPERATIONS, 923 | randomTreeList); 924 | } 925 | 926 | private void profileAddIndexRoddeListV2() { 927 | totalMillisRoddeList += 928 | profileAddIndex(roddeList, 929 | ADD_AT_OPERATIONS, 930 | randomRoddeList); 931 | } 932 | 933 | private void profileAddIndexLinkedList() { 934 | totalMillisLinkedList += 935 | profileAddIndex( 936 | linkedList, 937 | ADD_AT_OPERATIONS, 938 | randomJavaUtilLinkedList); 939 | } 940 | 941 | private void profileAddIndexArrayList() { 942 | totalMillisArrayList += 943 | profileAddIndex( 944 | arrayList, 945 | ADD_AT_OPERATIONS, 946 | randomJavaUtilArrayList); 947 | } 948 | 949 | private void profileAddIndexTreeList() { 950 | totalMillisTreeList += 951 | profileAddIndex( 952 | treeList, 953 | ADD_AT_OPERATIONS, 954 | randomTreeList); 955 | } 956 | 957 | private void profileAddCollectionRoddeListV2() { 958 | totalMillisRoddeList += 959 | profileAddCollection( 960 | roddeList, 961 | ADD_COLLECTION_AT_OPERATIONS, 962 | randomRoddeList); 963 | } 964 | 965 | private void profileAddCollectionLinkedList() { 966 | totalMillisLinkedList += 967 | profileAddCollection( 968 | linkedList, 969 | ADD_COLLECTION_AT_OPERATIONS, 970 | randomJavaUtilLinkedList); 971 | } 972 | 973 | private void profileAddCollectionArrayList() { 974 | totalMillisArrayList += 975 | profileAddCollection( 976 | arrayList, 977 | ADD_COLLECTION_AT_OPERATIONS, 978 | randomJavaUtilArrayList); 979 | } 980 | 981 | private void profileAddCollectionTreeList() { 982 | totalMillisTreeList += 983 | profileAddCollection( 984 | treeList, 985 | ADD_COLLECTION_AT_OPERATIONS, 986 | randomTreeList); 987 | } 988 | 989 | private void profileGetRoddeListV2() { 990 | totalMillisRoddeList += 991 | profileGet(roddeList, GET_OPERATIONS, randomRoddeList); 992 | } 993 | 994 | private void profileGetLinkedList() { 995 | totalMillisLinkedList += 996 | profileGet(linkedList, 997 | GET_OPERATIONS, 998 | randomJavaUtilLinkedList); 999 | } 1000 | 1001 | private void profileGetArrayList() { 1002 | totalMillisArrayList += 1003 | profileGet(arrayList, 1004 | GET_OPERATIONS, 1005 | randomJavaUtilArrayList); 1006 | } 1007 | 1008 | private void profileGetTreeList() { 1009 | totalMillisTreeList += 1010 | profileGet(treeList, GET_OPERATIONS, randomTreeList); 1011 | } 1012 | 1013 | private void profileRemoveFirstRoddeListV2() { 1014 | totalMillisRoddeList += profileRemoveFirst(roddeList); 1015 | } 1016 | 1017 | private void profileRemoveFirstLinkedList() { 1018 | totalMillisLinkedList += profileRemoveFirst(linkedList); 1019 | } 1020 | 1021 | private void profileRemoveFirstArrayList() { 1022 | totalMillisArrayList += profileRemoveFirst(arrayList); 1023 | } 1024 | 1025 | private void profileRemoveFirstTreeList() { 1026 | totalMillisTreeList += profileRemoveFirst(treeList); 1027 | } 1028 | 1029 | private void profileRemoveLastRoddeList() { 1030 | totalMillisRoddeList += profileRemoveLast(roddeList); 1031 | } 1032 | 1033 | private void profileRemoveLastLinkedList() { 1034 | totalMillisLinkedList += profileRemoveLast(linkedList); 1035 | } 1036 | 1037 | private void profileRemoveLastArrayList() { 1038 | totalMillisArrayList += profileRemoveLast(arrayList); 1039 | } 1040 | 1041 | private void profileRemoveLastTreeList() { 1042 | totalMillisTreeList += profileRemoveLast(treeList); 1043 | } 1044 | 1045 | private void profileAppendCollectionRoddeListV2() { 1046 | totalMillisRoddeList += 1047 | profileAppendCollection( 1048 | roddeList, 1049 | ADD_LAST_COLLECTION_OPERATIONS, 1050 | randomRoddeList); 1051 | } 1052 | 1053 | private void profileAppendCollectionLinkedList() { 1054 | totalMillisLinkedList += 1055 | profileAppendCollection( 1056 | linkedList, 1057 | ADD_LAST_COLLECTION_OPERATIONS, 1058 | randomJavaUtilLinkedList); 1059 | } 1060 | 1061 | private void profileAppendCollectionArrayList() { 1062 | totalMillisArrayList += 1063 | profileAppendCollection( 1064 | arrayList, 1065 | ADD_LAST_COLLECTION_OPERATIONS, 1066 | randomJavaUtilArrayList); 1067 | } 1068 | 1069 | private void profileAppendCollectionTreeList() { 1070 | totalMillisTreeList += 1071 | profileAppendCollection( 1072 | treeList, 1073 | ADD_LAST_COLLECTION_OPERATIONS, 1074 | randomTreeList); 1075 | } 1076 | 1077 | private void profileRemoveViaIndexRoddeListV2() { 1078 | totalMillisRoddeList += 1079 | profileRemoveViaIndex( 1080 | roddeList, 1081 | REMOVE_VIA_INDEX_OPERATIONS, 1082 | randomRoddeList); 1083 | } 1084 | 1085 | private void profileRemoveViaIndexLinkedList() { 1086 | totalMillisLinkedList += 1087 | profileRemoveViaIndex( 1088 | linkedList, 1089 | REMOVE_VIA_INDEX_OPERATIONS, 1090 | randomJavaUtilLinkedList); 1091 | } 1092 | 1093 | private void profileRemoveViaIndexArrayList() { 1094 | totalMillisArrayList += 1095 | profileRemoveViaIndex( 1096 | arrayList, 1097 | REMOVE_VIA_INDEX_OPERATIONS, 1098 | randomJavaUtilArrayList); 1099 | } 1100 | 1101 | private void profileRemoveViaIndexTreeList() { 1102 | totalMillisTreeList += 1103 | profileRemoveViaIndex( 1104 | treeList, 1105 | REMOVE_VIA_INDEX_OPERATIONS, 1106 | randomTreeList); 1107 | } 1108 | 1109 | private void profileRemoveObjectRoddeListV2() { 1110 | totalMillisRoddeList += 1111 | profileRemoveObject( 1112 | roddeList, 1113 | REMOVE_OBJECT_OPERATIONS, 1114 | randomRoddeList); 1115 | } 1116 | 1117 | private void profileRemoveObjectLinkedList() { 1118 | totalMillisLinkedList += 1119 | profileRemoveObject( 1120 | linkedList, 1121 | REMOVE_OBJECT_OPERATIONS, 1122 | randomJavaUtilLinkedList); 1123 | } 1124 | 1125 | private void profileRemoveObjectArrayList() { 1126 | totalMillisArrayList += 1127 | profileRemoveObject( 1128 | arrayList, 1129 | REMOVE_OBJECT_OPERATIONS, 1130 | randomJavaUtilArrayList); 1131 | } 1132 | 1133 | private void profileRemoveObjectTreeList() { 1134 | totalMillisTreeList += 1135 | profileRemoveObject( 1136 | treeList, 1137 | REMOVE_OBJECT_OPERATIONS, 1138 | randomTreeList); 1139 | } 1140 | 1141 | private void profileListIteratorRemovalRoddeListV2() { 1142 | totalMillisRoddeList += profileListIteratorRemoval(roddeList); 1143 | } 1144 | 1145 | private void profileListIteratorRemovalLinkedList() { 1146 | totalMillisLinkedList += profileListIteratorRemoval(linkedList); 1147 | } 1148 | 1149 | private void profileListIteratorRemovalArrayList() { 1150 | totalMillisArrayList += profileListIteratorRemoval(arrayList); 1151 | } 1152 | 1153 | private void profileListIteratorRemovalTreeList() { 1154 | totalMillisTreeList += profileListIteratorRemoval(treeList); 1155 | } 1156 | 1157 | private void profileListIteratorAdditionRoddeListV2() { 1158 | totalMillisRoddeList += 1159 | profileListIteratorAddition(roddeList, randomRoddeList); 1160 | } 1161 | 1162 | private void profileListIteratorAdditionLinkedList() { 1163 | totalMillisLinkedList += 1164 | profileListIteratorAddition( 1165 | linkedList, 1166 | randomJavaUtilLinkedList); 1167 | } 1168 | 1169 | private void profileListIteratorAdditionArrayList() { 1170 | totalMillisArrayList += 1171 | profileListIteratorAddition( 1172 | arrayList, 1173 | randomJavaUtilArrayList); 1174 | } 1175 | 1176 | private void profileListIteratorAdditionTreeList() { 1177 | totalMillisTreeList += 1178 | profileListIteratorAddition(treeList, randomTreeList); 1179 | } 1180 | 1181 | private void profileStreamRoddeListV2() { 1182 | totalMillisRoddeList += profileStream(roddeList); 1183 | } 1184 | 1185 | private void profileStreamLinkedList() { 1186 | totalMillisLinkedList += profileStream(linkedList); 1187 | } 1188 | 1189 | private void profileStreamArrayList() { 1190 | totalMillisArrayList += profileStream(arrayList); 1191 | } 1192 | 1193 | private void profileStreamTreeList() { 1194 | totalMillisTreeList += profileStream(treeList); 1195 | } 1196 | 1197 | private void profileParallelStreamRoddeList() { 1198 | totalMillisRoddeList += profileParallelStream(roddeList); 1199 | } 1200 | 1201 | private void profileParallelStreamLinkedList() { 1202 | totalMillisLinkedList += profileParallelStream(linkedList); 1203 | } 1204 | 1205 | private void profileParallelStreamArrayList() { 1206 | totalMillisArrayList += profileParallelStream(arrayList); 1207 | } 1208 | 1209 | private void profileParallelStreamTreeList() { 1210 | totalMillisTreeList += profileParallelStream(treeList); 1211 | } 1212 | 1213 | private void profileRemoveAllRoddeList(Collection toRemove) { 1214 | totalMillisRoddeList += profileRemoveAll(roddeList, toRemove); 1215 | } 1216 | 1217 | private void profileRemoveAllLinkedList(Collection toRemove) { 1218 | totalMillisLinkedList += profileRemoveAll(linkedList, toRemove); 1219 | } 1220 | 1221 | private void profileRemoveAllArrayList(Collection toRemove) { 1222 | totalMillisArrayList += profileRemoveAll(arrayList, toRemove); 1223 | } 1224 | 1225 | private void profileRemoveAllTreeList(Collection toRemove) { 1226 | totalMillisTreeList += profileRemoveAll(treeList, toRemove); 1227 | } 1228 | 1229 | private void profileSubListClearRoddeList() { 1230 | totalMillisRoddeList += profileSubListClear(roddeList); 1231 | } 1232 | 1233 | private void profileSubListClearLinkedList() { 1234 | totalMillisLinkedList += profileSubListClear(linkedList); 1235 | } 1236 | 1237 | private void profileSubListClearArrayList() { 1238 | totalMillisArrayList += profileSubListClear(arrayList); 1239 | } 1240 | 1241 | private void profileSubListClearTreeList() { 1242 | totalMillisTreeList += profileSubListClear(treeList); 1243 | } 1244 | 1245 | private void profileSortRoddeList() { 1246 | totalMillisRoddeList += profileSort(roddeList); 1247 | } 1248 | 1249 | private void profileSortLinkedList() { 1250 | totalMillisLinkedList += profileSort(linkedList); 1251 | } 1252 | 1253 | private void profileSortArrayList() { 1254 | totalMillisArrayList += profileSort(arrayList); 1255 | } 1256 | 1257 | private void profileSortTreeList() { 1258 | totalMillisTreeList += profileSort(treeList); 1259 | } 1260 | 1261 | private void printTitle(BenchmarkChoice benchmarkChoice) { 1262 | switch (benchmarkChoice) { 1263 | case WARMUP: 1264 | System.out.println("=== WARMUP RUN ==="); 1265 | break; 1266 | 1267 | case BENCHMARK: 1268 | System.out.println("=== BENCHMARK RUN ==="); 1269 | break; 1270 | } 1271 | } 1272 | 1273 | private void clearGetLists() { 1274 | for (List list : getLists) { 1275 | list.clear(); 1276 | } 1277 | } 1278 | } -------------------------------------------------------------------------------- /src/main/java/com/github/coderodde/util/benchmark/LinkedListBenchmark2.java: -------------------------------------------------------------------------------- 1 | package com.github.coderodde.util.benchmark; 2 | 3 | import com.github.coderodde.util.IndexedLinkedList; 4 | import java.util.ArrayList; 5 | import java.util.Deque; 6 | import java.util.HashMap; 7 | import java.util.LinkedHashMap; 8 | import java.util.LinkedList; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Random; 12 | import java.util.TreeMap; 13 | import net.openhft.affinity.AffinityLock; 14 | import org.apache.commons.collections4.list.TreeList; 15 | 16 | public class LinkedListBenchmark2 { 17 | 18 | private static final Object ELEMENT = new Object(); 19 | private static final Map DURATION_COUNTER_MAP = 20 | new HashMap<>(); 21 | 22 | private static final Map PER_OPERATION_DURATION_COUNTER_MAP = 23 | new TreeMap<>(); 24 | 25 | private static final Map 26 | BENCHMARK_OPERATION_NAME_TO_GNUPLOT_NAME = new LinkedHashMap<>(); 27 | 28 | private static final Map COLOR_MAP = new HashMap<>(); 29 | 30 | private static final int[] LIST_SIZES = { 31 | 100_000, 32 | 200_000, 33 | 300_000, 34 | 400_000, 35 | 500_000, 36 | 600_000, 37 | 700_000, 38 | 800_000, 39 | 900_000, 40 | 1_000_000, 41 | }; 42 | 43 | private static final class Bounds { 44 | static final int NUMBER_OF_ADDITIONS_AT_BEGINNING = 2_000; 45 | static final int NUMBER_OF_RANDOM_ADDS = 2_000; 46 | static final int NUMBER_OF_ADDITIONS_AT_END = 10_000; 47 | static final int NUMBER_OF_GETS = 500; 48 | static final int NUMBER_OF_REMOVE_FIRST_OPS = 5_000; 49 | static final int NUMBER_OF_REMOVE_LAST_OPS = 20_000; 50 | static final int NUMBER_OF_RANDOM_REMOVES = 10_000; 51 | static final int NUMBER_OF_COLLECTION_APPENDS = 50; 52 | static final int NUMBER_OF_COLLECTION_INSERTS = 50; 53 | static final int APPEND_COLLECTION_SIZE = 10_000; 54 | static final int INSERT_COLLECTION_SIZE = 3_500; 55 | static final int REMOVE_RANGE_SIZE = 500; 56 | } 57 | 58 | private static final String[] METHOD_NAMES = { 59 | "AddAtBeginning", 60 | "AddAtEnd", 61 | "AddRandom", 62 | "PrependCollection", 63 | "AppendCollection", 64 | "InsertCollection", 65 | "GetRandom", 66 | "RemoveFromBeginning", 67 | "RemoveFromEnd", 68 | "RemoveRandom", 69 | "RemoveRange", 70 | }; 71 | 72 | private static final String[] LIST_TYPE_NAMES = { 73 | "arrayList", 74 | "indexedLinkedList", 75 | "linkedList", 76 | "treeList", 77 | }; 78 | 79 | static { 80 | clearDurationCounterMap(); 81 | initializeOperationNames(); 82 | initializePerOperationMap(); 83 | initializeColorMap(); 84 | } 85 | 86 | private static void clearDurationCounterMap() { 87 | for (int i = 0; i < LIST_TYPE_NAMES.length; i++) { 88 | DURATION_COUNTER_MAP.put(LIST_TYPE_NAMES[i], 0L);; 89 | } 90 | } 91 | 92 | private static void clearPerOperationCounterMap() { 93 | for (final String listTypeName : LIST_TYPE_NAMES) { 94 | for (final String methodName : METHOD_NAMES) { 95 | final String line = 96 | String.format( 97 | "%s%s", 98 | listTypeName, 99 | methodName); 100 | 101 | PER_OPERATION_DURATION_COUNTER_MAP.put(line, 0L); 102 | } 103 | } 104 | } 105 | 106 | private static void initializeOperationNames() { 107 | Map m = BENCHMARK_OPERATION_NAME_TO_GNUPLOT_NAME; 108 | 109 | m.put("AddAtBeginning", "Push-Front"); 110 | m.put("AddAtEnd", "Push-Back"); 111 | m.put("AddRandom", "Insert"); 112 | m.put("PrependCollection", "Push-Front-Collection"); 113 | m.put("AppendCollection", "Push-Back-Collection"); 114 | m.put("InsertCollection", "Insert-Collection"); 115 | m.put("GetRandom", "Search"); 116 | m.put("RemoveFromBeginning", "Pop-Front"); 117 | m.put("RemoveFromEnd", "Pop-Back"); 118 | m.put("RemoveRandom", "Delete"); 119 | m.put("RemoveRange", "Delete-Range"); 120 | } 121 | 122 | private static void initializeColorMap() { 123 | final String insertColor = "0x289e37"; 124 | final String searchColor = "0x28579e"; 125 | final String deleteColor = "0xa83232"; 126 | 127 | COLOR_MAP.put("Push-Front", insertColor); 128 | COLOR_MAP.put("Push-Back", insertColor); 129 | COLOR_MAP.put("Insert", insertColor); 130 | COLOR_MAP.put("Push-Front-Collection", insertColor); 131 | COLOR_MAP.put("Push-Back-Collection", insertColor); 132 | COLOR_MAP.put("Insert-Collection", insertColor); 133 | 134 | COLOR_MAP.put("Search", searchColor); 135 | 136 | COLOR_MAP.put("Pop-Front", deleteColor); 137 | COLOR_MAP.put("Pop-Back", deleteColor); 138 | COLOR_MAP.put("Delete", deleteColor); 139 | COLOR_MAP.put("Delete-Range", deleteColor); 140 | } 141 | 142 | private static void initializePerOperationMap() { 143 | // Initialize the per operation duration map: 144 | for (String listName : LIST_TYPE_NAMES) { 145 | for (String methodName : METHOD_NAMES) { 146 | PER_OPERATION_DURATION_COUNTER_MAP 147 | .put(String.format( 148 | "%s%s", 149 | listName, 150 | methodName), 151 | 0L); 152 | } 153 | } 154 | } 155 | 156 | public static void main(String[] args) { 157 | // deleteRange(); 158 | // System.exit(0); 159 | 160 | try (AffinityLock al = AffinityLock.acquireLock()) { 161 | Thread.currentThread().setPriority(Thread.MAX_PRIORITY); 162 | warmupRemove(); 163 | benchmarkRemove(); 164 | System.exit(0); 165 | clearDurationCounterMap(); 166 | warmup(); 167 | benchmark(); 168 | System.out.println("<<< Total durations >>>"); 169 | printTotalDurations(); 170 | } 171 | } 172 | 173 | private static void deleteRange() { 174 | System.out.println("deleteRange():"); 175 | final List indexedList = new IndexedLinkedList<>(); 176 | final List treeList = new TreeList<>(); 177 | final Object obj = new Object(); 178 | final int N = 50_000_000; 179 | 180 | for (int i = 0; i < N; i++) { 181 | indexedList.add(obj); 182 | } 183 | 184 | System.out.println("Indexed list data loaded."); 185 | 186 | long start = System.currentTimeMillis(); 187 | indexedList.subList(100_000, N - 100_000).clear(); 188 | long end = System.currentTimeMillis(); 189 | 190 | indexedList.clear(); 191 | System.gc(); 192 | 193 | System.out.println("indexed list: " + (end - start) + " millis."); 194 | 195 | for (int i = 0; i < N; i++) { 196 | treeList.add(obj); 197 | } 198 | 199 | System.out.println("Tree list data loaded."); 200 | 201 | start = System.currentTimeMillis(); 202 | treeList.subList(100_000, N - 100_000).clear(); 203 | end = System.currentTimeMillis(); 204 | 205 | System.out.println("tree list: " + (end - start) + " millis."); 206 | treeList.clear(); 207 | System.gc(); 208 | } 209 | 210 | private static void warmup() { 211 | for (String methodName : METHOD_NAMES) { 212 | for (String listTypeName : LIST_TYPE_NAMES) { 213 | for (int listSize : LIST_SIZES) { 214 | benchmark(methodName, listTypeName, listSize, false); 215 | } 216 | } 217 | } 218 | 219 | printTotalDurations(); 220 | clearPerOperationCounterMap(); 221 | } 222 | 223 | private static void warmupRemove() { 224 | benchmarkRemoveImpl(false); 225 | } 226 | 227 | private static void benchmark() { 228 | clearDurationCounterMap(); // Clear from the warmup run. 229 | 230 | for (String methodName : METHOD_NAMES) { 231 | for (String listTypeName : LIST_TYPE_NAMES) { 232 | for (int listSize : LIST_SIZES) { 233 | benchmark(methodName, listTypeName, listSize, true); 234 | } 235 | } 236 | } 237 | 238 | showPerOperationStatistics(); 239 | } 240 | 241 | private static void benchmarkRemove() { 242 | benchmarkRemoveImpl(true); 243 | } 244 | 245 | private static void benchmarkRemoveImpl(boolean print) { 246 | 247 | } 248 | 249 | private static void showPerOperationStatistics() { 250 | System.out.println(); 251 | System.out.println(); 252 | 253 | for (final String listTypeName : LIST_TYPE_NAMES) { 254 | System.out.printf(">>> List type name: %s\n", listTypeName); 255 | long listDuration = 0L; 256 | 257 | for (final String methodName : METHOD_NAMES) { 258 | final String line = 259 | String.format( 260 | "%s%s", 261 | listTypeName, 262 | methodName); 263 | 264 | final String algorithmName = 265 | BENCHMARK_OPERATION_NAME_TO_GNUPLOT_NAME 266 | .get(methodName); 267 | 268 | final String fmt = 269 | String.format( 270 | "%%%ds %%10d %%8s\n", 271 | "\"Push-Front-Collection\"".length()); 272 | 273 | System.out.printf( 274 | fmt, 275 | String.format("\"%s\"", algorithmName), 276 | milliseconds( 277 | PER_OPERATION_DURATION_COUNTER_MAP.get(line)), 278 | COLOR_MAP.get(algorithmName) 279 | ); 280 | 281 | listDuration += PER_OPERATION_DURATION_COUNTER_MAP.get(line); 282 | } 283 | 284 | System.out.printf(" List duration: %d microseconds.\n\n", 285 | listDuration); 286 | } 287 | 288 | System.out.println(); 289 | System.out.println(); 290 | } 291 | 292 | private static void printTotalDurations() { 293 | for (Map.Entry e : DURATION_COUNTER_MAP.entrySet()) { 294 | System.out.printf("%-" + "indexedLinkedList".length() + "s: %d\n", 295 | e.getKey(), 296 | milliseconds(e.getValue())); 297 | } 298 | } 299 | 300 | private static List getEmptyList(String listTypeName) { 301 | if (listTypeName.equals(LIST_TYPE_NAMES[0])) { 302 | return new ArrayList<>(); 303 | } else if (listTypeName.equals(LIST_TYPE_NAMES[1])) { 304 | return new IndexedLinkedList<>(); 305 | } else if (listTypeName.equals(LIST_TYPE_NAMES[2])) { 306 | return new LinkedList<>(); 307 | } else if (listTypeName.equals(LIST_TYPE_NAMES[3])) { 308 | return new TreeList<>(); 309 | } else { 310 | throw new IllegalArgumentException( 311 | "Unknown list type name: " + listTypeName); 312 | } 313 | } 314 | 315 | private static String getListTypeName(List list) { 316 | String className = list.getClass().getSimpleName(); 317 | 318 | switch (className) { 319 | case "ArrayList": 320 | return "arrayList"; 321 | 322 | case "IndexedLinkedList": 323 | return "indexedLinkedList"; 324 | 325 | case "LinkedList": 326 | return "linkedList"; 327 | 328 | case "TreeList": 329 | return "treeList"; 330 | 331 | default: 332 | throw new IllegalArgumentException( 333 | "Uknown List class: " + className); 334 | } 335 | } 336 | 337 | private static void benchmark(String methodName, 338 | String listTypeName, 339 | int listSize, 340 | boolean print) { 341 | System.gc(); 342 | List list = getEmptyList(listTypeName); 343 | loadList(list, listSize); 344 | long duration; 345 | 346 | switch (methodName) { 347 | case "AddAtBeginning": 348 | duration = BenchmarkMethods.addAtBeginning(list, print); 349 | break; 350 | 351 | case "AddAtEnd": 352 | duration = BenchmarkMethods.addAtEnd(list, print); 353 | break; 354 | 355 | case "AddRandom": 356 | duration = BenchmarkMethods.addRandom(list, 357 | print, 358 | new Random(13L)); 359 | break; 360 | 361 | case "AppendCollection": 362 | List listToAppend = 363 | new ArrayList<>(Bounds.APPEND_COLLECTION_SIZE); 364 | 365 | loadList(listToAppend, Bounds.APPEND_COLLECTION_SIZE); 366 | duration = BenchmarkMethods.appendCollection(list, 367 | listToAppend, 368 | print); 369 | break; 370 | 371 | case "GetRandom": 372 | duration = BenchmarkMethods.getRandom(list, 373 | new Random(26L), 374 | print); 375 | break; 376 | 377 | case "InsertCollection": 378 | List listToInsert = 379 | new ArrayList<>(Bounds.INSERT_COLLECTION_SIZE); 380 | 381 | loadList(listToInsert, Bounds.INSERT_COLLECTION_SIZE); 382 | Random random = new Random(4L); 383 | duration = BenchmarkMethods.insertCollection(list, 384 | listToInsert, 385 | random, 386 | print); 387 | break; 388 | 389 | case "PrependCollection": 390 | List listToPrepend = 391 | new ArrayList<>(Bounds.APPEND_COLLECTION_SIZE); 392 | 393 | loadList(listToPrepend, Bounds.APPEND_COLLECTION_SIZE); 394 | duration = BenchmarkMethods.prependCollection(list, 395 | listToPrepend, 396 | print); 397 | break; 398 | 399 | case "RemoveFromBeginning": 400 | duration = BenchmarkMethods.removeFromBeginning(list, print); 401 | break; 402 | 403 | case "RemoveFromEnd": 404 | duration = BenchmarkMethods.removeFromEnd(list, print); 405 | break; 406 | 407 | case "RemoveRandom": 408 | duration = BenchmarkMethods.removeRandom(list, 409 | print, 410 | new Random(3L)); 411 | break; 412 | 413 | case "RemoveRange": 414 | duration = BenchmarkMethods.removeRange(list, 415 | print, 416 | new Random(4L)); 417 | break; 418 | 419 | default: 420 | throw new IllegalArgumentException( 421 | "Unknown method name: " + methodName); 422 | } 423 | 424 | final String line = String.format("%s%s", listTypeName, methodName); 425 | 426 | PER_OPERATION_DURATION_COUNTER_MAP.put( 427 | line, 428 | PER_OPERATION_DURATION_COUNTER_MAP.get(line) + duration); 429 | 430 | DURATION_COUNTER_MAP.put(listTypeName, 431 | DURATION_COUNTER_MAP.get(listTypeName) 432 | + duration); 433 | } 434 | 435 | private static void loadList(List list, int listSize) { 436 | for (int i = 0; i < listSize; i++) { 437 | list.add(ELEMENT); 438 | } 439 | } 440 | 441 | private static final class BenchmarkMethods { 442 | 443 | static long addAtBeginning(List list, boolean print) { 444 | long startTime; 445 | long endTime; 446 | 447 | if (list instanceof Deque) { 448 | startTime = microseconds(); 449 | Deque deque = (Deque) list; 450 | 451 | for (int i = 0; i < Bounds.NUMBER_OF_ADDITIONS_AT_BEGINNING; i++) { 452 | deque.addFirst(ELEMENT); 453 | } 454 | 455 | endTime = microseconds(); 456 | } else { 457 | startTime = microseconds(); 458 | 459 | for (int i = 0; i < Bounds.NUMBER_OF_ADDITIONS_AT_BEGINNING; i++) { 460 | list.add(0, ELEMENT); 461 | } 462 | 463 | endTime = microseconds(); 464 | } 465 | 466 | long duration = endTime - startTime; 467 | 468 | if (print) { 469 | String listTypeName = getListTypeName(list); 470 | 471 | System.out.println( 472 | listTypeName 473 | + "AddAtBeginning: " 474 | + duration); 475 | } 476 | 477 | return duration; 478 | } 479 | 480 | static long addAtEnd(List list, boolean print) { 481 | long startTime; 482 | long endTime; 483 | 484 | if (list instanceof Deque) { 485 | startTime = microseconds(); 486 | Deque deque = (Deque) list; 487 | 488 | for (int i = 0; i < Bounds.NUMBER_OF_ADDITIONS_AT_END; i++) { 489 | deque.addLast(ELEMENT); 490 | } 491 | 492 | endTime = microseconds(); 493 | } else { 494 | startTime = microseconds(); 495 | 496 | for (int i = 0; i < Bounds.NUMBER_OF_ADDITIONS_AT_END; i++) { 497 | list.add(ELEMENT); 498 | } 499 | 500 | endTime = microseconds(); 501 | } 502 | 503 | long duration = endTime - startTime; 504 | 505 | if (print) { 506 | String listTypeName = getListTypeName(list); 507 | System.out.println( 508 | listTypeName 509 | + "AddAtEnd: " 510 | + duration); 511 | } 512 | 513 | return duration; 514 | } 515 | 516 | static long addRandom(List list, boolean print, Random random) { 517 | long startTime = microseconds(); 518 | 519 | for (int i = 0; i < Bounds.NUMBER_OF_RANDOM_ADDS; i++) { 520 | list.add(random.nextInt(list.size() + 1), ELEMENT); 521 | } 522 | 523 | long endTime = microseconds(); 524 | long duration = endTime - startTime; 525 | 526 | if (print) { 527 | String listTypeName = getListTypeName(list); 528 | System.out.println( 529 | listTypeName 530 | + "AddRandom: " 531 | + duration); 532 | } 533 | 534 | return duration; 535 | } 536 | 537 | static long appendCollection(List list, 538 | List listToAdd, 539 | boolean print) { 540 | 541 | long startTime = microseconds(); 542 | 543 | for (int i = 0; i < Bounds.NUMBER_OF_COLLECTION_APPENDS; i++) { 544 | list.addAll(listToAdd); 545 | } 546 | 547 | long endTime = microseconds(); 548 | long duration = endTime - startTime; 549 | 550 | if (print) { 551 | String listTypeName = getListTypeName(list); 552 | System.out.println( 553 | listTypeName 554 | + "AppendCollection: " 555 | + duration); 556 | } 557 | 558 | return duration; 559 | } 560 | 561 | static long getRandom(List list, Random random, boolean print) { 562 | int[] indices = new int[Bounds.NUMBER_OF_GETS]; 563 | 564 | for (int i = 0; i < indices.length; i++) { 565 | indices[i] = random.nextInt(indices.length); 566 | } 567 | 568 | long startTime = microseconds(); 569 | 570 | for (int index : indices) { 571 | list.get(index); 572 | } 573 | 574 | long endTime = microseconds(); 575 | long duration = endTime - startTime; 576 | 577 | if (print) { 578 | String listTypeName = getListTypeName(list); 579 | System.out.println( 580 | listTypeName 581 | + "GetRandom: " 582 | + duration); 583 | } 584 | 585 | return duration; 586 | } 587 | 588 | static long insertCollection(List list, 589 | List listToInsert, 590 | Random random, 591 | boolean print) { 592 | long startTime = microseconds(); 593 | 594 | for (int i = 0; i < Bounds.NUMBER_OF_COLLECTION_INSERTS; i++) { 595 | int index = random.nextInt(list.size() + 1); 596 | list.addAll(index, listToInsert); 597 | } 598 | 599 | long endTime = microseconds(); 600 | long duration = endTime - startTime; 601 | 602 | if (print) { 603 | String listTypeName = getListTypeName(list); 604 | System.out.println( 605 | listTypeName 606 | + "InsertCollection: " 607 | + duration); 608 | } 609 | 610 | return duration; 611 | } 612 | 613 | static long prependCollection(List list, 614 | List listToPrepend, 615 | boolean print) { 616 | 617 | long startTime = microseconds(); 618 | 619 | for (int i = 0; i < Bounds.NUMBER_OF_COLLECTION_APPENDS; i++) { 620 | list.addAll(0, listToPrepend); 621 | } 622 | 623 | long endTime = microseconds(); 624 | long duration = endTime - startTime; 625 | 626 | if (print) { 627 | String listTypeName = getListTypeName(list); 628 | System.out.println( 629 | listTypeName 630 | + "PrependCollection: " 631 | + duration); 632 | } 633 | 634 | return duration; 635 | } 636 | 637 | static long removeFromBeginning(List list, boolean print) { 638 | 639 | long startTime; 640 | long endTime; 641 | long duration; 642 | 643 | if (list instanceof Deque) { 644 | Deque deque = (Deque) list; 645 | startTime = microseconds(); 646 | 647 | for (int i = 0; i < Bounds.NUMBER_OF_REMOVE_FIRST_OPS; i++) { 648 | deque.removeFirst(); 649 | } 650 | 651 | endTime = microseconds(); 652 | } else { 653 | startTime = microseconds(); 654 | 655 | for (int i = 0; i < Bounds.NUMBER_OF_REMOVE_FIRST_OPS; i++) { 656 | list.remove(0); 657 | } 658 | 659 | endTime = microseconds(); 660 | } 661 | 662 | duration = endTime - startTime; 663 | 664 | if (print) { 665 | String listTypeName = getListTypeName(list); 666 | System.out.println( 667 | listTypeName 668 | + "RemoveFromBeginning: " 669 | + duration); 670 | } 671 | 672 | return duration; 673 | } 674 | 675 | static long removeFromEnd(List list, boolean print) { 676 | 677 | long startTime; 678 | long endTime; 679 | 680 | if (list instanceof Deque) { 681 | Deque deque = (Deque) list; 682 | startTime = microseconds(); 683 | 684 | for (int i = 0; 685 | i < Bounds.NUMBER_OF_REMOVE_LAST_OPS 686 | && !deque.isEmpty(); 687 | i++) { 688 | deque.removeLast(); 689 | } 690 | 691 | endTime = microseconds(); 692 | } else { 693 | startTime = microseconds(); 694 | 695 | for (int i = 0; 696 | i < Bounds.NUMBER_OF_REMOVE_LAST_OPS && !list.isEmpty(); 697 | i++) { 698 | list.remove(list.size() - 1); 699 | } 700 | 701 | endTime = microseconds(); 702 | } 703 | 704 | long duration = endTime - startTime; 705 | 706 | if (print) { 707 | String listTypeName = getListTypeName(list); 708 | System.out.println( 709 | listTypeName 710 | + "RemoveFromEnd: " 711 | + duration); 712 | } 713 | 714 | return duration; 715 | } 716 | 717 | static long removeRandom(List list, 718 | boolean print, 719 | Random random) { 720 | long startTime = microseconds(); 721 | 722 | for (int i = 0; i < Bounds.NUMBER_OF_RANDOM_REMOVES; i++) { 723 | list.remove(random.nextInt(list.size())); 724 | } 725 | 726 | long endTime = microseconds(); 727 | long duration = endTime - startTime; 728 | 729 | if (print) { 730 | String listTypeName = getListTypeName(list); 731 | System.out.println( 732 | listTypeName 733 | + "RemoveRandom: " 734 | + duration); 735 | } 736 | 737 | return duration; 738 | } 739 | 740 | static long removeRange(List list, 741 | boolean print, 742 | Random random) { 743 | long startTime; 744 | long endTime; 745 | 746 | int requestedSize = (3 * list.size()) / 5; 747 | 748 | startTime = microseconds(); 749 | 750 | while (list.size() > requestedSize) { 751 | int fromIndex = random.nextInt(list.size()) - 752 | Bounds.REMOVE_RANGE_SIZE; 753 | 754 | fromIndex = Math.max(fromIndex, 0); 755 | int toIndex = fromIndex + Bounds.REMOVE_RANGE_SIZE; 756 | list.subList(fromIndex, toIndex).clear(); 757 | } 758 | 759 | endTime = microseconds(); 760 | long duration = endTime - startTime; 761 | 762 | if (print) { 763 | String listTypeName = getListTypeName(list); 764 | System.out.println( 765 | listTypeName 766 | + "RemoveRange: " 767 | + duration); 768 | } 769 | 770 | return duration; 771 | } 772 | } 773 | 774 | private static long microseconds() { 775 | return System.nanoTime() / 1000L; 776 | } 777 | 778 | private static long milliseconds(long microseconds) { 779 | return (long)(Math.round((double) microseconds) / 780 | (double) 1000.0); 781 | } 782 | } 783 | -------------------------------------------------------------------------------- /src/main/java/com/github/coderodde/util/benchmark/LinkedListBenchmarkRunner.java: -------------------------------------------------------------------------------- 1 | package com.github.coderodde.util.benchmark; 2 | 3 | import com.github.coderodde.util.IndexedLinkedList; 4 | import java.net.URISyntaxException; 5 | import java.util.HashSet; 6 | import java.util.List; 7 | import java.util.Set; 8 | import org.apache.commons.collections4.list.TreeList; 9 | 10 | /** 11 | * This class provides the entry point to the benchmark. 12 | * 13 | * @author Rodion "rodde" Efremov 14 | * @version 1.6 15 | * @since 1.6 16 | */ 17 | final class LinkedListBenchmarkRunner { 18 | 19 | private static final String ALL_BENCHMARK_METHODS_FLAG = "--all"; 20 | private static final String BENCHMARK_SUBLIST_CLEAR_FLAG = "--clear"; 21 | private static final String BENCHMARK_REMOVE_ALL = "--remove-all"; 22 | private static final String BENCHMARK_SORT = "--sort"; 23 | private static final String HELP = "-h"; 24 | 25 | private LinkedListBenchmarkRunner() { 26 | 27 | } 28 | 29 | /** 30 | * Runs the benchmark. 31 | * 32 | * @param args the command line arguments. Ignored. 33 | */ 34 | public static void main(String[] args) { 35 | // deleteRange(); 36 | // System.exit(0); 37 | boolean runSubListClear = false; 38 | boolean runRemoveAll = false; 39 | boolean runSort = false; 40 | 41 | Set commandLineArgumentSet = new HashSet<>(3); 42 | 43 | for (String arg : args) { 44 | commandLineArgumentSet.add(arg); 45 | } 46 | 47 | if (commandLineArgumentSet.contains(HELP)) { 48 | printHelp(); 49 | return; 50 | } 51 | 52 | if (commandLineArgumentSet.contains(ALL_BENCHMARK_METHODS_FLAG)) { 53 | runSubListClear = true; 54 | runRemoveAll = true; 55 | runSort = true; 56 | } else { 57 | if (commandLineArgumentSet.contains(BENCHMARK_SUBLIST_CLEAR_FLAG)) { 58 | runSubListClear = true; 59 | } 60 | 61 | if (commandLineArgumentSet.contains(BENCHMARK_REMOVE_ALL)) { 62 | runRemoveAll = true; 63 | } 64 | 65 | if (commandLineArgumentSet.contains(BENCHMARK_SORT)) { 66 | runSort = true; 67 | } 68 | } 69 | 70 | long seed = System.currentTimeMillis(); 71 | 72 | System.out.println("<<< Benchmark seed = " + seed + " >>>"); 73 | System.out.println(); 74 | 75 | System.out.println("<<< Flags >>>"); 76 | System.out.println("runSubListClear: " + runSubListClear); 77 | System.out.println("runRemoveAll : " + runRemoveAll); 78 | System.out.println("runSort : " + runSort); 79 | System.out.println(); 80 | 81 | LinkedListBenchmark benchmark = 82 | new LinkedListBenchmark(seed, 83 | runSubListClear, 84 | runRemoveAll, 85 | runSort); 86 | 87 | benchmark.warmup(); 88 | System.out.println(); 89 | benchmark.benchmark(); 90 | } 91 | 92 | private static void printHelp() { 93 | String jarFileName = getJarFileName(); 94 | 95 | if (jarFileName == null) { 96 | System.out.println("Could not read the name of the JAR file."); 97 | return; 98 | } 99 | 100 | String text = 101 | "Usage: java -jar " 102 | + jarFileName 103 | + " [" 104 | + HELP 105 | + " | " 106 | + ALL_BENCHMARK_METHODS_FLAG 107 | + " | " 108 | + BENCHMARK_SUBLIST_CLEAR_FLAG 109 | + " | " 110 | + BENCHMARK_REMOVE_ALL + "]\n"; 111 | 112 | System.out.println(text); 113 | } 114 | 115 | private static String getJarFileName() { 116 | try { 117 | String jarPath = 118 | LinkedListBenchmarkRunner 119 | .class 120 | .getProtectionDomain() 121 | .getCodeSource() 122 | .getLocation() 123 | .toURI() 124 | .getPath(); 125 | 126 | return jarPath.substring(jarPath.lastIndexOf("/") + 1); 127 | } catch (URISyntaxException ex) { 128 | return null; 129 | } 130 | } 131 | 132 | private static void deleteRange() { 133 | System.out.println("deleteRange():"); 134 | final List indexedList = new IndexedLinkedList<>(); 135 | final List treeList = new TreeList<>(); 136 | final Object obj = new Object(); 137 | 138 | for (int i = 0; i < 50_000_000; i++) { 139 | indexedList.add(obj); 140 | treeList.add(obj); 141 | } 142 | 143 | long start = System.currentTimeMillis(); 144 | indexedList.subList(10, 50_000_000 - 10); 145 | long end = System.currentTimeMillis(); 146 | 147 | System.out.println("indexed list: " + (end - start) + " millis."); 148 | 149 | start = System.currentTimeMillis(); 150 | treeList.subList(10, 50_000_000 - 10); 151 | end = System.currentTimeMillis(); 152 | 153 | System.out.println("tree list: " + (end - start) + " millis."); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/test/java/com/github/coderodde/util/FingerListTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package com.github.coderodde.util; 18 | 19 | import java.util.List; 20 | import static org.junit.Assert.assertEquals; 21 | import static org.junit.Assert.assertFalse; 22 | import static org.junit.Assert.assertTrue; 23 | import org.junit.Before; 24 | import org.junit.Test; 25 | 26 | public class FingerListTest { 27 | 28 | private final IndexedLinkedList list = new IndexedLinkedList<>(); 29 | private final FingerList fl = list.fingerList; 30 | 31 | @Before 32 | public void setUp() { 33 | fl.clear(); 34 | } 35 | 36 | @Test 37 | public void fingerToString() { 38 | Finger f = new Finger<>(new Node<>(13), 2); 39 | assertEquals("[Finger; index = 2, item = 13]", f.toString()); 40 | f = new Finger<>(new Node<>(null), 3); 41 | assertEquals("[Finger; index = 3, item = null]", f.toString()); 42 | } 43 | 44 | // @Test 45 | public void fingerListString() { 46 | fl.appendFinger(new Finger<>(new Node<>(Integer.valueOf(0)), 0)); 47 | fl.appendFinger(new Finger<>(new Node<>(Integer.valueOf(2)), 1)); 48 | assertEquals("[FingerList, size = 2]", fl.toString()); 49 | } 50 | 51 | @Test 52 | public void appendGetFinger() { 53 | fl.appendFinger(new Finger<>(new Node<>(Integer.valueOf(0)), 0)); 54 | fl.appendFinger(new Finger<>(new Node<>(Integer.valueOf(1)), 1)); 55 | fl.appendFinger(new Finger<>(new Node<>(Integer.valueOf(3)), 3)); 56 | fl.appendFinger(new Finger<>(new Node<>(Integer.valueOf(6)), 6)); 57 | fl.fingerArray[4].index = 8; 58 | fl.fingerArray[4].node = new Node<>(Integer.valueOf(1000)); 59 | 60 | Finger finger = fl.getFinger(fl.getClosestFingerIndex(0)); 61 | assertEquals(0, finger.index); 62 | assertEquals(Integer.valueOf(0), finger.node.item); 63 | 64 | finger = fl.getFinger(fl.getClosestFingerIndex(1)); 65 | assertEquals(1, finger.index); 66 | assertEquals(Integer.valueOf(1), finger.node.item); 67 | 68 | finger = fl.getFinger(fl.getClosestFingerIndex(2)); 69 | assertEquals(3, finger.index); 70 | assertEquals(Integer.valueOf(3), finger.node.item); 71 | 72 | finger = fl.getFinger(fl.getClosestFingerIndex(3)); 73 | assertEquals(3, finger.index); 74 | assertEquals(Integer.valueOf(3), finger.node.item); 75 | 76 | finger = fl.getFinger(fl.getClosestFingerIndex(4)); 77 | assertEquals(3, finger.index); 78 | assertEquals(Integer.valueOf(3), finger.node.item); 79 | 80 | finger = fl.getFinger(fl.getClosestFingerIndex(5)); 81 | assertEquals(6, finger.index); 82 | assertEquals(Integer.valueOf(6), finger.node.item); 83 | 84 | finger = fl.getFinger(fl.getClosestFingerIndex(6)); 85 | assertEquals(6, finger.index); 86 | assertEquals(Integer.valueOf(6), finger.node.item); 87 | } 88 | 89 | // @Test 90 | public void insertFingerAtFront() { 91 | fl.appendFinger(new Finger<>(new Node<>(Integer.valueOf(0)), 0)); 92 | fl.appendFinger(new Finger<>(new Node<>(Integer.valueOf(1)), 1)); 93 | fl.appendFinger(new Finger<>(new Node<>(Integer.valueOf(3)), 3)); 94 | fl.appendFinger(new Finger<>(new Node<>(Integer.valueOf(6)), 6)); 95 | 96 | Finger insertionFinger = new Finger<>(new Node<>(null), 0); 97 | 98 | // fl.insertFinger(insertionFinger); 99 | 100 | Finger finger = fl.getFinger(fl.getClosestFingerIndex(0)); 101 | assertEquals(insertionFinger.index, finger.index); 102 | 103 | assertEquals(5, fl.size()); 104 | } 105 | 106 | // @Test 107 | public void insertFingerAtTail() { 108 | fl.appendFinger(new Finger<>(new Node<>(Integer.valueOf(2)), 2)); 109 | fl.appendFinger(new Finger<>(new Node<>(Integer.valueOf(4)), 4)); 110 | fl.appendFinger(new Finger<>(new Node<>(Integer.valueOf(5)), 5)); 111 | 112 | // Add end of finger list sentinel: 113 | fl.fingerArray[3] = 114 | new Finger<>(new Node(Integer.valueOf(100)), 10); 115 | 116 | Finger insertionFinger = new Finger<>(new Node<>(null), 6); 117 | 118 | // fl.insertFinger(insertionFinger); 119 | 120 | Finger finger = fl.getFinger(fl.getClosestFingerIndex(6)); 121 | assertEquals(insertionFinger.index, finger.index); 122 | 123 | assertEquals(4, fl.size()); 124 | } 125 | 126 | // @Test 127 | public void insertFingerInBetween1() { 128 | fl.appendFinger(new Finger<>(new Node<>(Integer.valueOf(2)), 2)); 129 | fl.appendFinger(new Finger<>(new Node<>(Integer.valueOf(4)), 4)); 130 | fl.appendFinger(new Finger<>(new Node<>(Integer.valueOf(5)), 5)); 131 | 132 | Finger insertionFinger = new Finger<>(new Node<>(null), 4); 133 | 134 | // fl.insertFinger(insertionFinger); 135 | 136 | assertEquals(insertionFinger, fl.getFinger(1)); 137 | } 138 | 139 | // @Test 140 | public void insertFingerInBetween2() { 141 | fl.appendFinger(new Finger<>(new Node<>(Integer.valueOf(2)), 2)); 142 | fl.appendFinger(new Finger<>(new Node<>(Integer.valueOf(4)), 4)); 143 | fl.appendFinger(new Finger<>(new Node<>(Integer.valueOf(5)), 5)); 144 | 145 | Finger insertionFinger = new Finger<>(new Node<>(null), 3); 146 | 147 | // fl.insertFinger(insertionFinger); 148 | 149 | assertEquals(insertionFinger, fl.getFinger(1)); 150 | } 151 | 152 | // @Test 153 | public void makeRoomAtPrefix1Old() { 154 | for (int i = 0; i < 10; i++) { 155 | list.add(i); 156 | } 157 | 158 | list.fingerList.setFingerIndices(6, 7, 8, 9); 159 | 160 | list.fingerList.makeRoomAtPrefix(5, 0, 3); 161 | 162 | Finger finger0 = list.fingerList.fingerArray[0]; 163 | Finger finger1 = list.fingerList.fingerArray[1]; 164 | Finger finger2 = list.fingerList.fingerArray[2]; 165 | 166 | assertEquals(2, finger0.index); 167 | assertEquals(3, finger1.index); 168 | assertEquals(4, finger2.index); 169 | 170 | System.out.println("makeRoomAtPrefix1Oldl passed!"); 171 | } 172 | 173 | @Test 174 | public void makeRoomAtPrefix1() { 175 | 176 | loadList(10); 177 | 178 | list.fingerList.setFingerIndices(1, 3, 4, 6); 179 | list.fingerList.makeRoomAtPrefix(4, 2, 2); 180 | 181 | IndexedLinkedList expectedList = new IndexedLinkedList<>(list); 182 | expectedList.fingerList.setFingerIndices(0, 1, 4, 6); 183 | 184 | assertTrue(list.strongEquals(expectedList)); 185 | 186 | System.out.println("makeRoomAtPrefix1 passed!"); 187 | } 188 | 189 | @Test 190 | public void makeRoomAtPrefix2() { 191 | 192 | loadList(10); 193 | 194 | list.fingerList.setFingerIndices(1, 3, 5, 8); 195 | list.fingerList.makeRoomAtPrefix(4, 2, 1); 196 | 197 | IndexedLinkedList expectedList = new IndexedLinkedList<>(list); 198 | expectedList.fingerList.setFingerIndices(1, 2, 5, 8); 199 | 200 | assertTrue(list.strongEquals(expectedList)); 201 | 202 | System.out.println("makeRoomAtPrefix2 passed!"); 203 | } 204 | 205 | @Test 206 | public void makeRoomAtPrefix3() { 207 | 208 | loadList(10); 209 | 210 | list.fingerList.setFingerIndices(1, 4, 6, 9); 211 | list.fingerList.makeRoomAtPrefix(5, 2, 2); 212 | 213 | IndexedLinkedList expectedList = new IndexedLinkedList<>(list); 214 | expectedList.fingerList.setFingerIndices(1, 2, 6, 9); 215 | 216 | assertTrue(list.strongEquals(expectedList)); 217 | 218 | System.out.println("makeRoomAtPrefix3 passed!"); 219 | } 220 | 221 | @Test 222 | public void makeRoomAtPrefix4() { 223 | 224 | loadList(10); 225 | 226 | list.fingerList.setFingerIndices(0, 1, 7, 8); 227 | list.fingerList.makeRoomAtPrefix(4, 2, 2); 228 | 229 | IndexedLinkedList expectedList = new IndexedLinkedList<>(list); 230 | expectedList.fingerList.setFingerIndices(0, 1, 7, 8); 231 | 232 | assertTrue(list.strongEquals(expectedList)); 233 | 234 | System.out.println("makeRoomAtPrefix4 passed!"); 235 | } 236 | 237 | @Test 238 | public void makeRoomAtPrefix5() { 239 | 240 | loadList(10); 241 | 242 | list.fingerList.setFingerIndices(0, 2, 6, 8); 243 | list.fingerList.makeRoomAtPrefix(5, 2, 2); 244 | 245 | IndexedLinkedList expectedList = new IndexedLinkedList<>(list); 246 | expectedList.fingerList.setFingerIndices(0, 2, 6, 8); 247 | 248 | assertTrue(list.strongEquals(expectedList)); 249 | 250 | System.out.println("makeRoomAtPrefix5 passed!"); 251 | } 252 | 253 | @Test 254 | public void makeRoomAtPrefix6() { 255 | 256 | loadList(101); 257 | 258 | list.fingerList.setFingerIndices( 259 | 1, 3, 6, 10, 15, 29, 54, 57, 69, 72, 100 260 | ); 261 | 262 | list.fingerList.makeRoomAtPrefix(12, 4, 5); 263 | 264 | IndexedLinkedList expectedList = new IndexedLinkedList<>(list); 265 | expectedList.fingerList.setFingerIndices( 266 | 1, 3, 4, 5, 15, 29, 54, 57, 69, 72, 100 267 | ); 268 | 269 | assertTrue(list.strongEquals(expectedList)); 270 | 271 | System.out.println("makeRoomAtPrefix6 passed!"); 272 | } 273 | 274 | @Test 275 | public void makeRoomAtSuffix1() { 276 | loadList(10); 277 | 278 | list.fingerList.setFingerIndices(3, 4, 8, 9); 279 | list.fingerList.makeRoomAtSuffix(6, 2, 2, 2); 280 | 281 | IndexedLinkedList expectedList = new IndexedLinkedList<>(list); 282 | expectedList.fingerList.setFingerIndices(3, 4, 8, 9); 283 | 284 | assertTrue(list.strongEquals(expectedList)); 285 | 286 | System.out.println("makeRoomAtSuffix1 passed!"); 287 | } 288 | 289 | @Test 290 | public void makeRoomAtSuffix2() { 291 | loadList(10); 292 | 293 | list.fingerList.setFingerIndices(3, 4, 7, 9); 294 | list.fingerList.makeRoomAtSuffix(6, 2, 2, 2); 295 | 296 | IndexedLinkedList expectedList = new IndexedLinkedList<>(list); 297 | expectedList.fingerList.setFingerIndices(3, 4, 8, 9); 298 | 299 | assertTrue(list.strongEquals(expectedList)); 300 | 301 | System.out.println("makeRoomAtSuffix2 passed!"); 302 | } 303 | 304 | @Test 305 | public void makeRoomAtSuffix3() { 306 | loadList(10); 307 | 308 | list.fingerList.setFingerIndices(3, 4, 7, 8); 309 | list.fingerList.makeRoomAtSuffix(6, 2, 2, 2); 310 | 311 | IndexedLinkedList expectedList = new IndexedLinkedList<>(list); 312 | expectedList.fingerList.setFingerIndices(3, 4, 8, 9); 313 | 314 | assertTrue(list.strongEquals(expectedList)); 315 | 316 | System.out.println("makeRoomAtSuffix3 passed!"); 317 | } 318 | 319 | @Test 320 | public void makeRoomAtSuffix4() { 321 | loadList(10); 322 | 323 | list.fingerList.setFingerIndices(3, 4, 7, 8); 324 | list.fingerList.makeRoomAtSuffix(6, 2, 0, 2); 325 | 326 | IndexedLinkedList expectedList = new IndexedLinkedList<>(list); 327 | expectedList.fingerList.setFingerIndices(3, 4, 7, 8); 328 | 329 | assertTrue(list.strongEquals(expectedList)); 330 | 331 | System.out.println("makeRoomAtSuffix4 passed!"); 332 | } 333 | 334 | @Test 335 | public void arrangePrefix1() { 336 | loadList(10); 337 | 338 | list.fingerList.setFingerIndices(1, 3, 5, 8); 339 | list.fingerList.arrangePrefix(4, 2, 2); 340 | 341 | IndexedLinkedList expectedList = new IndexedLinkedList<>(list); 342 | expectedList.fingerList.setFingerIndices(0, 1, 2, 3); 343 | 344 | assertTrue(list.strongEquals(expectedList)); 345 | 346 | System.out.println("arrangePrefix1 passed!"); 347 | } 348 | 349 | @Test 350 | public void arrangePrefix2() { 351 | loadList(10); 352 | 353 | list.fingerList.setFingerIndices(5, 6, 8, 9); 354 | list.fingerList.arrangePrefix(5, 0, 4); 355 | 356 | IndexedLinkedList expectedList = new IndexedLinkedList<>(list); 357 | expectedList.fingerList.setFingerIndices(1, 2, 3, 4); 358 | 359 | assertTrue(list.strongEquals(expectedList)); 360 | 361 | System.out.println("arrangePrefix2 passed!"); 362 | } 363 | 364 | @Test 365 | public void arrangePrefix3() { 366 | 367 | loadList(101); 368 | 369 | list.fingerList.setFingerIndices( 370 | 1, 3, 6, 10, 15, 29, 54, 57, 69, 72, 100 371 | ); 372 | 373 | list.fingerList.arrangePrefix(12, 4, 5); 374 | 375 | IndexedLinkedList expectedList = new IndexedLinkedList<>(list); 376 | expectedList.fingerList.setFingerIndices( 377 | 1, 3, 4, 5, 6, 7, 8, 9, 10, 72, 100 378 | ); 379 | 380 | assertTrue(list.strongEquals(expectedList)); 381 | 382 | System.out.println("arrangePrefix3 passed!"); 383 | } 384 | 385 | @Test 386 | public void pushCoveredFingersToSuffix1() { 387 | 388 | loadList(10); 389 | 390 | list.fingerList.setFingerIndices(1, 3, 4, 5); 391 | list.fingerList.pushCoveredFingersToSuffix(6, 0, 3); 392 | 393 | IndexedLinkedList expectedList = new IndexedLinkedList<>(list); 394 | expectedList.fingerList.setFingerIndices(1, 6, 7, 8); 395 | 396 | assertTrue(list.strongEquals(expectedList)); 397 | 398 | System.out.println("pushCoveredFingersToSuffix1 passed!"); 399 | } 400 | 401 | @Test 402 | public void pushCoveredFingersToSuffix2() { 403 | 404 | loadList(10); 405 | 406 | list.fingerList.setFingerIndices(1, 3, 6, 9); 407 | list.fingerList.pushCoveredFingersToSuffix(4, 2, 2); 408 | 409 | IndexedLinkedList expectedList = new IndexedLinkedList<>(list); 410 | expectedList.fingerList.setFingerIndices(4, 5, 6, 9); 411 | 412 | assertTrue(list.strongEquals(expectedList)); 413 | 414 | System.out.println("pushCoveredFingersToSuffix2 passed!"); 415 | } 416 | 417 | @Test 418 | public void arrange1() { 419 | loadList(100); 420 | 421 | list.fingerList.setFingerIndices(5, 14, 15, 25, 26, 39, 49, 85, 86, 99); 422 | list.fingerList.arrangePrefix(17, 3, 2); 423 | list.fingerList.arrangeSuffix(83, 7, 3, 2); 424 | 425 | IndexedLinkedList expectedList = new IndexedLinkedList<>(list); 426 | expectedList.fingerList.setFingerIndices( 427 | 5, 6, 7, 8, 9, 83, 84, 85, 86, 99 428 | ); 429 | 430 | assertTrue(list.strongEquals(expectedList)); 431 | 432 | System.out.println("arrange1 passed!"); 433 | } 434 | 435 | @Test 436 | public void removeFingersOnDeleteRange1() { 437 | loadList(100); 438 | 439 | list.fingerList.removeFingersOnDeleteRange(3, 4, 20); 440 | FingerList expectedFingerList = new FingerList<>(null); 441 | 442 | expectedFingerList.size = 6; 443 | 444 | expectedFingerList.fingerArray[0] = new Finger<>(new Node<>(0), 0 ); 445 | expectedFingerList.fingerArray[1] = new Finger<>(new Node<>(1), 1 ); 446 | expectedFingerList.fingerArray[2] = new Finger<>(new Node<>(4), 4 ); 447 | expectedFingerList.fingerArray[3] = new Finger<>(new Node<>(49), 29 ); 448 | expectedFingerList.fingerArray[4] = new Finger<>(new Node<>(64), 44 ); 449 | expectedFingerList.fingerArray[5] = new Finger<>(new Node<>(81), 61 ); 450 | expectedFingerList.fingerArray[6] = new Finger<>(new Node<>(null), 80); 451 | 452 | assertEquals(expectedFingerList, list.fingerList); 453 | 454 | System.out.println("removeFingersOnDeleteRange1 passed!"); 455 | } 456 | 457 | @Test 458 | public void equals() { 459 | IndexedLinkedList list1 = new IndexedLinkedList<>(); 460 | IndexedLinkedList list2 = new IndexedLinkedList<>(); 461 | FingerList fingerList1 = new FingerList<>(list1); 462 | FingerList fingerList2 = new FingerList<>(list2); 463 | 464 | assertTrue(fingerList1.equals(fingerList1)); 465 | assertFalse(fingerList1.equals(null)); 466 | 467 | fingerList2.size = 1; 468 | 469 | assertFalse(fingerList1.equals(fingerList2)); 470 | assertFalse(fingerList1.equals(new Object())); 471 | 472 | fingerList1.size = 0; 473 | fingerList1.appendFinger(new Finger<>(new Node(1), 0)); 474 | fingerList1.appendFinger(new Finger<>(new Node(2), 1)); 475 | 476 | fingerList2.size = 0; 477 | fingerList2.appendFinger(new Finger<>(new Node(1), 0)); 478 | 479 | assertFalse(fingerList1.equals(fingerList2)); 480 | 481 | fingerList2.appendFinger(new Finger<>(new Node(2), 1)); 482 | 483 | assertTrue(fingerList1.equals(fingerList2)); 484 | 485 | fingerList2.fingerArray[1] = null; 486 | 487 | assertFalse(fingerList1.equals(fingerList2)); 488 | assertFalse(fingerList2.equals(fingerList1)); 489 | } 490 | 491 | private void loadList(int size) { 492 | for (int i = 0; i < size; i++) { 493 | list.add(i); 494 | } 495 | } 496 | } --------------------------------------------------------------------------------