java中 Map接口的基本使用及原理

Map接口

(一)、接口描述

  • 将键(key) 映射到值(value)的对象,
  • 一个映射不能包含相同的key
  • 一个key只能映射一个value
  • 可以存储多个键值对
  • 具体的实现类有: hashMap、hashtable、LinkedHashMap

(二)、hashMap 的基本使用

  • 基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。 (除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映 射的顺序,特别是它不保证该顺序恒久不变。
 // 创建一个映射
Map<Integer, String> dict = new HashMap<>();

// 向映射中添加一个键值对
dict.put(1, "张三");
dict.put(2, "李四");
dict.put(3, "王五");

// 根据key 获取 value
System.out.println(dict.get(1));
System.out.println(dict.get(2));
System.out.println(dict.get(3));



// 对映射进行遍历操作

  • 对映射进行遍历方法
System.out.println("--------------------------");
System.out.println("使用获取Entry对象集合的方式来遍历");
// 方法1. 获取Entry对象集合
Set<Map.Entry<Integer, String>> entryset = dict.entrySet();
// 集合可以使用foreach
// 每次返回的都是一个May.Entry 类型的一个对象, 这个对象主要存储 一个key, 和一个 value 两个属性
for (Map.Entry<Integer, String> entry : entryset) {
    System.out.println(entry.getKey() + "-->" + entry.getValue());
}

System.out.println("--------------------------");
System.out.println("使用获取key的集合方式来遍历");
// 方法2, 获取 key的集合
Set<Integer> keySet = dict.keySet();
// for(Integer k : keySet) {
//    System.out.println(k + "-->" + dict.get(k));
// }
keySet.forEach(k -> System.out.println(k + "-->" + dict.get(k)));

System.out.println("--------------------------");
System.out.println("直接获取value集合, 直接遍历");
// 方法3. 直接获取value 集合
Collection<String> valueSet = dict.values();
valueSet.forEach( v -> System.out.println(v));

System.out.println("--------------------------");
System.out.println("直接遍历");
// 方法4. 直接forEach
dict.forEach((k, v) -> System.out.println(k + "-->" + v));
  • HashMap实现原理

    • 基于哈希表(数组 + 链表 + 二叉树(红黑树) , JDK1.8后添加的二叉树

    • 默认的加载因子0.75,默认的数组(哈希表)大小为16

    • 怎么将对象存储在哈希表中呢?

      • 将key 通过内部的hash()方法 计算出hash值, 在通过这个值 与 数组的长度求余数, 来决定这个对象在数组中所存储的位置

        如果多个对象都存储这个位置时, 那么以链表的形式进行存储, 在JDK1.8中, 当链表的大小大于8

        时, 那么该链表会转换为红黑树结构进行存储。

    • 那么这个哈希表怎么扩充的呢?

      • 当数组的容量超过75%(加载因子的百分比), 那么数组需要扩充
      • 扩充算法: 当前的容量 << 1位(扩大到原来的两倍),
      • 注意:
        • 如果扩充的次数过多, 会影响性能 , 因为每次扩充,都要将原来的hash表重新散列(每个对象重新计算新的存储位置), 所以, 尽量减少扩充带来的性能影响。
        • 是线程不安全的, 建议在单线程中使用

(三)、Hashtable 接口的基本使用

public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, Serializable
  • 此类实现一个哈希表,该哈希表将键映射到相应的值。任何非 null 对象都可以用作键或值。 为了成功地在哈希表中存储和获取对象,用作键的对象必须实现 hashCode 方法和 equals 方法。
  • 基本使用
// 创建一个hashtable
Hashtable<Integer, String> dict = new Hashtable<>();
// 向映射中添加一个键值对
dict.put(1, "张三");
dict.put(2, "李四");
dict.put(3, "王五");

// 遍历
dict.forEach((k, v) -> System.out.println(k + "->>" + v));
  • 实现原理
    • 基于hash表(数组 + 链表)实现
    • 默认数组的大小位11, 加载因子为0.75
    • 数组扩充算法:原数组 << 1 + 1 (扩大到原来的两倍 + 1)
    • 是线程安全的, 建议使用在多线程中

(四)、LinkedHashMap 接口的基本使用

public class LinkedHashMap<K,V>extends HashMap<K,V> implements Map<K,V>
  • Map 接口的哈希表和链接列表实现,具有可预知的迭代顺序。此实现与 HashMap 的不同之处 在于,后者维护着一个运行于所有条目的双重链接列表。
  • 基本使用
// 创建LinkedHashMap 实例
LinkedHashMap<Integer, String> dict = new LinkedHashMap<>();
dict.put(1, "张三");
dict.put(2, "李四");
dict.put(3, "王五");

// 遍历
dict.forEach((k, v) -> System.out.println(k + "->>" + v));
  • 实现原理
    • 因为它继承与 HashMap, 所以底层原理差不多
    • 那么与 HashMap的区别呢?
      • 由于HashMap 不能保证顺序恒久不变, 此类使用了一个双重链接表来维护元素添加的顺序。

(五)、TreeMap 的接口的使用

  • 基于红黑树(Red-Black tree)的 NavigableMap 实现。该映射根据其键的自然顺序进行排序, 或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
public class TreeMap<K,V> extends AbstractMap<K,V>implements NavigableMap<K,V>, Cloneable, Serializable
  • 使用
// 创建TreeMap实例
TreeMap<Dog2, String> dict = new TreeMap<>();
dict.put(new Dog2("wangwang", 2, 1), "dog1");
dict.put(new Dog2("erha", 3, 1), "dog2");
dict.put(new Dog2("keji", 2, 3), "dog3");

dict.forEach((k, v) -> System.out.println(k + "-->" + v));
  • 实现原理
    • 基于二叉树的红黑数实现
    • 存储数据时, 当key 相同时,key不会变, 而value会被覆盖

(六)、JDK8中Map提供的新方法

HashMap<Integer, String> map = new HashMap<>();
// 添加
map.put(1, "张三");
// 如果不存在则添加
map.putIfAbsent(2, "李四");
map.putIfAbsent(3, "王五");
map.putIfAbsent(4, "赵六");
map.putIfAbsent(5, "王二麻子");
map.forEach((k, v) -> System.out.println(k + "->>" + v));
// 获取值, 如果不存在,则返回提供的默认值
System.out.println(map.getOrDefault(100, "null"));
/**
	// 输出结果
	1->>张三
	2->>李四
	3->>王五
	4->>赵六
	5->>王二麻子
	null
*/

System.out.println("------------------111");
// 删除值, 可以根据key来删除, 也可以根据key + value 来删除
map.remove(1); // 删除成功
// param1: 操作的key, param2: key的值
map.remove(5, "赵六"); // key为5 ,但value 不为 '赵六', 没有删除
map.forEach((k, v) -> System.out.println(k + "->>" + v));

/**
	2->>李四
	3->>王五
	4->>赵六
	5->>王二麻子
*/


System.out.println("----------------222");
// param1: 要操作的key, param2: 要替换的值
map.replace(5, "王三麻子");
// param1: 要操作的key, param2: 旧值, param3: 新值
map.replace(4, "赵六", "赵7");
map.forEach((k, v) -> System.out.println(k + "->>" + v));

/**
	2->>李四
	3->>王五
	4->>赵7
	5->>王三麻子
*/

System.out.println("--------------------333");
// param1: 要操作的key, param2: lambda表达式, 返回值就是key 的新值
map.compute(5, (k, oldV) -> oldV + "牛了b了");
map.forEach((k, v) -> System.out.println(k + "->>" + v));

/**
	2->>李四
	3->>王五
	4->>赵7
	5->>王三麻子牛了b了
*/

System.out.println("-----------------------4444");
// 如果存在则修改
map.computeIfPresent(2, (k, oldV) -> oldV + "n l b l");
map.forEach((k, v) -> System.out.println(k + "->>" + v));

/**
	2->>李四n l b l
	3->>王五
	4->>赵7
	5->>王三麻子牛了b了
*/

System.out.println("-----------------------555");
// 如果不存在则修改
map.computeIfAbsent(10, (v) -> "jbn");
map.forEach((k, v) -> System.out.println(k + "->>" + v));

/**
	2->>李四n l b l
	3->>王五
	4->>赵7
	5->>王三麻子牛了b了
	10->>jbn
*/


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值