Java中的數組和集合


一、List和Map

image

1、特點

(1)傳統的數組結構存儲數據會在內存中開辟連續得空間,結合下標從而使得可以快速訪問數據,但是刪除和添加數據就很浪費資源


(2)鏈表不需要開辟連續空間,使用指針來指向數據,因此刪除和添加操作比較快,但是查詢數據需要遍歷全部得元素

(3)而哈希表[散列表]結合兩者得長處,合二為一。使得哈希表比較牛掰(初始容量,數組長度默認為16,分為單指針和雙指針,雙指針每個元素指向前面元素同時指向后面元素

(1)、List

1、可以允許重復的對象。
2、可以插入多個null元素。
3、是一個有序容器,保持了每個元素的插入順序,輸出的順序就是插入的順序。
4、常用的實現類有 ArrayList、LinkedList 和 Vector。ArrayList 最為流行,它提供了使用索引的隨意訪問,而 LinkedList 則對於經常需要從 List 中添加或刪除元素的場合更為合適。

(2)、Map

1、不是collection的子接口或者實現類。Map是一個接口。
2、Map的每個 Entry 都持有兩個對象,也就是一個鍵一個值,Map 可能會持有相同的值對象但鍵對象必須是唯一的。
3、TreeMap 也通過 Comparator 或者 Comparable 維護了一個排序順序。
4、Map 里你可以擁有隨意個 null 值但最多只能有一個 null 鍵。
5、Map 接口最流行的幾個實現類是 HashMap、LinkedHashMap、Hashtable 和 TreeMap。(HashMap、TreeMap最常用)。

(3)、區別

1、list是存儲單列數據的集合,map是存儲雙列數據的集合;
2、list中存儲的數據是有序的,map中存儲的數據是無序的;
3、list允許重復,map的鍵不能重復,值可以重復。

(4)、Array和ArrayList

Array(數組)是基於索引(index)的數據結構,它使用索引在數組中搜索和讀取數據是很快的。
Array獲取數據的時間復雜度是O(1),但是要刪除數據卻是開銷很大,因為這需要重排數組中的所有數據。
List
List—是一個有序的集合,可以包含重復的元素,提供了按索引訪問的方式,它繼承Collection。
1、ArrayList底層采用數組實現,當使用不帶參數的構造方法生成ArrayList對象時,實際上會在底層生成一個長度為10的Object類型的數組。
2、如果增加的元素個數超過10個,那么ArrayList底層會生成一個新的數組,長度為原數組的1.5倍+1,然后將原數組的內容復制到新數組中,並且后續增加的內容都會放到新的數組當中,當新的數組無法容納增加的元素時,重讀該過程。
3、對於ArrayList元素的刪除操作,需要將被刪除元素的后續元素向前移動,代價比較大。
4、集合當中只能放置對象的引用,無法放置原生數據類型,我們必須使用原生數據的包裝類才能加入到集合當中。
5、集合當中都是Object類型,因此取出來的也是Object類型,那么必須要使用強制類型轉化將其轉換成真正的類型(放置進去的類型)。

二、List

1、Arraylist和Vector的區別

(1)、都實現了List接口(List接口繼承了Collection接口)都是有序集合List集合規范制定(數據允許重復,可有通過索引取出數據)

(2)、ArrayList和Vector都是基於數組,因此大致代碼相似。區別在於 Vector在Jdk1.0就有的古老集合, 因此包含大量方法名很長的方法。 Jdk1.2開始引入集合框架引入了List接口 Vector實現了List接口因此增加了一些List接口中的方法

(3)、總體來講ArrayList可以完全取代Vector,除非有一些老古董強制要求Vector。Vectory是線程安全的因此性能較差。ArrayList是非線程安全所以接口性能較好。即使在多線程中也應該用ArrayList,因為可以通過Collections吧ArrayList和LInkedList轉換成一個線程安全的集合
例子:List ls = Collections.synchronizedList(new ArrayList<>());

2、ArrrayList、Vecor、LinkedList的存儲性能和特性

(1)、ArrayList Vector ==>> 數組方式存儲數據
數組元素的數據 > 實際存儲的數據以及增加和插入元素
按序號索引元素
插入元素 ==>>數組移動等內存操作
索引數據快、插入數據慢
(2)、Vector ==>> synchronized方法(線程安全)
性能上較ArrayList差
(3)、LinkedList ==>> 雙向鏈表實現存儲
序號索引數據需要向前或向后遍歷
插入數據時只需要記錄本項的前后項即可
所以插入速度較快
線程不安全

3、ArrrayList、Vecor、LinkedList容量以及適用場景

(1)、ArrayList

特點:
1、ArrayList 內部是通過動態數組實現的,它允許對元素進行快速隨機訪問;
2、當數組大小不滿足時需要擴容,需要將已有數組移動到新的內存空間;
3、當從 ArrayList 的中間位置插入或者刪除元素時,需要對數組進行復制、移動代價比較高;
4、線程不安全。

初始容量:10初始容量:10
擴容容量:(原始容量 x 3 ) / 2 + 1。
適用場景:ArrayList 適合單線程,或多線程環境,但 List 只會被單個線程操作;隨機查找和遍歷,不適合插入和刪除。

(2)、LinkedList

特點:
1、LinkedList 是基於雙向鏈表存儲數據的,很適合數據的動態插入和刪除;
2、可根據索引值獲取(get(int index)))或刪除(remove(int index))節點(實現原理:通過計數索引值實現,當 index > 鏈表長度的1/2,從鏈表尾部開始遍歷;反之,從鏈表頭部開始遍歷;
3、可作為隊列和棧使用
4、線程不安全。
5、相對於ArrayList,LinkedList插入是更快的。因為LinkedList不像ArrayList一樣,不需要改變數組的大小,也不需要在數組裝滿的時候要將所有的數據重新裝入一個新的數組,這是ArrayList最壞的一種情況,時間復雜度是O(n),而LinkedList中插入或刪除的時間復雜度僅為O(1)。ArrayList在插入數據時還需要更新索引(除了插入數組的尾部)。
6、類似於插入數據,刪除數據時,LinkedList也優於ArrayList。
7、LinkedList需要更多的內存,因為ArrayList的每個索引的位置是實際的數據,而LinkedList中的每個節點中存儲的是實際的數據和前后節點的位置。

適用場景:適合單線程中,順序讀取,不適合隨機讀取和隨機刪除。

(3)、Vecor

特點:
1、其特點大致與 ArrayList 一樣。
2、線程安全(因為內部方法都是通過 synchronized 關鍵字同步的)。

初始容量:10初始容量:10
擴容容量:若擴容系數 > 0,則將容量的值增加“擴容系數”;否則,將容量大小增加一倍。
適用場景:能不用就別用。

(4)、ArrayList相比LinkedList

順序插入速度ArrayList會比較快,因為ArrayList是基於數組實現的,數組是事先new好的,只要往指定位置 塞一個數據就好了
LinkedList則不同,每次順序插入的時候LinkedList將new一個對象出來,如果對象比較大,那么new的時間 勢必會長一點,再加上一些引用賦值的操作,所以順序插入LinkedList必然慢於ArrayList
ArrayList的遍歷效率會比LinkedList的遍歷效率高一些

LinkedList做插入、刪除的時候,慢在尋址,快在只需要改變前后Node的引用地址

ArrayList做插入、刪除的時候,慢在數組元素的批量copy,快在尋址

如果確定插入、刪除的元素是在前半段,那么就使用LinkedList

如果確定插入、刪除的元素在比較靠后的位置,那么可以考慮使用ArrayList

如果不能確定插入、刪除是在哪兒呢?建議使用LinkedList,

一來LinkedList整體插入、刪除的執行效率比較穩定,沒有ArrayList這種越往后越快的情況

二來插入元素的時候,弄得不好ArrayList就要進行一次擴容,而ArrayList底層數組擴容是一個既消 耗時間又消耗空間的操作

三、Map

Hash也稱散列。基本原理就是把任意長度的輸入,通過Hash算法變成固定長度得輸出,這個映射得規則對應的就是Hash算法,而原始數據映射后得二進制串就是Hash值

entry----key----hash----index哈希值就是把各種類型的key算成統一的下標(.hashcode())

Hash特點
	1、從Hash值不可反向推導出原始數據
	2、輸入數據的微小變化也會得到完全不同的hash值,相同的數據的道相同的hash值
	3、哈希算法執行高效,長文本也能快速計算出哈希
	4、Hash算法沖突概率較小

Hash表在jdk1.7中使用數組+鏈表	jdk1.8中加入了紅黑樹
由於Hash的原理是將 輸入空間的值 映射成 hash空間內,而hash值空間遠小於輸入的空間。
根據抽屜原理,一定會存在不同的輸入被映射成相同的輸出。

抽屜原理:9個抽屜放10個蘋果,怎么放都會有一個抽屜里有2個及以上的蘋果

(5)HashMap的繼承體系

1、HashMap與HashTable簡介

類似於ArrayList和LinkedList的區別, HashTable是JDK1.0 就存在的老東西,因此 HashTable是一個繼承Dictionary 的古老集合。JDk1.2引入了集合框架的Map接口,HashTable也實現了Map接口,因此HashTable也增加了一些Map的方法

2、HashMap與HashTable區別

(1)、HashMap允許使用null作為Key或者Value,HashTable不允許
(2)、HashMap是線程不安全的因此性能較好,而HashTable是線程安全的因此性能較差

3、場景

多線程環境下也是用HashMap,Collections可以把HashMap轉換成線程安全的集合.
例:Map map = Collections.synchronizedList(new HashMap())

4、底層一些常量與屬性和構造方法

常量:
	//缺省大小
	static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

	//數組的最大長度
	static final int MAXIMUM_CAPACITY = 1 << 30;

	//缺省負載因子大小
	static final float DEFAULT_LOAD_FACTOR = 0.75f;

	//樹化閾值(鏈表長度超過8之后並且數組長度大於64就會升級成一個樹了)
	static final int TREEIFY_THRESHOLD = 8;

	//樹降級成為鏈表的閾值(刪除樹中元素,元素=6降級為鏈表)
	static final int UNTREEIFY_THRESHOLD = 6;

	//數組長度大於64(並且某個鏈表長度>8)升級為紅黑樹
	static final int MIN_TREEIFY_CAPACITY = 64;

屬性:
	//Hash表,put時初始化
	transient Node<K,V>[] table;
	
	//
	transient Set<Map.Entry<K,V>> entrySet;

	//當前Hash表中元素個數
	transient int size;

	//當前Hash表結構修改次數(插入元素或刪除元素,替換不會計數)
	transient int modCount;

	//擴容閾值(Hash表中元素超過閾值時觸發擴容,超過閾值不擴容影響查找性能。鏈化嚴重)
	int threshold;

	//負載因子(計算threshold = capacity數組長度	*loadFactor負載因子)
	final float loadFactor;
	
構造方法:
	//
	public HashMap(int initialCapacity, float loadFactor) {
		//做了邏輯校驗
		if (initialCapacity < 0)
		    throw new IllegalArgumentException("Illegal initial capacity: " +
						       initialCapacity);
		//傳入數組長度超過最大長度就設置為最大長度		
		if (initialCapacity > MAXIMUM_CAPACITY)
		    initialCapacity = MAXIMUM_CAPACITY;
		//負載因子<=0。。。。||不是數
		if (loadFactor <= 0 || Float.isNaN(loadFactor))
		    throw new IllegalArgumentException("Illegal load factor: " +
						       loadFactor);
		//賦值負載因子
		this.loadFactor = loadFactor;
		//賦值擴容閾值	   位移計算只能是2的次方數導致table的長度只能是2的次方
					傳過來init。。為7計算為8,9計算為16
		this.threshold = tableSizeFor(initialCapacity);
	}

	//設置默認負載因子
	public HashMap() {
		this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
	}

5、put源碼分析

put方法時會默認(new entry(key,value,null=指針next))


public V put(K key, V value) {
	//table的length不是特變長的情況下,讓讓key的hash值高於16位也參與路由運算
	return putVal(hash(key), key, value, false, true);
}

//實際用put向散列表插入數據
/**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent散列表存在欲插入的key就不插了(有就替換)
     * @param evict
     * @return previous value, or null if none
     */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {

	//tab:引用當前HashMap的散列表
	//p:表示當前散列表的元素
	//n:表示散列表數組的長度
	//i:表示路由尋址 結果
	
	Node<K,V>[] tab; Node<K,V> p; int n, i;
	
	//第一次插入數據時才會初始化,只是new出來並不會初始化(好多new出來但並不用)
        //延遲初始化邏輯
	if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

	//尋址找桶位,剛好為null,這時直接將當前key-value轉成node塞進去
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

總的來說向Vector和HashTable這種JDK1.0產物盡量不使用,除非某些api強制使用~~~果然老東西得退休~

image


免責聲明!

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



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