現實生活中:不少相同事物湊在一塊兒,好比人羣
數學中的集合:具備共同屬性的事物的整體
java中的集合框架:是一種工具類,就像是容器,儲存任意數量的具備共同屬性的對象html
其實說白了,能夠把一個集合當作一個微型數據庫,操做不外乎「增刪改查」四種操做,咱們在學習使用一個具體的集合類時,須要把這四個操做的時空複雜度弄清楚了,基本上就能夠說掌握這個類了。java
在集合框架的類繼承體系中,最頂層有兩個接口:node
通常繼承自Collection或Map的集合類,會提供兩個「標準」的構造函數:面試
由於接口中不能包含構造函數,因此上面這兩個構造函數的約定並非強制性的,可是在目前的集合框架中,全部繼承自Collection或Map的子類都遵循這一約定。算法
如上圖所示,Collection類主要有三個接口:數據庫
Collection接口部分源碼數組
public interface Collection<E> extends Iterable<E> { int size(); boolean isEmpty(); boolean contains(Object o); boolean add(E e); boolean remove(Object o); boolean containsAll(Collection<?> c); boolean addAll(Collection<? extends E> c); boolean removeAll(Collection<?> c); void clear(); Iterator<E> iterator(); Object[] toArray(); }
以上是Collection
接口的經常使用方法,由於3個子接口都繼承了這個接口,所以在它們各自特有方法外,都會實現以上方法。安全
Java 中有四種常見的Map實現——HashMap, TreeMap, Hashtable和LinkedHashMap:session
說到Map接口的話你們也許在熟悉不過了。Map接口實現的是一組Key-Value的鍵值對的組合。 Map中的每一個成員方法由一個關鍵字(key)和一個值(value)構成。Map接口不直接繼承於Collection接口(須要注意啦),由於它包裝的是一組成對的「鍵-值」對象的集合,並且在Map接口的集合中也不能有重複的key出現,由於每一個鍵只能與一個成員元素相對應。多線程
另外,Set接口的底層是基於Map接口實現的。Set中存儲的值,其實就是Map中的key,它們都是不容許重複的。
Map接口部分源碼
public interface Map<K,V> { int size(); boolean isEmpty(); boolean containsKey(Object key); boolean containsValue(Object value); V get(Object key); V put(K key, V value); void putAll(Map<? extends K, ? extends V> m); V remove(Object key); void clear(); Collection<V> values(); Set<K> keySet(); Set<Map.Entry<K, V>> entrySet(); interface Entry<K,V> { K getKey(); V getValue(); V setValue(V value); } }
源碼中的方法你們也比較熟悉。在遍歷Map時,咱們能夠經過keySet()
方法獲取到全部key,它返回的是一個Set對象,遍歷Set,經過key獲取value。也能夠經過entrySet()
的方式獲取Entry的Set,遍歷Set,經過Entry的getValue()
和getKey()
方法獲取值和對象,這些在後面會詳細講到。
List接口對Collection進行了簡單的擴充,所以它繼承了Collection接口。其中大部分方法和其繼承的Collection相同,至於不一樣之處也不太經常使用,你們能夠參考源碼。
特色:
List中存儲的元素是有序的,並且能夠重複的存儲相關元素。
特色:
ArrayList的底層使用數組實現,當元素的數量超過數組長度時,經過新建更大容量數組,將原數組內容拷貝一份,而後新增元素的方式存儲元素。
優勢:
相似數組的形式進行存儲,所以它的隨機訪問速度極快。
缺點:
不適合於在線性表中間須要頻繁進行插入和刪除操做。由於每次插入和刪除都須要移動數組中的元素。
能夠這樣理解ArrayList就是基於數組的一個線性表,只不過數組的長度能夠動態改變而已。
對於ArrayList的詳細使用信息以及建立的過程能夠查看jdk中ArrayList的源碼,這裏不作過多的講解。
對於使用ArrayList的開發者而言,下面幾點內容必定要注意啦,尤爲找工做面試的時候常常會被問到。做者去年面試的時候,都已經被問煩了
注意啦!!!!!!!!
一、關於擴容問題:
默認ArrayListde的默認構造函數ArrayList()
會構造一個長度爲10的數組。
ArrayList(int initialCapacity)
構造函數會構造一個指定長度的數組。
添加元素時,若是超出了長度,則以每次舊長度的3/2倍增加。
例:
new ArrayList(20);
擴容幾回?
答案: 0次,由於直接產生了一個長度20的數組
二、ArrayList是線程不安全的,在多線程的狀況下不要使用。
若是必定在多線程使用List的,你可使用Vector,由於Vector和ArrayList基本一致,區別在於Vector中的絕大部分方法都使用了同步關鍵字修飾,這樣在多線程的狀況下不會出現併發錯誤哦,還有就是它們的擴容方案不一樣,ArrayList是經過原始容量*3/2,而Vector是容許設置默認的增加長度,Vector的默認擴容方式爲原來的2倍(能夠經過構造函數設置,如設置爲2,擴容後長度爲舊長度+2)。
切記Vector是ArrayList的多線程的一個替代品。
三、ArrayList實現遍歷的幾種方法
ArrayList<String> list = new ArrayList(); list.add("hello"); list.add(","); list.add("world"); // 第一種遍歷方式使用foreach遍歷List,編譯器編譯時,會將這種方式轉化爲迭代器方式 for (String str : list) { System.out.print(str); } System.out.println(); // 第二種遍歷方式使用for循環依次獲得元素 for (int i = 0; i < list.size(); i++) { System.out.print(list.get(i)); } System.out.println(); // 第三種遍歷方式使用迭代器遍歷 Iterator<String> iterator = list.iterator(); while(iterator.hasNext()) { System.out.print(iterator.next()); } System.out.println();
特色:
LinkedList的底層用雙向鏈表實現。另外建議閱讀其源碼,並不難,會讓你有醍醐灌頂的感受。
優勢:
鏈表相對於實現ArrayList的數組來講,其存儲空間是散列的而不是連續的,所以在鏈表中間插入和刪除元素時,無需移動後面的元素,只須要改變3個節點的關聯便可。
缺點:
由於LinkedList不是空間連續的,所以隨機讀取時,須要從頭至尾的讀取,所以不如ArrayList來得快。另外,在使用雙向鏈表實現時,須要額外提供空間供記錄前驅節點和後繼節點的地址,消耗了額外空間。
對於使用LinkedList而言,下面幾點內容必定要注意啦
注意啦!!!!!!!!
一、LinkedList和ArrayList的區別和聯繫
主要從底層實現、優缺點(隨機讀取、新增、刪除)等方面總結,詳見以前總結的特色、優缺點,再也不贅述。
二、LinkedList的內部實現
強烈建議你們去看看源碼,其內部使用雙鏈表實現,若是你實在不想看,下面的代碼提供了其節點結構,和最經常使用的add()
和remove()
方法。
// 長度 transient int size = 0; // 節點的表示 private static class Node<E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } } // 此段不是源碼,僅供理解思想 private void add(Node<E> node, E data) { Node<E> newNode = new Node<E>(node.prev, data, node); newNode.prev.next = newNode; node.prev = newNode; size++; } // 此段不是源碼,僅供理解思想 private E remove(Node<E> node) { node.next.prev = node.prev; node.prev.next = node.next; size--; return node.item; }
三、LinkedList不是線程安全的
注意LinkedList和ArrayList同樣也不是線程安全的,若是在對線程下面訪問能夠本身重寫LinkedList
而後在須要同步的方法上面加上同步關鍵字synchronized
四、LinkedList的遍歷方法
同ArrayList
LinkedList<String> list = new LinkedList(); list.add("hello"); list.add(","); list.add("world"); // 第一種遍歷方式使用foreach遍歷List for (String str : list) { System.out.print(str); } System.out.println(); // 第二種遍歷方式使用for循環依次獲得元素,這種方式用到了隨機讀取,特別特別不推薦 for (int i = 0; i < list.size(); i++) { System.out.print(list.get(i)); } System.out.println(); // 第三種遍歷方式使用迭代器遍歷 Iterator<String> iterator = list.iterator(); while(iterator.hasNext()) { System.out.print(iterator.next()); } System.out.println(); }
五、LinkedList能夠被當作堆棧來使用
因爲LinkedList實現了接口Dueue,因此LinkedList能夠被當作堆棧來使用,這個你本身研究吧。
Vector和ArrayList不論在實現,仍是使用上,都大同小異。所以也就不細說,他們主要的不一樣就是Vector是線程安全的,它在一些方法上加了synchronized
關鍵字。
劃重點了!!!!!!!!
一、Arraylist與Vector的區別
參見 https://zhuanlan.zhihu.com/p/28241176
Set接口也是Collection接口的一個經常使用子接口,它區別於List接口的特色在於:
Set中的元素實現了不重複,有點象集合的概念,無序,不容許有重複的元素,最多容許有一個null元素對象。
須要注意的是:雖然Set中元素沒有順序,可是元素在set中的位置是有由該元素的HashCode決定的,其具體位置實際上是固定的。
此外須要說明一點,在set接口中的不重複是由特殊要求的。
舉一個例子:對象A和對象B,原本是不一樣的兩個對象,正常狀況下它們是可以放入到Set裏面的
可是
若是對象A和B的都重寫了hashcode和equals方法,而且重寫後的hashcode和equals方法是相同的話。那麼A和B是不能同時放入到Set集合中去的
也就是Set集合中的去重和hashcode與equals方法直接相關。
Set接口的常見實現類有HashSet,LinedHashSet和TreeSet這三個,如今依次介紹這三個類:
HashSet是Set接口的最多見的實現類了。其最底層是經過Hash表(一個元素爲鏈表的數組)實現的。另外,Hash表底層依賴的兩個方法hashcode與equals方法,想存入HashSet的元素須要複寫這兩個方法。
爲何說其最底層呢?請先看下這段源碼:
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { private transient HashMap<E,Object> map; public HashSet() { map = new HashMap<>(); } }
顯而易見,HashSet的內部是基於HashMap實現的,咱們都知道在HashMap中的key是不容許重複的,你換個角度看看,那不就是說Set集合嗎?
咱們只須要用一個固定值值代替Map中的value,
private static final Object PRESENT = new Object(); public boolean add(E e) { return map.put(e, PRESENT)==null; }
jdk中用了一個靜態的Object對象代替了value。那Set中的元素作key即可以保證元素不重複。
下面講解一下HashSet使用和理解中容易出現的誤區:
一、HashSet中存放null值
HashSet中時容許出入null值的,可是在HashSet中僅僅可以存入一個null值哦。
二、 HashSet中存儲元素的位置是固定的
HashSet中存儲的元素的是無序的,這個沒什麼好說的,可是因爲HashSet底層是基於Hash算法實現的,使用了hashcode,因此HashSet中相應的元素的位置是固定的哦。
三、 遍歷HashSet的幾種方法
HashSet<String> hashSet = new HashSet<String>(); hashSet.add("hello,"); hashSet.add("hello,"); hashSet.add("world"); // 第一種遍歷方式,使用foreach遍歷 for (String str : hashSet) { System.out.print(str); } System.out.println(); // 第二種遍歷方式,使用迭代器遍歷 Iterator<String> iterator = hashSet.iterator(); while (iterator.hasNext()) { System.out.print(iterator.next()); }
LinkedHashSet不只是Set接口的子接口並且仍是上面HashSet接口的子接口,和HashSet由HashMap實現同樣,LinkedHashSet的底部由LinkedHashMap實現。
Set<String> set = new HashSet<String>(); set.add("hello"); set.add("world"); for (String str : set) { System.out.print(str); } System.out.println(); set = new LinkedHashSet<String>(); set.add("hello"); set.add("world"); for (String str : set) { System.out.print(str); }
上面的程序輸出結果以下:
可見,LinkedHashSet集合一樣是根據元素的hashCode值來決定元素的存儲位置,可是它同時使用鏈表維護元素的次序。這樣使得元素看起來像是以插入順序保存的,也就是說,當遍歷該集合時候,LinkedHashSet將會以元素的添加順序訪問集合的元素。
由於它底層由LinkedHashMap實現,因此更多細節參加LinkedHashMap。
TreeSet是SortedSet接口的惟一實現類,TreeSet能夠確保集合元素處於排序狀態。
TreeSet支持兩種排序方式,天然排序 和定製排序,其中天然排序爲默認的排序方式。
向TreeSet中加入的應該是同一個類的對象。TreeSet判斷兩個對象不相等的方式是兩個對象經過equals方法返回false,或者經過CompareTo方法比較沒有返回0
天然排序
天然排序使用要排序元素的CompareTo(Object obj)
方法來比較元素之間大小關係,而後將元素按照升序排列。
Java提供了一個Comparable接口,該接口裏定義了一個compareTo(Object obj)方法,該方法返回一個整數值,實現了該接口的對象就能夠比較大小。obj1.compareTo(obj2)方法若是返回0,則說明被比較的兩個對象相等,若是返回一個正數,則代表obj1大於obj2,若是是 負數,則代表obj1小於obj2。
定製排序
天然排序是根據集合元素的大小,以升序排列,若是要定製排序,應該使用Comparator接口,實現 int compare(T o1,T o2)方法
因爲水平和時間有限,Map相關內容暫不總結
https://www.cnblogs.com/xiohao/p/4309462.html
https://zhuanlan.zhihu.com/p/24338517?utm_source=wechat_session&utm_medium=social