java中的集合之Map体系集合,Map接口及其实现类

2022-07-31,,,,

Map体系集合

  • Map接口特点:

    1. 用于存储任意键值对(Key-Value)
    2. 键:无序、无下标、不允许重复(唯一)
    3. 值:无序、无下标、允许重复
  • 方法:

    • V put(K key, V value) //将指定的值与该映射中的指定键相关联(可选操作)。
    • Object get (Object key) //返回到指定键所映射的值,或 null如果此映射包含该键的映射。
    • Set<K> KeySet( )//返回此地图中包含的键的Set视图。
    • Collection<V> values()返回此地图中包含的值的Collection视图。
    • Set<Map.Entry<K,V>> entrySet() //返回此地图中包含的映射的set视图。
package com.map.demo; import java.util.HashMap; import java.util.Map; import java.util.Set; public class Demo1 { public static void main(String[] args) { Map<String, String> map = new HashMap<>(); /* 增加*/ map.put("cn", "中国"); map.put("uk", "英国"); map.put("usa", "美国"); /* 删除*/ //        map.remove("cn"); //        map.clear(); // 清空 /* 遍历*/ System.out.println("--------keySet遍历-------------"); for (String key : map.keySet()) { System.out.println(key + "------>" + map.get(key)); } System.out.println("--------entrySet遍历-------------");//效率更高 Set<Map.Entry<String, String>> entries = map.entrySet(); for (Map.Entry<String, String> item : entries) { System.out.println(item.getKey() + "------->" + item.getValue()); } /*查找判断*/ System.out.println(map.containsKey("cn")); System.out.println(map.containsValue("cn")); } } 

Map实现类

HashMap【重点】:

  • JDK1.2版本,线程不安全,运行效率高,允许使用null作为key或value。

基于哈希表的实现的Map接口。

此实现提供了所有可选的地图操作,并允许null的值和null键。

HashMap类大致相当于Hashtable ,除了它是不同步的,并允许null)。这个类不能保证地图的顺序;

特别是,它不能保证订单在一段时间内保持不变。

假设哈希函数在这些存储桶之间正确分散元素,这个实现为基本操作( getput )提供了恒定的时间性能。 收集视图的迭代需要与HashMap实例(桶数)加上其大小(键值映射数)的“容量” 成正比 。 因此,如果迭代性能很重要,不要将初始容量设置得太高(或负载因子太低)是非常重要的。

HashMap的一个实例有两个影响其性能的参数: 初始容量负载因子容量是哈希表中的桶数,初始容量只是创建哈希表时的容量。 负载因子是在容量自动增加之前允许哈希表得到满足的度量。 当在散列表中的条目的数量超过了负载因数和电流容量的乘积,哈希表被重新散列 (即,内部数据结构被重建),使得哈希表具有桶的大约两倍。

作为一般规则,默认负载因子(.75)提供了时间和空间成本之间的良好折中。 更高的值会降低空间开销,但会增加查找成本(反映在HashMap类的大部分操作中,包括getput )。 在设置其初始容量时,应考虑地图中预期的条目数及其负载因子,以便最小化重新组播操作的数量。 如果初始容量大于最大条目数除以负载因子,则不会发生重新排列操作。

如果许多映射要存储在HashMap实例中,则以足够大的容量创建映射将允许映射的存储效率高于使其根据需要执行自动重新排序以增长表。 请注意,使用同一个hashCode()多个密钥是降低任何哈希表的hashCode()的一种方法。 为了改善影响,当按键是Comparable时,这个类可以使用键之间的比较顺序来帮助打破关系。

  • 存储结构:哈希表(数组+链表+红黑树)
package com.map.demo; import java.util.HashMap; import java.util.Map; import java.util.Set; public class Demo1 { public static void main(String[] args) { Map<String, String> map = new HashMap<>(); /* 增加*/ map.put("cn", "中国"); map.put("uk", "英国"); map.put("usa", "美国"); /* 删除*/ //        map.remove("cn"); //        map.clear(); // 清空 /* 遍历*/ System.out.println("--------keySet遍历-------------"); for (String key : map.keySet()) { System.out.println(key + "------>" + map.get(key)); } System.out.println("--------entrySet遍历-------------");//效率更高 Set<Map.Entry<String, String>> entries = map.entrySet(); for (Map.Entry<String, String> item : entries) { System.out.println(item.getKey() + "------->" + item.getValue()); } /*查找判断*/ System.out.println(map.containsKey("cn")); System.out.println(map.containsValue("cn")); } } 
  • 在判断Key是否重复时,需要重写hashCodeeauals方法【注意】

源码分析

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 默认初始容量 static final int MAXIMUM_CAPACITY = 1 << 30; // 最大容量 static final float DEFAULT_LOAD_FACTOR = 0.75f; // 当达到容量的0.75时开始扩容 static final int TREEIFY_THRESHOLD = 8; // 当链表的长度大于8时,会调整成红黑树 static final int UNTREEIFY_THRESHOLD = 6;// 当链表的长度小于时,会调整成链表 static final int MIN_TREEIFY_CAPACITY = 64; //当链表的长度大于8时,元素个数超过64时,调整为红黑树 transient Node<K,V>[] table;// 哈希表中的数组 size; //元素个数 
  • 构造方法,设定扩展容量界限,初始的table —>null, size----> 0;
public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } 
  • 当添加第一个元素时,table—>16
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } 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); else { Node<K,V> e; K k; 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; } if (e.hash == hash && ((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) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; } 
  • 当元素个数大于阀值(16*0.75=12)时,会进行扩容,扩容后大小是原来的两倍。目的是减少调整元素个数

  • jdk1.8 当每个链表长度大于8,并且数组元素个数大于等于8,并且元素个数大于等于64时,会调整为红黑树,目的是提高执行效率

  • 当jdk1.8, 当链表长度小于6时,调整为链表

  • jdk1.8以前,链表是头插入,1.8以后是尾插入

    Hashtable【了解】

  • JDK1.0版本,线程安全,运行效率低,不允许有null作为key或者value

    该类实现了一个哈希表,它将键映射到值。

    任何非null对象都可以用作键值或值。

    为了从散列表成功存储和检索对象,用作键的对象必须实现hashCode方法和equals方法。

    Hashtable一个实例有两个影响其性能的参数: 初始容量负载因子容量是哈希表中的数, 初始容量只是创建哈希表时的容量。 请注意,哈希表是打开的 :在“哈希冲突”的情况下,单个存储桶存储多个条目,必须依次搜索。 负载因子是在容量自动增加之前允许哈希表得到满足的度量。 初始容量和负载因子参数仅仅是实现的暗示。 关于何时以及是否调用rehash方法的具体细节是依赖于实现的。

    通常,默认负载因子(.75)提供了时间和空间成本之间的良好折衷。 更高的值会减少空间开销,但会增加查询条目的时间成本(这反映在大多数Hashtable操作中,包括getput )。

    初始容量控制了浪费空间与需要rehash操作之间的折中,这是耗时的。 没有rehash如果初始容量大于项Hashtable将其负载因子包含除以最大数量永远不会发生的操作。 然而,设置初始容量太高可能会浪费空间。

    如果将多个条目制作为Hashtable ,则以足够大的容量创建它可能会使条目更有效地插入,以使其根据需要执行自动重新排序以增长表。

    • 更多了解参见api文档
  • 其有一个子类Properties:

    • Hashtable的子类,要求key和value都是String。通常与配置文件的读取。

      Properties类表示一组持久的属性。

      Properties可以保存到流中或从流中加载。

      属性列表中的每个键及其对应的值都是一个字符串。

      属性列表可以包含另一个属性列表作为其“默认值”; 如果在原始属性列表中找不到属性键,则会搜索此第二个属性列表。

      因为Properties从继承Hashtable时, putputAll方法可应用于Properties对象。 强烈不鼓励使用它们,因为它们允许调用者插入其键或值不是Strings 。 应该使用setProperty方法。 如果storesave方法在包含非String键或值的“受损害” Properties对象上调用,则调用将失败。 类似地,如果在包含非String密钥的“受损害” Properties对象上调用propertyNameslist方法的调用将失败。

      load(Reader) / store(Writer, String)方法从以下指定的简单的线性导向格式加载和存储基于字符的流的属性。 load(InputStream) / store(OutputStream, String)方法与加载(读取器)/存储(Writer,String)对相同,除了输入/输出流以ISO 8859-1字符编码编码。 在该编码中不能直接表示的字符可以使用The Java™ Language Specification的 3.3节中定义的Unicode转义来编写; 在转义序列中只允许使用单个“u”字符。 native2ascii工具可用于将属性文件转换为其他字符编码。

TreeMap

  • 实现了SortedMap接口(是Map接口的子接口),可以对key自动排序。
  • 存储结构红黑树,需要实现comParable接口或者实现Comparator接口来实现
package com.map.demo; import java.util.Comparator; import java.util.Map; import java.util.Set; import java.util.TreeMap; public class Demo2 { public static void main(String[] args) { TreeMap<Student, String> stuTreeMap = new TreeMap<>(new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { return o1.getStuNum() - o2.getStuNum(); } }); Student s1 = new Student("孙悟空", 101); Student s2 = new Student("牛魔王", 102); Student s3 = new Student("猪八戒", 103); Student s4 = new Student("沙和尚", 104); stuTreeMap.put(s1, "齐天大圣"); stuTreeMap.put(s2, "平天大圣"); stuTreeMap.put(s3, "天蓬元帅"); stuTreeMap.put(s4, "卷帘大将"); System.out.println(stuTreeMap.size()); System.out.println(stuTreeMap.toString()); stuTreeMap.remove(new Student("牛魔王", 102)); System.out.println("------------------keySet------------------"); for (Student s : stuTreeMap.keySet()) { System.out.println(s+"---------->"+stuTreeMap.get(s)); } System.out.println("------------------entrySet------------------"); for (Map.Entry<Student, String> item : stuTreeMap.entrySet()) { System.out.println(item.getKey()+"------->"+item.getValue()); } } } 

本文地址:https://blog.csdn.net/weixin_44064134/article/details/107699339

《java中的集合之Map体系集合,Map接口及其实现类.doc》

下载本文的Word格式文档,以方便收藏与打印。