Map接口的常用实现类HashMap类、Hashtable类、Properties类
HashMap类
1)HashMap是Map接口使用频率最高的实现类
2)HashMap是以键值对的方式来存储数据的
3)key值不可以重复,value可以重复,允许使用null值
4)添加相同的key,会将原来的键值对覆盖
5)数据存储和取出顺序不一致
6)HashMap没有实现同步,线程不安全
HashMap底层说明
1)HashMap扩容机制(和HashSet相同)
2)HashMap底层维护了Node类型的数组table,默认为null
3)当提那家键值对时,通过key的哈希值,得到其在table数组中对应的索引,然后判断索引处是否有元素,没有元素直接添加,有元素,继续判断该元素与加入的元素的key是否相同,相同则替换value值,不相等,判断是树结构还是链表结构,是树结构添加,是链表结构,进行逐个判断,有一个相同则替换value值,没有则添加在尾部,若添加时,数组中所有元素总和达到阈值,则需要扩容
4)第一次添加,扩容table数组容量为16,阈值为12
5)以后再扩容,则扩容table数组容量和阈值都变为原来的2倍
【当元素个数达到阈值12时,会进行下次扩容,扩容数组容量32,阈值24】
6)在Java8中,一条链表的元素个数超过8不一定马上树化
在一条链表元素的个数超过8并且table的数组容量大于等于64时,才会树化
HashMap的底层源码
public static void main(String[] args) {
HashMap map = new HashMap();
map.put(1,"a");
map.put(2,"b");
map.put(1,"c");
}
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
//DEFAULT_LOAD_FACTOR 0.75 默认的加载因子
// 阈值 = 数组容量的 0.75 倍
// all other fields defaulted
}
public V put(K key, V value) {
//添加键值对的主体函数
return putVal(hash(key), key, value, false, true);
}
// hash值 计算hash值
static final int hash(Object key) {
int h;
return (key == null)?0:(h = key.hashCode())^(h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//辅助变量
Node<K,V>[] tab; Node<K,V> p; int n, i;
//如果数组第一次创建 为空,或数组长度为 0
if ((tab = table) == null || (n = tab.length) == 0)
//数组进行扩容 第一次扩容为16 阈值达到12 = 16 * 0.75
n = (tab = resize()).length;
//计算根据hash值得出的索引, 如果数组table该索引上 没有元素--直接添加
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else { //若该位置上有元素
Node<K,V> e; K k;
//该位置上有元素 情况1:其hash值相同,值也相同,
if (p.hash == hash &&
((k = p.key) == key ||
(key != null && key.equals(k))))
e = p;
//该位置上有元素 情况2:该位置上的元素,是一个红黑树
else if (p instanceof TreeNode)
//则进行树结点的添加操作,属于算法的范畴,这里不展开
e = ((TreeNode<K,V>)p).putTreeVal(this, tab,
hash, key, value);
//该位置上有元素 情况3:该位置上的元素,是一个链表
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;
}
//存在相同的hash值相同且值相同,跳出循环
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k))))
break;
p = e; //相当于 p=p.next指向下一个元链表结点
}
}
//相同的key,对值进行覆盖(修改)
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;
}
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
//数组
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//数组容量
int oldThr = threshold;
//数组扩容的阈值
//数组扩容后的容量和阈值
int newCap, newThr = 0;
if (oldCap > 0) {
//旧数组的容量不为0
if (oldCap >= MAXIMUM_CAPACITY) {
//容量达到了最大值
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY
&& oldCap >= DEFAULT_INITIAL_CAPACITY)
//新数组的容量和阈值为原来数组的两倍
newThr = oldThr << 1;
// double threshold
}
else if (oldThr > 0)
// initial capacity was placed in threshold
newCap = oldThr;
else {
// zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
//在这里,oldCap和oldThr都是<=0的,数组的容量和阈值取默认值,16和12
}
if (newThr == 0) {
//newThr为0时,newCao为16,加载因子loadFactor为0.75
float ft = (float)newCap * loadFactor;//ft计算可得12
newThr = (newCap < MAXIMUM_CAPACITY
&& ft < (float)MAXIMUM_CAPACITY
?(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
//表示数组下次需要扩容的阈值
@SuppressWarnings({"rawtypes","unchecked"})
//在这里数组才真正的被创建出来
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
//如果原来的数组不为null,将原来的数组拷贝到新数组中
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab,
j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}//else { preserve order
}//if((e = oldTab[j]) != null)
}//for
}//if
return newTab;
}
Hashtable类
1)存放的元素是键值对,但都不能为null
2)Hashtable使用方法上和HashMap基本上一致
3)Hashtable是线程安全的
Hashtable底层说明
1)底层维护数组Hashtable$Entry[] 初始化大小为11
2)阈值threshold 8 = 11 * 0.75(加载因子为0.75)
3)扩容按照(容量大小 * 2 +1)计算得出
Hashtable的底层源码
---- jdk 17 ----
//在Hashtable中有静态内部类Entry实现了Map.Entry<K,V>接口
private static class Entry<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Entry<K,V> next;
protected Entry(int hash, K key, V value, Entry<K,V> next)
{
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
@SuppressWarnings("unchecked")
protected Object clone() {
return new Entry<>(hash, key, value,
(next==null ? null : (Entry<K,V>) next.clone()));
}
// Map.Entry Ops
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public V setValue(V value) {
if (value == null)
throw new NullPointerException();
V oldValue = this.value;
this.value = value;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry<?, ?> e))
return false;
return (key==null ? e.getKey()==null :
key.equals(e.getKey())) &&
(value==null ? e.getValue()==null :
value.equals(e.getValue()));
}
public int hashCode() {
return hash ^ Objects.hashCode(value);
}
public String toString() {
return key.toString()+"="+value.toString();
}
}
---- jdk 17 ----
// 主函数调用
public static void main(String[] args) {
Hashtable map = new Hashtable();
map.put(1,"a");
map.put(2,"b");
map.put(1,"c");
}
//构造函数调用
public Hashtable() {
this(11, 0.75f);
}
//this(11, 0.75f); initialCapacity = 11 loadFactor = 0.75f
public Hashtable(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException(
"Illegal Capacity: "+ initialCapacity);
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException(
"Illegal Load: "+loadFactor);
if (initialCapacity==0)
initialCapacity = 1;
this.loadFactor = loadFactor;
table = new Entry<?,?>[initialCapacity];
threshold = (int)Math.min(initialCapacity * loadFactor,
MAX_ARRAY_SIZE + 1);
//MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//Integer.MAX_VALUE = 0x7fffffff;
}
//put方法
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
//得到key的哈希值
int hash = key.hashCode();
//通过key的哈希值计算得出索引
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
//看看数组中通过该索引的出的值是否为空
//不为空,意味着该数组上有元素
Entry<K,V> entry = (Entry<K,V>)tab[index];
//遍历该数组结点中的链表
for(; entry != null ; entry = entry.next) {
//如果该位置上的元素hash值和值均相等 --- 对原来的值进行覆盖
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
//终止 循环
return old;
}
}
//将该元素加入数组
addEntry(hash, key, value, index);
return null;
}
//addEntry(hash, key, value, index);
private void addEntry(int hash, K key, V value, int index) {
Entry<?,?> tab[] = table;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
modCount++;
}
//数组扩容机制
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
// overflow-conscious code
int newCapacity = (oldCapacity << 1) + 1;
//扩容数组容量 为原来数组容量的两倍+1
if (newCapacity - MAX_ARRAY_SIZE > 0) {
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
modCount++;
threshold = (int)Math.min(newCapacity * loadFactor,
MAX_ARRAY_SIZE + 1);
table = newMap;
for (int i = oldCapacity ; i-- > 0 ;) {
for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ;
old != null ; ) {
Entry<K,V> e = old;
old = old.next;
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = (Entry<K,V>)newMap[index];
newMap[index] = e;
}
}
}
Properties类
基本介绍
1)Properties类继承了Hashtable类并且实现了Map接口
2)也使用键值对存储数据,但都不能为null
3)Properties可以用于xxx.properties文件中,加载数据到Properties中,并进行读取和修改
【xxx.properties文件通常为配置文件】
简单常用方法
put()增,改
remove()删
Properties.get()查找
Properties.getProperty()查找
【在IO流中会进行介绍】
TreeMap类
TreeSet与其相一致
1)传入的元素有序,key无重复,value无要求
2)添加的元素可以自定义比较器
3)使用TreeMap提供的构造器,可以传一个比较器
TreeMap的底层说明
【以字符串长度作为排序的依据为例】
1)当自定义排序规则时,两个值一致则认为两元素相同
2)在下面的主程序中,是按照字符串的长度进行排序的
注意:若出现长度相同的字符串,在TreeMap中则认为是相同类型,将不会假如在sets变量中
TreeMap的底层源码
//主程序
public static void main(String[] args) {
TreeMap sets = new TreeMap(new Comparator(){
@Override
public int compare(Object o1,Object o2){
return ((String) o1).length() -
((String) o2).length();
}
});
sets.put("a",1);
sets.put("a",2);
sets.put("ab",3);
System.out.println(sets);
}
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
//this.comparator
private final Comparator<? super K> comparator;
public V put(K key, V value) {
return put(key, value, true);
}
//put方法
private V put(K key, V value, boolean replaceOld) {
Entry<K,V> t = root;
if (t == null) {
addEntryToEmptyMap(key, value);
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
//比较器comparator传入的比较器(匿名内部类)
Comparator<? super K> cpr = comparator;
if (cpr != null) {
//逐个比较
do {
parent = t;
cmp = cpr.compare(key, t.key);
//compare动态绑定到匿名内部类然后实现排序规则
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else {
//当两个值相等
V oldValue = t.value;
if (replaceOld || oldValue == null) {
t.value = value;
}
//终止循环,并返回,输入值重复,结束添加
return oldValue;
}
} while (t != null);
} else {
Objects.requireNonNull(key);
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else {
V oldValue = t.value;
if (replaceOld || oldValue == null) {
t.value = value;
}
return oldValue;
}
} while (t != null);
}
addEntry(key, value, parent, cmp < 0);
return null;
}