Java集合淺析

Java集合類框架圖java

Java集合類框架圖

Collection

List

| 類型      | 數據結構 | 查詢速度 | 插入速度 | 是否線程安全 |
| ArrayList | 數組     | 快       | 慢       | 否      |
| LinkList  | 雙向鏈表,| 慢       | 快       | 否      |
| Vector    | 數組     | 慢       | 慢       | 是      |

對於它們三種類型的List的查詢、刪除、插入的性能(時間複雜度和空間複雜度)能夠根據它們底層所依賴的數據結構和算法具體分析,上面的圖表只是簡單的總結。在實際開發中,存在大量的隨機訪問和少許的刪除、插入時推薦使用ArrayList,反之使用LinkList。在須要保證線程安全的狀況下使用Vetor,一樣Collections.synchronizedList(new ArrayList())和Collections.synchronizedList(new LinkList())也能得到線程安全的List。

Set

Set和List比較

| 類型 | 是否有序 | 元素是否容許重複 |
| List | 是       | 否           |
| Set  | 否       | 是           |

HashSet

    實現Set接口,基於HashMap實現,容器內元素不能重複。
程序員

Map

HashMap

HashMap的數據結構

HashMap中的數據結構是數組+鏈表實現,如圖: 算法

HashMap數據結構                  HashMap的查找:首先根據hashcode在table數組中查找,而後根據key在其後的鏈表中得到key。編程

hashcode()和equals()方法

  • equals()數組

指示其餘某個對象是否與此對象「相等」。安全

equals 方法在非空對象引用上實現相等關係: 數據結構

(1)自反性:對於任何非空引用值 x,x.equals(x) 都應返回 true。 併發

(2)對稱性:對於任何非空引用值 x 和 y,當且僅當 y.equals(x) 返回 true 時,x.equals(y) 才應返回 true。 (3)傳遞性:對於任何非空引用值 x、y 和 z,若是 x.equals(y) 返回 true,而且 y.equals(z) 返回 true,那麼 x.equals(z) 應返回 true。 框架

(4)一致性:對於任何非空引用值 x 和 y,屢次調用 x.equals(y) 始終返回 true 或始終返回 false,前提是對象上 equals 比較中所用的信息沒有被修改。 數據結構和算法

(5)對於任何非空引用值 x,x.equals(null) 都應返回 false。

 (6)Object 類的 equals 方法實現對象上差異可能性最大的相等關係;即,對於任何非空引用值 x 和 y,當且僅當 x 和 y 引用同一個對象時,此方法才返回 true(x == y 具備值 true)。(實際JDK中對equals()方法進行了重寫) 


注意:當此方法被重寫時,一般有必要重寫 hashCode 方法,以維護 hashCode 方法的常規協定,該協定聲明相等對象必須具備相等的哈希碼。

//重寫equals()方法Demo
public class Student {
    protected String name;

    public Student(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean equals(Object obj){
        if(obj instanceof Student){
            Student student = (Student) obj;
            if(student.getName() == null || name == null){
                return false;
            }else {
                return  name.equalsIgnoreCase(student.getName());
            }
        }
        return false;
    }
}


public class SeniorStudent extends Student {
    //增長一個id屬性
    private int id;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public SeniorStudent(String name, int id) {
        super(name);
        this.id = id;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof SeniorStudent) {
            SeniorStudent ss = (SeniorStudent) obj;
            return super.equals(obj) && ss.getId() == id;
        }
        return false;
    }   
}

//測試
public class Test {
    public static void main(String[] args) {
        SeniorStudent ss1 = new SeniorStudent("zhangsan", 10);
        SeniorStudent ss2 = new SeniorStudent("zhangsan", 20);
        Student s = new Student("zhangsan");

        System.out.println(s.equals(ss1));
        System.out.println(s.equals(ss2));
        System.out.println(ss1.equals(ss2));
    }
}
預期:
false
false
false
結果:
true
true
false

緣由分析:
咱們使用instanceof關鍵字來檢查ss否爲Student類,而instanceof是判斷其左邊對象是否爲其右邊類的實例,也能夠用來判斷繼承中的子類的實例是否爲父類的實現,因此當有繼承的時便會出現問題。
//因此在equals()中使用getClass進行類型判斷
  • hashcode()

返回該對象的哈希碼值。支持此方法是爲了提升哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。 hashCode 的常規協定是:

 (1)在 Java 應用程序執行期間,在對同一對象屢次調用 hashCode 方法時,必須一致地返回相同的整數,前提是將對象進行 equals 比較時所用的信息沒有被修改。

 (2)從某一應用程序的一次執行到同一應用程序的另外一次執行,該整數無需保持一致。 若是根據 equals(Object) 方法,兩個對象是相等的,那麼對這兩個對象中的每一個對象調用 hashCode 方法都必須生成相同的整數結果。

 (3)若是根據 equals(java.lang.Object) 方法,兩個對象不相等,那麼對這兩個對象中的任一對象上調用 hashCode 方法不要求必定生成不一樣的整數結果。可是,程序員應該意識到,爲不相等的對象生成不一樣整數結果能夠提升哈希表的性能。 


實際上,由 Object 類定義的 hashCode 方法確實會針對不一樣的對象返回不一樣的整數。(這通常是經過將該對象的內部地址轉換成一個整數來實現的,可是 JavaTM 編程語言不須要這種實現技巧。)

hashCode()和equals配合使用

//重寫hashCode()和equals()的Demo
public class Student1 {
    private int age;
    private String name;

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        System.out.println("調用equals()方法");
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student1 student1 = (Student1) o;

        if (age != student1.age) return false;
        return !(name != null ? !name.equals(student1.name) : student1.name != null);

    }

    @Override
    public int hashCode() {
        System.out.println("調用hashCode()方法");
        int result = age;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        System.out.println("name:   " + name + "  age:    " + age + "   hashCode    "+result);
        return result;
    }

    public static void main(String[] args) {
        Student1 s1 = new Student1(1,"張三");
        Student1 s2 = new Student1(2,"張三");
        Student1 s3 = new Student1(1,"李四");
        Student1 s4 = new Student1(1,"張三");
        System.out.println("s1 == s4    " + (s1==s4 ));
        System.out.println("s1.equals(s4)" + s1.equals(s4));
        HashSet<Student1> hashSet = new HashSet();
        hashSet.add(s1);
        hashSet.add(s2);
        hashSet.add(s3);
        hashSet.add(s4);

        System.out.println(hashSet.size());
    }
}

//結果
s1 == s4    false
調用equals()方法
s1.equals(s4)true
調用hashCode()方法
name:   張三  age:    1   hashCode    774920
調用hashCode()方法
name:   張三  age:    2   hashCode    774951
調用hashCode()方法
name:   李四  age:    1   hashCode    842092
調用hashCode()方法
name:   張三  age:    1   hashCode    774920
調用equals()方法  //注意調用equals()方法 s1.equals(s4)==true時
3

HashMap常見遍歷方式比較

//(方法1)同時獲取key-value
Map<String,String> map = new HashMap();
for (Map.Entry<String,String> entry : map.entrySet()) {
    entry.getKey();
    entry.getValue();
}
//(方法2)獲取key
Map<String, String> map = new HashMap();
for (String key : map.keySet()) {
    System.out.println(key);
}
//(方法3)獲取value
Map<String, String> map = new HashMap();
for (String key : map.keySet()) {
    String value = map.get(key);
}
//(方法4)獲取value
Map<String, String> map = new HashMap();
for (String value : map.values()) {
    System.out.println(value);
}
//上面的四個例子也可使用迭代器,方法(1)能夠以下,可是不推薦
Map<String, String> map = new HashMap();
Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
    Map.Entry<String, String> entry = it.next();
}
/**
*同時獲取key-value是推薦使用方法(1)
*只是獲取key推薦方法(2)
*獲取方法value(4)
**/

Hashtable

Hashtable和HashMap採用一樣的數據結構,實現基本相同。

Hashtable和HashMap的比較

| 類型          | key爲是否容許爲null | key爲是否容許爲null  | 是否線程安全 | 效率 |     父類     |

| Hashtable | 是                          | 是                          | 否               | 高   | Dictionary    |

| HashMap  | 否                          | 否                          | 是               | 低   | AbstractMap |

ConcurrentHashMap

併發下HashMap死循環問題

//案例以下:運行下面代碼,使用top命令,CPU會飆升趨於100%
public class HashMapInfLoop {

    private HashMap map = new HashMap();

    public HashMapInfLoop() {
        for(int i =0; i<100000; i++) {
            Thread thread = new Thread(new Runnable() {
                public void run() {
                    for (int i = 0; i < 50000000; i++) {
                        map.put(new Integer(i), Integer.valueOf(i));
                    }
                }
            });
            thread.start();
        }

    }
    public static void main(String[] args) {
        new HashMapInfLoop();
    }
}

HashMap死循環問題分析

問題出在擴容時,移動oldTable裏的數據到newTable裏時,在併發條件下會出現環路。源碼以下:

void transfer(Entry[] newTable) { 
    Entry[] src = table;
    int newCapacity = newTable.length; 
    for (int j = 0; j < src.length; j++) {
        Entry<K,V> e = src[j]; 
        if (e != null) {
            src[j] = null; 
            do {
                Entry<K,V> next = e.next; 
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            } while (e != null); 
        }
    }
}

圖解分析能夠參考:http://ifeve.com/hashmap-infinite-loop/

上面的例子能夠說明併發下不能使用HashMap,那咱們可使用線程安全的Hashtable嗎?

HashTable中是使用synchronized實現同步,synchronized鎖的HashTable的實例對象,其餘線程訪問HashTable的同步方法時,可能會進入阻塞或輪詢狀態。如線程A使用put進行添加元素,線程B不但不能使用put方法添加元素,而且也不能使用get方法來獲取元素,因此競爭越激烈效率越低。因此就得使用ConcurrentHashMap。

ConcurrentHashMap實現原理

ConcurrentHashMap結構圖:

根據ConcurrentHashMap的結構圖能夠明顯看出,ConcurrentHashMap採用的是二次hash的方式,

第一次hash將key映射到對應的segment(Segment是一種可重入鎖ReentrantLock),而第二次hash則是映射到segment的不一樣桶中。爲何要用二次hash,主要緣由是爲了構造分離鎖,使得對於map的修改不會鎖住整個容器,提升併發能力。可是二次hash帶來的問題是整個hash的過程比hashmap單次hash要長,因此不是併發狀況下不要使用ConcurrentHashMap。

WeakHashMap

WeakHashMap 使用 WeakReference 做爲 key, 一旦沒有指向 key 的強引用, WeakHashMap 在 GC 後將自動刪除相關的 entry。

 WeakHashMap<String,String> map = new WeakHashMap<String, String>();
    map.put("wjk","hello");
    map.put("snail","world");
    map.put(new String("wjk"),"hello");
    map.put(new String("snail"),"world");

    System.gc();
    System.out.println(map.size());
}
//輸出
2

Java引用類型能夠參考:http://my.oschina.net/u/2361475/blog/603125

TreeMap

擴展

使用Google Guava框架和Apache Commons框架的工具類集合工具類,編寫優雅的代碼。

相關文章
相關標籤/搜索