Java集合:總體結構

1、Java中集合java

  Java中集合類是Java編程中使用最頻繁、最方便的類。集合類做爲容器類能夠存儲任何類型的數據,固然也能夠結合泛型存儲指定的類型(不過泛型僅僅在編譯期有效,運行時是會被擦除的)。集合類中存儲的僅僅是對象的引用,並不存儲對象自己。集合類的容量能夠在運行期間進行動態擴展,而且還提供不少很方便的方法,如求集合的並集、交集等。編程

2、集合類結構數組

  Java中的集合包含多種數據結構,如鏈表、隊列、哈希表等。從類的繼承結構來講,能夠分爲兩大類,一類是繼承自Collection接口,這類集合包含List、Set和Queue等集合類。另外一類是繼承自Map接口,這主要包含了哈希表相關的集合類。下面咱們看一下這兩大類的繼承結構圖:安全

一、List、Set和Queue數據結構

 圖中的綠色的虛線表明實現,綠色實線表明接口之間的繼承,藍色實線表明類之間的繼承。多線程

   (1)List:咱們用的比較多List包括ArrayList和LinkedList,這二者的區別也很明顯,從其名稱上就能夠看出。ArrayList的底層的經過數組實現,因此其隨機訪問的速度比較快,可是對於須要頻繁的增刪的狀況,效率就比較低了。而對於LinkedList,底層經過鏈表來實現,因此增刪操做比較容易完成,可是對於隨機訪問的效率比較低。框架

咱們先看下二者的插入效率:dom

 1 package com.paddx.test.collection;
 2 
 3 import java.util.ArrayList;
 4 import java.util.LinkedList;
 5 
 6 public class ListTest {
 7     public static void main(String[] args) {
 8         for(int i=0;i<10000;i++){
 9 
10         }
11         long start = System.currentTimeMillis();
12 
13         LinkedList<Integer> linkedList = new LinkedList<Integer>();
14         for(int i=0;i<100000;i++){
15             linkedList.add(0,i);
16         }
17 
18         long end = System.currentTimeMillis();
19         System.out.println(end - start);
20 
21         ArrayList<Integer> arrayList = new ArrayList<Integer>();
22         for(int i=0;i<100000;i++){
23             arrayList.add(0,i);
24         }
25 
26         System.out.println(System.currentTimeMillis() - end);
27     }
28 }

下面是本地執行的結果:ide

23
1227

  能夠看出,在這種狀況下,LinkedList的插入效率遠遠高於ArrayList,固然這是一種比較極端的狀況。咱們再來比較一下二者隨機訪問的效率:工具

 1 package com.paddx.test.collection;
 2 
 3 import java.util.ArrayList;
 4 import java.util.LinkedList;
 5 import java.util.Random;
 6 
 7 public class ListTest {
 8     public static void main(String[] args) {
 9 
10         Random random = new Random();
11 
12         for(int i=0;i<10000;i++){
13 
14         }
15         LinkedList<Integer> linkedList = new LinkedList<Integer>();
16         for(int i=0;i<100000;i++){
17             linkedList.add(i);
18         }
19 
20         ArrayList<Integer> arrayList = new ArrayList<Integer>();
21         for(int i=0;i<100000;i++){
22             arrayList.add(i);
23         }
24 
25         long start = System.currentTimeMillis();
26 
27 
28         for(int i=0;i<100000;i++){
29             int j = random.nextInt(i+1);
30             int k = linkedList.get(j);
31         }
32 
33         long end = System.currentTimeMillis();
34         System.out.println(end - start);
35 
36         for(int i=0;i<100000;i++){
37             int j = random.nextInt(i+1);
38             int k = arrayList.get(j);
39         }
40 
41         System.out.println(System.currentTimeMillis() - end);
42     }
43 }

下面是我本機執行的結果:

5277
6

  很明顯能夠看出,ArrayList的隨機訪問效率比LinkedList高出好幾個數量級。經過這兩段代碼,咱們應該可以比較清楚的知道LinkedList和ArrayList的區別和適應的場景。至於Vector,它是ArrayList的線程安全版本,而Stack則對應棧數據結構,這二者用的比較少,這裏就不舉例了。

  (2)Queue:通常能夠直接使用LinkedList完成,從上述類圖也能夠看出,LinkedList繼承自Deque,因此LinkedList具備雙端隊列的功能。PriorityQueue的特色是爲每一個元素提供一個優先級,優先級高的元素會優先出隊列。

  (3)Set:Set與List的主要區別是Set是不容許元素重複的,而List則能夠容許元素重複的。判斷元素的重複須要根據對象的hash方法和equals方法來決定。這也是咱們一般要爲集合中的元素類重寫hashCode方法和equals方法的緣由。咱們仍是經過一個例子來看一下Set和List的區別,以及hashcode方法和equals方法的做用:

package com.paddx.test.collection;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;

public class SetTest {

    public static void main(String[] args) {
        Person p1 = new Person("lxp",10);
        Person p2 = new Person("lxp",10);
        Person p3 = new Person("lxp",20);

        ArrayList<Person> list = new ArrayList<Person>();
        list.add(p1);
        System.out.println("---------");
        list.add(p2);
        System.out.println("---------");
        list.add(p3);
        System.out.println("List size=" + list.size());

        System.out.println("----分割線-----");

        Set<Person> set = new HashSet<Person>();
        set.add(p1);
        System.out.println("---------");
        set.add(p2);
        System.out.println("---------");
        set.add(p3);
        System.out.println("Set size="+set.size());
    }


    static class Person{
        private String name;
        private int age;

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        @Override
        public boolean equals(Object o) {
            System.out.println("Call equals();name="+name);
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            Person person = (Person) o;

            return name.equals(person.name);

        }

        @Override
        public int hashCode() {
            System.out.println("Call hashCode(),age="+age);
            return age;
        }
    }
}

  上述代碼的執行結果以下:

---------
---------
List size=3
----分割線-----
Call hashCode(),age=10
---------
Call hashCode(),age=10
Call equals();name=lxp
---------
Call hashCode(),age=20
Set size=2

  從結果看出,元素加入List的時候,不執行額外的操做,而且能夠重複。而加入Set以前須要先執行hashCode方法,若是返回的值在集合中已存在,則要繼續執行equals方法,若是equals方法返回的結果也爲真,則證實該元素已經存在,會將新的元素覆蓋老的元素,若是返回hashCode值不一樣,則直接加入集合。這裏記住一點,對於集合中元素,hashCode值不一樣的元素必定不相等,可是不相等的元素,hashCode值可能相同。

  HashSet和LinkedHashSet的區別在於後者能夠保證元素插入集合的元素順序與輸出順序保持一致。而TresSet的區別在於其排序是按照Comparator來進行排序的,默認狀況下按照字符的天然順序進行升序排列。

  (4)Iterable:從這個圖裏面能夠看到Collection類繼承自Iterable,該接口的做用是提供元素遍歷的功能,也就是說全部的集合類(除Map相關的類)都提供元素遍歷的功能。Iterable裏面包含了Iterator的迭代器,其源碼以下,你們若是熟悉迭代器模式的話,應該很容易理解。

1 public interface Iterator<E> {
2 
3     boolean hasNext();
4 
5     E next();
6 
7     void remove();
8 }

二、Map:

      Map類型的集合最大的優勢在於其查找效率比較高,理想狀況下能夠實現O(1)的時間複雜度。Map中最經常使用的是HashMap,LinkedHashMap與HashMap的區別在於前者可以保證插入集合的元素順序與輸出順序一致。這二者與TreeMap的區別在於TreeMap是根據鍵值進行排序的,固然其底層的實現也有本質的區別,如HashMap底層是一個哈希表,而TreeMap的底層數據結構是一棵樹。咱們如今看下TreeMap與LinkedHashMap的區別:

package com.paddx.test.collection;

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;

public class MapTest {
    public static void main(String[] args) {
        Map<String,String> treeMap = new TreeMap<String,String>();
        Map<String,String> linkedMap = new LinkedHashMap<String, String>();

        treeMap.put("b",null);
        treeMap.put("c",null);
        treeMap.put("a",null);

        for (Iterator<String> iter = treeMap.keySet().iterator();iter.hasNext();){
            System.out.println("TreeMap="+iter.next());
        }

        System.out.println("----------分割線---------");

        linkedMap.put("b",null);
        linkedMap.put("c",null);
        linkedMap.put("a",null);

        for (Iterator<String> iter = linkedMap.keySet().iterator();iter.hasNext();){
            System.out.println("LinkedHashMap="+iter.next());
        }
    }
}

運行上述代碼,執行結果以下:

TreeMap=a
TreeMap=b
TreeMap=c
----------分割線---------
LinkedHashMap=b
LinkedHashMap=c
LinkedHashMap=a

  從運行結果能夠很明顯的看出這TreeMap和LinkedHashMap的區別,前者是按字符串排序進行輸出的,然後者是根據插入順序進行輸出的。細心的讀者能夠發現,HashMap與TreeMap的區別,與以前提到的HashSet與TreeSet的區別是一致的,在後續進行源碼分析的時候,咱們能夠看到HashSet和TreeSet本質上分別是經過HashMap和TreeMap來實現的,因此它們的區別天然也是相同的。HashTable如今已經不多使用了,與HashMap的主要區別是HashTable是線程安全的,不過因爲其效率比較低,因此一般使用HashMap,在多線程環境下,一般用CurrentHashMap來代替。

3、總結

  本文只是從總體上介紹了Java集合框架及其繼承關係。除了上述類,集合還提供Collections和Arrays兩個工具類,此外,集合中排序跟Comparable和Comparator緊密相關。在以後的文章中將對上述提的類在JDK中實現源碼進行詳細分析。

相關文章
相關標籤/搜索