Map接口的具体实现类:HashMap、HashTable、Properties。其中HashMap是使用最多的类。
HashMap:
通过HashMap讲解Map类的特点,也可以知道HashMap类的特点:
1.HashMap是以key-value健值对的形式存储数据的(HashMap$Node类型)
2.HashMap中的key不能重复,而value可以重复,允许两者为null
3.如果向HashMap中存储相同的key,则会覆盖原来的key-value,等同于修改
4.HashMap无法保证映射顺序,底层使用哈希表进行存储(数组+链表+红黑树)
HashMap源代码:
HashMap hashMap = new HashMap();
hashMap.put("java", 10);
hashMap.put("php", 10);
hashMap.put("java", 20);
System.out.println(hashMap);
首先,执行构造器,初始化加载因子loadFactor=0.75,此时HashMap$Node[] table = null
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
向表中添加元素,执行put方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
继续执行putVal方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i; // 辅助变量
// table为null,执行resize方法进行扩容:Node<K,V>[] newTab = new Node[newCap];
// table变为length=16的空表
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 判断所在索引位置是否为空
if ((p = tab[i = (n - 1) & hash]) == null)
// 索引位置为null,直接将数据封装成Node类型放在该位置
tab[i] = newNode(hash, key, value, null);
// 该索引位置不为null
else {
Node<K,V> e; K k;
// 索引位置key与加入的key哈希值相同并key是同一个对象或者equals方法返回真
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
// 索引位置节点还是原来的Node,不做改变
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) {
// 将新数据封装成Node插入到链表最后
p.next = newNode(hash, key, value, null);
// 判断链表长度是否进行树化
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// 比较到链表某个位置,key哈希值相等,是同一个对象或equals相同
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; // 没增加一个Node,size++
// 数组table到达临界值,完成数组扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
链表树化
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
// 如果table表为null,或者没有达到64,不会直接树化,而是进行扩容
// 树化后,数据减少到一定程度也会发生剪枝(树结构转为链表结构)
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
}
HashTable:
HashTable特点:
1.存放的元素是键值对key-value
2.HashTable中的键和值都不能为null,否则会抛出空指针异常
3.HashTable的使用与HashMap相同
Hashtable hashtable = new Hashtable();
hashtable.put("lucy", 100);
hashtable.put("lic", 100);
hashtable.put("lic", 80); // 替换
hashtable.put("hello1", 1);
hashtable.put("hello2", 2);
hashtable.put("hello3", 3);
hashtable.put("hello4", 4);
hashtable.put("hello5", 5);
hashtable.put("hello6", 6);
hashtable.put("hello7", 7);
底层实现:
1.HashTable中存在数组HashTable$Entry[],数组初始化大小为11
2.数据封装为Entry类型存储在数组table中
3.临界值threshold:11*0.75 =8
4.按照自己的扩容机制进行扩容
HashTable的扩容机制:
执行addEntry()方法
addEntry(hash, key, value, index);
判断数组当前大小是否大于临界值threshold,执行rehash扩容
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
将数组扩容为原先数组的二倍
int newCapacity = (oldCapacity << 1) + 1;
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]
因此,数组大小变化:11(8)-> 23(17) -> 47(35) ->.......
TreeMap:
TreeMap实现了Map接口,存储数据的同时,可以实现数据排序
使用默认构造器创建TreeMap对象,按照默认顺序进行排序
TreeMap treeMap1 = new TreeMap();
treeMap1.put("jack", "杰克");
treeMap1.put("tom", "汤姆");
treeMap1.put("kristina", "克瑞斯提娜");
treeMap1.put("smith", "史密斯");
System.out.println(treeMap1); // {jack=杰克, kristina=克瑞斯提娜, smith=史密斯, tom=汤姆}
向构造器中传递参数(实现了Comparator的匿名内部类),按照指定的顺序进行排序
TreeMap treeMap2 = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
// 按照传入的key的大小进行比较(使用的是String类中实现的Compare方法)
//return ((String) o1).compareTo(((String) o2));
// 按照传入的key的长度进行比较
return ((String) o1).length() - (((String) o2).length());
}
});
treeMap2.put("jack", "杰克");
treeMap2.put("tom", "汤姆");
treeMap2.put("kristina", "克瑞斯提娜");
treeMap2.put("smith", "史密斯");
treeMap2.put("mary", "玛瑞"); // jack=玛瑞
System.out.println(treeMap2); // {tom=汤姆, jack=杰克, smith=史密斯, kristina=克瑞斯提娜}
源码解读:
调用构造器,将实现comparator接口的匿名内部类传给comparator属性
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
添加元素,调用add方法
return m.put(e, PRESENT)==null
继续调用put方法
public V put(K key, V value) {
Entry<K,V> t = root;
// 第一次添加,直接添加
if (t == null) {
compare(key, key); // type (and possibly null) check
// 调用compare方法,对同一个key进行比较,不会对添加元素产生影响,仅仅检查是否为null
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;//添加成功
}
// 之后添加,启用比较器
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
// 通过compare得到的两个key相等时,替换value
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
// 根据cmp的取值确定新元素加入的位置
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null; // 添加成功
}