Map
Map接口
基本结论
注意哈(解释并列存在):
我们平时所说的 集合 大部分认为的是
collection 接口
,不包括map 接口
但是,
map
算不算是集合呢?这是一个翻译导致的问题,因为一些书 翻译集合是包括两者的,
collection、map接口
一部分书,翻译的是 集合就是
Collection
,两者一起是叫集合类、容器、集合框架
翻译问题:
Map是不是集合并不重要,重要的是在我们的工作和学习中应该明确说明和指出集合是指容器(即《java编程思想》中的'集合类') 还是指Collection接口下的实现类。
Entry
接口
entrySet
是HashMap
的属性,为了方便 取 key、value,遍历而设置的跟
hashSet
大体一样,table 还是 Node[] 数组,entry 是 table 索引位置上面存放的链表的数据结构证明
这个是
entrySet
存放元素的地址,两者地址是一致的,所以是一个指向的指针
keySet
其实就是entrySet
里面的getKey
,当然还有getValue
方法所以
keySet
的地址也是一样的
Entry、Node、EntrySet之间关系
HashMap
底层维护的Set<Map.Entry<K,V>> entrySet
存储的是HashMap$EntrySet,而这个EntrySet集合里边存储的是HashMap$Node
,并且Node是Entry接口的实现类entrySet
内部类存储了node节点的引用,返回一个集合,方便遍历entrySet
是个方法,entry是个接口,只有set是集合,set<Map.entry>,又因为Node是个链表,实现了entry接口,所以通过多态,可以认为set集合里存放了Node- Entry提供了两个方法,getKey(),getValue(),方便进行遍历,获取
- 存的是newNode(hash, key, value, null),会转换为entry,然后会放在entrySet里边,
HashMap hashMap = new HashMap(); hashMap.put("1","a"); hashMap.put("2","b"); Set set =hashMap.entrySet(); System.out.println(set.getClass()); // class java.util.HashMap$EntrySet for (Object o : set) { System.out.println(o.getClass()); // class java.util.HashMap$Node // 为了从HashMap$Node 取出k,v Map.Entry o1 = (Map.Entry) o; System.out.println(o1.getKey() + "-" + o1.getValue()); }
六种遍历方式
- 取出
keySet
推荐- 直接取出
value
不推荐- 取出
entrySet
推荐
hashMap
线程不安全
k-v
最后是HashMap$Node
node = newNode(hash,key,value,null)
k-v
为了方便程序员的遍历,还会创建EntrySet 集合,该集合存放的元素的类型Entry
,而一个Entry
对象就有k,v
EntrySet<Entry<K,V>>
即:transient Set<Map.Entry<K, V>>entrySet;
entrySet
中,定义的类型是Map.Entry
,但是实际上存放的还是HashMap$Node
这是因为static class Node<K,V> implements Map.Entry<K, V>
当把
HashMap$Node
对象存放到entrySet
就方便我们的遍历,因为Map.Entry
提供了重要方法
K getKey () ;
V getValue();
底层原理
其实代码逻辑,源码在 hashSet 已经分析过了,自己去看就行
table
数组 + 节点都是Node
,每一个节点Node
都实现了Entry
接口,所以一个entry
相当于一个K_V
hashTable(安全)
线程安全的,扩容机制是 2 倍 + 1
hashTable 的 key、value不能为空,会报错
空指针异常
因为
int hash = key.hashCode
这个语句导致的。
底层原理
代码逻辑
- 底层
table
数组是 HashTable$Entry[] 对象,初始化大小是 11- 临界值
threshold
= 8 = 11 * 0.75(默认的初始化大小是 11)- 扩容:按照自己的扩容机制进行即可 2倍+1
- 执行方法
addEntry(hash,key,value,index)
添加 k-v 封装到 Entry 对象中- 当
if(count >= threshold)
满足的时候,就进行扩容- 按照
int newCapacity = (oldCapacity << 1) + 1
进行扩容代码逻辑
1. 无参构造
2.
put
方法(好像是 头插法,待确定)3.
addEntry
方法(添加到 table 数组位置的,不是添加到 链表)
- 这个code和size一样,只要添加元素元素就会++,不是添加到table数组上才++
4.
rehash
扩容protected void rehash() { int oldCapacity = table.length; //旧 table 长度 Entry<?,?>[] oldMap = table; //旧 table // overflow-conscious code int newCapacity = (oldCapacity << 1) + 1; //扩容 2倍+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++; //临界值,扩容之后的长度 * 负载因子(默认是0.75) 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; } } }
hashMap、hashTable的区别
Hashtable
的数据结构:
- Hashtable 是基于哈希表实现的,内部使用一个数组来存储元素。
- Hashtable 使用哈希函数将键映射到数组的索引位置,并使用链表来解决哈希冲突。
- 当发生哈希冲突时,新的键值对会添加到冲突位置的链表的末尾。
- 如果链表的长度超过一定阈值(默认为8),Hashtable 会将链表转换为红黑树,以提高查询和删除操作的性能。
HashMap
的数据结构:
- HashMap 也是基于哈希表实现的,同样使用一个数组来存储元素。
- HashMap 使用哈希函数将键映射到数组的索引位置,并使用链表或红黑树来解决哈希冲突。
- 当发生哈希冲突时,新的键值对会添加到冲突位置的链表或红黑树中,具体取决于链表的长度和当前容量的比值。
- 如果链表的长度超过一定阈值(默认为8),或者 HashMap 的容量达到一定大小(默认为64),HashMap 会将链表转换为红黑树。
- 当链表或红黑树中的元素数量较少时,HashMap 会将红黑树转换回链表,以节省空间。
需要注意的是,Hashtable 是线程安全的,对所有公共方法进行了同步处理,因此适用于多线程环境。而 HashMap 不是线程安全的,如果在多线程环境下使用 HashMap,需要进行额外的同步处理或者使用线程安全的 ConcurrentHashMap。
Properties
其实就是我们平常使用的 配置文件<img
alt="image-20230326180941677" style="zoom: 67%;" />
- 继承了
hashTable
,所以跟hashTable
大体一样- key、value同样是不能为 null
treeMap
逻辑基本和 treeSet 那里介绍的一样
put
方法比较器,比较key相同的时候,value进行替换,但是 key 不变,不操作,值替换
本文来自博客园,作者:小小俊少,转载请注明原文链接:https://www.cnblogs.com/xxjs168/p/17487300.html