java中的Map接口及相关实现类

105 阅读7分钟

本文已参与[新人创作礼]活动,一起开启掘金创作之路.

本文档由该视频整理而来:视频链接

Map的常用方法

V put(K key, v value)向Nap集合中添加键值对
get(object key)通过key获取value
void clear()清空Map集合
boolean containsKey(Object key)判断Map中是否包含某个key
boolean containsValue(Object value)判断Map中是否包舍某个value
boolean isEmpty()判断vap集合中元素个数是否为0
Set<K> keySet()获取Map集合所有的key (所有的键是一个set集合)
remove(Object key)通过key删除键值对
int size()获取Map集合中键值对的个数
Collection<V> values()获取Map集合中所有的value ,返回一个collection
Set<Map.Entry<K, V>> entrySet()将Map集合转换成set集合(将key和value放在一起,作为set的一个元素)

遍历Map

  1. 第一种方式:获取所有的key,通过遍历key(迭代器遍历或者foreach遍历)来遍历value
  2. 第二种方式: Set<Map.Entry<K,V>> entrySet() 以上这个方法是把Map集合亘接全部转换感Set集合。Set集合中元素的类型是:Map.Entry。 遍历Set中的每一个Node,从Node对象中调用getKey和getValue方法取出Key和Value即可。 第二种方式效率比较高。

HashMap

  1. HashMap集合底层是哈希表/散列表的数据结构。
  2. 哈希表是一个怎样的数据结构呢? 哈希表/散列表:一维数组,这个数组中每一个元素是一个单向链表。(数组和链表的结合体。)
  3. HashMap集合底层的源代码:
public class HashMap{
	//HashMap底层实际上就是一个数组。(一维数组)
	Node<K,V>[] table;

	//静态的内部类HashMap.Node
	static class Node<K,V> {
		final int hash;//哈希值(哈希值是key的hashCode()方法的执行结果。hash值通过哈希函数/算法,可以得到数组下标。)
		final K key; 	//存储到Map中的Key
		V value;		//存储到Map中的Value
		Node<K,V> next;		//下一个节点的内存地址
	}
}
  1. HashMap集合的默认初始化容量是16,默认加载因子是0.75,扩容为原来2倍 这个默认加载因子是当HashMap集合底层数组的容量达到75%的时候,数组开始扩容
  2. HashMap集合初始化容量必须是2的倍数,这也是官方推荐的,这是因为达到散列均匀,为了提高HashMap集合的存取效率,所必须的。
  3. HashMap中,当一个链表上的元素个数超过8,则会将链表转换为红黑树;如果元素个数小于6,会将红黑树转换为链表结构
  4. HashMap中,同一个链表上的元素的key的hash值可能不相同(哈希碰撞),但是hash值相同的元素,一定在同一个链表上。
  5. key和value可以为null,但是HashTable(不常用)的key和value不能为null。

HashMap的V put(K key, V value)方法

  1. 先将k,v封装到Node对象当中。
  2. 底层会调用k的hashCode0方法得出hash值,然后通过哈希函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node添加到这个位置上了。如果说下标对应的位置上有链表,此时会拿着k和链表上每一个节点中的k进行equals ,如果所有的equals方法返回都是false,那么这个新节点将会被添加到链表的末尾。如果其中有一个equals返回了true ,那么这个节点的value将被会覆盖。

HashMap的V get(Object key)方法

先调用k的hashCode方法得出哈希值,通过哈希算法转换成数组下标,通过数组下标快速定位到某个位置上,如果这个位置上什么也没有,返回null。如果这个位置上有单向链表,那么会拿着参数k和单向链表上的每个节点中的k进行equals ,如果所有equals方法返回false ,那么get方法返回null,只要其中有一个节点的k和参数k进行equals的时候返回true,那么此时这个节点的value就是我们要找的value , get方法最终返回这个要找的value.

HashMap的key需要重写两个方法

可以得出HashMap集合的key ,会先后调用两个方法,一个方法是hashCode,一个方法是equals,那么这两个方法都需要重写。 注: 由于HashSet内部通过HashMap来实现,所以HashSet也需要重写这两个方法。 我们可以通过IDE来自动生成这两个方法。

HashMap中的Key的特点

HashMap集合的key部分特点:无序,不可重复。 为什么无序?因为不一定挂到哪个单向链表上。 不可重复是怎么保证的? equals方法来保证HashMap集合的key不可重复。如果key重复了,value会覆盖。

为什么哈希表的随机增删,以及查询效率都很高?

增删是在链表上完成。 查询也不需要都扫描,只需要部分扫描。

哈希表HashMap使用不当时无法发挥性能!

假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了纯单向链表。这种情况我们成为:散列分布不均匀。 什么是散列分布均匀? 假设有100个元素,10个单向链表,那么每个单向链表上有10个节点,这是最好的,是散列分布均匀的。 假设将所有的hashCode()方法返回值都设定为不一样的值,可以吗,有什么问题? 不行,因为这样的话导致底层哈希表就成为一维数组了,没有链表的概念了。也是散列分布不均匀。 散列分布均匀需要你重写hashcode( )方法时有一定的技巧。

如果一个类的equals方法重写了,那么hashCode()方法必须重写。

**注:**如果两个对象equals方法返回true,则hashCode方法返回值必须相同。如果两个对象equals方法返回false,则hashCode方法返回值可能相同也可能不相同。 我们可以这样理解:equals方法返回true表示两个对象相同,在同一个单向链表上比较。那么对于同一个单向链表上的节点来说,他们的哈希值都是相同的。

Properties

  1. Properties是一个Map集合,继承Hashtable , Properties的key和value都是string类型。
  2. Properties被称为属性类对象。
  3. Properties是线程安全的。
  4. setProperty和getProperty可以存取元素。

TreeSet

  1. TreeSet集合底层实际上是一个TreeMap,TreeMap集合底层是一个二叉树。
  2. 放到TreeSet集合中的元素,等同于故到TreeMap集合key部分了。
  3. TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序。称为:可排序集合。
  4. 要实现TreeSet中元素的比较,有两种方法。 第一种方法是放在TreeSet中的类需要实现Comparable接口,并实现compareTo方法。equals方法可以不重写。 第二种方法是实现Comparator类,重写compare方法。并在TreeSet的构造方法中传递Comparator接口实现类的对象。这里往往使用匿名内部类的方式实现。 那么我们如何两个比较方法呢? 当比较规则不会发生改变的时候,或者说当比较规则只有1个的时候,建议实现comparable接口。 如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口。