Map
集合体系图
Collection 下有:
- List
- ArrayList
- LinkedList
- Vector
- Set
- HashSet
- Linked HashSet
- Tree Set
- HashSet
//Map就是 顶层接口。public interface Map<K,V>
- Map
- HashMap
- Linked HashMap
- SortedMap(接口)
- TreeMap
- Hashtable
- Properties
- HashMap
Map规则
1)Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value
2) Map 中的key和 value可以是任何引用类型的数据,会封装到HashMap$Node
对象中
3) Map中的key 不允许重复,原因和HashSet一样,前面分析过源码.4) Map 中的value可以重复
5) Map 的key可以为null, value也可以为null,注意key 为null,只能有一个,
value为null ,可以多个.
6)常用String类作为Map的key
7) key 和 value 之间存在单向一对一关系,即通过指定的key 总能找到对应的value
Map map = new HashMap();
map.put("no1", "韩顺平");//k-v
map.put("no1", "张三丰");//当有相同的k , 就等价于替换.
- Map存放数据的key-value示意图,一对k-v是放在一个Node中的,有因为Node实现了 Entry接口,有些书上也说一对k-v就是一个Entry(如图)
HashMap内部类 Node
- putValue的时候,如果 tab[索引]节点为null,说明可以放入。
- 就 newNode
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
return new Node<>(hash, key, value, next);
}
//static class Entry<K,V> extends HashMap.Node<K,V> { 这是LinkedHashMap的内部类
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
}
public final int hashCode() {
//异或 相同为0,不同为1
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
HashMap 的内部类 EntrySet
transient Set<Map.Entry<K,V>> entrySet;
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
}
Map map = new HashMap();
map.put("no1", "韩顺平");//k-v
map.put("no2", "张无忌");//k-v
map.put(new Car(), new Person());//k-v
//老韩解读
//1. k-v 最后是 HashMap$Node node = newNode(hash, key, value, null)
//2. k-v 为了方便程序员的遍历,还会 创建 EntrySet 集合 ,该集合存放的元素的类型 Entry, 而一个Entry
// 对象就有k,v EntrySet<Entry<K,V>> 即: transient Set<Map.Entry<K,V>> entrySet;
//3. entrySet 中, 定义的类型是 Map.Entry ,但是实际上存放的还是 HashMap$Node
//values的方法返回 Collection 集合。 指向HashMap$Node 的Value
//keySet的方法返回 Set集合。 指向HashMap$Node 的Key
// 这时因为 static class Node<K,V> implements Map.Entry<K,V>
//4. 当把 HashMap$Node 对象 存放到 entrySet 就方便我们的遍历, 因为 Map.Entry 提供了重要方法 : K getKey(); V getValue();
Set set = map.entrySet();
System.out.println(set.getClass());// HashMap$EntrySet,里面放的 Map.Entry,真实为实现类 HashMap$Node
for (Object obj : set) {
//System.out.println(obj.getClass()); //HashMap$Node
//为了从 HashMap$Node 取出k-v
//1. 先做一个向下转型
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "-" + entry.getValue() );
}
//第二个遍历:
Set set1 = map.keySet();
System.out.println(set1.getClass()); //class java.util.HashMap$KeySet
//使用 map.getValue(key)
Collection values = map.values();
System.out.println(values.getClass());//class java.util.HashMap$Values
常用方法
- put:添加
- remove:根据键删除映射关系3) get:根据键获取值
- size:获取元素个数
- isEmpty:判断个数是否为06) clear:清除
7)containskey:查找键是否存在
//演示map接口常用方法
Map map = new HashMap();
map.put("邓超", new Book("", 100));//OK
map.put("邓超", "孙俪");//替换-> 一会分析源码
map.put("王宝强", "马蓉");//OK
map.put("宋喆", "马蓉");//OK
map.put("刘令博", null);//OK
map.put(null, "刘亦菲");//OK
map.put("鹿晗", "关晓彤");//OK
map.put("hsp", "hsp的老婆");
System.out.println("map=" + map);
// remove:根据键删除映射关系
map.remove(null);
System.out.println("map=" + map);
// get:根据键获取值
Object val = map.get("鹿晗");
System.out.println("val=" + val);
// size:获取元素个数
System.out.println("k-v=" + map.size());
// isEmpty:判断个数是否为0
System.out.println(map.isEmpty());//F
// clear:清除k-v
//map.clear();
System.out.println("map=" + map);
// containsKey:查找键是否存在
System.out.println("结果=" + map.containsKey("hsp"));//T
Map遍历
我的遍历
HashMap h = new HashMap();
h.put("test1", "我的测试1");
h.put("test2", "我的测试2");
//遍历1,使用keySet
for (Object key : h.keySet()) {
System.out.println(h.get(key));
}
//遍历2,使用values
for (Object value : h.values()) {
System.out.println(value);
}
//遍历3,使用entrySEt
Set<Map.Entry> set = h.entrySet();
System.out.println(set.getClass()); //class java.util.HashMap$KeySet
//使用 map.getValue(key),也行。
//set 还可以使用 迭代器
for (Map.Entry e : set) {
System.out.println(e.getKey() + " " + e.getValue());
}
//使用迭代器
Iterator<Map.Entry> i = set.iterator();
while (i.hasNext()) {
System.out.println(i.next().getValue());
}
1)containsKey:查找键是否存在
2) keySet:获取所有的键
3)entrySet:获取所有关系k-v
4)values:获取所有的值
老师的遍历
//HashMap的 Node 实现的 Map.Entry 接口
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
public interface Map<K,V> {
//接口里,有 子接口
interface Entry<K,V> {
K getKey();
}
}
- 第一组: keySet() 先取出 所有的Key , 通过Key 取出对应的Value
- 第二组: 把所有的values取出。values的两种遍历
- 第三组: 通过EntrySet 来获取 k-v。
public static void main(String[] args) {
Map map = new HashMap();
map.put("邓超", "孙俪");
map.put("鹿晗", "关晓彤");
//第一组: keySet() 先取出 所有的Key , 通过Key 取出对应的Value
Set keyset = map.keySet();
//(1) 增强for key的两种遍历
System.out.println("-----第一种方式-------");
for (Object key : keyset) {
System.out.println(key + "-" + map.get(key));
}
//(2) 迭代器
System.out.println("----第二种方式--------");
Iterator iterator = keyset.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key + "-" + map.get(key));
}
//第二组: 把所有的values取出。values的两种遍历
Collection values = map.values();
//这里可以使用所有的Collections使用的遍历方法
//(1) 增强for
System.out.println("---取出所有的value 增强for----");
for (Object value : values) {
System.out.println(value);
}
//(2) 迭代器
System.out.println("---取出所有的value 迭代器----");
Iterator iterator2 = values.iterator();
while (iterator2.hasNext()) {
Object value = iterator2.next();
System.out.println(value);
}
//第三组: 通过EntrySet 来获取 k-v。
// Set<Map.Entry<K, V>> entrySet();
Set entrySet = map.entrySet();// EntrySet<Map.Entry<K,V>>
//(1) 增强for
System.out.println("----使用EntrySet 的 for增强(第3种)----");
for (Object entry : entrySet) {
//将entry 转成 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
//(2) 迭代器
System.out.println("----使用EntrySet 的 迭代器(第4种)----");
Iterator iterator3 = entrySet.iterator();
while (iterator3.hasNext()) {
Object entry = iterator3.next();
//System.out.println(next.getClass());//HashMap$Node -实现-> Map.Entry (getKey,getValue)
//向下转型 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
}
HashMap小结
-
Map接口的常用实现类:HashMap、Hashtable和Properties(Hashtable的子类).
-
HashMap是 Map 接口使用频率最高的实现类。
-
HashMap是以 key-val对的方式来存储数据(HashMap$Node类型)[案例Entry]
-
key不能重复,但是值可以重复,允许使用null键和null值。
5)如果添加相同的key,则会覆盖原来的key-val ,等同于修改.(key不会替换,val会替换)
6)与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的. (jdk8的hashMap底层数组+链表+红黑树)
7)HashMap没有实现同步,因此是线程不安全的,方法没有做同步互斥的操作,没有synchronized
添加的时候 覆盖value
- 添加重复的键
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
//扩容逻辑 不执行
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);//tab表 不重复
else {
Node<K,V> e; K k;
//hash相同,遇到重复的了, key的 values也相同。
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
//执行
e = p;
else if (p instanceof TreeNode)
else {
}
if (e != null) { // existing mapping for key
//取出 旧的值
V oldValue = e.value;
//仅仅进是 缺席的 为假,取反为 真
if (!onlyIfAbsent || oldValue == null)
//核心代码:e是 旧指针,把 它的值,赋值 value
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
//这里 onlyIfAbsent 固定传递的 false
return putVal(hash(key), key, value, false, true);
HashMap 底层机制
1)(k,v)是一个Node实现了Map.Entry<K,V>,查看HashMap的源码可以看到.
- jdk7.0的hashmap底层实现[数组+链表], jdk8.0底层[数组+链表+红黑树]
transient Node<K,V>[] table; //底层的值是 放在 内部类Node 节点的
//HashMap$Node 数组里面放置的是 链表 或 树
//就是 Node 对象
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
//转为树后:HashMap$TreeNode
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
}
//所以 严格的讲:TreeNode 是 HashMap.Node 的孙子。
//HashMap$TreeNode —— LinkedHashMap.Entry —— HashMap.Node
//这是LinkedHashMap static class Entry<K,V> extends HashMap.Node<K,V> {}
//链表,变成红黑树。链表的长度 到了8个(不是超过),数组(也叫table)的大小,>= 64
//HashSet 底层是 HashMap
public HashSet() {
map = new HashMap<>();
}
HashMapSource.java 扩容机制[和HashSet相同]
1)HashMap 底层是 table 数组+链表(Node对象模拟的),默认大小是16
2)当创建对象时,将加载因子(lIoadfactor)初始化为0.75.
3)当添加key-val时,通过key的哈希值得到在table的索引。
然后判断该索引处是否有元素,如果没有元素直接添加。
如果该索引处有元素,继续判断该元素的key和准备加入的kev相是否等,
如果相等,则直接替换val;
如果不相等需要判断是树结构还是链表结构,做出相应处理。
如果添加时发现容量不够,则需要扩容。
4)第1次添加,则需要扩容table容量为16,临界值(threshold)为12(16*0.75)
5)以后再扩容,则需要扩容table容量为原来的2倍(32),临界值为原来的2倍,即24.依次类推
6)在Java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并且
table的大小>= MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
HashMap 源码 二讲
static final int hash(Object key) { //搜索上一篇文章即可
HashMap map = new HashMap();
map.put("java", 10);//ok
map.put("php", 10);//ok
map.put("java", 20);//替换value
System.out.println("map=" + map);
/*老韩解读HashMap的源码+图解
1. 执行构造器 new HashMap()
初始化加载因子 loadfactor = 0.75
HashMap$Node[] table = null
2. 执行put 调用 hash方法,计算 key的 hash值 (h = key.hashCode()) ^ (h >>> 16)
public V put(K key, V value) {//K = "java" value = 10
return putVal(hash(key), key, value, false, true); //整理如上
}
3. 执行 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;//辅助变量
//首次添加 必然 null
//如果底层的table 数组为null, 或者 length =0 , 就扩容到16
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//取出hash值对应的table的索引位置的Node, 如果为null, 就直接把加入的k-v
//, 创建成一个 Node ,加入该位置即可
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;//辅助变量
// 如果table的索引位置的key的hash相同(新的key的hash值相同),
// 并 满足(table现有的结点的key和准备添加的key是同一个对象 || equals返回真)
// 就认为不能加入新的k-v
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
//不能添加
e = p;
else if (p instanceof TreeNode)//如果当前的table的已有的Node 是红黑树,就按照红黑树的方式处理
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);
//加入后,判断当前链表的个数,是否已经到8个,到8个后
//就调用 treeifyBin 方法进行红黑树的转换。
//并不是真的扩容,如果表的长度 < 64 的,依然不会扩容,代码如下
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash && //如果在循环比较过程中,发现有相同,就break,就只是替换value
((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)
//替换,key对应value
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//扩容逻辑
++modCount;
//每增加一个Node ,就size++
if (++size > threshold[12-24-48])//如size > 临界值,就扩容。
resize();
afterNodeInsertion(evict);
return null;
}
5. 关于树化(转成红黑树)
//如果table 为null ,或者大小还没有到 64,暂时不树化,而是进行扩容.
//否则才会真正的树化 -> 剪枝 (如果一直移除,元素特别少,又从树 变成了 链表。)
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
//<64 不扩容
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
}
*/
扩容源码
首次添加,进入扩容,进入到此 逻辑
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) {
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
//首次添加,进入扩容,进入到此 逻辑
//默认为 16
newCap = DEFAULT_INITIAL_CAPACITY;
// 0.75 * 16 ,取整。 = 12
//12 个 空间,就是 临界值。 就是到了 12 就扩容 (担心 有大量的线程 同时添加)
//就是 16个座位,老师 看到用了 12个座位,就加 凳子了。
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
//核心代码。
@SuppressWarnings({"rawtypes","unchecked"})
//new 了个 一个 16 的数组
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
//赋值给 talbe
table = newTab;
if (oldTab != null) {
}
return newTab;
}
Node扩容成树 和 table表扩容
public class HashMapSource2 {
public static void main(String[] args) {
HashMap hashMap = new HashMap();
for (int i = 1; i <= 12; i++) {
hashMap.put(new A(i), "hello");
}//第9次,table长度为32,第10次为64。第11次,table长度依然为64,但节点为TreeNode
hashMap.put("aaa", "bbb");
System.out.println("hashMap=" + hashMap);//12个 k-v
//布置一个任务,自己设计代码去验证,table 的扩容
//0 -> 16(12) -> 32(24) -> 64(64*0.75=48)-> 128 (96) ->
//自己设计程序,验证-》 增强自己阅读源码能力. 看别人代码.
}
}
class A {
private int num;
public A(int num) {
this.num = num;
}
//所有的A对象的hashCode都是100
@Override
public int hashCode() {
return 100;
}
@Override
public String toString() {
return "\nA{" +
"num=" + num +
'}';
}
}