Java 常用數據結構對象的實現原理 集合類 List Set Map 哪些線程安全 (美團面試題目)


Java中的集合包括三大類,它們是Set、List和Map,
它們都處於java.util包中,Set、List和Map都是接口,它們有各自的實現類。
List、Set都繼承自Collection接口,Collection (所有集合類的接口)
  • Set(集) 實現類主要有HashSet和TreeSet
  • List(列表) 實現類主要有ArrayList,LinkedList,Vector
  • Map(映射)實現類主要有HashMap和TreeMap,HashTable
  • 線程安全類

  • 在集合框架中,有些類是線程安全的,這些都是jdk1.1中的出現的。在jdk1.2之后,就出現許許多多非線程安全的類。 下面是這些線程安全的同步的類:

    vector:就比arraylist多了個同步化機制(線程安全),因為效率較低,現在已經不太建議使用。在web應用中,特別是前台頁面,往往效率(頁面響應速度)是優先考慮的。

    statck:堆棧類,先進后出

    hashtable:就比hashmap多了個線程安全

    enumeration:枚舉,相當於迭代器

  •  

    StringBuffer也是線程安全的,(StringBuilder 不是線程安全的)

  • 除了這些之外,其他的都是非線程安全的類和接口。

    線程安全的類其方法是同步的,每次只能一個訪問。是重量級對象,效率較低。  

  • Collection 和 Map 的區別

    容器內每個為之所存儲的元素個數不同。Collection類型者,每個位置只有一個元素。Map類型者,持有 key-value pair,像個小型數據庫。

    List,Set都是繼承自Collection接口,Map則不是

    (a) Collection
         --List:將以特定次序存儲元素。所以取出來的順序可能和放入順序不同。
               --ArrayList / LinkedList / Vector
         --Set : 不能含有重復的元素
               --HashSet / TreeSet
     (b) Map
         --HashMap
         --HashTable
         --TreeMap 

    總結
  • List,Set,Map將持有對象一律視為Object型別。

    Collection、List、Set、Map都是接口,不能實例化。    繼承自它們的 ArrayList, Vector, HashTable, HashMap是具象class,這些才可被實例化。
    vector容器確切知道它所持有的對象隸屬什么型別。vector不進行邊界檢查。

    1. 如果涉及到堆棧,隊列等操作,應該考慮用List,對於需要快速插入,刪除元素,應該使用LinkedList,如果需要快速隨機訪問元素,應該使用ArrayList。
    2. 如果程序在單線程環境中,或者訪問僅僅在一個線程中進行,考慮非同步的類,其效率較高,如果多個線程可能同時操作一個類,應該使用同步的類。
    3. 在除需要排序時使用TreeSet,TreeMap外,都應使用HashSet,HashMap,因為他們 的效率更高。
    4. 要特別注意對哈希表的操作,作為key的對象要正確復寫equals和hashCode方法。 
    5. 容器類僅能持有對象引用(指向對象的指針),而不是將對象信息copy一份至數列某位置。一旦將對象置入容器內,便損失了該對象的型別信息。
    6. 盡量返回接口而非實際的類型,如返回List而非ArrayList,這樣如果以后需要將ArrayList換成LinkedList時,客戶端代碼不用改變。這就是針對抽象編程。 
  • 7. set中不允許有重復的元素存在
  • 8. map的 key 集合和 set集合都不允許有重復的元素存在,list可以有重復的元素存在 

    注意:
    1、Collection沒有get()方法來取得某個元素。只能通過iterator()遍歷元素。
    2、Set和Collection擁有一模一樣的接口。
    3、List,可以通過get()方法來一次取出一個元素。使用數字來選擇一堆對象中的一個,get(0)...。(add/get)
    4、一般使用ArrayList。用LinkedList構造堆棧stack、隊列queue。
    5、Map用 put(k,v) / get(k),還可以使用containsKey()/containsValue()來檢查其中是否含有某個key/value。
          HashMap會利用對象的hashCode來快速找到key。
    6、Map中元素,可以將key序列、value序列單獨抽取出來。
    使用keySet()抽取key序列,將map中的所有keys生成一個Set。
    使用values()抽取value序列,將map中的所有values生成一個Collection。
    為什么一個生成Set,一個生成Collection?那是因為,key總是獨一無二的,value允許重復。  

    (2)List中的對象按照索引位置排序,可以有重復對象,允許按照對象在集合中的索引位置檢索對象,如通過list.get(i)方式來獲得List集合中的元素。
    (3)Set中的對象不按特定方式排序,並且沒有重復對象。但它的有些實現類能對集合中的對象按特定方式排序,例如TreeSet類,它可以按照默認排序,也可以通過實現             java.util.Comparator<Type>接口來自定義排序方式。  
    (4) Map中的每一個元素包含一個鍵對象和值對象,它們成對出現。鍵對象不能重復,值對象可以重復。

關系如圖:

 

或者:

Collections (操作集合的工具類)
對於集合類的操作不得不提到工具類Collections,它提供了許多方便的方法,如求兩個集合的差集、並集、拷貝、排序等等。
由於大部分的集合接口實現類都是不同步的,可以使用Collections.synchronized*方法創建同步的集合類對象。
如創建一個同步的List:
List synList = Collections.synchronizedList(new ArrayList());
其實現原理就是重新封裝new出來的對象,操作對象時用關鍵字synchronized同步。看源碼很容易理解。
Collections部分源碼: 

static class SynchronizedCollection<e> implements Collection<e>, Serializable {
 
        final Collection<e> c;  // Backing Collection
        final Object mutex;     // Object on which to synchronize
 
        SynchronizedCollection(Collection<e> c) {
            if (c==null)
                throw new NullPointerException();
            this.c = c;
            mutex = this;
        }
        //...
        public boolean add(E e) {
            //操作集合時簡單調用原本的ArrayList對象,只是做了同步
            synchronized (mutex) {return c.add(e);}
        }
        //...

 
 
        

List(列表)

ArrayList、Vector是線性表,使用Object數組作為容器去存儲數據的,添加了很多方法維護這個數組,使其容量可以動態增長,極大地提升了開發效率。它們明顯的區別是ArrayList是非同步的,Vector是同步的。不用考慮多線程時應使用ArrayList來提升效率。

LinkedList是鏈表,略懂數據結構就知道其實現原理了。鏈表隨機位置插入、刪除數據時比線性表快,遍歷比線性表慢。 

由此可根據實際情況來選擇使用ArrayList(非同步、非頻繁刪除時選擇)、Vector(需同步時選擇)、LinkedList(頻繁在任意位置插入、刪除時選擇)。

Map(存儲鍵值對,key唯一)

HashMap結構的實現原理是將put進來的key-value封裝成一個Entry對象存儲到一個Entry數組中,位置(數組下標)由key的哈希值與數組長度計算而來。如果數組當前下標已有值,則將數組當前下標的值指向新添加的Entry對象。

public class HashMap<k,v> extends AbstractMap<k,v> implements Map<k,v>, Cloneable, Serializable
{
    transient Entry<k,v>[] table;
    public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        //遍歷當前下標的Entry對象鏈,如果key已存在則替換
        for (Entry<k,v> e = table[i]; e != null; e = e.next) {
        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;
        }
    }
       addEntry(hash, key, value, i);
        return null;
    }
}
static class Entry<k,v> implements Map.Entry<k,v> {
    final K key;
    V value;
    Entry<k,v> next;
    int hash;
}

TreeMap是由Entry對象為節點組成的一顆紅黑樹,put到TreeMap的數據默認按key的自然順序排序,new TreeMap時傳入Comparator自定義排序。紅黑樹網上很多資料,我講不清,這里就不介紹了。

Set(保證容器內元素唯一性)

之所以先講Map是因為Set結構其實就是維護一個Map來存儲數據的,利用Map結構key值唯一性。 
HashSet部分源碼:

public class HashSet<e>
extends AbstractSet<e>
implements Set<e>, Cloneable, java.io.Serializable
{    

    //無意義對象來作為Map的value 
    private static final Object PRESENT = new Object();
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
}

 

HashMap和HashTable的區別

具體的區別請參考:HashMap、HashTable、ConcurrentHashMap、HashSet區別 線程安全類

  StringBuffer是線程安全,而StringBuilder是線程不安全的。對於安全與不安全沒有深入的理解情況下,易造成這樣的錯覺,如果對於StringBuffer的操作均是線程安全的,然而,Java給你的保證的線程安全,是說它的方法是執行是排它的,而不是對這個對象本身的多次調用情況下,還是安全的。看看下邊的例子,在StringBufferTest中有一個數據成員contents它是用來擴展的,它的每一次append是線程安全的,但眾多次append的組合並不是線程安全的,這個輸出結果不是太可控的,但如果對於log和getContest方法加關鍵字synchronized,那么結果就會變得非常條理,如果換成StringBuider甚至是append到一半,它也會讓位於其它在此基礎上操作的線程: 

參考:Java基礎之常用數據結構及原理分析

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM