主要围绕3个方面说明runtime-Associated Objects (关联对象)

1. 使用场景

2.如何使用

3.底层实现

3.1  实现原理

3.2 关联对象被存储在什么地方,是不是存放在被关联对象本身的内存中?

3.3 关联对象的五种关联策略有什么区别,有什么坑?

3.3 关联对象的生命周期是怎样的,什么时候被释放,什么时候被移除?

  • 本文所使用的源码为 objc4-723

1. 使用场景

我们知道哈,在 Objective-C 中可以通过 Category 给一个现有类/系统类添加方法,但是Objective-C与Swift 不同的是不能添加实例变量, 然而我们可以通过runtime - Associated Objects(关联对象) 来弥补这一不足.

2. 如何使用

思路其实很简单, 都知道在 Objective-C里面类的属性其实在编译的时候, 编译器都会转换成setter/getter方法, 所以我们就直接声明/实现属性的setter/getter方法,  我们来看看代码怎么写吧.

2.1 相关函数

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy); // 为object对象关联以key为键value值, 传入 nil 则可以移除已有的关联对象
id objc_getAssociatedObject(id object, const void *key); // 读取object对象key键对应的值
void objc_removeAssociatedObjects(id object); // 移除object关联所有对象

2.1.1 key 值

关于前两个函数中的 key 值是我们需要重点关注的一个点,这个 key 值必须保证是一个唯一常量。一般来说,有以下三种推荐的 key 值:

. 声明 static char kAssociatedObjectKey; // 使用 &kAssociatedObjectKey 作为 key 值;
. 声明 static void *kAssociatedObjectKey = &kAssociatedObjectKey; // 使用 kAssociatedObjectKey 作为 key 值;
. 用 selector; // 使用 getter/setter 方法的名称作为 key 值, 还可以省一个变量.

那种写法都可以, 看个人喜好

2.1.2 关联策略  policy

关联策略                                   等价属性                                                说明
OBJC_ASSOCIATION_ASSIGN @property (assign) or @property (unsafe_unretained) 弱引用关联对象
OBJC_ASSOCIATION_RETAIN_NONATOMIC @property (strong, nonatomic) 强引用关联对象,且为非原子操作
OBJC_ASSOCIATION_COPY_NONATOMIC @property (copy, nonatomic) 复制关联对象,且为非原子操作
OBJC_ASSOCIATION_RETAIN @property (strong, atomic) 强引用关联对象,且为原子操作
OBJC_ASSOCIATION_COPY @property (copy, atomic) 复制关联对象,且为原子操作

2.2 如何使用

下面我们对NSString 进行关联一个 tempValue 属性

声明 NSString+TTExtension.h 文件

#import <Foundation/Foundation.h>

@interface NSString (TTExtension)

- (NSString *)tempValue; // getter

- (void)setTempValue:(NSString *)tempValue; // setter

@end

实现 NSString+TTExtension.m 文件

#import "NSString+TTExtension.h"
#import <objc/runtime.h> static char dm_associated_key_id; @implementation NSString (TTExtension) - (NSString *)tempValue {
return objc_getAssociatedObject(self, &dm_associated_key_id);} - (void)setTempValue:(NSString *)tempValue {
objc_setAssociatedObject(self, &dm_associated_key_id, tempValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
} @end

3. 底层实现

3.1 实现原理

3.1.1 objc_setAssociatedObject 设置关联对象

我们可以在 objc-runtime.mm 文件中找到 objc_setAssociatedObject 函数, 我们会发现这个函数内部调用了 _object_set_associative_reference 函数

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}

接着可以在 objc-references.mm 文件中找到 _object_set_associative_reference 函数

/**
* 关联引用对象
** Objective-C: objc_setAssociatedObject 底层调用的是 objc_setAssociatedObject_non_gc(有的版本是直接调用object_set_associative_refrence)
** 底层: objc_setAssociatedObject_non_gc调用了_object_set_associative_reference
* object : 关联对象
* key : 可以认为是一个属性名
* value : 值
* policy : 关联策略
*/
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
ObjcAssociation old_association(, nil); // 创建一个关联
id new_value = value ? acquireValue(value, policy) : nil; // acquireValue判断关联策略类型
{
AssociationsManager manager; // 用 mutex_t(互斥锁)维护的AssociationsHashMap哈希表映射; 哈希表可以认为就是个dictionary
AssociationsHashMap &associations(manager.associations()); // 获取到所有的关联对象
disguised_ptr_t disguised_object = DISGUISE(object); // 把object对象包装成disguised_ptr_t类型
if (new_value) {
// break any existing association.
// 遍历所有的被关联对象查找 object 对象对应的被关联对象
AssociationsHashMap::iterator i = associations.find(disguised_object);
/// end()返回指向map末尾的迭代器, 并非最优一个元素. 如果想获取最后一个元素需要last=end()之后last--,last就是最后一个元素
if (i != associations.end()) { // i != (末尾迭代器) 代表object之前有过关联
// secondary table exists
ObjectAssociationMap *refs = i->second; // 获取键值对i对应的value值, ObjectAssociationMap哈希表映射
ObjectAssociationMap::iterator j = refs->find(key); // 遍历所有的关联策略查找 key 对应的关联策略
if (j != refs->end()) { // 存在
old_association = j->second; // 缓存之前的值, 在下面会release这个值
j->second = ObjcAssociation(policy, new_value); // 把{(}最新值&关联策略}更新
} else { // 不存在
(*refs)[key] = ObjcAssociation(policy, new_value); // 不存在直接根据key赋值{值&关联策略}
}
} else { // 不存在, 代表当前对象没有关联过对象, 所以直接创建ObjectAssociationMap储存{值&关联策略}
// create the new association (first time).
ObjectAssociationMap *refs = new ObjectAssociationMap; // 创建ObjectAssociationMap
associations[disguised_object] = refs; // 储存 ObjectAssociationMap
(*refs)[key] = ObjcAssociation(policy, new_value); // 创建关联策略
// 储存关联策略
object->setHasAssociatedObjects(); // data()->flags & RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS;
}
} else {
// setting the association to nil breaks the association.
// 代表赋值的为nil
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) { // 存在, 代表之前关联过
ObjectAssociationMap *refs = i->second; // 找到 对象对应的ObjectAssociationMap
ObjectAssociationMap::iterator j = refs->find(key); // 遍历查找是否有过赋值
if (j != refs->end()) { // 如果有需要释放之前的对象
old_association = j->second; // 缓存之前的value对象, 后期释放
refs->erase(j); // 移除j元素
}
}
}
}
// release the old value (outside of the lock).
if (old_association.hasValue()) ReleaseValue()(old_association); // release之前的关联对象对应的value
}

备注:  win32不考虑

AssociationsManager

一个维护全部关联记录单例类.

// 一个单例类, 维护全部的关联内容
class AssociationsManager {
// associative references: object pointer -> PtrPtrHashMap.
static AssociationsHashMap *_map; // 静态的说明维护所有的对象的关联对象
public:
AssociationsManager() { AssociationsManagerLock.lock(); } // mutex_t 互斥锁: 加锁
~AssociationsManager() { AssociationsManagerLock.unlock(); } // mutex_t 互斥锁: 解锁 AssociationsHashMap &associations() { // 是一个无序的哈希表, 维护了从对象地址到 ObjectAssociationMap 的映射; 我们发现了它还是一个懒加载的对象
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
}; AssociationsHashMap *AssociationsManager::_map = NULL; // 全局的说明所有的关联是一个单利对象管理

AssociationsHashMap

无序 hash 表,key 是被关联对象的地址. value 是 ObjectAssociationMap.

// 可以理解为所有被关联对象的全部关联记录;
class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};

ObjectAssociationMap

也是一个无序 hash 表,key 是关联对象设置的 key, value 是 ObjcAssociation

// 可以理解为一个被关联对象的所有关联对象
class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};

ObjcAssociation

1个关联对象的封装,里面维护了关联对象的 value 和 关联策略

//一个关联对象的封装
class ObjcAssociation {
uintptr_t _policy; // 关联策略
id _value; // 关联值
public:
ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
ObjcAssociation() : _policy(), _value(nil) {} uintptr_t policy() const { return _policy; }
id value() const { return _value; } bool hasValue() { return _value != nil; } // 关联对象是否有值
};

几个类的 大致关系

objc_setAssociatedObject(object, kAssociatedKey, value, policy);
AssociationsManager -> {
lock // 锁保证数据的安全 AssociationsHashMap -> { // 无序 hash 表
&object = ObjectAssociationMap -> { // 无序 hash 表
kAssociatedKey = ObjcAssociation - > { // 关联对象的封装
value,
policy
}
}
} unlock
}

3.1.2 objc_getAssociatedObject

我们可以在 objc-runtime.mm 文件中找到 objc_getAssociatedObject 函数, 我们会发现这个函数内部调用了_object_get_associative_reference 函数

id objc_getAssociatedObject(id object, const void *key) {
return _object_get_associative_reference(object, (void *)key);
}

接着在 objc-references.mm 文件中找到_object_get_associative_reference 函数

/** 不多解释. 看完_object_set_associative_reference看这个很简单
* 关联引用对象
** Objective-C: objc_getAssociatedObject 最终会调用 _object_get_associative_reference来获取关联对象。
** 底层: _object_get_associative_reference
* object : 被关联对象
* key : 可以认为是一个属性名
*/
id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
ObjcAssociation &entry = j->second;
value = entry.value();
policy = entry.policy();
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
objc_retain(value);
}
}
}
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
objc_autorelease(value);
}
return value;
}

3.1.3 objc_removeAssociatedObjects

在 objc-runtime.mm 文件中找到 objc_removeAssociatedObjects 函数, 我们会发现这个函数内部调用了_object_remove_assocations 函数

void objc_removeAssociatedObjects(id object)
{
if (object && object->hasAssociatedObjects()) { // 判断当前对象是否有关联对象
_object_remove_assocations(object);
}
}

在 objc-references.mm 文件中找到_object_remove_assocations 函数

/**
* 移除关联对象
** Objective-C: objc_removeAssociatedObjects 底层调用的是 _object_remove_assocations
** 底层: _object_remove_assocations
* object : 被关联对象
*/
void _object_remove_assocations(id object) {
// 全部的关联策略
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
if (associations.size() == ) return; // map中元素的个数为0直接return
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// copy all of the associations that need to be removed.
ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
// remove the secondary table.
delete refs; // 释放refs内存
associations.erase(i); // 从associations 移除object
}
}
// the calls to releaseValue() happen outside of the lock.
// 遍历移除被关联对象中所有的 AssociationsHashMap 对象,并且对全部对象release
for_each(elements.begin(), elements.end(), ReleaseValue());
}

3.2 关联对象被存储在什么地方,是不是存放在被关联对象本身的内存中?

其实看过AssociationsManager 类之后你就已经知道答案了, 关联的对象都存放在一个维护关联的单例类里

 
// 一个单例类, 维护全部的关联内容
class AssociationsManager {
// associative references: object pointer -> PtrPtrHashMap.
static AssociationsHashMap *_map; // 静态的说明维护所有的对象的关联对象
public:
AssociationsManager() { AssociationsManagerLock.lock(); } // mutex_t 互斥锁: 加锁
~AssociationsManager() { AssociationsManagerLock.unlock(); } // mutex_t 互斥锁: 解锁 AssociationsHashMap &associations() { // 是一个无序的哈希表, 维护了从对象地址到 ObjectAssociationMap 的映射; 我们发现了它还是一个懒加载的对象
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
}; AssociationsHashMap *AssociationsManager::_map = NULL; // 全局的说明所有的关联是一个单利对象管理

3.3 关联对象的五种关联策略为什么与底层的策略不一样?

这个是关联对象暴露的策略

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = , // 0000 0000
OBJC_ASSOCIATION_RETAIN_NONATOMIC = , // 0000 0001
OBJC_ASSOCIATION_COPY_NONATOMIC = , // 0000 0011
// 我们只看后8位, 从第0位开始算
OBJC_ASSOCIATION_RETAIN = , // 0111 1001
OBJC_ASSOCIATION_COPY = // 0111 1011
};

这个是底层的策略

// 有人很好奇怎么跟 objc_AssociationPolicy 不一样,其实objc_AssociationPolicy是根据NONATOMIC把runtime的OBJC_ASSOCIATION_SETTER 细分了, 仔细研究你就会发现objc_AssociationPolicy 与OBJC_ASSOCIATION_SETTER 有很大的联系的

enum {
OBJC_ASSOCIATION_SETTER_ASSIGN = ,
OBJC_ASSOCIATION_SETTER_RETAIN = ,
OBJC_ASSOCIATION_SETTER_COPY = , // NOTE: both bits are set, so we can simply test 1 bit in releaseValue below.
OBJC_ASSOCIATION_GETTER_READ = ( << ),
OBJC_ASSOCIATION_GETTER_RETAIN = ( << ),
OBJC_ASSOCIATION_GETTER_AUTORELEASE = ( << )
};

我们可以看出下面代码在判断关联策略的时候都是 & 0xFF

/**
* 判断关联值类型
* value : 值
* policy : 关联策略
*/
static id acquireValue(id value, uintptr_t policy) {
switch (policy & 0xFF) {
case OBJC_ASSOCIATION_SETTER_RETAIN:
return objc_retain(value); // retain value
case OBJC_ASSOCIATION_SETTER_COPY:
return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy); // copy object
}
return value;
}

而 objc_AssociationPolicy & 0xFF之后 再 & OBJC_ASSOCIATION_SETTER_*  会怎么样呢请看

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = , // 0000 0000 & 1111 1111 = 0000 0000
OBJC_ASSOCIATION_RETAIN_NONATOMIC = , // 0000 0001 & 1111 1111 = 0000 0001 & OBJC_ASSOCIATION_SETTER_RETAIN(0000 0001) = 0000 0001
OBJC_ASSOCIATION_COPY_NONATOMIC = , // 0000 0011 & 1111 1111 = 0000 0011 & OBJC_ASSOCIATION_SETTER_COPY (0000 0011) = 0000 0011
// 我们只看后8位, 从第0位开始算
OBJC_ASSOCIATION_RETAIN = , // 0111 1001 & 1111 1111 = 0111 1001 & OBJC_ASSOCIATION_SETTER_RETAIN(0000 0001) = 0000 0001
OBJC_ASSOCIATION_COPY = // 0111 1011 & 1111 1111 = 0111 1011 & OBJC_ASSOCIATION_SETTER_COPY (0000 0011) = 0000 0011
};

& 0xFF 之后我们会发现

OBJC_ASSOCIATION_RETAIN_NONATOMIC 和 OBJC_ASSOCIATION_RETAIN 其实最后 再 & OBJC_ASSOCIATION_RETAIN_NONATOMIC 居然就是 OBJC_ASSOCIATION_RETAIN_NONATOMIC, 所以虽然不一样但是其实是有内在联系的

而 OBJC_ASSOCIATION_COPY_NONATOMIC 和 OBJC_ASSOCIATION_COPY 其实也是一样的

OBJC_ASSOCIATION_GETTER_* 就不解释了其实也一样, 只不过我还没找到在什么位置用到, 如果你知道请留言

3.3 关联对象的生命周期是怎样的,什么时候被释放,什么时候被移除?

这个问题我们就得看看一个对象对销毁之后调用了上面, 都知道对象销毁会调用 - (void)dealloc {} 方法, 那么我们就看看runtime原码是怎么调用的

我们可以在 NSObject.mm  文件中找到 dealloc 函数, 可是dealloc 又调用了_objc_rootDealloc 函数

- (void)dealloc {
_objc_rootDealloc(self);
}

_objc_rootDealloc 又调用了 对象内部的rootDealloc方法

// 释放内存, dealloc发起
void
_objc_rootDealloc(id obj)
{
assert(obj); obj->rootDealloc(); // objc-object.h
}
 我们可以在 objc-object.h  文件中找到对象的实现方法 rootDealloc, 方法内部又调用object_dispose函数

// 释放内存
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return;
object_dispose((id)this); // objc-runtime-new.m
}
在 objc-runtime-new.m 文件中找到object_dispose 函数,函数内部又调用 objc_destructInstance 函数

void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects(); // This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj); // 移除关联对象, 释放value
obj->clearDeallocating();
} return obj;

1波三折, 大量的搜索之后我们终于找到了答案, 其实对象销毁前 最终会调用objc_destructInstance 函数, 其内部做了判断如果有关联对象会调用 _object_remove_assocations函数, 而_object_remove_assocations 函数内部把被关联对象的所有关联对象值对象 进行release 并且释放销毁当前被关联对象.

就写到这里, 如果那里写的不对请留言指正, 我们一起共同进步, 谢啦

相关参考技术blog

C++ : https://www.cnblogs.com/fnlingnzb-learner/p/5833051.html

其他有关Associated Objects文章:

https://www.jianshu.com/p/48b1b04d0b87

 

Runtime - Associated Objects (关联对象) 的实现原理的更多相关文章

  1. iOS Category 添加属性实现原理 - 关联对象

    iOS Category 添加属性实现原理 - 关联对象 RunTime为Category动态关联对象 使用RunTime给系统的类添加属性,首先需要了解对象与属性的关系.对象一开始初始化的时候其属性 ...

  2. OC - runtime 之关联对象

    header{font-size:1em;padding-top:1.5em;padding-bottom:1.5em} .markdown-body{overflow:hidden} .markdo ...

  3. iOS开发--Runtime的简单使用之关联对象

    一.Runtime关联对象的方法简介: 在<objc/runtime.h>中,有三个关联的方法,分别是: objc_setAssociatedObject objc_getAssociat ...

  4. Runtime之成员变量&属性&关联对象

    上篇介绍了Runtime类和对象的相关知识点,在4.5和4.6小节,也介绍了成员变量和属性的一些方法应用.本篇将讨论实现细节的相关内容. 在讨论之前,我们先来介绍一个很冷僻但又很有用的一个关键字:@e ...

  5. AssociatedObject关联对象原理实现

    介绍 关联对象(AssociatedObject)是Objective-C 2.0运行时的一个特性,允许开发者对已经存在的类在扩展中添加自定义的属性.在实际生产过程中,比较常用的方式是给分类(Cate ...

  6. Runtime - 关联对象使用方法及注意点

    大家都知道在分类里,可以间接的添加属性,运用runtime关联对象. 如下图,只是声明了btnClickedCount的set, get方法而已 并没有生成_btnClickedCount 成员变量, ...

  7. 【OC底层】AssociatedObject 关联对象

    如何实现给分类“添加成员变量”? 默认情况下,因为分类底层结构的限制,不能添加成员变量到分类中.但可以通过关联对象来间接实现 关联对象提供了以下API 1> 添加关联对象 void objc_s ...

  8. Effective Objective-C 2.0 — 第10条:在既有类中使用关联对象存放自定义数据

    可以通过“关联对象”机制来把两个对象连起来 定义关联对象时可指定内存管理语义,用以模仿定义属性时所采用的“拥有关系”与“非拥有关系” 只有在其他做法不可行时才应选用关联对象,因为这种做法通常会引入难于 ...

  9. ios 关联对象运用 objc_setAssociatedObject

    点按钮的时候,给alertView添加一个关联对象(被点击这个按钮), objc_setAssociatedObject(alert, &kRepresentedObject, sender, ...

随机推荐

  1. hdu 5195 DZY Loves Topological Sorting (拓扑排序+线段树)

    DZY Loves Topological Sorting Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 131072/131072 ...

  2. MT【153】缩小包围圈

    (清华2017.4.29标准学术能力测试3) 集合$S=\{1,2,\cdots,25\}$,$A\subseteq S$,且$A$ 的所有子集中元素之和不同.则下列选项正确的有(      ) A. ...

  3. 【刷题】LOJ 2480 「CEOI2017」One-Way Streets

    题目描述 给定一张 \(n\) 个点 \(m\) 条边的无向图,现在想要把这张图定向. 有 \(p\) 个限制条件,每个条件形如 \((xi,yi)\) ,表示在新的有向图当中,\(x_i\) 要能够 ...

  4. 【刷题】BZOJ 3626 [LNOI2014]LCA

    Description 给出一个n个节点的有根树(编号为0到n-1,根节点为0).一个点的深度定义为这个节点到根的距离+1. 设dep[i]表示点i的深度,LCA(i,j)表示i与j的最近公共祖先. ...

  5. 安装GourdScanV2的踩坑过程

    环境:ubuntu 16.04.1 1.安装dcoker sudo apt-get install docker.io 坑:sudo apt-get install docker 2.下载关于dock ...

  6. Redis学习 - 主从拷贝

    Redis主从拷贝的特点 同一个Master可以拥有多个Slaves. Master下的Slave还可以接受同一架构中其它slave的链接与同步请求,实现数据的级联复制,即Master->Sla ...

  7. Java之Stream流

    Stream流的初步学习 初次学习Stream流的学习笔记,学习之前先了解一下函数式接口 概述 API是一个程序向使用者提供的一些方法,通过这些方法就能实现某些功能.所以对于流API来 说,重点是怎么 ...

  8. 解题:NOI 2009 管道取珠

    题面 考虑这个平方的实际意义,实际是说取两次取出一样的序列 那么设$dp[i][j][k][h]$表示第一次在上面取$i$个下面取$j$个,第二次在上面取$k$个下面取$h$个的方案数 等等$n^4$ ...

  9. 【bzoj4231】回忆树

    题解: 树上的串匹配,模式串的总长$|S|$,令$\overline {S} $为$S$的反串: 对$S$和$\overline {S} $分别建自动机 $u -> v$可以分成三个部分去统计 ...

  10. java基础-Map集合

    java基础-Map集合 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Map集合概述 我们通过查看Map接口描述,发现Map接口下的集合与Collection接口下的集合,它 ...