java基礎-StringBuild、StringBuffer,集合List、Map、Set

desaco發表於2016-02-22

Java集合架構詳解http://blog.csdn.net/qq_35101189/article/details/55000689

> StringBuild、StringBuffer區別

String中的物件是不可變的,也就可以理解為常量,顯然執行緒安全。
  AbstractStringBuilder是StringBuilder與StringBuffer的公共父類,定義了一些字串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。
StringBuffer對方法加了同步鎖或者對呼叫的方法加了同步鎖,所以是執行緒安全的
String可以儲存和操作字串,即包含多個字元的字元資料。這個String類提供了儲存數值不可改變的字串。
StringBuilder是執行緒不安全的,執行效率高,如果一個字串變數是在方法裡面定義,這種情況只可能有一個執行緒訪問它,不存在不安全的因素了,則用StringBuilder。如果要在類裡面定義成員變數,並且這個類的例項物件會在多執行緒環境下使用或者變數的內容不斷變化,那麼最好用StringBuffer。
      StringBuilder與StringBuffer有公共父類AbstractStringBuilder(抽象類)。
  抽象類與介面的其中一個區別是:抽象類中可以定義一些子類的公共方法,子類只需要增加新的功能,不需要重複寫已經存在的方法;而介面中只是對方法的申明和常量的定義。
  StringBuilder、StringBuffer的方法都會呼叫AbstractStringBuilder中的公共方法,如super.append(...)。只是StringBuffer會在方法上加synchronized關鍵字,進行同步。
  最後,如果程式不是多執行緒的,那麼使用StringBuilder效率高於StringBuffer。

-- StringBuffer初始化及擴容機制
 1.StringBuffer()的初始容量可以容納16個字元,當該物件的實體存放的字元的長度大於16時,實體容量就自動增加。StringBuffer物件可以通過length()方法獲取實體中存放的字元序列長度,通過capacity()方法來獲取當前實體的實際容量。
 2.StringBuffer(int size)可以指定分配給該物件的實體的初始容量引數為引數size指定的字元個數。當該物件的實體存放的字元序列的長度大於size個字元時,實體的容量就自動的增加。以便存放所增加的字元。
 3.StringBuffer(String s)可以指定給物件的實體的初始容量為引數字串s的長度額外再加16個字元。當該物件的實體存放的字元序列長度大於size個字元時,實體的容量自動的增加,以便存放所增加的字元。

  String類、以及value都是final型別的,這樣就表明String是無法被繼承的,value是無法被改寫的。當通過String的建構函式初始化新的String物件時,也只是根據傳入的引用物件的value和hashcode進行了賦值。
  對於相同的字串“abc”的引用都是相同的(對於常量池中的相同位置),這樣能夠節省記憶體空間,但是缺點就是對於頻繁的字串拼接操作,會造成記憶體空間的浪費。(需要注意的是這種字串的拼接操作,從JDK8 開始,會自動被編譯成StringBuilder,是不是很666^_^,但還是建議不通過JDK途徑去自動轉。
  StringBuilder的value是個char陣列,(當然從JDK9開始,value從char陣列變成了byte陣列)。每次append時都是通過呼叫native的System.arraycopy實現的(在getChars中呼叫的)。
  和StringBuilder一樣,都是用了char陣列儲存value,append也是呼叫了AbstractStringBuilder的append方法。區別只是在於char陣列加了transient關鍵字,以及方法上加了synchronized方法。

 String、StringBuilder、StringBuffer的使用場景如下:
 當處理定長字串時,建議用String;
 當處理變長字串時,並且是單執行緒環境時,建議用StringBuilder;
 當處理變長字串時,並且是多執行緒環境時,建議用StringBuffer。

List:LinkedList, ArrayList, Vector stack;
Set:HashSet 和 TreeSet
Map: HashMap, HashTable, WeakHashMap TreeMap

> List、Map、Set的區別

Collection中最常用的又分為兩種型別的介面:List和Set,兩者最明顯的差別為List支援放入重複的元素,而Set不支援。
   List最常用的實現類有:ArrayList、LinkedList、Vector及Stack;Set介面常用的實現類有:HashSet、TreeSet。

List,Set,Map是否繼承自Collection介面? 
答:List,Set是,Map不是。 Collection是最基本的集合介面,一個Collection代表一組Object,即Collection的元素。一些Collection允許相同的元素而另一些不行。一些能排序而另一些不行。Java JDK不能提供直接繼承自Collection的類,Java JDK提供的類都是繼承自Collection的"子介面",如:List和Set。 
  注意:Map沒有繼承Collection介面,Map提供key到value的對映。一個Map中不能包含相同key,每個key只能對映一個value。Map介面提供3種集合的檢視,Map的內容可以被當做一組key集合,一組value集合,或者一組key-value對映。 
  List按物件進入的順序儲存物件,不做排序或編輯操作。Set對每個物件只接受一次,並使用自己內部的排序方法(通常,你只關心某個元素是否屬於Set,而不關心它的順序--否則應該使用List)。Map同樣對每個元素儲存一份,但這是基於"鍵"的,Map也有內建的排序,因而不關心元素新增的順序。如果新增元素的順序對你很重要,應該使用 LinkedHashSet或者LinkedHashMap.
詳細介紹: 
  List特點:元素有放入順序,元素可重複 
  Map特點:元素按鍵值對儲存,無放入順序 
  Set特點:元素無放入順序,元素不可重複(注意:元素雖然無放入順序,但是元素在set中的位置是有該元素的HashCode決定的,其位置其實是固定的) 

 >> List介面有三個實現類:LinkedList,ArrayList,Vector 

Vector每次請求其大小的雙倍空間,而ArrayList每次對size增長50%。HashMap() 構造一個具有預設初始容量 (16) 和預設載入因子 (0.75) 的空 HashMap。

LinkedList:底層基於連結串列實現,連結串列記憶體是散亂的,每一個元素儲存本身記憶體地址的同時還儲存下一個元素的地址。連結串列增刪快,查詢慢 
ArrayList和Vector的區別:ArrayList是非執行緒安全的,效率高;Vector是基於執行緒安全的,效率低 .ArrayList底層是基於陣列的實現。
 >> Set介面有兩個實現類:HashSet(底層由HashMap實現),LinkedHashSet 
SortedSet介面有一個實現類:TreeSet(底層由平衡二叉樹實現) 
Query介面有一個實現類:LinkList 

 >> Map介面有三個實現類:HashMap,HashTable,LinkeHashMap 

JDK 1.7 HashMap資料結構=陣列+連結串列;而JDK 1.8 HashMap資料結構=陣列+連結串列+紅黑樹。

  HashMap非執行緒安全,高效,支援null;底層是基於陣列+連結串列的形式實現的。

  HashMap多執行緒下發生死迴圈的原因-http://blog.csdn.net/linsongbin1/article/details/54708694

  HashTable執行緒安全,低效,不支援null 

Java8之HashMap原始碼分析- https://mp.weixin.qq.com/s/J0EBI6y7oz_sRTIT8XTVPQ?ref=myread
HashMap的資料結構(陣列+連結串列+紅黑樹),桶中的結構可能是連結串列,也可能是紅黑樹,紅黑樹的引入是為了提高效率。

-- 如何保證HashMap的執行緒安全?
Java HashMap 是非執行緒安全的。在多執行緒條件下,容易導致死迴圈,具體表現為CPU使用率100%。因此多執行緒環境下保證 HashMap 的執行緒安全性,主要有如下幾種方法:
使用 java.util.Hashtable 類,此類是執行緒安全的。
使用 java.util.concurrent.ConcurrentHashMap,此類是執行緒安全的。
使用 java.util.Collections.synchronizedMap() 方法包裝 HashMap object,得到執行緒安全的Map,並在此Map上進行操作。

  -- SortedMap有一個實現類:TreeMap 
其實最主要的是,list是用來處理序列的,而set是用來處理集的。Map是知道的,儲存的是鍵值對 

set 一般無序不重複.map k-v 結構 list 有序 。

-  HashMap是陣列+連結串列+紅黑樹(JDK1.8增加了紅黑樹部分)實現的,連結串列長度大於8轉紅黑數。

 HashMap Hash衝突後怎麼解決

  衝突後resize,那麼HashMap什麼時候進行擴容呢?當HashMap中的元素個數超過陣列大小*loadFactor時,就會進行陣列擴容,loadFactor的預設值為0.75,這是一個折中的取值。也就是說,預設情況下,陣列大小為16,那麼當HashMap中元素個數超過16*0.75=12的時候,就把陣列的大小擴充套件為 2*16=32,即擴大一倍,然後重新計算每個元素在陣列中的位置,擴容是需要進行陣列複製的,複製陣列是非常消耗效能的操作,所以如果我們已經預知HashMap中元素的個數,那麼預設元素的個數能夠有效的提高HashMap的效能。   java.util.HashMap採用的連結串列法的方式,連結串列是單向連結串列。連結串列法就是將相同hash值的物件組織成一個連結串列放在hash值對應的槽位。

> 對hashmap與hashcode()、equals()的理解
HashMap實現原理分析(面試問題:兩個hashcode相同的物件怎麼存入hashmap)-https://blog.csdn.net/weixin_37864013/article/details/77428919
hashMap的hashCode() 和equal()的使用- https://www.cnblogs.com/xll1025/p/6420925.html
public final native Class<?> getClass(); 
public native int hashCode(); 
public boolean equals(Object obj) {   return (this == obj); }  
public String toString() {  return getClass().getName() + "@" +  Integer.toHexString(hashCode()); }

1.equals方法沒被重寫的時候   比較的只是物件的地址  重寫之後 比較的才是物件裡的內容
2.重寫equals的時候 務必需要重寫hashcode 不然在用到容器的時候 會出現問題 因為容器會去判斷新加入的物件的hashcode 在集合中是否存在 再去判斷物件的內容
3.hashmap的理解
hashmap其實就是陣列+連結串列   這裡所謂的連結串列 無非就是 在hashmap裡定義了一個靜態Node類 這個類有Node next這個引用 可以指向當前下一個在當前索引下標下的Node節點
進行put的時候 會根據傳入的key進行hash(key.hashcode())  然後算出索引 去陣列裡找
 a.如果沒找到下標 那麼直接addentry()
 b.存在下標的話(其實就是連結串列的第一個元素),判斷是否存在相同的key 相同那麼就覆蓋原來的value,不同就是直接放在連結串列的第一個,為什麼放在第一個,那是因為定義的Node節點,屬就是Node next
 c.同時有兩個執行緒put的時候 一旦超出陣列長度  會進行resize雙倍擴容 此時存在對table這個公共變數資源 進行競爭  所以存在多執行緒安全問題  
 所以在判斷高併發 高訪問的時候 可以考慮用concurrenthashmap

HashMap中的比較key是這樣的,先求出key的hashcode(),比較其值是否相等,若相等再比較equals(),若相等則認為他們是相等的。若equals()不相等則認為他們不相等。如果只重寫hashcode()不重寫equals()方法,當比較equals()時只是看他們是否為同一物件(即進行記憶體地址的比較),所以必定要兩個方法一起重寫。HashMap用來判斷key是否相等的方法,其實是呼叫了HashSet判斷加入元素是否相等。

  在Java中也一樣,hashCode方法的主要作用是為了配合基於雜湊的集合一起正常執行,這樣的雜湊集合包括HashSet、HashMap以及HashTable。
  也許大多數人都會想到呼叫equals方法來逐個進行比較,這個方法確實可行。但是如果集合中已經存在一萬條資料或者更多的資料,如果採用equals方法去逐一比較,效率必然是一個問題。此時hashCode方法的作用就體現出來了,當集合要新增新的物件時,先呼叫這個物件的hashCode方法,得到對應的hashcode值,實際上在HashMap的具體實現中會用一個table儲存已經存進去的物件的hashcode值,如果table中沒有該hashcode值,它就可以直接存進去,不用再進行任何比較了;如果存在該hashcode值, 就呼叫它的equals方法與新元素進行比較,相同的話就不存了,不相同就雜湊其它的地址,所以這裡存在一個衝突解決的問題,這樣一來實際呼叫equals方法的次數就大大降低了,說通俗一點:Java中的hashCode方法就是根據一定的規則將與物件相關的資訊(比如物件的儲存地址,物件的欄位等)對映成一個數值,這個數值稱作為雜湊值。

可以直接根據hashcode值判斷兩個物件是否相等嗎?肯定是不可以的,因為不同的物件可能會生成相同的hashcode值。雖然不能根據hashcode值判斷兩個物件是否相等,但是可以直接根據hashcode值判斷兩個物件不等,如果兩個物件的hashcode值不等,則必定是兩個不同的物件。如果要判斷兩個物件是否真正相等,必須通過equals方法。
  也就是說對於兩個物件,如果呼叫equals方法得到的結果為true,則兩個物件的hashcode值必定相等;
  如果equals方法得到的結果為false,則兩個物件的hashcode值不一定不同;
  如果兩個物件的hashcode值不等,則equals方法得到的結果必定為false;
  如果兩個物件的hashcode值相等,則equals方法得到的結果未知。
在Java中,hashCode()方法的主要作用是為了配合基於雜湊的集合(HashSet、HashMap)一起正常執行。當向集合中插入物件時,呼叫equals()逐個進行比較,這個方法可行卻效率低下。因此,先比較hashCode再呼叫equals()會快很多。

-- List的功能方法:
  實際上有兩種List: 一種是基本的ArrayList,其優點在於隨機訪問元素,另一種是更強大的LinkedList,它並不是為快速隨機訪問設計的,而是具有一套更通用的方法。
  List : 次序是List最重要的特點:它保證維護元素特定的順序。List為Collection新增了許多方法,使得能夠向List中間插入與移除元素(這隻推薦LinkedList使用。)一個List可以生成ListIterator,使用它可以從兩個方向遍歷List,也可以從List中間插入和移除元素。
  ArrayList : 由陣列實現的List。允許對元素進行快速隨機訪問,但是向List中間插入與移除元素的速度很慢。ListIterator只應該用來由後向前遍歷ArrayList,而不是用來插入和移除元素。因為那比LinkedList開銷要大很多。
  LinkedList : 對順序訪問進行了優化,向List中間插入與刪除的開銷並不大。隨機訪問則相對較慢。(使用ArrayList代替。)還具有下列方法:addFirst(), addLast(), getFirst(), getLast(), removeFirst() 和 removeLast(), 這些方法 (沒有在任何介面或基類中定義過)使得LinkedList可以當作堆疊、佇列和雙向佇列使用。

 

 

Set的功能方法:

  Set具有與Collection完全一樣的介面,因此沒有任何額外的功能,不像前面有兩個不同的List。實際上Set就是Collection,只是行為不同。(這是繼承與多型思想的典型應用:表現不同的行為。) Set不儲存重複的元素(至於如何判斷元素相同則較為負責)
  Set : 存入Set的每個元素都必須是唯一的,因為Set不儲存重複元素。加入Set的元素必須定義equals()方法以確保物件的唯一性。Set與Collection有完全一樣的介面。Set介面不保證維護元素的次序。
  HashSet : 為快速查詢設計的Set。存入HashSet的物件必須定義hashCode()。
  TreeSet : 儲存次序的Set, 底層為樹結構。使用它可以從Set中提取有序的序列。
  LinkedHashSet : 具有HashSet的查詢速度,且內部使用連結串列維護元素的順序(插入的次序)。於是在使用迭代器遍歷Set時,結果會按元素插入的次序顯示。

  STL中set底層實現是RB樹(即紅黑樹)。首先set,不像map那樣是key-value對,它的key與value是相同的。關於set有兩種說法,第一個是STL中的set,用的是紅黑樹;第二個是hash_set,底層用得是hash table。紅黑樹與hash table最大的不同是,紅黑樹是有序結構,而hash table不是。但不是說set就不能用hash,如果只是判斷set中的元素是否存在,那麼hash顯然更合適,因為set 的訪問操作時間複雜度是log(N)的,而使用hash底層實現的hash_set是近似O(1)的。然而,set應該更加被強調理解為“集合”,而集合所涉及的操作並、交、差等,即STL提供的如交集set_intersection()、並集set_union()、差集set_difference()和對稱差集set_symmetric_difference(),都需要進行大量的比較工作,那麼使用底層是有序結構的紅黑樹就十分恰當了,這也是其相對hash結構的優勢所在。

Map的功能方法:

  方法put(Object key, Object value)新增一個“值”(想要得東西)和與“值”相關聯的“鍵”(key)(使用它來查詢)。方法get(Object key)返回與給定“鍵”相關聯的“值”。可以用containsKey()和containsValue()測試Map中是否包含某個“鍵”或“值”。標準的Java類庫中包含了幾種不同的Map:HashMap, TreeMap, LinkedHashMap, WeakHashMap, IdentityHashMap。它們都有同樣的基本介面Map,但是行為、效率、排序策略、儲存物件的生命週期和判定“鍵”等價的策略等各不相同。
  執行效率是Map的一個大問題。看看get()要做哪些事,就會明白為什麼在ArrayList中搜尋“鍵”是相當慢的。而這正是HashMap提高速度的地方。HashMap使用了特殊的值,稱為“雜湊碼”(hash code),來取代對鍵的緩慢搜尋。“雜湊碼”是“相對唯一”用以代表物件的int值,它是通過將該物件的某些資訊進行轉換而生成的。所有Java物件都能產生雜湊碼,因為hashCode()是定義在基類Object中的方法。

  HashMap就是使用物件的hashCode()進行快速查詢的。此方法能夠顯著提高效能。HashMap的底層主要是基於陣列和連結串列來實現的。

  Map : 維護“鍵值對”的關聯性,使你可以通過“鍵”查詢“值”
  HashMap : Map基於雜湊表的實現。插入和查詢“鍵值對”的開銷是固定的。可以通過構造器設定容量capacity和負載因子load factor,以調整容器的效能。
  LinkedHashMap : 類似於HashMap,但是迭代遍歷它時,取得“鍵值對”的順序是其插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一點。而在迭代訪問時發而更快,因為它使用連結串列維護內部次序。
  TreeMap : 基於紅黑樹資料結構的實現。檢視“鍵”或“鍵值對”時,它們會被排序(次序由Comparabel或Comparator決定)。TreeMap的特點在於,你得到的結果是經過排序的。TreeMap 是唯一的帶有subMap()方法的Map,它可以返回一個子樹。
  WeakHashMap : 弱鍵(weak key)Map,Map中使用的物件也被允許釋放: 這是為解決特殊問題設計的。如果沒有map之外的引用指向某個“鍵”,則此“鍵”可以被垃圾收集器回收。

  IdentifyHashMap : 使用==代替equals()對“鍵”作比較的hash map。專為解決特殊問題而設計。

-------------------

  HashMap:
HashMap內部使用一個預設容量為16的陣列來儲存資料,陣列中每一個元素存放一個連結串列的頭結點,其實整個HashMap內部結構就是一個雜湊表的拉鍊結構。HashMap預設實現的擴容是以2倍增加,且獲取一個節點採用了遍歷法,所以相對來說無論從記憶體消耗還是節點查詢上都是十分昂貴的。

  SparseArray:
SparseArray比HashMap省記憶體是因為它避免了對Key進行自動裝箱(int轉Integer),它內部是用兩個陣列來進行資料儲存的(一個存Key,一個存Value),它內部對資料採用了壓縮方式來表示稀疏陣列資料,從而節約記憶體空間,而且其查詢節點的實現採用了二分法,很明顯可以看見效能的提升。

  ArrayMap:
ArrayMap內部使用兩個陣列進行資料儲存,一個記錄Key的Hash值,一個記錄Value值,它和SparseArray類似,也會在查詢時對Key採用二分法。
有了上面的基本瞭解我們可以得出結論供開發時參考,當資料量不大(千位級內)且Key為int型別時使用SparseArray替換HashMap效率高;當資料量不大(千位級內)且資料型別為Map型別時使用ArrayMap替換HashMap效率高;其他情況下HashMap效率相對高於二者。

-----------------

深層次思考:

在Java程式語言中,最基本的結構就是兩種,一種是陣列,一種是模擬指標(引用),所有的資料結構都可以用這兩個基本結構構造。

hashmap衝突的解決方法以及原理分析-http://www.cnblogs.com/peizhe123/p/5790252.html
hash衝突的解決方法以及hashMap的底層實現- http://blog.csdn.net/qq_25901775/article/details/50930140
  Hashmap裡面的bucket出現了單連結串列的形式,雜湊表要解決的一個問題就是雜湊值的衝突問題,通常是兩種方法:連結串列法和開放地址法。連結串列法就是將相同hash值的物件組織成一個連結串列放在hash值對應的槽位;開放地址法是通過一個探測演算法,當某個槽位已經被佔據的情況下繼續查詢下一個可以使用的槽位。java.util.HashMap採用的連結串列法的方式,連結串列是單向連結串列。形成單連結串列的核心程式碼如下:
  void addEntry(int hash, K key, V value, int bucketIndex) {  
    Entry<K,V> e = table[bucketIndex];  
    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);  
    if (size++ >= threshold)  
        resize(2 * table.length); 

 

hashmap與Hashtable實現原理淺析- http://blog.csdn.net/Double2hao/article/details/53411594
HashMap和Hashtable的底層實現都是陣列+連結串列結構實現的.

相關文章