1、集合類簡介 java
數組是很經常使用的一種的數據結構,咱們用它能夠知足不少的功能,可是,有時咱們會遇到以下這樣的問題: 面試
一、咱們須要該容器的長度是不肯定的。 算法
二、咱們須要它能自動排序。 編程
三、咱們須要存儲以鍵值對方式存在的數據。 數組
若是遇到上述的狀況,數組是很難知足需求的,接下來本章將介紹另外一種與數組相似的數據結構——集合類,集合類在Java中有很重要的意義,保存臨時數據,管理對象,泛型,Web框架等,不少都大量用到了集合類。 緩存
常見的集合類有這些種: 安全
實現Collection接口的:Set、List以及他們的實現類。 數據結構
實現Map接口的:HashMap及其實現類,咱們經常使用的有Map及其實現類HashMap,HashTable,List、Set及其實現類ArrayList、HashSet,由於集合類是很大的一塊內容,咱們不方便把它的所有內容寫出來,只能慢慢的增長,但願各位讀者有本身想法的,踊躍向我提出,咱們共同打造精美的博客,供廣大編程愛好者學習,下面我咱們經過一個圖來總體描述一下: 多線程
這個圖片無法顯示的很清楚,因此我將原始圖片上傳到了個人資源裏:http://download.csdn.net/detail/zhangerqing/4711389。願意看清楚的就去下吧。 框架
下面的表格也許能夠更直接的表現出他們之間的區別和聯繫:
接口 |
簡述 |
實現 |
操做特性 |
成員要求 |
Set |
成員不能重複 |
HashSet |
外部無序地遍歷成員 |
成員可爲任意Object子類的對象,但若是覆蓋了equals方法,同時注意修改hashCode方法。 |
TreeSet |
外部有序地遍歷成員;附加實現了SortedSet, 支持子集等要求順序的操做 |
成員要求實現caparable接口,或者使用 Comparator構造TreeSet。成員通常爲同一類型。 |
||
LinkedHashSet |
外部按成員的插入順序遍歷成員 |
成員與HashSet成員相似 |
||
List |
提供基於索引的對成員的隨機訪問 |
ArrayList |
提供快速的基於索引的成員訪問,對尾部成員的增長和刪除支持較好 |
成員可爲任意Object子類的對象 |
LinkedList |
對列表中任何位置的成員的增長和刪除支持較好,但對基於索引的成員訪問支持性能較差 |
成員可爲任意Object子類的對象 |
||
Map |
保存鍵值對成員,基於鍵找值操做,compareTo或compare方法對鍵排序 |
HashMap |
能知足用戶對Map的通用需求 |
鍵成員可爲任意Object子類的對象,但若是覆蓋了equals方法,同時注意修改hashCode方法。 |
TreeMap |
支持對鍵有序地遍歷,使用時建議先用HashMap增長和刪除成員,最後從HashMap生成TreeMap;附加實現了SortedMap接口,支持子Map等要求順序的操做 |
鍵成員要求實現caparable接口,或者使用Comparator構造TreeMap。鍵成員通常爲同一類型。 |
||
LinkedHashMap |
保留鍵的插入順序,用equals 方法檢查鍵和值的相等性 |
成員可爲任意Object子類的對象,但若是覆蓋了equals方法,同時注意修改hashCode方法。 |
||
IdentityHashMap |
使用== 來檢查鍵和值的相等性。 |
成員使用的是嚴格相等 |
||
WeakHashMap |
其行爲依賴於垃圾回收線程,沒有絕對理由則少用 |
|
(上圖來源於網友的總結,已不知是哪位的原創,恕不貼出地址,如原做者看到請聯繫我,必將貼出連接!)
實現Collection接口的類,如Set和List,他們都是單值元素(其實Set內部也是採用的是Map來實現的,只是鍵值同樣,從表面理解,就是單值),不像實現Map接口的類同樣,裏面存放的是key-value(鍵值對)形式的數據。這方面就形成他們不少的不一樣點,如遍歷方式,前者只能採用迭代或者循環來取出值,可是後者可使用鍵來得到值得值。
2、基本方法及使用
---------------------------
實現Map接口的
HashMap
Set的實現類HashSet,底層仍是調用Map接口來處理,因此,此處,我將說下Map接口及其實現類的一些方法。Map接口中的原始方法有:
public abstract int size();
public abstract boolean isEmpty();
public abstract boolean containsKey(Object paramObject);
public abstract boolean containsValue(Object paramObject);
public abstract V get(Object paramObject);
public abstract V put(K paramK, V paramV);
public abstract V remove(Object paramObject);
public abstract void putAll(Map<? extends K, ? extends V> paramMap);
public abstract void clear();
public abstract Set<K> keySet();
public abstract Collection<V> values();
public abstract Set<Entry<K, V>> entrySet();
public abstract boolean equals(Object paramObject);
public abstract int hashCode();
此處細心的讀者會看到,每一個方法前都有abstract關鍵字來修飾,其實就是說,接口中的每一個方法都是抽象的,有的時候咱們寫的時候不加abstract關鍵字,可是在編譯的過程當中,JVM會給加上的,因此這點要注意。抽象的方法意味着它沒有方法實現體,同時必須在實現類中重寫,接下來咱們依次分析一下他們的實現類HashMap中是怎麼作的。
HashMap的設計複雜而又巧妙,用處普遍,值得咱們深究一下,由於HashMap是一塊很大很重要的知識點,而在這兒咱們重點介紹集合類,因此請看這篇文章,關於【深刻解讀HashMap內部結構】的文章。
重點方法介紹:
首先是構造方法,大多數狀況下,咱們採起無參的構造函數來構造哈希表,
public HashMap() { this.entrySet = null; this.loadFactor = 0.75F; this.threshold = 12; this.table = new Entry[16]; init(); }此處先介紹三個重要的變量:loadFactor、threshold、table,loadFactor是一個加載因子,threshold是臨界值,table說明哈希表的底層,實際上是個數組。能夠看看他們的聲明方式:
transient Entry[] table;;
int threshold;
final float loadFactor;
細心的讀者彷佛又發現一個新問題,爲何table前面採用的是transient關鍵字呢,那咱們得閒來研究下聲明爲transient關鍵字的含義:變量若是被聲明爲transient類型的話,那麼在序列化的時候,忽略其的值,就是說此處的table,若是將要進行持久化的話,是不會對table的值進行處理的,直接忽略,爲何此處table會這樣處理呢?由於HashMap的存儲結構,其實就是一個數組+多個鏈表,數組裏存放對象的地址,鏈表存放數據,因此對地址進行持久化是沒有任何意義的。關於這塊知識,我會在另外一篇文章【深刻解讀HashMap內部結構】中重點介紹。
HashMap的初始容量爲0,每增長一對值,容量曾1,這點好理解,咱們經過一個小的例子,來看看HashMap的基本使用方法。
package com.xtfggef.map.test; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * HashMap的使用 * @author erqing * */ public class MapTest { public static void main(String[] args) { /* 初始化map */ Map<String, Integer> map = new HashMap<String, Integer>(); System.out.println("HashMap的初始值:" + map.size()); System.out.println("HashMap是否爲空:" + (map.isEmpty() ? "是" : "否")); /* 想map中添加元素 */ map.put("erqing", 1); map.put("niuniu", 2); map.put("egg", 3); System.out.println(map.size()); ; System.out.println("HashMap是否爲空:" + (map.isEmpty() ? "是" : "否")); /* 遍歷HashMap中的元素 */ Set<String> set = map.keySet(); for (String s : set) { System.out.println(s + " " + map.get(s) + " " + "hashcode:" + s.hashCode()); } /*檢測是否含有某個Key*/ System.out.println(map.containsKey("egg")); /*檢測是否含有某個Value*/ System.out.println(map.containsValue(2)); /*打印hashCode*/ System.out.println(map.hashCode()); } }輸出:
HashMap的初始值:0
HashMap是否爲空:是
3
HashMap是否爲空:否
niuniu 2 hashcode:-1045196352
egg 3 hashcode:100357
erqing 1 hashcode:-1294670850
true
true
1955200455
此處附一個利用HashMap來簡單處理問題的例子,需求在註釋中已經給出,但願讀者好好看看,代碼不難,可是不少的面試及面試題都用到這個思路,筆者曾經面試的時候,常常會被問題這題的思想,可是就是沒有去親自實現一下,以至在hashmap的操做上被難住了。
package com.xtfggef.hashmap; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * 打印在數組中出現n/2以上的元素 * 利用一個HashMap來存放數組元素及出現的次數 * @author erqing * */ public class HashMapTest { public static void main(String[] args) { int [] a = {2,3,2,2,1,4,2,2,2,7,9,6,2,2,3,1,0}; Map<Integer, Integer> map = new HashMap<Integer,Integer>(); for(int i=0; i<a.length; i++){ if(map.containsKey(a[i])){ int tmp = map.get(a[i]); tmp+=1; map.put(a[i], tmp); }else{ map.put(a[i], 1); } } Set<Integer> set = map.keySet(); for (Integer s : set) { if(map.get(s)>=a.length/2){ System.out.println(s); } } } }關於HashMap的一些其餘底層的東西及與HashTable的區別和聯繫,我會在另外一篇文章裏介紹,此處暫很少說。
實現Collection接口的
ArrayList
後面的博文,我會以分析和例子爲主,畢竟源碼這種東西,不適合大量的貼出來,你們都會本身去看,有些很差懂的地方或者容易忽略的知識,我會貼出來,其它的建議讀者本身去看JDK源碼。ArrayList底層採用數組實現,具備較高的查詢速度。
boolean isEmpty():若是容器裏面沒有保存任何元素,就返回true。
boolean contains(Object):若是容器持有參數Object,就返回true。
Iterator iterator():返回一個能夠在容器的各元素之間移動的Iterator。
Object[] toArray():返回一個包含容器中全部元素的數組。
Object[] toArray(Object[] a):返回一個包含容器中全部元素的數組,且這個數組不是普通的Object數組,它的類型應該同參數數組a的類型相同(要作類型轉換)。
void clear():清除容器所保存的全部元素。(「可選」)
boolean remove(Object o);
boolean add(Object):確保容器能持有你傳給它的那個參數。若是沒有把它加進去,就返回false。(這是個「可選」的方法,本章稍後會再做解釋。)
boolean addAll(Collection):加入參數Collection所含的全部元素。只要加了元素,就返回true。
boolean containsAll(Collection):若是容器持有參數Collection所含的所有元素,就返回true
boolean removeAll(Collection):刪除容器裏面全部參數Collection所包含的元素。只要刪過東西,就返回true。(「可選」)
boolean retainAll(Collection):只保存參數Collection所包括的元素(集合論中「交集」的概念)。若是發生過變化,則返回true。(「可選」)
boolean equals(Object o);
int hashCode();
int size():返回容器所含元素的數量。
這些方法也就是Set和List及它們的實現類裏有的方法。
List接口在Collection接口的基礎上,有添加了本身的一系列方法:
E get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);
int indexOf(Object o);
int lastIndexOf(Object o);
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
List<E> subList(int fromIndex, int toIndex);
先看一下構造函數,和Map同樣,咱們一樣習慣使用默認的無參構造函數,
private transient Object[] elementData; private int size; public ArrayList(int paramInt) { if (paramInt < 0) throw new IllegalArgumentException("Illegal Capacity: " + paramInt); this.elementData = new Object[paramInt]; } public ArrayList() { this(10); }此處elementData就是它底層用來存放數據的數組元素,仔細看一下,不管採用無參還有有參的構造
函數,最終都歸結於一句話:this.elementData = new Object[paramInt];若是沒有傳入參數的話,會默認開闢一個10個字節大小的空間,但是當咱們用的時候,咱們寫下以下的語句:
List<String> list = new ArrayList<String>( );
List<String> list = new ArrayList<String>(5);
當咱們輸出它的size值時:System.out.println(list.size());咱們發現,輸出的都是0.這讓人貌似有一絲迷惑,明明是10或者5,這兒應該用清楚,elementData數組的長度並非size的值,size是裏面元素的個數,上面的10或者是5,意思是向內容開闢10個大小的空間,初始化的時候開闢必定數量的內存,可是裏面並無聽任何對象,因此用size()計算獲得的結果仍爲0.
這時,咱們又有新問題了,由於咱們知道List是能夠自動擴容的,這個功能就取決於以下的方法:
public void ensureCapacity(int paramInt) { this.modCount += 1; int i = this.elementData.length; if (paramInt > i) { Object[] arrayOfObject = this.elementData; int j = i * 3 / 2 + 1; if (j < paramInt) j = paramInt; this.elementData = Arrays.copyOf(this.elementData, j); } }ensureCapacity(int paramInt)用來初始化或者擴大ArrayList的空間。
從上述代碼中能夠看出,數組進行擴容時,會將老數組中的元素從新拷貝一份到新的數組中,每次數組容量的增加大約是其原容量的1.5倍。這種操做的代價是很高的,所以在實際使用時,咱們應該儘可能避免數組容量的擴張。當咱們可預知要保存的元素的多少時,要在構造ArrayList實例時,就指定其容量,以免數組擴容的發生。或者根據實際需求,經過調用ensureCapacity方法來手動增長ArrayList實例的容量。
ArrayList還給咱們提供了將底層數組的容量調整爲當前列表保存的實際元素的大小的功能。它能夠經過trimToSize方法來實現。代碼以下:
public void trimToSize() { this.modCount += 1; int i = this.elementData.length; if (this.size < i) this.elementData = Arrays.copyOf(this.elementData, this.size); }經過ensureCapacity(int paramInt)方法能夠提升ArrayList的初始化速度,請看下面的代碼:
package com.xtfggef.list.test; import java.util.ArrayList; public class EnsureCapacityTest { @SuppressWarnings("unchecked") public static void main(String[] args) { final int N = 1000000; Object obj = new Object(); /*沒用調用ensureCapacity()方法初始化ArrayList對象*/ ArrayList list = new ArrayList(); long startTime = System.currentTimeMillis(); for (int i = 0; i <= N; i++) { list.add(obj); } long endTime = System.currentTimeMillis(); System.out.println("沒有調用ensureCapacity()方法所用時間:" + (endTime - startTime) + "ms"); /*調用ensureCapacity()方法初始化ArrayList對象*/ list = new ArrayList(); startTime = System.currentTimeMillis(); // 預先設置list的大小 list.ensureCapacity(N); for (int i = 0; i <= N; i++) { list.add(obj); } endTime = System.currentTimeMillis(); System.out.println("調用ensureCapacity()方法所用時間:" + (endTime - startTime) + "ms"); } }輸出:
沒有調用ensureCapacity()方法所用時間:102ms
調用ensureCapacity()方法所用時間:46ms
很明顯,使用ensureCapacity()能提升很多效率!
下面實現一個簡單的例子,來展示下ArrayList的基本使用。
package com.xtfggef.list.test; import java.util.ArrayList; /** * * 關於ArrayList的基本操做 * 其它操做感興趣的讀者能夠本身結合源碼實現一下 * * @author erqing * */ public class ListTest { public static void main(String[] args) { /* 新建一個ArrayList */ ArrayList<String> list = new ArrayList<String>(); System.out.println("初始化大小:" + list.size()); /* 添加元素 */ list.add("zzz"); list.add("egg"); list.add("hell"); list.add("child"); System.out.println("當前容量:" + list.size()); /* 將ArrayList的大小和實際所含元素的大小設置一致 */ list.trimToSize(); /* 遍歷 */ for (String string : list) { System.out.println(string); } /* 在指定位置插入元素 */ list.add(2, "zhu"); for (String string : list) { System.out.println(string); } System.out.println("--------------"); /* 清空list */ list.clear(); /* 遍歷 */ for (String string : list) { System.out.println(string); } System.out.println("--------------"); } }
ArrayList基於數組實現,因此它具有數組的特色,即查詢速度較快,可是修改、插入的速度卻有點兒慢,可是,下面將要介紹的LinkedList就是來解決這個問題的,LinkedList基於鏈表,與ArrayList互補,因此實際開發中咱們應該按照本身的需求來定到底用哪個。
Where there is a will,there is a way.With the boat burned,Qin's territory final belonged to Chu.
The God won't cheat the hard working people.As the undergo self-imposed hardships
so as to strengthen his resolve,three thousand soldiers from Yue destroyed the country of Wu!
LinkedList
LinkedList底層採用雙向循環列表實現,進行插入和刪除操做時具備較高的速度,咱們還可使用LinkedList來實現隊列和棧。
private static class Entry<E> { E element; Entry<E> next; Entry<E> previous; Entry(E element, Entry<E> next, Entry<E> previous) { this.element = element; this.next = next; this.previous = previous; } }
這是LinkedList的原始存儲模型,由於是雙向循環列表,咱們能夠回憶一下數據結構中雙向列表是什麼狀況:一個數據data,兩個指針,一個指向前一個節點,名爲previous,一個指向下一個節點,名爲next,可是循環怎麼體現了,來看下她的無參構造函數:
public LinkedList() { header.next = header.previous = header; }
頭尾相等,就是說初始化的時候就已經設置成了循環的。仔細觀察源碼,不難理解,若是熟悉數據結構的讀者,必定很快就能掌握她的原理。下面我簡單分析一個操做,就是LinkedList的add()。讀者能夠經過這個本身去理解其餘操做。
public boolean add(E e) { addBefore(e, header); return true; }
private Entry<E> addBefore(E e, Entry<E> entry) { Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);//-------1--------- newEntry.previous.next = newEntry; newEntry.next.previous = newEntry; size++; modCount++; return newEntry; }
咱們先來觀察下上面給出的Entity類,構造方法有三個參數,第二個是她的next域,第三個是她的previous域,因此上述代碼1行處將傳進來的entry實體,即header對象做爲newEntry的next域,而將entry.previous即header.previous做爲previous域。也就是說在header節點和header的前置節點之間插入新的節點。看下面的圖:
一目瞭然。
歡迎廣大讀者進行建議、反饋,筆者定會及時改正,更新!
其餘的道理同樣,下面我會給出一個LinkedList使用的例子,須要注意的地方,我會特別說明。
package com.xtfggef.list.test; import java.util.LinkedList; public class LinkedListTest { public static void main(String[] args) { /* 新建一個list */ LinkedList<Integer> list = new LinkedList<Integer>(); System.out.println(list.size()); /* 向list中添加元素 */ list.add(222); list.add(111); list.add(0); list.add(3333); list.add(8888); System.out.println(list.size()); /* 遍歷list */ for (Integer integer : list) { System.out.println(integer); } /* 獲取第一個元素 ,即header的next域*/ System.out.println("第一個元素是:" + list.getFirst()); /*獲取最後一個元素,即header的previous域*/ System.out.println("最後一個元素是:"+list.getLast()); } }
比較簡單,其餘的方法,請讀者本身去嘗試,結合源碼。
實現Map接口的
HashMap
關於HashMap的詳細介紹,請看深刻解析HashMap章節
WeakHashMap
理解該集合類以前,建議先去了解Java的垃圾回收機制,WeakHashMap多用於緩存系統,就是說在系統內存緊張的時候可隨時進行GC,可是若是內存不緊張則能夠用來存放一些緩存數據。由於若是使用HashMap的話,它裏面的值基本都是強引用,即便內存不足,它也不會進行GC,這樣系統就會報異常。看一下WeakHashMap中Entry的實現:
private static class Entry<K,V> extends WeakReference<K> implements Map.Entry<K,V> { private V value; private final int hash; private Entry<K,V> next; /** * Creates new entry. */ Entry(K key, V value, ReferenceQueue<K> queue, int hash, Entry<K,V> next) { super(key, queue); this.value = value; this.hash = hash; this.next = next; }......
Entry繼承了WeakReference類,且在構造函數中構造了Key的弱引用,當進行put或者get操做時,都會調用一個函數叫expungeStaleEntries(),以下:
private void expungeStaleEntries() { Entry<K,V> e; while ( (e = (Entry<K,V>) queue.poll()) != null) { int h = e.hash; int i = indexFor(h, table.length); Entry<K,V> prev = table[i]; Entry<K,V> p = prev; while (p != null) { Entry<K,V> next = p.next; if (p == e) { if (prev == e) table[i] = next; else prev.next = next; e.next = null; // Help GC e.value = null; // " " size--; break; } prev = p; p = next; } } }
就是用來判斷,若是key存在弱引用,則進行垃圾回收,因此這個就是WeakHashMap的工做原理。它與HashMap的區別就是:數據量大的時候,它可根據內存的狀況,自動進行垃圾回收。若是手動將key置爲強引用,那麼它將和HashMap變得同樣,失去其功能。
3、比較(性能,功能方面)
這一塊主要就是對咱們平時接觸的這些集合類作一個簡單的總結,一方面有助於本身整理思路,再者面試的時候,面試官總喜歡問一些他們之間的區別,凡是Java面試,幾乎都要問到集合類的東西,問的形式有兩種:1、整體介紹下集合類有哪些。這個問題只要把我上文中的圖介紹一下就好了。2、比較一下XXX和XXXX。固然了,確定包括相同點和不一樣的地方。這個稍微麻煩一點,須要咱們完全理解了,才能回答的比較準確。如下是我對常被比較的一些類的分析:
一、HashMap和HashTable
相同點:兩者都實現了Map接口,所以具備一系列Map接口提供的方法。
不一樣點:
一、HashMap繼承了AbstractMap,而HashTable繼承了Dictionary。
二、HashMap非線程安全,HashTable線程安全,處處都是synchronized關鍵字。
三、由於HashMap沒有同步,因此處理起來效率較高。
四、HashMap鍵、值都容許爲null,HashTable鍵、值都不容許有null。
五、HashTable使用Enumeration,HashMap使用Iterator。
這些就是一些比較突出的不一樣點,實際上他們在實現的過程當中會有不少的不一樣,如初始化的大小、計算hash值的方式等等。畢竟這兩個類包含了不少方法,有很重要的功能,因此其餘不一樣點,請感興趣的讀者本身去看源碼,去研究。筆者推薦使用HashMap,由於她提供了比HashTable更多的方法,以及較高的效率,若是你們須要在多線程環境中使用,那麼用Collections類來作一下同步便可。
二、Set接口和List接口
相同點:都實現了Collection接口
不一樣點:
一、Set接口不保證維護元素的順序,並且元素不能重複。List接口維護元素的順序,並且元素能夠重複。
二、關於Set元素如何保證元素不重複,我將在下面的博文中給出。
三、ArrayList和LinkList
相同點:都實現了Collection接口
不一樣點:ArrayList基於數組,具備較高的查詢速度,而LinkedList基於雙向循環列表,具備較快的添加或者刪除的速度,兩者的區別,其實就是數組和列表的區別。上文有詳細的分析。
四、SortedSet和SortedMap
兩者都提供了排序的功能。 來看一個小例子:
public static void main(String[] args) { SortedMap<String, Integer> map = new TreeMap<String, Integer>(); map.put("zgg", 1); map.put("erqing", 3); map.put("niu", 0); map.put("abc", 2); map.put("aaa", 5); Set<String> keySet = map.keySet(); for (String string : keySet) { System.out.print(map.get(string)+" "); } }
輸出:5 2 3 0 1
從結果看得出:SortedMap具備自動排序功能
五、TreeMap和HashMap
HashMap具備較高的速度(查詢),TreeMap則提供了按照鍵進行排序的功能。
六、HashSet和LinkedHashSet
HashSet,爲快速查找而設計的Set。存入HashSet的對象必須實現hashCode()和equals()。
LinkedHashSet,具備HashSet的查詢速度,且內部使用鏈表維護元素的順序(插入的次序),因而在使用迭代器遍歷Set時,結果會按元素插入的次序顯示。
七、TreeSet和HashSet
TreeSet: 提供排序功能的Set,底層爲樹結構 。相比較HashSet其查詢速度低,若是隻是進行元素的查詢,咱們通常使用HashSet。
八、ArrayList和Vector
同步性:Vector是線程安全的,也就是說是同步的,而ArrayList是線程序不安全的,不是同步的。
數據增加:當須要增加時,Vector默認增加爲原來一培,而ArrayList倒是原來的一半
九、Collection和Collections
Collection是一系列單值集合類的父接口,提供了基本的一些方法,而Collections則是一系列算法的集合。裏面的屬性和方法基本都是static的,也就是說咱們不須要實例化,直接可使用類名來調用。下面是Collections類的一些功能列表:
生成單元素集合
Collections中的單元素集合指的是集合中只有一個元素並且集合只讀。
Collections.singletonList——用來生成只讀的單一元素的List
Collections.singletonMap——用來生成只讀的單Key和Value組成的Map
Collections.singleton——用來生成只讀的單一元素的Set
以下面的例子:
public static void main(String[] args) { Map<Integer, Integer> map = Collections.singletonMap(1, 1); //map.put(2, 2); ----------1------------- System.out.println(map.size()); }
Collections.singletonMap(1, 1)生成一個單元素的map,若是加上1處的代碼,會報異常。
Checked集合
Checked集合具備檢查插入集合元素類型的特性,例如當咱們設定checkedList中元素的類型是String的時候,若是插入其餘類型的元素就會拋出ClassCastExceptions異常,Collections中提供瞭如下生成Checked集合的方法checkedCollection,checkedList,checkedMap,checkedSet,checkedSortedMap,checkedSortedSet
同步集合
Collections類提供一系列同步方法,爲一些非線程安全的集合類提供同步機制。
查找替換
fill——使用指定元素替換指定列表中的全部元素。
frequency——返回指定 collection 中等於指定對象的元素數。
indexOfSubList—— 返回指定源列表中第一次出現指定目標列表的起始位置,若是沒有出現這樣的列表,則返回 -1。
lastIndexOfSubList——返回指定源列表中最後一次出現指定目標列表的起始位置,若是沒有出現這樣的列表,則返回-1。
max—— 根據元素的天然順序,返回給定 collection 的最大元素。
min——根據元素的天然順序 返回給定 collection 的最小元素。
replaceAll——使用另外一個值替換列表中出現的全部某一指定值。
附一個小例子:
public static void main(String[] args) { List<Integer> list = new ArrayList<Integer>(); list.add(1); list.add(2); for (Integer integer : list) { System.out.println(integer); } /*找出最大值*/ int max = Collections.max(list); System.out.println("最大的爲:"+max); /*用指定元素替換指定list中的元素*/ Collections.fill(list, 6); System.out.println("替換後:"); for (Integer integer : list) { System.out.println(integer); } /*找出某個list裏某個元素的個數*/ int count = Collections.frequency(list, 6); System.out.println("裏面有6的個數:"+count); }
集合排序
Collections還提供了集中對集合進行排序的方法。
reverse——對List中的元素進行轉置
shuffle——對List中的元素隨即排列
sort——對List中的元素排序
swap——交換List中某兩個指定下標位元素在集合中的位置。
rotate——循環移動
public static void main(String[] args) { List<Integer> list = new ArrayList<Integer>(); list.add(5); list.add(2); list.add(1); list.add(9); list.add(0); System.out.println("排序前:"); for (Integer integer : list) { System.out.print(integer+" "); } System.out.println(); /*排序*/ Collections.sort(list); System.out.println("排序後"); for (Integer integer : list) { System.out.print(integer+" "); } }
輸出:
排序前:
5 2 1 9 0
排序後
0 1 2 5 9
下面是關於rotate(List<?> list, int distance)的一個例子:
public static void main(String[] args) { List<Integer> list = new ArrayList<Integer>(); list.add(5); list.add(2); list.add(1); list.add(9); list.add(0); System.out.println("原序列:"); for (Integer integer : list) { System.out.print(integer+" "); } System.out.println(); /*根據步長進行循環*/ Collections.rotate(list, -1); System.out.println("循環後:"); for (Integer integer : list) { System.out.print(integer+" "); } }
讀者能夠屢次換換第二個參數,來觀察它的變化。
總結一下,帶Tree的集合類,底層通常是基於二叉樹的,因此具備自動排序功能。有些功能方面差異不大,具體開發的時候需根據實際狀況選擇使用哪一個類。
本博客持久更新,歡迎你們積極建議、補充!若有轉載,敬請說明出處!
4、常見問題
這塊內容,我講就一些常見的問題作一下分析,歡迎廣大讀者提出更多的問題,咱們一塊兒討論,解決!
一、Set集合如何保證對象不重複
這兒我採用HashSet來實現Set接口,先看個例子:
public static void main(String[] args) { Set<String> set = new HashSet<String>(); String a = "hello"; String b = "hello"; String s = new String("hello"); String s1 = new String("hello"); set.add(a); set.add(s); set.add(s1); set.add(b); System.out.println("size:"+set.size()); for (String ss : set) { System.out.println(ss); } }
輸出:
size:1
hello
說明,Set集合不容許有重複出現的對象,且最終的判斷是根據equals()的。其實原理是這樣的:HashSet的底層採用HashMap來存放數據,HashMap的put()方法是這樣的:
public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode());//----------1---------- int i = indexFor(hash, table.length);//-----------2--------- for (Entry<K,V> e = table[i]; e != null; e = e.next) {//-----------3--------- Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } }//------------------4-------------------- modCount++; addEntry(hash, key, value, i); return null; }
當向HashMap中添加元素的時候,首先計算元素的hashcode值,而後根據1處的代碼計算出Hashcode的值,再根據2處的代碼計算出這個元素的存儲位置,若是這個位置爲空,就將元素添加進去;若是不爲空,則看3-4的代碼,遍歷索引爲i的鏈上的元素,若是key重複,則替換並返回oldValue值。<關於這部份內容,請看另外一篇文章:《深刻解讀HashMap》有詳細介紹>
二、集合類排序問題
一種狀況是集合類自己自帶排序功能,如前面說過的TreeSet、SortedSet、SortedMap等,另外一種就是自己不帶排序功能,咱們經過爲須要排序的類實現Comparable或者Comparator接口來實現。
先來看兩個例子,一個是實現Comparable的,一個是實現Comparator的,爲了方便,我將類都寫在了一個文件中。
package com.xtfggef.list.test; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @SuppressWarnings("unchecked") public class ComparableTest { public static void main(String[] args) { // User[] users = { new User("egg", 23), new User("niuniu", 22), // new User("qing", 28) }; // Arrays.sort(users); // for (User user : users) { // System.out.println(user.getName() + " " + user.getAge()); // } List<User> users = new ArrayList<User>(); users.add(new User("egg", 23)); users.add(new User("niu", 22)); users.add(new User("qing", 28)); Collections.sort(users); for (User user : users) { System.out.println(user.getName() + " " + user.getAge()); } } } @SuppressWarnings("unchecked") class User implements Comparable { private String name; private int age; public User(String name, int age) { super(); this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public int compareTo(Object o) { return this.age - ((User) o).getAge(); } }下面是實現Comparator接口的:
package com.xtfggef.comparator.test; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; public class ComparatorTest { public static void main(String[] args) { List<User> users = new ArrayList<User>(); users.add(new User("egg", 21)); users.add(new User("niu", 22)); users.add(new User("gg", 29)); UserComparator comparator = new UserComparator(); Collections.sort(users, comparator); for (User user : users) { System.out.println(user.getUsername() + " " + user.getAge()); } } } class User { private String username; private int age; public User(String username, int age) { super(); this.username = username; this.age = age; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } class UserComparator implements Comparator<User> { @Override public int compare(User user1, User user2) { int age1 = user1.getAge(); int age2 = user2.getAge(); if (age1 < age2) { return 1; } return 0; } }經過上面的這兩個小例子,咱們能夠看出,Comparator和Comparable用於不一樣的場景,實現對對象的比較從而進行排序。
總結爲:
相同點:
一、兩者均可以實現對象的排序,不論用Arrays的方法仍是用Collections的sort()方法。
不一樣點:
一、實現Comparable接口的類,彷佛是預先知道該類將要進行排序,須要排序的類實現Comparable接口,是一種「靜態綁定排序」。
二、實現Comparator的類不須要,設計者無需事先爲須要排序的類實現任何接口。
三、Comparator接口裏有兩個抽象方法compare()和equals(),而Comparable接口裏只有一個方法:compareTo()。
四、Comparator接口無需改變排序類的內部,也就是說實現算法和數據分離,是一個良好的設計,是一種「動態綁定排序」。
五、Comparator接口可使用多種排序標準,好比升序、降序等。
三、使用for循環刪除元素陷阱
先來看看下面這個程序:
public class Test { public static void main(String[] args) { List<String> list = new LinkedList<String>(); list.add("A"); list.add("B"); list.add("C"); for(int i=0; i<list.size(); i++){ list.remove(i); } for(String item:list){ System.out.println(item); } } }讀者朋友們能夠先猜猜這個程序輸出什麼?按咱們的思路,應該是輸不出什麼,可是執行它,輸出的倒是:B。這是爲何呢?咱們分部分析下這個程序,當地一步remove完後,集合內還剩2個元素,此時i爲1,而list.size()的值爲2,從0開始的話,i爲1時,正好指向第二個元素,也就是說當remove完A後,直接就跳到C,將B漏了。
解決辦法: