Map
Map称之为映射,也称之为双列集合。Map中包含key(键)和value(值),key不能重复,一个key对应一个value.
key和value称之为键值对,键值对也是一个类,是Map.Entry
一个映射由多个键值对组成。
public interface Map
K 键值 (键值不能重复) V Value ( value 可以重复)
public static void main(String[] args) {
// Map常用方法
// 创建Map
Map<String, String> map = new HashMap<>();
// 添加数据
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转换成一个集合
import java.util.Collections;
import java.util.HashMap;
import java.util.Set;
public static void main(String[] args) {
//Map常用方法
//创建Map
Map<String, String> map = new HashMap<>();
//添加数据
// 添加数据
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<Map.Entry<String, String>> entries = map.entrySet();
// lambda表达式
entries.forEach(entry -> System.out.println(entry.getKey() + entry.getValue()));
//判断map是否为空
boolean empty = map.isEmpty();
System.out.println(empty);
//获取所有的键
Set
<String> strings = map.KeySet();
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
<String> values = map.values;
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()
源码
成员变量
// 默认加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 加载因子
final float loadFactor;
// 扩容的阈值
int threshold;
// 底层的数组
transient Node<K, V>[] table;
// 初始化容量
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方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
// tab 底层数组 p是数组中的一个结点
Node<K, V>[] tab;
Node<K, V> p;
// 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, V> e;
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<K, V>) p).putTreeVal(this, tab, hash, key, value);
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;
}
final Node<K, V>[] resize() {
// 旧的数组
Node<K, V>[] oldTab = table;
// 旧的数组的长度
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<K, V>[] newTab = (Node<K, V>[]) new Node[newCap];
table = newTab;
// 把结点从旧的数组中移动到新的数组中
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;
}
}
}
}
}
return newTab;
}
Hashtable
- Hashtable的key和value不能是null,线程安全。
- 默认的初始容量是11,加载因子是0.75
- 默认扩容是先翻倍再加一
- 指定的容量给多少就是多少
| 实现类 | 底层数据结构 | key/value为null | 线程是否安全 |
|---|---|---|---|
| HashMap | 数组 + 单向链表 + 红黑树 | 都可以是null | 不安全 |
| LinkedHashMap | 数组 + 双向链表 + 红黑树 | 都可以是null | 不安全 |
| TreeMap | 红黑树 | key不能是null,value可以是null | 不安全 |
| Hashtable | 哈希表 | 都不能是null | 安全 |
| ConcurrentHashMap | 哈希表 | 都不能是null | 安全 |