HashMap類
在介紹hashMap之前,有必要介紹下關於散列表的知識。(太久沒用到散列表,沒想到一時竟然忘記了它的存在的意義了,看來不多做筆記真不行)
散列表:支持以常數時間對任何命名項的檢索或刪除。爲什麼能夠這麼會有這種效果呢?原理是:定義一個空的tablesize大小數組,每個要插入元素根據散列函數取得數組的下標,所以要能根據元素進行線性的檢索;
衝突:就是不同的項通過散列函數取得相同的下標(專業術語:相撞),就會造成衝突,我們可以通過線性探測,二次探測等方法來獲得新的下標,也可以用hashMap類中的解決方案,如果出現不同的項有相同的下標,將第二項掛在第一項下,(就是第一項的next指向第二項)依次類推;
但是散列表犧牲了元素插入的有序性;
hashMap簡單介紹:
hashMap是基於散列表的原理,類中定義了變量table數組來存放數據,數組類型爲Entry類型,(Entry是hashMap的一個靜態內部類);包含這些基本的元素,capacity(容量),loadFactor(加載因子),size,threshold;Capacity:即數組的長度(初始值爲16),loadFactor來決定threshold的大小(默認爲0.75),Threshold = loadFactor* Capacity;也就是說如果size>=Threshold,那麼我們需要擴充數組的大小capacity,如果此時的capacity的大小已經達到最大值Integer.MAX_VALUE,那麼不需改變數據大小,繼續添加數組即可;
每組數據,對應一個Entry類和一個table的下標,這個小標的取值跟key的hash值和table的長度有關;所以有一點特別重要,在擴充數組的時候,定義了一個新的數組後,由於table的長度變化,需要對每一個Entry對象重新計算小標值,重新將數據填入數組中;
hashMap的實現不是同步的。
它的部分源代碼如下:我們一一分析:
//繼承類AbstractMap和接口Map,Cloneable,Serializable;
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
類包含的成員
//散列表
static final Entry<?,?>[] EMPTY_TABLE = {};
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
//元素的個數
transient int size;
//加載因子,就是說如果要存放size=4的數據,根據加載因子,我們定義散列表的大小爲4/loadFactor;
//加載因子的默認值是0.75
final float loadFactor;
//值爲 (capacity * load factor).
int threshold;
存放一組元素的操作如下:
//向map裏頭存放數據
public V put(K key, V value) {
//如果數組爲空,初始化數組inflateTable(),第一次存放//時出現
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
//允許key爲空,此時的數組下標爲0,(剩下的)操作相同;
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
//這段代碼是重點,告訴我們散列表存數的形式,找到下標後,判斷該位置是否已經有數據,如果有掛載在已有的數據下;
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;
}
}
modCount++;
//該方法,將這組數據創建一個Entry對象保存,同時存放了hash值和小標值,當然要先判斷是否數組達到了上限,具體請看方法的內容
addEntry(hash, key, value, i);
return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
//用來擴充數組的,如果數組已經達到上限,立刻return;
//如果沒有,那麼遍歷原來的數組,重新定義下標。。。
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
//重新定義下標
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
//新建一個Entry對象,然後存放在table中
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
接下來,是時候介紹小Entry類了,其實它僅僅就是用來存放一組數據和一些基本信息的結構而已哈;
//內部類 Entry,繼承接口Map.Entry<K,V>,簡化了部分代碼
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
}
接下來介紹下,如果根據key來取得value值,知道原理後,我們應該知道怎麼取值了吧,由key獲得hash值,在與table.length計算出下標,從該位置上找出key對應的value。
public V get(Object key) {
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
簡單的介紹:
LinkedHashMap繼承類HashMap,在HashMap的基礎上提供了記錄輸入順序的方法,也就是用鏈表的方式,對每一次輸入進行記錄;
//定義了成員變量,記錄鏈表的頭部
private transient Entry<K,V> header;
//覆蓋了HashMap的init()的方法,原本在HashMap中它是一個空方法,但是在第一次初始化數組的時候會被調用;
@Override
void init() {
header = new Entry<>(-1, null, null, null);
header.before = header.after = header;
}
//當數組需要調整大小的時候,會調用這個方法,(覆蓋的原因僅僅是因爲用鏈表來遍歷效率更高一點哈)
@Override
void transfer(HashMap.Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e = header.after; e != header; e = e.after) {
if (rehash)
e.hash = (e.key == null) ? 0 : hash(e.key);
int index = indexFor(e.hash, newCapacity);
e.next = newTable[index];
newTable[index] = e;
}
}
//這是ListHashMap中類Entry<K,V>一個方法,當插入新數據的時候會調用該方法,此方法在HashMap. Entry類中是一個空的方法;在此,該方法是爲了維護數據的有序性
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
TreeMap
TreeMap是基於紅黑樹實現的,紅黑樹的具體分析在以後的章節會詳細介紹,現在大致說下紅黑樹的特點:
二叉查找樹特點:1,父節點的值大於兩個兒子的值;2,同一個父親,右兒子的值大於左兒子的值;二叉查找樹有個明顯的缺陷,就是不斷的操作最終會導致樹非常不平衡(也就是樹的左右孩子的深度不一樣),所以誕生了平衡二叉樹,但是平衡二叉樹對樹的要求比較高;由此產生了紅黑樹;對於紅黑樹的操作的時間複雜度,構造樹的方法,以後再詳細介紹;
針對TreeMap分析:
類成員變量
private final Comparator<? super K> comparator;
private transient Entry<K,V> root = null;
構造函數:
//如果不傳入參數,則默認按照自然順序(就是hashcode的大小)排序
public TreeMap() {
comparator = null;
}
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
如果compare後,返回0那麼認爲這兩個數是一樣的,在TreeMap中對同樣的數,覆蓋原則哈,
測試代碼
public class person1{
int height;
int age;
person1(int height,int age){
this.height = height;
this.age = age;
}
public static void main(String args[]){
TreeMap<person1,String> tr = new TreeMap<person1, String>( new personComparator());
person1 p1 = new person1(1,1);
person1 p2 = new person1(1,2);
person1 p3 = new person1(2,3);
tr.put(p1, "p1");
tr.put(p2, "p2");
tr.put(p3, "p3");
System.out.print((tr.get(p1)+tr.get(p2)+tr.get(p3)));
}
}
class personComparator implements Comparator<person1>{
@Override
public int compare(person1 o1, person1 o2) {
// TODO Auto-generated method stub
if(o1.height==o2.height){
return 0;
}else if(o1.height>o2.height){
return 1;
}else{
return -1;
}
}
}
測試結果如下:
p2p2p3