# Map
Map称之为映射,也称之为双列集合。Map中包含key(键)和value(值),key不能重复,一个key对应一个value.
key和value称之为键值对,键值对也是一个类,是Map.Entry
一个映射由多个键值对组成。
### public interface Map
K 键值 (键值不能重复)
V Value ( value 可以重复)
“`java
public static void main(String[] args) {
// Map常用方法
// 创建Map
Map
// 添加数据
map.put(“郭德纲”, “于谦”);
map.put(“赵本山”, “范伟”);
map.put(“虹猫”, “蓝兔”);
map.put(“熊大”, “熊二”);
map.put(“灰太狼”, “喜羊羊”);
// String put = map.put(“赵本山”, “高秀敏”);
// System.out.println(put);
map.put(“林志玲”, “范伟”);
// 根据key获取value
String s = map.get(“赵本山”);
System.out.println(s);
System.out.println(map);
}
“`
– 常用方法
– map.put(K,V) 添加数据
– map.get() 根据Key获取Value
– map.clear() 清空映射
– map.containsKey() 判断是否包含键值
– map.containsValue() 判断是否包含value
– map.entrySet()
– map.isEmpty() 是否为空
– map.keySet() 获取所有的键
– map.remove(K) 根据键值删除value
– map.remove(K,V) 根据键值和Value删除
– map.size() 返回键值对数量
– map.values() 把所有的value转换成一个集合
“`java
import java.util.Collections;
import java.util.HashMap;
import java.util.Set;
public static void main(String[] args) {
//Map常用方法
//创建Map
Map
//添加数据
// 添加数据
map.put(“郭德纲”, “于谦”);
map.put(“赵本山”, “范伟”);
map.put(“虹猫”, “蓝兔”);
map.put(“熊大”, “熊二”);
map.put(“灰太狼”, “喜羊羊”);
map.put(“林志玲”, “范伟”);
//根据Key值获取value
String s = map.get(“赵本山”);
System.out.println(s);
//清空映射
map.clear();
//判断是否包含某一个key
boolean containsKey = map.containsKey(“林志玲”);
System.out.println(containsKey);
//判断是否包含某一个value
System.out.println(map.containsValue(“宋丹丹”));
Set
// lambda表达式
entries.forEach(entry -> System.out.println(entry.getKey() + entry.getValue()));
//判断map是否为空
boolean empty = map.isEmpty();
System.out.println(empty);
//获取所有的键
Set
System.out.println(strings);
//根据key删除键值对,返回value
String remove = map.remove(“林志玲”);
System.out.println(remove);
//删除指定的key和value,返回删除成功或者失败
boolean remove1 = map.remove(“灰太狼”, “红太狼”);
System.out.println(remove1);
System.out.println(map.size());
Collection
System.out.println(values);
System.out.println(map);
}
“`
# HashMap
HashMap是哈希表的一个实现,是无需的,Key是唯一的,并且Key和Value都可以是null,元素位置可能发生改变,是线程不安全的映射
## 构造方法
– HashMap()
是默认初始容量16,默认加载因子为0.75,构造一个空HashMap(只当的初始容量(如果不是2的n次方,底层会通过算法变为2的n次方形式)
– HashMap(int initialCapacity)
使用指定的初始容量(如果不是2的n次方,底层会通过算法变成2的n次方的形式)和指定加载因子构造一个空 HashMap
– HashMap()
## 源码
### 成员变量
“`java
// 默认加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 加载因子
final float loadFactor;
// 扩容的阈值
int threshold;
// 底层的数组
transient Node
// 初始化容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
// 实际元素个数
transient int size;
// 扭转成红黑数的值
static final int TREEIFY_THRESHOLD = 8;
```
### 源码
- 当创建HashMap时,会把加载因子赋值为0.75
- 当第一次put时,会创建一个长度为16的Node类型的数组,默认的阈值是12,吧数据包装成结点放入数组中
- 如果key相同,hash值肯定相同,会用新值覆盖旧值,并返回旧值
- 如果key不同,但是hash值相同,会在对应的桶(bucket)上的位置进行一个个比较,知道链表节点的next为null为止,然后把结点放到链表的最后
- 当链表长度大于8,并且数组长度大于等于64时,链表会扭转成红黑树,如果数组长度<64,会进行一次扩容
- 扩容是原来的容量翻倍,如果原来桶上的是链表,那么扩容后要么在原来的位置,要么是原来位置 + 原来数组的长度
- HashMap的初始化容量一定是2的n次方的形式
- 当扩容时,如果桶上是红黑树,当长度 <=6 时,会把红黑树扭转成链表
注意点:使用HashMap时,key一般不要是自定义的对象。如果真的要使用,建议重写equals和hashCode方法
```java
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
// tab 底层数组 p是数组中的一个结点
Node
Node
// n 数组的长度
// i 是元素要放的数组的索引
int n, i;
// 首次调用put时,或执行resize方法,创建长度为16的数组
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// (n – 1) & hash 计算元素的索引
if ((p = tab[i = (n – 1) & hash]) == null)
// 如果索引处没有元素,直接创建结点放到索引处
tab[i] = newNode(hash, key, value, null);
else {
Node
K k;
// 比较key是否相同
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)// 判断是否是红黑树
e = ((TreeNode
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;
}
// 比较key是否相同
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// key相同的情况下,用新的value替换旧的value
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;
}
“`
“`java
final Node
// 旧的数组
Node
// 旧的数组的长度
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// 旧的阈值
int oldThr = threshold;
// 新的数组长度和新的阈值
int newCap, newThr = 0;
// 旧的数组长度 > 0 说明要扩容
if (oldCap > 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);
}
if (newThr == 0) {
float ft = (float) newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ?
(int) ft : Integer.MAX_VALUE);
}
// 设置扩容的阈值
threshold = newThr;
// 创建数组
@SuppressWarnings({"rawtypes", "unchecked"})
Node
table = newTab;
// 把结点从旧的数组中移动到新的数组中
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap – 1)] = e;
else if (e instanceof TreeNode)
((TreeNode
else { // preserve order
Node
Node
Node
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;
}
}
}
}
}
return newTab;
}
“`
# Hashtable
– Hashtable的key和value不能是null,线程安全。
– 默认的初始容量是11,加载因子是0.75
– 默认扩容是先翻倍再加一
– 指定的容量给多少就是多少
| 实现类 | 底层数据结构 | key/value为null | 线程是否安全 |
| ———————- | ———————— | —————————– | ———— |
| HashMap
| LinkedHashMap
| TreeMap
| Hashtable
| ConcurrentHashMap