Java集合框架之Collection集合

1、引言

    Java集合框架和IO框架同樣,看似很龐雜的體系框架,可是當你逐一深刻每一個集合的用法後,就能明顯的看出他們之間的區別和聯繫。最後拎出一個框架圖,就一目瞭然了。因爲Java的集合框架分兩大系Collection系和Map系,之因此要分開是由於Collection內存儲的是線性集合,而Map的元素是以鍵值對(Key-Value)的形式存的。其實Map和Collection內部實現是存在聯繫的,學完就懂了。本文主要介紹的Collection中經常使用的集合的用法以及之間的區別。java

2、Collection集合框架介紹

面這張圖,是Collection框架的類圖,中間省略的幾個可有可無的類,總體還算詳細。斜體加粗表示接口(規範的類圖應該是加上<<interface>>)。數組

 Collection接口是根接口,Iterable接口能夠簡單理解成標記接口,它約定了全部線性集合(數組、隊列、棧都屬於線性集合,Map是二維集合不能直接遍歷)都必須能夠遍歷,同時也定義了hasNext()、next()、和remove()三個遍歷方法,後邊還會詳細講到。從根接口派生出兩個子接口Set和List,分別做爲Set集合和List集合的根接口。最後是五個經常使用的集合類HashSet、TreeSet、LinkedList、ArrayList、Vector。不過從類圖中發現每一個集合接口都有一個抽象的實現類,並且每一個最終集合類同時繼承了抽象類和實現了集合接口。                             例如:ArrayList extends AbstractList implements List,AbstractList自己就已經實現了List接口,這裏再寫implements List是爲了使整個集合框架結構更清晰,不少集合框架圖爲了看着方便都省略了實現接口這條虛線,可是我以爲須要解釋。數據結構

    Set和List具體有什麼區別呢,咱們先來看下面這張Collection、List和Set的類圖。List接口的類圖中只列出特有的方法,從Collection中繼承的方法沒有重複列舉。爲何沒有Set接口?Set接口中的方法和Collection徹底同樣,沒有增長任何新的方法,就再也不重複畫圖了。Set再重複定義一遍Collection接口中的方法,一是爲了做爲類庫要添加屬於Set的特有方法約定,雖然方法定義相同,可是具體方法實現是不一樣的,例如Set的add方法不能添加劇復元素,List則能夠;二是爲了讓集合框架結構更清晰。框架

主要區別:less

  一、List必須是有序集合,Set能夠有序也能夠無序,這裏的有序無序是指集合內部的存儲順序是否和元素的添加順序相同。其實Set接口註釋中並無特別說明Set就應該是無序的,但也沒有像List接口同樣開始就聲明「An ordered collection 」,可是在Set集合的iterator()方法註釋中有這樣一句話:The elements are returned in no particular order (unless this set is an instance of some class that provides a guarantee).結論就是Set的實現類也能夠有序。ide

  二、List容許有重複元素,更確切地講,List容許知足e1.equals(e2)的元素對e一、e2,而且若是列表自己容許 null 元素的話,一般它們容許多個 null 元素。而Set集合不容許有重複元素,若是容許有null值,最多隻能有一個null值。函數

  三、List能對元素精準定位,能夠根據元素下標對任意位置的元素實現增刪改查。索引下標和數組同樣從0開始。例如List接口中定義了add(int index, E element)、get(int index)、set(int index, E element)等能夠訪問指定位置元素的方法。Set只能對集合遍歷而不能進行隨機訪問元素。性能

把List都放在前面說,好像List要比Set優秀似得,並不是如此,各有優點,下面詳細對5個集合類詳細比較測試

3、Collection集合類的實現原理和具體區別

一、ArrayList

    ArrayList是比較經常使用的一個集合,之因此叫數組列表是由於其底層是用數組實現的,數組咱們熟悉啊,那就先來看一下數組有什麼特色吧。若是是對象數組,元素能夠爲null,元素能夠重複N次,能夠用元素下標訪問任意位置(不要越界),而這些如今都成了ArrayList的特色。數組還有一個大特色就是查詢速度很快,這裏說一下數組的尋址方式。當建立一個數組時,會在內存中分配一段地址連續的空間,數組名就是這塊內存空間的首地址。好比int[] arr=new int[10]; 假設首地址是22005,int[3]就是訪問第4個元素,其地址是22005+4*sizeof(int),int大小爲4,那麼結果就是22005+4*4=22021,直接去222021這個位置區拿數據就好了。很明顯,數組是直接尋址,查找速度至關快。可是若是根據內容查找,就只能遍歷數組了,ArrayList有些個方法indexOf(Object o)、lastIndexOf(Object o)、remove(Object o)都是遍歷數組查找元素的,若是數據量很大的話,經測試,性能有所降低。簡單點理解就是,ArrayList就是數組的一個封裝類,能用數組的地方都能用它。最後說一下,ArrayList內部數組的增加策略是oldLength>>1,每次增加原來的一半的說法並不許確。this

二、LinkedList

    LinkedList內部是一個雙向鏈表,學過數據結構的都知道雙向鏈表,刪除添加很方便,直接修改先後兩個元素的指針就搞定了。因此LinkedList中提供了不少方便雙向增刪元素的方法,下面看一下其類圖,只列舉了LinkedList中特有的方法。

能夠看到有不少相似xxxFrist()和xxxLash()的方法,這真是充分發揮了雙向鏈表的做用。有了這些方法就能夠將連接列表用做堆棧、隊列或雙端隊列。例如操做堆棧用pop()彈出最上面的元素,push()壓入棧。其實這些方法只是用戶接口,僅僅是換了換方法名字,其中道理看下源碼便知。

    public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }
    public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }
    public boolean offer(E e) {
        return add(e);
    }
    public boolean offerFirst(E e) {
        addFirst(e);
        return true;
    }
    public boolean offerLast(E e) {
        addLast(e);
        return true;
    }
    public E peekFirst() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
     }
    public E peekLast() {
        final Node<E> l = last;
        return (l == null) ? null : l.item;
    }
    public E pollFirst() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }
    public E pollLast() {
        final Node<E> l = last;
        return (l == null) ? null : unlinkLast(l);
    }

能夠看到方法調來調去最後就是在訪問鏈表頭尾的元素。這也給咱們一個提示,好的代碼方法命名真的很重要!這些方法都是在LinkedList中單獨定義的,因此想用這些方法,就不能用多態的方式去建立對象,如List list=new LinkedList();這樣只能調用List中定義的方法。鏈表還有一個很坑的特色就是,若是要找一個元素每次都要遍歷一下鏈表,查找速度略慢。另外Linkedlist使用了一個特殊的迭代器listIterator,也是其特有的,這個迭代器的特殊之處就是能夠從雙向迭代,便可從前日後迭代也可從後往前迭代,其實現仍是要依賴於雙向鏈表,使用比較方便,應該記住。

三、Vector

    Vector是從JDK1.0就有了,它是集合框架在JDK1.2出現後才加入到集合框架的,因此這個Vector很古老了,其功能和ArrayList基本同樣,內部也是數組實現。惟一不一樣就是Vector是線程同步,Arraylist線程不一樣步。這裏再也不贅述了,此類已基本棄用!

 四、HashSet

   Set集合的特色就是無重複元素,那麼HashSet是怎麼實現元素的惟一性呢?經過比較hashcode和equals,若是hashcode相同,纔會繼續比較equals,若是hashcode不一樣,再也不比較equals,上個例子說明:

package com.heima.collection;
import java.util.*;
public class HashSetDemo {
	public static void main(String[] args) {
		A a1=new A("hector",22);
		A a3=new A("paul",33);
		A a4=new A("zeus",100);
		A a5=new A("zeus",100);
		HashSet<A> set=new HashSet<A>();
		set.add(a1);
		set.add(a3);
		set.add(a4);
		set.add(a5);
		for(Iterator<A> it=set.iterator();it.hasNext();){
			System.out.println(it.next());
		}
		
	}
}
class A{
	private int age;
	private String name;
	public A() {
		super();
	}
	@Override
	public String toString() {
		return "[age=" + age + ", name=" + name + "]";
	}
	public A(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	public int getAge() {
		return age;
	}
	public String getName() {
		return name;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public void setName(String name) {
		this.name = name;
	}
	@Override
	public int hashCode() {
		System.out.println("hashcode..."+this.getName());
		return 60;
	}
	@Override
	public boolean equals(Object obj) {
		//name和age都相等,爲true
		A a=(A)obj;
		return this.getName().equals(this.getName())&&a.getAge()==this.getAge();
	}
	
	
}

運行結果:

這裏重寫了hashcode()和equals(),故意讓hashcode返回固定值60,經過運行結果能夠看到當hashcode相等時會繼續判斷equals,equals爲true就視爲重複元素,false則添加,若是不重寫hashcode你會發現不會調用equals方法。由於hasdcode和equals方法都是繼承自超類Object,Object中默認hashcode是根據對象的內存地址計算的,equals用「==」比較,因此不一樣對象的hashcode必定不一樣,equals也不會爲true。可是java中有約定:若是兩個對象相等(equal),那麼必須擁有相同的哈希碼(hash code),因此通常都會同時重寫equals和hashcode,這個之後慢慢再說。

 HashSet還有一個特色就是無序,HashSet內部是一個哈希表(又稱散列表),每個輸入都會通過哈希函數獲得一個固定長度的哈希值hash(Key)=hashcode,元素的存儲位置是根據這個哈希值而定的,因此纔會顯得無序。

四、TreeSet

    前面說過Set集合也能夠有序,TreeSet就是那個有序的集合類,從整體框架圖中能夠看到TreeSet並無直接實現Set接口而是實現了其子接口Sorted,該接口進一步提供了關於元素整體排序的Set,這些元素使用其天然順序進行排序,或者根據一般在建立有序 set 時提供的Comparator進行排序。就是是的TreeSet中的元素必須提供排序規則。對TreeSet元素排序有兩種方法,一是元素自己具備排序規則就是實現Comparable接口,而是給TreeSet提供一個Comparator比較器。

public class CompareDemo {
	public static void main(String[] args) {
		Student stu1=new Student("hector",29);
		Student stu2=new Student("guoke",23);
		Student stu3=new Student("paul",40);
		Student stu4=new Student("aple",23);
		Student[] stu={stu1,stu2,stu3,stu4};
		TreeSet<Student> tree=new TreeSet<Student>();
		tree.add(stu1);
		tree.add(stu2);
		tree.add(stu3);
		tree.add(stu4);
		Iterator it=tree.iterator();
		while(it.hasNext()){
			System.out.println(it.next());
		}
	}
}
//實現Comparable接口
class Student implements Comparable{
	public int age;
	public String name;
	public Student() {
		super();
	}
	public Student(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	//實現compareTo方法,定義比較規則,先按age排序,age相同按name排序
	//該方法是內部自動調用
	@Override
	public int compareTo(Object o) {
		int res=this.age-((Student)o).age;
		if(res==0)
			return this.name.compareTo(((Student)o).name);
		return res;
	}
	@Override
	public String toString() {
		return "Student [age=" + age + ", name=" + name + "]"+"\n";
	}
}

這是第一種實現方法,Student 類實現了Comparable接口,這就使得Student本身有了排序規則。第二種就是給TreeSet提供一個比較器:

class StuComparator implements Comparator<Student>{
	@Override
	public int compare(Student o1, Student o2) {
		int res=o1.age-o2.age;
		if(res==0)
			return o1.name.compareTo(o2.name);
		return res;
	}
}

建立TreeSet時把比較器傳過去就行TreeSet<Student> tree=new TreeSet<Student>(new StuComarator());一樣能夠實現排序效果,若是兩種方式同時存在,以這種方式爲主!另外注意一點,若是這兩種方式都沒有,程序會出現異常ClassCastException。

第二種方式更靈活,可讓TreeSet的一個實例定義本身的排序規則,無論存儲什麼對象都使用同一種排序方式,並且很方面程序的擴展,爲首選方式。

    第二個特色就是沒有重複元素了,TreeSet底層是一個紅黑樹(又稱自平衡二叉樹),這個數據結構略微複雜,在TreeMap中詳細說明。

4、集合特色比較

相關文章
相關標籤/搜索