集合学习(三):Map双列集合
1. Map接口常用方法
方法 | 描述 |
---|---|
put(K key, V value) |
添加或更新键值对 |
putAll(Map<? extends K, ? extends V> m) |
将指定映射中的所有键值对复制到当前映射中 |
get(Object key) |
获取指定键对应的值 |
containsKey(Object key) |
检查映射中是否包含指定键 |
containsValue(Object value) |
检查映射中是否包含指定值 |
remove(Object key) |
移除指定键对应的键值对 |
remove(Object key, Object value) |
仅在键的当前映射值等于指定值时移除该键的条目 |
size() |
返回映射中键值对的数量 |
isEmpty() |
检查映射是否为空 |
clear() |
清空所有键值对 |
keySet() |
返回包含所有键的集合 |
values() |
返回包含所有值的集合 |
entrySet() |
返回包含所有键值对的 Set 视图 |
public class MapDemo {
public static void main(String[] args) {
// 创建一个 HashMap
Map<String, Integer> map = new HashMap<>();
// 1. put(K key, V value) - 添加键值对
map.put("Apple", 10);
map.put("Banana", 20);
map.put("Cherry", 30);
System.out.println("After put: " + map);
// 2. putAll(Map<? extends K, ? extends V> m) - 添加多个键值对
Map<String, Integer> anotherMap = new HashMap<>();
anotherMap.put("Date", 40);
anotherMap.put("Elderberry", 50);
map.putAll(anotherMap);
System.out.println("After putAll: " + map);
// 3. get(Object key) - 获取值
System.out.println("Value for 'Banana': " + map.get("Banana"));
// 4. containsKey(Object key) - 检查是否包含键
System.out.println("Contains key 'Cherry': " + map.containsKey("Cherry"));
// 5. containsValue(Object value) - 检查是否包含值
System.out.println("Contains value 50: " + map.containsValue(50));
// 6. remove(Object key) - 移除键值对
map.remove("Apple");
System.out.println("After remove 'Apple': " + map);
// 7. remove(Object key, Object value) - 条件移除键值对
map.remove("Banana", 20);
System.out.println("After remove 'Banana' with value 20: " + map);
// 8. size() - 获取键值对数量
System.out.println("Size of map: " + map.size());
// 9. isEmpty() - 检查是否为空
System.out.println("Is map empty: " + map.isEmpty());
// 10. clear() - 清空映射
map.clear();
System.out.println("After clear: " + map);
// 重新填充映射以演示其他方法
map.put("Fig", 60);
map.put("Grape", 70);
map.put("Honeydew", 80);
// 11. keySet() - 获取所有键
Set<String> keys = map.keySet();
System.out.println("Keys: " + keys);
// 12. values() - 获取所有值
Collection<Integer> values = map.values();
System.out.println("Values: " + values);
// 13. entrySet() - 获取所有键值对
Set<Map.Entry<String, Integer>> entries = map.entrySet();
System.out.println("Entries:");
for (Map.Entry<String, Integer> entry : entries) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
}
}
2. HashMap
2.1 源码分析(关于EntrySet的分析)
- HashSet的底层就是HashMap,因此有关HashMap的扩容机制、树化条件不在赘述
- HashMap有字段transient Set<Map.Entry<K,V>> entrySet;
- 在debug模式,跟踪put()方法时,会发现代码没有涉及到entrySet,却能在监控区看到entrySet中有数据
![[Pasted image 20250118173337.png]] - 这值得研究!!!
为什么entrySet中会有数据?
这就不得不提entrySet到底是什么了。
entrySet
实际上是 HashMap
的一个视图,它并不存储实际的数据,而是基于 HashMap
的内部数据结构(即存储键值对的数组和链表)动态生成的。
也就是说,entrySet本身不存储数据,当访问entrySet的数据时,entrySet会从table中动态的查询数据,就好像数据本身就存储在entrySet中一样。
在debug时,idea访问entrySet拿到了数据,并展示在了监控区,让我们以为entrySet中存储了数据。
entrySet是如何获取数据的?
- 首先,HashMap.EntrySet类继承AbstractSet类,继承AbstractCollection类
final class EntrySet extends AbstractSet<Map.Entry<K,V>>{}
public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E> {}
- 对下面的程序进行调试,断点在
System.out.println(entry.toString());
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<>();
map.put("a", 1);
Set<Map.Entry<String, Integer>> entry = map.entrySet();
System.out.println(entry.toString());
}
- 从该断点强制不如,会进入到AbstractCollection.toString()方法
public String toString() {
Iterator<E> it = iterator();
if (! it.hasNext())
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = it.next();
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())
return sb.append(']').toString();
sb.append(',').append(' ');
}
}
- 在该方法中,会调用iterator()方法,强制步入,将进入到了HashMap.EntrySet.iterator()方法
- 该方法会返回一个EntryIterator对象,而HashMap.EntryIterator继承HashMap.HashIterator,并实现了Iterator接口
- 调用EntryIterator的无参构造,将进入HashIterator的无参构造
public final Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
}
final class EntryIterator extends HashIterator implements Iterator<Map.Entry<K,V>> {}
- 在HashIterator的无参构造中,会将HashMap的table赋值给t
HashMapNode<K,V>[] t = table;
- 此时,答案就已经呼之欲出了
HashIterator() {
expectedModCount = modCount;
Node<K,V>[] t = table;
current = next = null;
index = 0;
if (t != null && size > 0) { // advance to first entry
do {} while (index < t.length && (next = t[index++]) == null);
}
}
- 接下来,会循环调用hashNext()方法和next()方法,都是使用t中的数据,也就是table中的数据
- 这就印证了entrySet会从table中动态的查询数据
public final boolean hasNext() {
return next != null;
}
final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
if ((next = (current = e).next) == null && (t = table) != null) {
do {} while (index < t.length && (next = t[index++]) == null);
}
return e;
}
同样的,keySet()、values()获取数据类似的过程
2.2 HashMap的几种遍历方式
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
map.put("d", 4);
map.put("e", 5);
Set<Map.Entry<String, Integer>> entries = map.entrySet();
// 1.
Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// 2.
for (Map.Entry<String, Integer> entry : entries) {
System.out.println(entry.toString());
}
// 3.
entries.forEach(entry -> {
System.out.println(entry.toString());
});
// -------------------------------
Set<String> keySet = map.keySet();
// 1.
Iterator<String> keyIterator = keySet.iterator();
while (keyIterator.hasNext()) {
String key = keyIterator.next();
System.out.println(key + ":" + map.get(key));
}
//2.
for (String key : keySet) {
System.out.println(key + ":" + map.get(key));
}
//3.
keySet.forEach(key -> {
System.out.println(key + ":" + map.get(key));
});
// -------------------------------
Collection<Integer> collection = map.values();
//1.
Iterator<Integer> values = collection.iterator();
while (values.hasNext()) {
System.out.println(values.next());
}
// 2.
for (Integer value : collection) {
System.out.println(value);
}
// 3.
collection.forEach(System.out::println);
}
3. HashTable的使用
- 线程安全
- 不允许键或值为null
- 初始容量为11,临界值为
容量*0.75
- 扩容方案:
newCapacity = oldCapacity * 2 + 1
- 例如:从 11 扩容到 23,再扩容到 47。
4. TreeMap的使用
- 默认排序:
TreeMap
默认按照键的自然顺序(由键实现的Comparable
接口定义)排序,例如数字从小到大,字符串按字典顺序排序。 - 自定义排序:可以通过在构造
TreeMap
时传入Comparator
对象,自定义排序规则。 TreeMap
不允许键为null
,否则会抛出NullPointerException
。- 这是因为在排序时,
null
键无法进行比较。 - 值可以为
null
,没有限制。
- 这是因为在排序时,
本文是原创文章,转载请注明来自 Lazyking.site
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果
Steam卡片