## ArrayList:
1. ArrayList是List接口的大小可變數組的實現,此實現是不同步的。
2. ArrayList內部使用類型為Object[]的數組存儲元素。
3. ArrayList默認的數組長度為10, 當需要擴大容量時,擴大后的容量為:newCapacity = (oldCapacity * 3)/2 + 1;
4. ArrayList的clone方法為淺拷貝(shallow copy)
5. ArrayList的remove方法根據參數類型的不同有兩種重載:
remove(int index) : 刪除指定位置的元素;
remove(Object o) ?: 刪除第一個遇到的元素,如果沒有不做改變
6. ArrayList允許null值、允許重復值、不排序,獲取快速,增刪麻煩。
## HashMap:
?HashMap是不同步的。
?HashMap內部使用類型為Entry[]的數組存儲元素。Entry是HashMap的一個內部類,定義如下所示。
?每一個Entry對象其實是一個單向鏈表,之后的解析可以看到,最后存入的元素在最前面。
??備注:下面出現的代碼都是HashMap.java中的源碼,中文描述是作者加的。
~~~
transient Entry[] table;//HashMap內部定義的數據存儲變量
//內部類
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
/**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
*****
省略
*****
}
~~~
### ?HashMap中幾個概念:
?capacity:容量,即Entry[]數組的長度
?loadFactor:負載因子,Entry[]數組中實際數據量/容量的比例達到loadFactor時,HashMap就需要擴大容量了,一般擴大為原來的兩倍。
?threshold: 當HashMap中的元素個數超過這個數值時,就將擴大容量。
### ?put方法:
~~~
public V put(K key, V value) {
if (key == null)
//如果key為null,特殊處理,key為null直接存儲在table[0]位置。
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);//此處得到的i即為key對應的HashMap中的存儲位置table[i]
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
//從Entry鏈表的第一個開始如果找到與key執行equals方法為true的Entry,則修改對應Entry的value值為新值,key不做修改
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;
}
}
modCount++;
//如果沒有找到對應的key,則執行增加操作
addEntry(hash, key, value, i);
return null;
}
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)
//如果大小超過了threshold,擴大容量為原來的兩倍。擴大容量時,所有的key-value需要重新hash。
resize(2 * table.length);
}
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable);//將原來hash表中的數據放入新的hash表中,需要重新hash。
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
/**
* Transfers all entries from current table to newTable.
*/
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
src[j] = null;
//此處使用循環,將原來hash鏈中的所有的key-value都重新獲取hash值,重新放置。
//因為放置位置是跟hash表的大小有關的,當hash表容量擴大后,之前放在一個地方的key-value對現在可能hash不到同一個地方了。
do {
Entry<K,V> next = e.next;//記錄此處的下一個地址
int i = indexFor(e.hash, newCapacity);//重新計算當前的key-value在新hash表中的位置
e.next = newTable[i];//將之前在同一位置的數據放在e的next位置,沒有則為null
newTable[i] = e;//將e作為hash表i位置的第一個元素
e = next;//將next賦值給e, 對原來j位置的所有的元素都執行重新hash,重新放置
} while (e != null);
}
}
}
~~~
?get方法:按照put時的邏輯根據key獲取value。不再詳述。
### ?keySet與values方法:
這兩個方法作用好理解,但需要注意的是,當對keySet()和values()方法獲取到的集合執行remove操作的時候就相當于對HashMap集合本身執行remove操作。看源碼通過keySet和values獲取到的好像是HashMap的迭代器,這里我沒有深究。如果誰明白具體原因不吝賜教。
# HashSet:
?HashSet的內部是用的HashMap實現的,使用Entry將每一個HashSet元素的引用存儲在key位置,value位置使用默認的數據填充。
?在此也可以看到,HashMap中的key-value對其實可以看成value是每一個key的附屬,只需要找到每一個key的位置,然后把對應的value放入即可。