java 常用數據結構

目錄
一、數組
二、List
三、棧(Stack)
四、隊列(Queue)
五、集合(Set)
六 、哈希表(Map)
七、各遍歷方式的適用于什么場合?
八、數據元素是怎樣在內存中存放的?

簡介:本文只為對java常用數據結構的特點和常用方法做總結。歡迎評論留言,文章持續更新優化

一、數組

概念:數組是相同類型數據有序集合。數組描述的是相同類型的若干個數據,按照一定的先后次序排列組合而成。其中,每一個數據稱作一個數組元素,每個數組元素可以通過一個下標來訪問它們。

基本特點:

  • 數組中的元素在內存中連續存儲的,可以根據是下標快速訪問元素,因此查詢速度很快
  • 其長度是確定的。數組一旦被創建,它的大小就是不可以改變的。插入和刪除時,需要擴容并且對元素移動空間,比較慢。
  • 其元素必須是相同類型,不允許出現混合類型,
  • 數組中的元素可以是任何數據類型 ,包括基本類型和引用類型,
  • 數組變量屬引用類型,數組也可以看成是對象,數組中的每個元素相當于該對象的成員變量。數組本身就是對象,Java中對象是在堆中的,因此數組無論保存原始類型還是其他對象類型,數組對象本身是在堆中的。

使用場景:
頻繁查詢,很少增加和刪除的情況。

代碼實現

        /**
         * 數組
         * 特點:我們都知道數組中的元素在內存中連續存儲的,可以根據是下標快速訪問元素,
         * 因此,查詢速度很快,然而插入和刪除時,需要對元素移動空間,比較慢。
         *
         * 使用場景:頻繁查詢,很少增加和刪除的情況。
         */
        //一維數組
        int[] sourceArray = new int[6];

        System.out.println("一位數組的長度 length = " + sourceArray.length);
        System.out.println("----------------------------------");

        //二維數組
        int[][] sourceArray1 = new int[2][3];
        //行數
        int rows = sourceArray1.length;
        //列數
        int columns = sourceArray1[0].length;

        System.out.println("二維數組行數 rows = " + rows);

        System.out.println("二維數組列數 columns = " + columns);
        System.out.println("----------------------------------");

二、List

常用實現包括:

1.AarryList:通過數組實現,屬于動態數組。因為是通過數組實現,每一個ArrayList都有一個初始容量(10),該容量代表了數組的大小。隨著容器中的元素不斷增加,容器的大小也會隨著增加。在每次向容器中增加元素的同時都會進行容量檢查,當快溢出時,就會進行擴容操作。所以插入和刪除慢,查詢速度快。

2.LinkedList:通過鏈表實現。查詢慢,插入和刪除快。適合少查詢,需要頻繁的插入或刪除的場景。

鏈表的特點如下:
元素可以不連續內存中,是以索引將數據聯系起來的,當查詢元素的時候需要從頭開始查詢,所以效率比較低,然而添加和刪除的只需要修改索引就可以了。

        /**
         * List
         */
        List<String> stringList = new ArrayList<>();

        //添加一個元素
        stringList.add("第一個元素");

        //把元素插入到某個位置上
        stringList.add(0, "第零個元素");

        for (String str : stringList) {
            System.out.println(str);
        }
        System.out.println("----------------------------------");

        //返回列表長度
        int length = stringList.size();
        System.out.println("列表長度 = " + length);

        System.out.println("----------------------------------");
        //判斷是否包含某個元素
        if (stringList.contains("第一個元素")) {
            System.out.println();
        }
        System.out.println("----------------------------------");


        List<String> jihe = new ArrayList<>();
        jihe.add("第二個元素");
        jihe.add("第三個元素");
        //添加一個集合,實現自Collection接口的都可以添加
        stringList.addAll(jihe);

        List<String> jihe1 = new ArrayList<>();
        jihe1.add("第負二個元素");
        jihe1.add("第負一個元素");
        //從某個位置開始插入集合
        stringList.addAll(0, jihe1);

        for (String str : stringList) {
            System.out.println(str);
        }
        System.out.println("----------------------------------");

        //返回List中與傳入的字符串相等的第一個元素的下標
        int firstIndexOf = stringList.indexOf("第一個元素");
        System.out.println("字符串 第一個元素在 List中的位置 = " + firstIndexOf);
        System.out.println("----------------------------------");

        //先在隊尾插入一個一樣的元素
        stringList.add("第一個元素");
        //返回List中與傳入的字符串相等的最后一個元素的下標
        int lastIndexOf = stringList.lastIndexOf("第一個元素");
        System.out.println("字符串 第一個元素在 List中的位置 = " + lastIndexOf);

        //移除指定下標的元素
        stringList.remove(0);

        //移除指定值元素
        stringList.remove("第一個元素");
        for (String str : stringList) {
            System.out.println(str);
        }
        System.out.println("----------------------------------");

三、棧(Stack)

概述:Stack繼承自Vector,實現一個后進先出的堆棧。Stack提供5個額外的方法使得Vector得以被當作堆棧使用。基本的push和pop 方法,還有peek方法得到棧頂的元素,empty方法測試堆棧是否為空,search方法檢測一個元素在堆棧中的位置。Stack剛創建后是空棧。

代碼實現

        /**
         * Stack
         * 特點:先進后出,就像一個箱子。
         * 使用場景:實現遞歸以及表示式。
         */
        Stack<String> stringStack = new Stack<>();

        //添加元素,等同于add
        stringStack.push("第一個元素");
        stringStack.push("第二個元素");

        //插入元素到指定位置,需要注意的插入的位置大小不能大于棧的長度
        stringStack.add(1, "第三個元素");

        //返回元素在stack中的位置
        int position = stringStack.search("第二個元素");
        System.out.println("第二個元素在棧中的位置 = "+position);
        System.out.println("----------------------------------");

        //遍歷棧
        while (!stringStack.isEmpty()) {
            System.out.println(stringStack.pop());
        }

四、隊列(Queue)

概述:

隊列是一種先進先出(FIFO)的抽象數據結構,在Java中,隊列使用了兩種數據類型來實現的,分別是:數組和鏈表這兩種數據結構。

代碼實現

/**
         * 隊列 queue
         * 特點:先進先出。
         * 使用場景:多線程阻塞隊列管理非常有用。
         */

        Queue<String> queue = new LinkedList<>();

        queue.add("第一個元素");
        queue.offer("第二個元素");
        queue.offer("第三個元素");

        System.out.println("隊列中第一個元素 = " + queue.peek());
        System.out.println("隊列中第一個元素 = " + queue.element());
        System.out.println("隊列中第一個元素 = " + queue.poll());
        //移除掉隊列中第一個元素
        queue.remove();
        while (!queue.isEmpty()) {
            System.out.println(queue.poll());
        }

五、集合(Set)

Set是一種不包括重復元素的Collection。它維持它自己的內部排序,所以隨機訪問沒有任何意義。與List一樣,它同樣允許null的存在但是僅有一個。由于Set接口的特殊性,所有傳入Set集合中的元素都必須不同,同時要注意任何可變對象,如果在對集合中元素進行操作時,導致e1.equals(e2)==true,則必定會產生某些問題。Set接口有三個具體實現類,分別是散列集HashSet、鏈式散列集LinkedHashSet和樹形集TreeSet。

Set是一種不包含重復的元素的Collection,無序,即任意的兩個元素e1和e2都有e1.equals(e2)=false,Set最多有一個null元素。需要注意的是:雖然Set中元素沒有順序,但是元素在set中的位置是由該元素的HashCode決定的,其具體位置其實是固定的。

1.HashSet
HashSet 是一個沒有重復元素的集合。它是由HashMap實現的,不保證元素的順序(這里所說的沒有順序是指:元素插入的順序與輸出的順序不一致),而且HashSet允許使用null 元素。HashSet是非同步的,如果多個線程同時訪問一個哈希set,而其中至少一個線程修改了該set,那么它必須保持外部同步。 HashSet按Hash算法來存儲集合的元素,因此具有很好的存取和查找性能。
HashSet的實現方式大致如下,通過一個HashMap存儲元素,元素是存放在HashMap的Key中,而Value統一使用一個Object對象。
HashSet使用和理解中容易出現的誤區:
a.HashSet中存放null值
HashSet中是允許存入null值的,但是在HashSet中僅僅能夠存入一個null值。
b.HashSet中存儲元素的位置是固定的
HashSet中存儲的元素的是無序的,這個沒什么好說的,但是由于HashSet底層是基于Hash算法實現的,使用了hashcode,所以HashSet中相應的元素的位置是固定的。

c.必須小心操作可變對象(Mutable Object)。如果一個Set中的可變元素改變了自身狀態導致Object.equals(Object)=true將導致一些問題。

2.LinkedHashSet
LinkedHashSet繼承自HashSet,其底層是基于LinkedHashMap來實現的,有序,非同步。LinkedHashSet集合同樣是根據元素的hashCode值來決定元素的存儲位置,但是它同時使用鏈表維護元素的次序。這樣使得元素看起來像是以插入順序保存的,也就是說,當遍歷該集合時候,LinkedHashSet將會以元素的添加順序訪問集合的元素。

3.TreeSet
TreeSet是一個有序集合,其底層是基于TreeMap實現的,非線程安全。TreeSet可以確保集合元素處于排序狀態。TreeSet支持兩種排序方式,自然排序和定制排序,其中自然排序為默認的排序方式。當我們構造TreeSet時,若使用不帶參數的構造函數,則TreeSet的使用自然比較器;若用戶需要使用自定義的比較器,則需要使用帶比較器的參數。
注意:TreeSet集合不是通過hashcode和equals函數來比較元素的.它是通過compare或者comparaeTo函數來判斷元素是否相等.compare函數通過判斷兩個對象的id,相同的id判斷為重復元素,不會被加入到集合中。

      /**
         * Set 集合
         */
        Set<String> stringSet = new HashSet<>();

        System.out.println("add result = " + stringSet.add("第一個元素"));
        //第二次add 同樣的元素返回結果為false
        System.out.println("add result = " + stringSet.add("第一個元素"));
        System.out.println("add result = " + stringSet.add("第二個元素"));
        System.out.println("add result = " + stringSet.add("第三個元素"));

        //foreach 遍歷
        for (String str : stringSet) {
            System.out.println(str);
        }

        //通過迭代器遍歷
        Iterator iterator = stringSet.iterator();
        while ((iterator.hasNext())) {
            System.out.println(iterator.next());
        }

        //for循環遍歷
        for (Iterator itera = stringSet.iterator(); itera.hasNext(); ) {
            System.out.println(itera.next());
        }

六 、哈希表(Map)

概述:
Map與List、Set接口不同,它是由一系列鍵值對組成的集合,提供了key到Value的映射。同時它也沒有繼承Collection。在Map中它保證了key與value之間的一一對應關系。也就是說一個key對應一個value,所以它不能存在相同的key值,當然value值可以相同。

1.HashMap
以哈希表數據結構實現,查找對象時通過哈希函數計算其位置,它是為快速查詢而設計的,其內部定義了一個hash表數組(Entry[] table),元素會通過哈希轉換函數將元素的哈希地址轉換成數組中存放的索引,如果有沖突,則使用散列鏈表的形式將所有相同哈希地址的元素串起來,可能通過查看HashMap.Entry的源碼它是一個單鏈表結構。Jdk 1.8中對HashMap的實現做了優化,當鏈表中的節點數據超過八個之后,該鏈表會轉為紅黑樹來提高查詢效率,從原來的O(n)到O(logn),JDK 1.7 的 HashMap 是基于數組 + 鏈表實現,所以 hash 沖突時鏈表的查詢效率低。hash(Object key) 方法的具體算法是 (h = key.hashCode()) ^ (h >>> 16),經過這樣的運算,讓計算的 hash 值分布更均勻

2.LinkedHashMap
LinkedHashMap是HashMap的一個子類,它保留插入的順序,如果需要輸出的順序和輸入時的相同,那么就選用LinkedHashMap。
LinkedHashMap是Map接口的哈希表和鏈接列表實現,具有可預知的迭代順序。此實現提供所有可選的映射操作,并允許使用null值和null鍵。此類不保證映射的順序,特別是它不保證該順序恒久不變。
LinkedHashMap實現與HashMap的不同之處在于,后者維護著一個運行于所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序,該迭代順序可以是插入順序或者是訪問順序。
根據鏈表中元素的順序可以分為:按插入順序的鏈表,和按訪問順序(調用get方法)的鏈表。默認是按插入順序排序,如果指定按訪問順序排序,那么調用get方法后,會將這次訪問的元素移至鏈表尾部,不斷訪問可以形成按訪問順序排序的鏈表。
注意,此實現不是同步的。如果多個線程同時訪問鏈接的哈希映射,而其中至少一個線程從結構上修改了該映射,則它必須保持外部同步。
由于LinkedHashMap需要維護元素的插入順序,因此性能略低于HashMap的性能,但在迭代訪問Map里的全部元素時將有很好的性能,因為它以鏈表來維護內部順序。

3.TreeMap
TreeMap 是一個有序的key-value集合,非同步,基于紅黑樹(Red-Black tree)實現,每一個key-value節點作為紅黑樹的一個節點。TreeMap存儲時會進行排序的,會根據key來對key-value鍵值對進行排序,其中排序方式也是分為兩種,一種是自然排序,一種是定制排序,具體取決于使用的構造方法。
自然排序:TreeMap中所有的key必須實現Comparable接口,并且所有的key都應該是同一個類的對象,否則會報ClassCastException異常。
定制排序:定義TreeMap時,創建一個comparator對象,該對象對所有的treeMap中所有的key值進行排序,采用定制排序的時候不需要TreeMap中所有的key必須實現Comparable接口。
TreeMap判斷兩個元素相等的標準:兩個key通過compareTo()方法返回0,則認為這兩個key相等。
如果使用自定義的類來作為TreeMap中的key值,且想讓TreeMap能夠良好的工作,則必須重寫自定義類中的equals()方法,TreeMap中判斷相等的標準是:兩個key通過equals()方法返回為true,并且通過compareTo()方法比較應該返回為0。

       /**
         * Map集合
         */
        Map<String, String> stringMap = new HashMap<>();
        System.out.println(stringMap.put("1", "第一個元素"));
        System.out.println(stringMap.put("2", "第二個元素"));
        System.out.println(stringMap.put("3", "第三個元素"));
        System.out.println(stringMap.put("4", "第四個元素"));
        System.out.println(stringMap.put("5", "第五個元素"));

        //foreach直接遍歷Map 的 entrySet()
        for (Map.Entry<String, String> entry : stringMap.entrySet()) {
            System.out.println(entry.getKey());
            System.out.println(entry.getValue());
        }

        //單獨遍歷key集合
        for (String str : stringMap.keySet()) {
            System.out.println(str);

        }

        //單獨遍歷value集合
        for (String str : stringMap.values()) {
            System.out.println(str);
        }

        //通過迭代器遍歷
        Iterator<Map.Entry<String, String>> iterator = stringMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, String> entry = iterator.next();
            System.out.println(entry.getKey());
            System.out.println(entry.getValue());
        }

七、各遍歷方式的適用于什么場合?

1、傳統的for循環遍歷,基于計數器的:
順序存儲:讀取性能比較高。適用于遍歷順序存儲集合。
鏈式存儲:時間復雜度太大,不適用于遍歷鏈式存儲的集合。
普通for循環中若將list的長度聲明為臨時變量使用效果更佳

2、迭代器遍歷,Iterator:
順序存儲:如果不是太在意時間,推薦選擇此方式,畢竟代碼更加簡潔,也防止了Off-By-One的問題。
鏈式存儲:意義就重大了,平均時間復雜度降為O(n),還是挺誘人的,所以推薦此種遍歷方式。

3、foreach循環遍歷:
foreach只是讓代碼更加簡潔了,但是他有一些缺點,就是遍歷過程中不能操作數據集合(刪除等),所以有些場合不使用。而且它本身就是基于Iterator實現的,但是由于類型轉換的問題,所以會比直接使用Iterator慢一點,但是還好,時間復雜度都是一樣的。所以怎么選擇,參考上面兩種方式,做一個折中的選擇。

Java數據集合框架中,提供了一個RandomAccess接口,該接口沒有方法,只是一個標記。通常被List接口的實現使用,用來標記該List的實現是否支持Random Access。一個數據集合實現了該接口,就意味著它支持Random Access,按位置讀取元素的平均時間復雜度為O(1)。比如ArrayList。而沒有實現該接口的,就表示不支持Random Access。比如LinkedList。所以看來JDK開發者也是注意到這個問題的,那么推薦的做法就是,如果想要遍歷一個List,那么先判斷是否支持Random Access,也就是 list instanceof RandomAccess。

比如:
if (list instanceof RandomAccess) {
//使用傳統的for循環遍歷。
} else {
//使用Iterator或者foreach。
}

八、數據元素是怎樣在內存中存放的?

數據元素在內存中,主要有2種存儲方式:

1、順序存儲,Random Access(Direct Access):
這種方式,相鄰的數據元素存放于相鄰的內存地址中,整塊內存地址是連續的。可以根據元素的位置直接計算出內存地址,直接進行讀取。讀取一個特定位置元素的平均時間復雜度為O(1)。正常來說,只有基于數組實現的集合,才有這種特性。Java中以ArrayList為代表。

2、鏈式存儲,Sequential Access:
這種方式,每一個數據元素,在內存中都不要求處于相鄰的位置,每個數據元素包含它下一個元素的內存地址。不可以根據元素的位置直接計算出內存地址,只能按順序讀取元素。讀取一個特定位置元素的平均時間復雜度為O(n)。主要以鏈表為代表。Java中以LinkedList為代表。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,238評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,430評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,134評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,893評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,653評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,136評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,212評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,372評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,888評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,738評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,482評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,179評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,588評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,829評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,610評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,916評論 2 372