再說Java集合,subList之於ArrayList

来源:https://www.cnblogs.com/LiaHon/archive/2019/06/29/11105573.html
-Advertisement-
Play Games

本文續接上一章ArrayList原理及使用,對ArrayList中的常用方法subList進行了剖析,從源碼的角度對通過subList方法得到的集合和原集合有何關係,有何不同點,從而避免工作中遇到各種坑 ...


上一章說了很多ArrayList相關的內容,但還有一塊兒內容沒說到,那就是subList方法。先看一段代碼

public static void testSubList() {
    List<String> stringList = new ArrayList<>();
    stringList.add("牛魔王");
    stringList.add("蛟魔王");
    stringList.add("鵬魔王");
    stringList.add("獅駝王");
    stringList.add("獼猴王");
    stringList.add("禺賊王");
    stringList.add("美猴王");

    List<String> substrings = stringList.subList(3,5);
    System.out.println(substrings.toString());
    System.out.println(substrings.size());
    substrings.set(1, "豬八戒");
    System.out.println(substrings.toString());
    System.out.println(stringList.toString());
}

看看執行結果如何?

[獅駝王, 獼猴王]
2
[獅駝王, 豬八戒]
[牛魔王, 蛟魔王, 鵬魔王, 獅駝王, 豬八戒, 禺賊王, 美猴王]

第一和第二的執行結果,非常容易理解,subList()方法作用就是截取集合stringList中一個範圍內的元素。

第三和第四的執行結果都值得分析了,首先截取的字元串集合值為 [獅駝王, 獼猴王] ,但因為獼猴王在大雷音寺被美猴王打死了,我們用豬八戒來代替獼猴王;

因此我們通過substrings.set(1, "豬八戒"),將這個集合中第二個位置的值“獼猴王”設置為“豬八戒”,最終列印出來的結果也正是我們所預期的;但同時我們列印原集合stringList,發現其中的“獼猴王”也變成了“豬八戒”。這就比較奇怪了,兩個問題:

1.我們操作的是截取後的集合,為什麼原集合會變?

2.我們設置截取後某個位置(如第2個位置)的值,原集合改變的卻不是對應位置的值?

一. subList原理初探

接下來我們帶著問題尋找答案,我們看一下subList()的源碼

/**
 * Returns a view of the portion of this list between the specified
 * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive.  (If 
 * {@code fromIndex} and {@code toIndex} are equal, the returned list is 
 * empty.)  The returned list is backed by this list, so non-structural
 * changes in the returned list are reflected in this list, and vice-versa.
 * The returned list supports all of the optional list operations.
 *
 * <p>This method eliminates the need for explicit range operations (of
 * the sort that commonly exist for arrays).  Any operation that expects
 * a list can be used as a range operation by passing a subList view
 * instead of a whole list.  For example, the following idiom
 * removes a range of elements from a list:
 * <pre>
 *      list.subList(from, to).clear();
 * </pre>
 * Similar idioms may be constructed for {@link #indexOf(Object)} and
 * {@link #lastIndexOf(Object)}, and all of the algorithms in the
 * {@link Collections} class can be applied to a subList.
 *
 * <p>The semantics of the list returned by this method become undefined if
 * the backing list (i.e., this list) is <i>structurally modified</i> in
 * any way other than via the returned list.  (Structural modifications are
 * those that change the size of this list, or otherwise perturb it in such
 * a fashion that iterations in progress may yield incorrect results.)
 *
 * @throws IndexOutOfBoundsException {@inheritDoc}
 * @throws IllegalArgumentException {@inheritDoc}
 */
public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, 0, fromIndex, toIndex);
}

看註釋,大概有以下幾個意思

  1. 返回的是原集合在fromIndex和toIndex之間的元素的視圖,雖然為視圖,但支持集合的所有方法;
  2. 當fromIndex和toIndex相同時,返回空的視圖;
  3. 任何對截取的視圖的操作都會被原集合所取代;

看註釋僅能知道我們例子最後的運行結果是正常的,但是對原理也還並不是特別清楚。我們繼續看源碼。

首先我們在例子中調用subList(3, 5)時,是new了一個SubList,這個SubList是ArrayList內部類,繼承了AbstractList

private class SubList extends AbstractList<E> implements RandomAccess {
    private final AbstractList<E> parent;
    private final int parentOffset;
    private final int offset;
    int size;

    SubList(AbstractList<E> parent, int offset, int fromIndex, int toIndex) {
        this.parent = parent;
        this.parentOffset = fromIndex;
        this.offset = offset + fromIndex;
        this.size = toIndex - fromIndex;
        this.modCount = ArrayList.this.modCount;
    }
}

從這個內部類的源碼中,我們可以看到:

  1. SubList並沒有像ArrayList一樣定義Object[]來存放數據,而定義了一個變數parent來保存傳遞的原集合;
  2. 定義了一個offset用於保存進行偏移量,當對SubList修改時,就可以通過偏移量找到parent中對應的位置;
  3. 定義了size用來表示我們在parent集合中可見範圍是多少;

有了上面的說明,其實SubList的原理已經很清晰了,接下來,我們用SubList中常用的方法來印證一下。

二. add(E e)方法

substrings.add("九頭蛇");
System.out.println(substrings.toString());
System.out.println(stringList.toString());

接著上面的例子,在substrings中添加“九頭蛇”,按照規則,add()方法添加元素會在集合的最後,也就是說substrings的第3個位置(下標為2),對應parent原集合的位置下標就是2+3=5,會在stringList第六個位置插入“九頭蛇”。看一下輸出的結果

[獅駝王, 豬八戒, 九頭蛇]
[牛魔王, 蛟魔王, 鵬魔王, 獅駝王, 豬八戒, 九頭蛇, 禺賊王, 美猴王]

可以看到結果的確如此,那麼我們在看一下add(E e),在SubList這個內部類裡面並沒有發現該方法,因此我去父類中找。

在AbstractList中找到了

public boolean add(E e) {
    add(size(), e);
    return true;
}

接下來,我們在SubList中找到了實現方法

public void add(int index, E e) {
    rangeCheckForAdd(index);
    checkForComodification();
    parent.add(parentOffset + index, e);
    this.modCount = parent.modCount;
    this.size++;
}

很明顯,源代碼和我們開始的分析是一致的,當然在添加之間需要進行空間容量判斷,是否足以添加新的元素,擴容規則,我們上一章已經講過。

三. 其他方法

關於SubList的其他方法,其實和add原理一樣,不論是set(int index, E e),get(int index),addAll(Collection<? extends E> c),remove(int index),都是先判斷當前傳入的位置索引是否正確(如是否大於size,小於0等),再根據規則計算出原集合中的位置下標,最終完成對集合的操作。

四. 總結

本文續接上一章ArrayList原理及使用,對ArrayList中的常用方法subList進行了剖析,從源碼的角度對通過subList方法得到的集合和原集合有何關係,有何不同點,從而避免工作中遇到各種坑,若有不對之處,請批評指正,望共同進步,謝謝!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • web開發整體架構設計,優化提速。 通過前後端分離+系統拆分:獨立部署,提高系統的可使用性,提高資源的使用效率 通過多級緩存+消息隊列:提高系統相應時間、系統的吞吐量、併發數 資料庫讀寫分離+主從備份,縱向分表+橫向分區存儲:提高資料庫的處理效率,和降低處理壓力。 ...
  • 工廠顧名思義就是創建產品,根據產品是具體產品還是具體工廠可分為簡單工廠模式和工廠方法模式,根據工廠的抽象程度可分為工廠方法模式和抽象工廠模式。該模式用於封裝和管理對象的創建,是一種創建型模式。本文從一個具體的例子逐步深入分析,來體會三種工廠模式的應用場景和利弊。 1. 簡單工廠模式 該模式對對象創建 ...
  • 1、簡介 首先,他是一個全新的基於容器技術的分散式架構領先方案。Kubernetes(k8s)是Google開源的容器集群管理系統(谷歌內部:Borg)。在Docker技術的基礎上,為容器化的應用提供部署運行、資源調度、服務發現和動態伸縮等一系列完整功能,提高了大規模容器集群管理的便捷性。 Kube ...
  • 各位讀者朋友們,好久不見了! 最近博主一直在忙於工作以及寫《Flutter入門與應用實戰》的書,所以沒有時間打理博客。今天來給大家分享一個博主在GitChat上發起的一場Chat。 下麵是本場Chat的簡介: Flutter 是 Google 發佈的 UI 框架,可以快速在 iOS 和 Androi ...
  • 如果第二次看到我的文章,歡迎右側掃碼訂閱我喲~ 👉 每周五早8點 按時送達。當然了,也會時不時加個餐~ 這篇是「分散式系統理論」系列的第22篇,也是最後一篇。我們來聊聊分散式系統中的最後一道保障——監控。 監控這個事情,有點像我們平時對人的健康體檢。想要效果好、結果靠譜,就得“全面體檢”,每一項都 ...
  • 如果第二次看到我的文章,歡迎右側掃碼訂閱我喲~ 👉 每周五早8點 按時送達。當然了,也會時不時加個餐~ 是的,這份禮物最佳受眾是程式員。但是,如果你不是程式員,相信這些能使你更懂程式員,能更好的與他們交流。 有些小伙伴們應該知道了,之前的《分散式系統關註點——360°的全方位監控》是我去年開始寫的 ...
  • 在springMVC controller中返回json數據出現亂碼問題,因為沒有進行編碼,只需要簡單的註解就可以了 在@RequestMapping()中加入produces="text/html;charset=UTF-8"屬性即可,如下: ...
  • 1.Java 發展史 1991年01月 Sun公司成立了Green項目小組,專攻智能家電的嵌入式控制系統 1991年02月 放棄C++,開 發新語言,命名為“Oak” 1991年06月 JamesGosling開發了Oak的解釋器 1992年01月 Green完成了Green操作系 統、Oak語言、 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...