Map集合

上一篇文章:Collection集合及其子接口

上一篇文章分享到特殊的Map集合与映射遍历,本篇文章就继续分享本人的学习:

映射

映射(map)是存储键和值间关联(即,键值对)的对象。给定一个键,可以找到其值。键和值都是对象。键必须唯一,但是值可以重复。

映射围绕两个基本操作:get()和put()。为了将值放入到映射中,使用put(),指定键和值。为了获取值,调用get(),传递键作为变元,值会被返回。具体的实现操作在上一篇结尾时有详细代码。

映射类:

常用的映射类:HashMap、LinkedHashMap、TreeMap。

映射的遍历:

映射不是集合,没有实现Collection接口。因此不能直接遍历映射。原理见下图:

 

 

Entry接口是在Map接口的内部定义的,是内部接口。

在Map中存储的元素是键-值对,一个键-值对作为一个整体使用Map.Entry接口表示。Map.Entry定义的方法:

K  getKey()

返回该映射条目的键

V  getValue()

返回该映射条目的值

V  setValue(V v)

将这个映射条目的值设置为v

为了遍历映射需要采用变通的方法:获得映射的集合视图。

使用entrySet()方法,返回包含映射中所有元素(键-值对)的Set对象

使用keySet(),返回包含映射中所有键的Set对象。

使用values(),返回包含映射中所有值的Collection对象。

对于这三个集合视图都是基于映射的,修改其中的一个集合会影响其他集合。

 

 

Map

 

是一个映射接口,Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个value。

Map中常用方法:

  1. contianskey
  2. contiansvalue
  3. isEmpty
  4. remove
  5. size
  6. clear   等等。

Map集合主要分为以下三类:

    1,HashMap

  • HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
  • HashMap 继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口。
  • HashMap 的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的。

HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”。容量 是哈希表中桶的数量,初始容量 只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。
通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。

构造器其声明如下:

classHashMap<K, V>

HashMap类定义了以下构造方法:

HashMap()

HashMap(intcapacity)  //指定容量,默认是16

HashMap(intcapacity, float fillRatio) //充填比率,0.0-1.0之间,默认0.75

HashMap(Map<?extends K, ? extends V> m)

提供的API:

void                 clear()
Object               clone()
boolean              containsKey(Object key)
boolean              containsValue(Object value)
Set<Entry<K, V>>     entrySet()
V                    get(Object key)
boolean              isEmpty()
Set<K>               keySet()
V                    put(K key, V value)
void                 putAll(Map<? extends K, ? extends V> map)
V                    remove(Object key)
int                  size()
Collection<V>        values()

继承关系:

从图中可以看出:
(01) HashMap继承于AbstractMap类,实现了Map接口。Map是"key-value键值对"接口,AbstractMap实现了"键值对"的通用函数接口。
(02) HashMap是通过"拉链法"实现的哈希表。它包括几个重要的成员变量:table, size, threshold, loadFactor, modCount。
  table是一个Entry[]数组类型,而Entry实际上就是一个单向链表。哈希表的"key-value键值对"都是存储在Entry数组中的。
  size是HashMap的大小,它是HashMap保存的键值对的数量。
  threshold是HashMap的阈值,用于判断是否需要调整HashMap的容量。threshold的值="容量*加载因子",当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。
  loadFactor就是加载因子。
  modCount是用来实现fail-fast机制的。

 

    2,LinkedHashMap

LinkedHashMap扩展了Hashmap类,该类在内部使用一个链表维护条目添加到映射中的顺序,从而可以按照插入顺序迭代整个映射。

LinkedHashMap定义了以下构造方法:

LinkedHashMap();

LinkedHashMap(intcapacity);

LinkedHashMap(intcapacity, float fillRatio);

LinkedHashMap(Map<?extends K, ? extends V> m);

    3,TreeMap

TreeMap类扩展了AbstractMap类,并实现了NavigableMap接口。该类使用树接口存储条目。TreeMap提供了有序存储键/值对的高效手段,并支持快速检索。应当注意,TreeMap确保其元素以键的升序存储。

TreeMap类定义了以下构造方法:

TreeMap( );

TreeMap(Comparator<?super K> comp);  //比较器对象作为参数

TreeMap(Map<? extendsK, ? extends V> m);

TreeMap(SortedMap<K, ?extends V> sm);

 

下面看一下HashMap集合的一个实现栗子:

System.out.println("遍历方式(映射):用映射的方式遍历,映射为Set集合");
	for (Map.Entry<String, String> m : map.entrySet()) {//映射遍历
		System.out.println(m.getKey()+","+m.getValue());
	}
//	Set<Entry<String, String>> it = map.entrySet();//等同于上一种方式(映射遍历)
//	for (Entry<String, String> entry : it) {
//		System.out.println(entry.getKey()+","+entry.getValue());
//	}
	System.out.println("---------------------------------");
	//等同于上一个方式,映射到Set集合后调用迭代器进行遍历
	Set<Entry<String, String>> se = map.entrySet();//映射,调用迭代器遍历
	Iterator<Entry<String,String>> it = se.iterator();
	while(it.hasNext()){
		System.out.println(it.next());
	}

拓展

ConcurrentHashMap在这里简单拓展一下,

1、jdk1.7 中的ConcurrrentHashMap

  1. 在jdk1.7中,ConcurrrentHashMap采用分段锁机制,实现并发的更新操作,底层采用数组 + 链表的存储结构,其核心组件是 静态内部类Segment 和 HashEntry。
  2. Segment 继承ReentrantLock 用来充当锁的角色,每个Segment 对象守护每个散列映射表的若干个桶。
  3. HashEntry 是用来封装映射表的键值对。
  4. 每个桶是由若干个HashEntry 对象链接起来的链表。

一个ConcurrentHashMap实例中包含由若干个Segment对象组成的数组,下面我们通过一个图来演示一下ConcurrentHashMap的结构:


2、在jdk1.8 中已经抛弃了分段锁的机制,采用CAS + 同步机制来保证并发更新的安全,底层采用数组+链表+红黑树的存储结构。JDK1.8版本的ConcurrentHashMap的数据结构已经接近HashMap,相对而言,ConcurrentHashMap只是增加了同步的操作来控制并发,从JDK1.7版本的ReentrantLock + Segment + HashEntry,到 JDK1.8 版本中synchronized + CAS + HashEntry + 红黑树,比较如下:

  • JDK1.8的实现降低锁的粒度,JDK1.7版本锁的粒度是基于Segment的,包含多个HashEntry,而JDK1.8锁的粒度就是HashEntry(首节点)。
  • JDK1.8版本的数据结构变得更加简单,使得操作也更加清晰流畅,因为已经使用synchronized来进行同步,所以不需要分段锁的概念,也就不需要Segment这种数据结构了,由于粒度的降低,实现的复杂度也增加了。
  • JDK1.8使用红黑树来优化链表,对于长度很长的链表的遍历是一个很漫长的过程,而红黑树的遍历效率是很快的,代替一定阈值的链表,这样形成一个最佳拍档。
  • JDK1.8为什么使用内置锁synchronized来代替重入锁ReentrantLock,以下几点:
  • 因为粒度降低了,在相对而言的低粒度加锁方式,synchronized并不比ReentrantLock差,在粗粒度加锁中ReentrantLock可能通过Condition来控制各个低粒度的边界,更加的灵活,而在低粒度中,Condition的优势就没有了。
  • JVM的开发团队从来都没有放弃synchronized,而且基于JVM的synchronized优化空间更大,使用内嵌的关键字比使用API更加自然。在大量的数据操作下,对于JVM的内存压力,基于API的ReentrantLock会开销更多的内存,虽然不是瓶颈,但是也是一个选择依据。

 

 

 

感谢材料提供:

https://blog.csdn.net/weixin_43352448/article/details/87947105 

https://www.cnblogs.com/skywang12345/p/3310835.html

 

其他相关Map集合的实现栗子:点击这里

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值