Java 記憶鏈表,LinkedList 的升級版

文章目錄

  • 記憶鏈表 MemoryLinkedList
  • 實戰
  • 源代碼

??眾所周知,ArrayList 和 LinkedList 是 Java 集合中兩個基本的數據結構,對應數據結構理論中的數組和鏈表。但在這兩個數據結構,開發者們通常使用 ArrayList,而不使用 LinkedList。JDK 1.2 源碼 LinkedList 的作者 Josh Bloch 也幾乎不使用 LinkedList。

??為什么會這樣呢?從數據結構理論中上看,它們之間應該只是各有千秋。其中,ArrayList 的定標訪問效率應該優于 LinkedList,而 LinkedList 的插入和刪除效率應該優于 ArrayList 才對。簡單來說,ArrayList 的讀性能應該優于 LinkedList,而 LinkedList 的寫性能應該優于 ArrayList。但實際上,ArrayList 的大多數指標都要優于 LinkedList。

??出現這一現象的原因有很多。在內存占用來說,Java 的對象都是匿名的,有名稱的變量實際儲存的是該變量的指針。因此 ArrayList 和 LinkedList 相當于一個指針數組,而指針本身的數據占用是很小的。由于 LinkedList 每個結點的數據結構過于復雜,有很多額外的數據,如上一個結點的指針、下一個結點的指針。因為儲存實際數據元素本身用的就是指針,因此如果除去實際存儲那部分有用的數據,LinkedList 占用的的空間將會是 ArrayList 的 2 ~ 3 倍。

??內存占用還會體現在時間上。ArrayList 因為內存占用小,在數據量小的情形下,插入和刪除時移動整片區域也實際不會耗費多少時間。

??另外,就刪除來言,一般都是要先查找到目標元素之后才能刪除,因此 LinkedList 的理論刪除效率也沒有比 ArrayList 高多少。

??此外,操作系統可能會對內存進行優化(局部性原理),緩存之前用到數據的附近位置的數據,這對 ArrayList 這種底層為連續內存的數據結構非常有利。

??總而言之,相比于 ArrayList,LinkedList 是比較糟糕的,通常大家不會使用 LinkedList。

記憶鏈表 MemoryLinkedList

??雖然通常 LinkedList 比 ArrayList 要差,不過我們通常是根據實際場景選擇技術,而不能根據刻板印象一概而論。在大數據的定點刪除,LinkedList 將會優于 ArrayList。但是,LinkedList 每次刪除都要從頭開始遍歷,有時候,這是沒有必要的。而這種從每次頭開始遍歷會大大削弱 LinkedList 的效率優勢。

??想象這樣的一種情況,需要根據某個條件,在 LinkedList 中刪除多個元素。這種情況下,如果每次刪除都頭開始遍歷,就非常浪費時間。因為已經遍歷過的區域實際上不存在需要刪除的元素。

??如果可以在遍歷時記住上次遍歷的位置就可以解決這一情況。為此,筆者在 LinkedList 的基礎上,開發了一種記憶鏈表 MemoryLinkedList,它可以記住上次遍歷的位置,讓下一次遍歷時可以從上次中止遍歷的地方開始。此外,它還提供一種閉包用于進行基本操作的嵌套,如在遍歷時刪除當前元素、進行子遍歷等等。


核心代碼如下:

/*** 提供帶自定義比較器的判斷是否包含的方法。這個自定義比較器用于判斷表中的元素是否相等。* 通常在元素為數組時使用本方法,因為數組的 equals 方法無法重寫,* 而數組的原 equals 方法只是一種指針比較,因此可以使用本方法來彌補這一缺陷** @since 2023-2-5*/
public boolean contains(E other, Comparator<E> comparator) {if (other == null) {for (Node<E> x = this.first; x != null; x = x.next) {if (x.item == null) {return true;}}} else {for (Node<E> x = this.first; x != null; x = x.next) {if (comparator.compare(x.item, other) == 0) {return true;}}}return false;
}/*** 記憶點** 指向沒有被遍歷的第一個元素的位置** @since 2023-1-15*/
private transient Node<E> memory = this.first;/*** 快照記憶點** @since 2023-1-15*/
private transient Node<E> snapshotMemory;/*** 開啟記憶模式** 調用后面的所有“記憶”函數之前,必須先調用本方法。記憶模式只對記憶函數起作用,其它函數不受影響,因此記憶模式不需要關閉** 個人自增方法** @since 2023-1-15*/
public MemoryLinkedList<E> setMemoryMode() {return this.resetMemory();
}/*** @since 2023-1-15*/
public MemoryLinkedList<E> resetMemory() {this.memory = this.first;return this;
}/*** 保存當前 memory 的一個快照** @since 2023-1-15*/
public MemoryLinkedList<E> saveSnapshotMemory() {this.snapshotMemory = this.memory;return this;
}/*** 將快照 memory 加載到 memory 中。此方法必須在 saveSnapshotMemory 至少調用一次后才能調用** @since 2023-1-15*/
public MemoryLinkedList<E> loadSnapshotMemory() {this.memory = this.snapshotMemory;return this;
}/*** 遍歷。此方法不是記憶方法** Function:此方法的入參代表遍歷的每個元素,返回值代表是否繼續循環(true 代表繼續循環)** 個人自增方法** @since 2023-1-15*/
public MemoryLinkedList<E> traverse(ListTraverseProcess<E> process) {for (Node<E> x = this.first; x != null; x = x.next) {if (!process.foreach(x.item)) {break;}}return this;
}/*** 遍歷。此方法是記憶方法。在遍歷正常到表尾時,此方法會自動重置記憶點** Function:此方法的入參代表遍歷的每個元素,返回值代表是否繼續循環(true 代表繼續循環)** 個人自增方法** @since 2023-1-15*/
public MemoryLinkedList<E> traverseContinuously(ListTraverseProcess<E> process) {Node<E> xN = null;for (Node<E> x = this.memory; x != null; x = xN) {xN = x.next; // 這是為了防止下面的 apply 方法中含刪除本結點的操作導致本結點的指針無效,因此需要提前保存下一個結點if (!process.foreach(x.item)) { // 當執行 apply 方法時,memory 指向當前正在遍歷的元素 xif (xN == null) {this.resetMemory(); // 此處說明當前遍歷的是表尾元素,因此需要重置記憶點} else {this.memory = xN; // 保存遍歷元素的下一個元素的位置}return this;}this.memory = xN; // 遍歷時,快照記憶點必須立刻保存,而不能等到退出循環的那一刻才保存}this.resetMemory(); // 重置記憶點return this;
}/*** 刪除。此方法是記憶方法** 個人自增方法** @param needFeedback 如果為 true,則刪除失敗時會拋出異常。如果為 false,什么也不會發生* @since 2023-1-15*/
public MemoryLinkedList<E> removeContinuously(Object obj, boolean needFeedback) {for (Node<E> x = this.memory; x != null; x = x.next) {if (Objects.equals(x.item, obj)) {this.memory = x.next; // 保存刪除元素的下一個元素的位置。只有在退出循環的那一刻才需要保存this.unlink(x);return this;}}if (needFeedback) {throw new RuntimeException("沒有找到刪除元素,刪除失敗");}return this;
}

實戰

??紙上得來終覺淺,沒有實戰的講解沒有任何意義。這里結合具體代碼來介紹記憶鏈表 MemoryLinkedList 的使用。

??筆者在早年間曾經進行過大量的彩票、股票研究。其中有這樣一個場景,需要列出所有滿足指定條件雙色球號碼。這里采取的策略是,先使用 MemoryLinkedList 收集所有的雙色球號碼,然后遍歷每一個號碼,從中去掉不符條件的號碼。因為雙色球總號碼數量巨大,所以不適合基于連續內存的 ArrayList。由于在一次遍歷中,需要大量的刪除操作,因此也不適合原始的 LinkedList。于是,筆者編寫的 LinkedList 升級版,記憶鏈表 MemoryLinkedList 就派上用場了。

??雙色球的投注規則是,從紅色球?的 1 ~ 33 號碼中選擇? 6 個,從藍色球?的 1 ~ 16 號碼中選擇? 1 個。


下面的代碼給出了紅球 膽碼 的篩選過程。

/*** 膽碼。numbers 中所有的號碼都要存在,因此 numbers 的長度不能超過 6** @since 2023-1-14*/
public DcRedLottery braveNumber(int... numbers) {this.redResults.setMemoryMode().traverseContinuously(ele -> {if (!IntMathSetUtil.contain(ele, numbers)) {this.redResults.removeContinuously(ele, false);}return true;});return this;
}

下面的代碼給出了紅球 殺碼 的篩選過程。

/*** 殺碼。去掉所有含 numbers 中任意一個元素的組合** @since 2023-1-14*/
public DcRedLottery killNumber(int... numbers) {this.redResults.setMemoryMode().traverseContinuously(ele -> {// 此處需要求交集,不是用包含來判斷if (IntMathSetUtil.intersect(ele, numbers).length >= 1) {this.redResults.removeContinuously(ele, false);}return true;});return this;
}

下面的代碼給出了紅球 復式 的篩選過程。

/*** 復式。從 numbers 中選擇 choiceNum 個號碼作為膽碼** @param only 為 true 代表 numbers 中只能選擇 choiceNum 個號碼,numbers 中的其它號碼不能存在* @since 2023-1-19*/
public DcRedLottery multiple(int[] numbers, int choiceNum, boolean only) {// 為了提高效率,此層判斷必須放到外面if (only) {this.redResults.setMemoryMode().traverseContinuously(ele -> {if (!(IntMathSetUtil.intersect(ele, numbers).length == choiceNum)) {this.redResults.removeContinuously(ele, false);}return true;});} else {this.redResults.setMemoryMode().traverseContinuously(ele -> {if (!(IntMathSetUtil.intersect(ele, numbers).length >= choiceNum)) {this.redResults.removeContinuously(ele, false);}return true;});}return this;
}

下面是一些更復雜的情況。

/*** 橫向號碼連續情況。本方法允許 continuousNum 或 groupNum 出現大于的情況。* 比如,* 就算限定 2 連續,也可以出現 3 連續。* 就算限定 2 連續為 2 個,則也可以允許 2 連續出現 3 個。* 就算限定 2 連續為 2 個,則也可以允許 2 連續出現 1 個,然后 3 連續出現 1 個。** @param continuousNum 有 continuousNum 個號碼連續* @param groupNum 對于 continuousNum 個號碼連續的情況,出現了 groupNum 組(不連續的號碼群稱為一組)* @since 2023-12-17*/
public DcRedLottery hContinuousBigger(int continuousNum, int groupNum) {this.redResults.setMemoryMode().traverseContinuously(ele -> {var groups = GroupAnalysisUtil.generateGroups(ele);var distribution = GroupAnalysisUtil.continuousDistribution(groups);int sum = 0;// 統計連續數不小于 continuousNum 的組數for (int num = continuousNum; num < distribution.length; ++num) {sum += distribution[num];}if (sum < groupNum) {this.redResults.removeContinuously(ele, false);return true;}return true;});return this;
}

下面的代碼了篩選出了滿足如下條件的雙色球號碼。

  • 至少有一個號碼與 ref 的距離為 1 及以內。其中,ref = {1, 2, 10, 22, 24, 25}
  • 3 區比為 2:3:1
  • 奇偶比為 2:4
  • 有且只有兩個號碼連續
int[] ref = {1, 2, 10, 22, 24, 25};
var redLottery = DcRedLottery.getInstance().selectAll().near(ref, 1, ComparativePredicate.MOST, 1, ComparativePredicate.LEAST).section3Proportion(2, 3, 1).parity(2, 4).hContinuousEqually(2, 1);

從下圖可以看出,滿足上述條件的號碼有 10244 個。

在這里插入圖片描述

源代碼

??記憶鏈表 MemoryLinkedList 的源代碼被收錄在筆者的開源項目 jdk-enhance 中,可免費下載。GitHub 地址:https://github.com/wangpai-common-util-Java/jdk-enhance

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/72922.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/72922.shtml
英文地址,請注明出處:http://en.pswp.cn/web/72922.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

《白帽子講 Web 安全》之開發語言安全深度解讀

目錄 引言 1.PHP 安全 1.1變量覆蓋 1.2空字節問題 1.3弱類型 1.4反序列化 1.5安全配置 2Java 安全 2.1Security Manager 2.2反射 2.3反序列化 3Python 安全 3.1反序列化 3.2代碼保護 4.JavaScript 安全 4.1第三方 JavaScript 資源 4.2JavaScript 框架 5.Node.…

鴻蒙HarmonyOS NEXT應用崩潰分析及修復

鴻蒙HarmonyOS NEXT應用崩潰分析及修復 如何保證應用的健壯性&#xff0c;其中一個指標就是看崩潰率&#xff0c;如何降低崩潰率&#xff0c;就需要知道存在哪些崩潰&#xff0c;然后對癥下藥&#xff0c;解決崩潰。那么鴻蒙應用中存在哪些崩潰類型呢&#xff1f;又改如何解決…

分析K8S中Node狀態為`NotReady`問題

在Kubernetes&#xff08;k8s&#xff09;集群中&#xff0c;Node狀態為NotReady通常意味著節點上存在某些問題&#xff0c;下面為你分析正常情況下節點應運行的容器以及解決NotReady狀態的方法。 正常情況下Node節點應運行的容器 1. kubelet kubelet是節點上的核心組件&…

第六屆機電一體化技術與智能制造國際學術會議(ICMTIM 2025)

重要信息 4月11-13日 南京江北新區工業大學亞朵酒店 www.icmtim.org&#xff08;點擊了解參會投稿等&#xff09; 簡介 由南京工業大學主辦&#xff0c;南京工業大學電氣工程與控制科學學院、中國礦業大學、黑龍江大學、江蘇省自動化學會承辦的第六屆機電一體化技術…

INT202 Complexity of Algroithms 算法的復雜度 Pt.2 Search Algorithm 搜索算法

文章目錄 1.樹的數據結構1.1 有序數據(Ordered Data)1.1.1 有序字典&#xff08;Ordered Dictonary&#xff09;1.1.1.1 排序表&#xff08;Sorted Tables&#xff09; 1.2 二分查找&#xff08;Binary Search&#xff09;1.2.1 二分查找的時間復雜度 1.3 二叉搜索樹&#xff0…

【AVRCP】藍牙鏈路控制器(LC)與AVRCP互操作性要求深度解析

目錄 一 、Link Controller&#xff08;LC&#xff09;概述 1.1 LC的定義與功能 1.2 LC在藍牙技術中的重要性 二、Link Controller&#xff08;LC&#xff09;互操作性要求 2.1 互操作性要求概述 2.2 物理層互操作性要求 2.3 鏈路管理互操作性要求 2.4 其他互操作性要求…

高級背景摳圖工具(python)

這是一個專業的圖像背景處理工具,基于Python開發,主要功能包括:1. 智能背景去除 - 使用rembg庫的深度學習模型自動識別并移除圖片背景。 2. 背景自定義 - 支持純色背景替換,保留透明通道(Alpha通道)。3. 高級參數調節 - 提供5種專業級圖像處理參數。4. 實時預覽 - 雙窗口…

如何設計外貿郵件開發信主題

開發信是打開客戶大門的第一步&#xff0c;而郵件主題則是決定客戶是否打開郵件的關鍵。一個吸引人的主題不僅能提高打開率&#xff0c;還能為后續溝通打下良好基礎。 一、突出價值和利益 郵件主題要直接傳達收件人能從中獲得的價值和利益&#xff0c;引起他們的興趣和關注。…

wordpress表單插件CF7調用方式

Contact Form 7(CF7)是WordPress中非常流行的表單插件&#xff0c;以下是其常見的調用方式&#xff1a; 通過短代碼調用 在頁面或文章編輯器中添加&#xff1a;完成表單設置后&#xff0c;復制表單對應的短代碼&#xff0c;然后在需要顯示表單的頁面或文章的編輯器中直接粘貼…

快速入手-基于Django的主子表間操作mysql(五)

1、如果該表中存在外鍵&#xff0c;結合實際業務情況&#xff0c;那可以這么寫&#xff1a; 2、針對特殊的字典類型&#xff0c;可以這么定義 3、獲取元組中的字典值和子表中的value值方法 4、對應的前端頁面寫法

網絡運維學習筆記(DeepSeek優化版) 021 HCIA-Datacom新增知識點03園區網典型組網架構及案例實戰

文章目錄 園區網典型組網架構及案例實戰1 園區網定義2 園區網絡典型架構3 各層級協議與技術4 項目生命周期管理5 小型園區網絡設計框架5.1 組網方案設計5.2 IP地址規劃5.3 園區內部的路由設計5.4 NAT設計5.5 WLAN設計5.6 安全設計5.7 運維管理設計 6 小型園區的實施方案與運維手…

1.8 函數的連續性和間斷點

1.連續的定義 2.間斷點的定義 3.間斷點的分類

基于Arm GNU Toolchain編譯生成的.elf轉hex/bin文件格式方法

基于Arm GNU Toolchain編譯生成的.elf轉hex/bin文件格式方法 已經棄用的版本&#xff08;Version 10.3-2021.10&#xff09;&#xff1a;gcc-arm-none-eabi&#xff1a;https://developer.arm.com/downloads/-/gnu-rmArm GNU Toolchain當前版本&#xff1a;https://developer.a…

希爾排序中的Hibbard序列

一 定義 Hibbard序列的每個元素由以下公式生成: h_k = 2^k - 1 其中k從1開始遞增,序列為:1, 3, 7, 15, 31, 63, … 二 生成方式 起始條件:k=1,對應h_1=2^1-1=1 遞推公式:每次k增加1,計算 h_{k+1}=2^{k+1}-1 示例:前5項…

失敗的面試經歷(??∧??)

一.面向對象的三大特性 1.封裝&#xff1a;將對象內部的屬性私有化&#xff0c;外部對象不能夠直接訪問&#xff0c;但是可以提供一些可以使外部對象操作內部屬性的方法。 2.繼承&#xff1a;類與類之間會有一些相似之處&#xff0c;但也會有一些異處&#xff0c;使得他們與眾…

算法及數據結構系列 - 二分查找

系列文章目錄 算法及數據結構系列 - BFS算法 文章目錄 二分查找框架思路經典題型二分查找尋找左側邊界尋找右側邊界 刷題875. 愛吃香蕉的珂珂1011. 在 D 天內送達包裹的能力392. 判斷子序列 二分查找 框架思路 int binarySearch(int[] nums, int target) {int left 0, righ…

SpringBoot的啟動原理?

大家好&#xff0c;我是鋒哥。今天分享關于【SpringBoot的啟動原理&#xff1f;】面試題。希望對大家有幫助&#xff1b; SpringBoot的啟動原理&#xff1f; 1000道 互聯網大廠Java工程師 精選面試題-Java資源分享網 Spring Boot的啟動原理主要是通過 SpringApplication 類來…

代碼隨想錄第55期訓練營第八天|LeetCode344.反轉字符串、541.反轉字符串II、卡碼網:54.替換數字

前言 這是我參加的第二次訓練營&#xff01;&#xff01;&#xff01;爽&#xff01;這次我將更加細致的寫清每一道難題&#xff0c;不僅是提升自己&#xff0c;也希望我自己的寫的文章對讀者有一定的幫助&#xff01; 打卡代碼隨想錄算法訓練營第55期第八天&#xff08;づ&a…

Json的應用實例——cad 二次開發c#

以下是一個使用AutoCAD C#.NET API實現你需求的示例代碼&#xff0c;代碼實現了提示用戶選擇一個實體&#xff0c;將一些字符串變量及其對應的值組成JSON格式數據存儲到實體的擴展數據&#xff08;XData&#xff09;中&#xff0c;并在彈出窗口中顯示該實體的所有擴展數據信息。…

Springboot的jak安裝與配置教程

目錄 Windows系統 macOS系統 Linux系統 Windows系統 下載JDK&#xff1a; 訪問Oracle官網或其他JDK提供商網站&#xff0c;下載適合Windows系統的JDK版本。網站地址&#xff1a;Oracle 甲骨文中國 | 云應用和云平臺點擊進入下滑&#xff0c;點擊進入下載根據自己的系統選擇&…