1. 程式人生 > >JAVA學習記錄(一)————JAVA中的集合類

JAVA學習記錄(一)————JAVA中的集合類

數組 節點 唯一性 list 接口規範 hashcode 工具類 collect 上進

技術分享圖片

技術分享圖片

這個圖是總體的框架圖,主要是兩個接口Collection和Map都繼承接口Iterator(Iterable),為了實現可以使用叠代器。Collection和Map類似平級關系。
1、這裏我先學習下ArrayList和LinkedList:
ArrayList先從源碼的註釋看起,講述ArrayList的特性;
》允許添加的元素為NULL
》size、get、set、isEmpty、iterator、listIterator操作都是常量時間,add是均攤時間,其他的操作都是線性時間,但是常數因子是小LlinkedList
》非同步,如果多個線程讀取,至少一個線程進行結構上的修改(add、remove、顯式修改數組大小),需要同步,可以使用Collections.synchronizedList(list)把其包裝為同步對象。

》快速失敗:如果使用叠代器列表後(Iterator iteratoList=arrayList.iterator();)這一步初始化創建new Itr時,就已經把賦值expectModCount=modCount,如果在遍歷結束之前,對原始list進行任何結構上的修改,都將會拋出ConcurrentModificationException異常,這是因為使用next時,都會先檢查(checkForComodification()檢查modCount記錄結構別修改的次數是否與叠代前的期望一樣)是否已經發生結構上的修改。(除非使用iterator或者listiterator自帶方法add和remove因為自帶方法實現了expectedModCount = ArrayList.this.modCount;兩者值相等)。但這個特性只能用於測試程序中的bug,而不能驗證程序的正確性。這個值沒有進行同步或可見性操作因此也是不一定可靠:修改後也可能沒有即時發現,真正防止修改應該是對那些操作加鎖。
下面進入源碼:
ArrayList繼承了抽象類AbstractList,實現了接口list、cloneable(通過源碼發現,使用的應該是淺克隆,如果數據元素是對象,則並沒有進行clone)等。
ArrayList底層實現是一個object數組,當創建一個對象時,如果沒有指定大小,那麽默認數組大小為0(指向空數組),當添加一個元素後,使用默認值10來作為初始大小。
容量增長最終是通過grow()方法來增長,在1.7中是newCapacity = oldCapacity + (oldCapacity >> 1);(8+4;9+4 )即小於等於1.5倍.
通過兩個內部類Itr、ListItr來實現叠代。
LinkedList先從源碼的註釋看起,講述LinkedList的特性;
》繼承了抽象類AbstractSequenceList,實現了接口List、Deque、Cloneable(淺克隆)等。允許添加元素為NULL
》雙向鏈表操作,插入特定下標位置元素,需要從頭或者尾部開始遍歷。
》非同步的,如果是多個線程訪問和至少一個線程進行結構上修改,則需要同步。同步最好在創建的時候就進行同步,比如Collections.synchronizedList(new LinkedList())包裝成同步對象。
》同樣在使用iterator和listinterator方法的時候,如果已創建了叠代對象,指向原來的對象且值expectedModCount值被賦予LinkedList域modCount,那麽在叠代結束期間如果有線程對LinkedList對象結構上進行了修改,那麽也會拋出ConcurrentModificationException異常,即即時失敗。
下面進入源碼:
繼承了抽象類AbstractSequenceList,實現了接口List、Deque、Cloneable(淺克隆)等。
底層實現是通過內部Node類來實現雙向鏈表結構,first指向鏈表頭節點,last指向鏈表尾節點。
同樣是通過內部類ListItr繼承接口Iterator來實現叠代功能。
可以被用作棧或者隊列
ArrayList與LinkedList的相同與區別:
相同點:
都是線程不安全的、繼承了接口list、serializable、cloneable接口,可以進行叠代訪問,同時支持快速失敗。
區別:
它們之間的區別主要是它們的底層實現原理不同造成的:
AllayList因為底層是數組來實現的,因此在查找元素的時候,是十分快速的,當然這個優點也變成了其缺點,當要插入(刪除)元素的時候,就需要移動後面的元素,同時當插入數量查過現有的容量時會以接近1.5倍的因子擴容,同時要復制數組,因此開銷比較大。如果沒有指定大小,默認數組大小為10。
LinkedList因為底層實現是利用雙向鏈表來實現,所以對於查找某個元素,是從就近的頭或者尾節點開始遍歷,相對來說,查找速率比較慢,但是對於插入和刪除元素的時候,由於不需要移動元素,只需要修改指針,因此速度比較快。由於使用鏈表,默認為空,只有添加元素才會出現元素節點。
2、學習下HashSet和TreeSet:
從HashSet源碼的註釋看起,講述HashSet的特性:
》實現了Set接口,底層是實現是HashMap,不保證有序,且添加元素可以為NULL
》基礎操作add、remove、size、contains常量時間,叠代時間是HashSet的size+HashMap的capacity(因此初始化時capacity不能太大或太小)。
》非同步的,如果多個線程並發訪問,至少一個線程進行修改,則必須進行同步:Collections.synchronizedSet(new HashSet());
》同樣支持快速失敗,性質與上面集合一樣。
下面進入源碼
HashSet繼承了AbstractSet,實現了接口Set、Cloneable(淺克隆)等,內部是以HashMap作為底層存儲結構,因此很多方法都是調用map的方法。
HashSet默認無參構造方法調用HashMap的構造方法(初始大小16,參數因子是0.75),HashSet的值是作為底層HashMap的Key,因此這也是不允許重復的,同時value是一個空的Object對象。如果使用add添加重復數據是會返回false(失敗)。而它也是無序(準確說不保證順序),位置可能會變或者根據不能預測位置。
從TreeSet的源碼的註釋看起,講述TreeSet的特性:
》實現了基於TreeMap的NavigableSet接口,元素的排序是根據他們的自然排序或者實現的比較器接口進行排序。
》add、remove、contains方法操作時間是logn
》由於Set接口規範是按照equals方法來比較的,如果按照Set接口規範,就要與equals方法一致。但TreeSet實例使用它的compareTo方法執行所有元素比較,所以如果比較的方法實現中出現了相等就認為相等(比如兩個不同對象只有屬性年齡一樣,如果比較方法是按照年齡來比較,那麽相等就會返回0)。按照Set的方法相等就不能插入,所以要修改,比如相等就返回1,而不是0。
》非同步,並發訪問修改可以使用包裝:Collections.synchronizedSortedSet(new TreeSet())
》快速失敗,一其他集合一樣特性。
下面進入源碼
TreeSet繼承了AbstractSet,實現了接口NavigableSet(並沒有直接實現Set),Cloneable(淺克隆)
TreeSet的底層實現是treeMap,方法跟多也是直接調用原本方法,同樣是使用Map所以也是值作為map的key來使用,而value是一個占位的object對象。TreeSet是SortedSet接口的唯一實現類,TreeSet可以確保集合元素處於排序狀態。
TreeSet判斷兩個對象不相等的方式是兩個對象通過equals方法返回false,或者通過CompareTo方法比較沒有返回0。
如果我們將兩個對象的equals方法總是返回true,則這兩個對象的compareTo方法返回應該返回0。插入的元素不能是NULL,否則無法比較,拋出空指針異常。
相同點:
元素不可重復,非同步,但都可以使用類庫包裝成線程安全類。都支持快速失敗。
不同點:
底層數據結構不一樣:HashSet底層結構是HashMap,TreeSet底層實現是treeMap
add、remove、contains操作中HashSet時間復雜度為常量,而TreeSet需要logn
HashSet的數據是無序的,TreeSet中的數據是排好序的
保證唯一性不同:HashSet的唯一性是通過HashCode和equals方法來保證;Treeset則是通過實現comparable或者利用comparator來比較,返回0則表示相等(則屬於向重復元素,適當的修改可以實現插入重復元素)
HashSet可以插入一個NULL,但TreeSet不允許
根據特性可以看出如果需要排好序的結構,且不是很頻繁變化的可以使用TreeSet。但最常用的就是hashSet,通常性能優於TreeSet。
3、學習下HashMap與TreeMap
HashMap從源碼註釋看起,講述HashMap的特性
》基於Hash Table實現,允許key和value值為NULL。與Hashtable大致上相等,除了在非同步和允許為null。不保證有序,隨著時間變化,不保證順序不會變
》基本操作get和put時間為常量時間,叠代遍歷集合需要花費代價:Map的capacity加上map的size
》HashMap主要有兩個重要參數:capacity和load factor。capacity是HashMap的創建時桶的數量,load factor是權重因子,當前容量乘以權重因子值小於需要擴充的數量時,進行擴容,並且是原來的容量的兩倍。
》load factor默認值是0.75,這是一個在時間和空間的平衡,如果值過高,則會造成時間上浪費,如果值過低,則會造成重新進行hash計算分配造成時間上的開銷。
》如果有很多元素需要放進去,如果是按照系統自動增長的來擴容,則不如一開始創建一個滿足的容量來進行放入原來效率好
》非同步,如果有多個線程進行訪問和至少一個線程進行修改,則需要進行同步,可以使用類庫工具類來進行包裝:Collections.synchronizedMap(new HashMap())
》使用Iterator進行叠代時,支持快速失敗,當遍歷對象一旦被創建,如果期間有結構上的修改則會拋出異常:ConcurrentModificationException,但如果使用Iterator自帶的方法remove的方法除外
》快速失敗只能被用於來發現bug,不能被用於正確的程序
下面進入源碼
HashMap繼承了抽象類AbstractMap,實現了Map接口和cloneable(淺克隆)接口等。
默認初始容量是16,權重因子是0.75,內部元素是內部類Entry數組,Entry包含K、V、hash值和Next對象指針。
當key值為字符串,並且hashSeed不為0時(默認為0),會調用sun.misc.Hashing.stringHash32((String) k)來計算hash值,否則則會調用對象自己的hashcode方法基礎上進行hash計算,key如果為NULL,則hash值永遠為0。
容量必須是2的n次方,
get方法如果參數key為NULL,則會進行專門查找(hash值為0;table[0]),返回NULL有兩種情況,要麽值為NULL,要麽HashMap的size為0;
put方法如果傳入的參數key已經存在,那麽再次放入將會把原來的值給替換,同時返回原來的value。如果不存在則會返回NULL(或者key不存在或者key對應的原來的值為NULL)
如果如果擴容,則最終通過transfer來進行擴容,重新計算hash值,並進行分配位置。
TreeMap從源碼註釋看起,講述TreeMap的特性:
》TreeMap是基於紅黑樹的算法實現,並且排序標準是根據comparable(實現改接口的方法compareTo(參數為1個))或comparator(實現方法compare(兩個比較的參數))的接口實現來進行排序具體使用詳情參考。
》該集合可以實現在logn的時間裏完成containsKey、put、remove、get操作
》集合的順序是依靠tree map來進行維護的,不論是否顯示實現比較接口,如果要正確實現Map接口,那麽就需要保持比較器實現與equlas方法一致,這是因為Map是依靠equals方法來實現比較相等,而sorted map判斷比較則是根據compareTo或者compare方法來實現的,sorted map這種實現對於排序Map來說是很符合的規範,只不過是違背了Map的規範
》非同步的,如果多個線程都並發訪問和至少一個線程進行結構上的修改,則都需要進行額外的同步,同步可以通過類庫進行包裝:Collections.synchronizedSortedMap(new TreeMap())
》快速失敗 當進行叠代的時候,一旦叠代對象(復制一份)被創建,在叠代結束期間,如果集合有任何結構上的修改,都會對拋出異常ConcurrentModificationException,除了使用iterator自帶的remove方法。
》同樣快速失敗只能用於去尋找bug,並不能用於真正的正確代碼。
》Map.Entry返回的都是當前時間的快照,不能使用Map.setValue來進行改變映射,但可以使用put進行改變映射。
下面進入源碼
TreeMap繼承了抽象類AbstractMap,實現了接口NavigableMap、cloneable(淺克隆)等。
內部有一個比較器comparator,如果參數有則可以直接用,沒有則用戶實現comparable接口也可以。
內部通過實現類Entry(實現接口Map.Entry)來來作為樹的節點包含K key; V value;Entry
---------------------
作者:深山裏的天空
來源:CSDN
原文:https://blog.csdn.net/qq_26564827/article/details/80853769

JAVA學習記錄(一)————JAVA中的集合類