---
title: "Solidity 中的 mapping 是什么？"
description: "了解 Solidity 中 mapping 的工作原理"
---

# Solidity 中的 mapping 是什么？

[Solidity](https://www.alchemy.com/overviews/solidity) 中的映射（mapping）是以键值对形式存储数据的哈希表，其中键可以是 Ethereum 支持的任意内置数据类型。映射是[学习 Solidity 开发](https://www.alchemy.com/overviews/learn-solidity)时需要理解的基础概念。

本文将介绍映射是什么、映射如何工作、映射与数组的区别，并给出映射的示例，帮助你在 Ethereum 及 Optimism、Arbitrum 等兼容 Solidity 的区块链上开发出更好的智能合约。

## **什么是哈希表？**

哈希表是一种以关联方式存储数据的数据结构。哈希表中的数据以数组形式保存，每个数据值都有自己唯一的索引值。哈希表使用数组作为存储介质，并利用哈希技术建立索引，用于插入或定位元素。

<ImageBlock
  src="https://media.alchemy.com/1703922693-mapping-in-solidity.png"
  alt="Mappings in Solidity Explained by Doug Crescenzi"
  width={429}
  height={329}
  caption="来源：Mappings in Solidity Explained by Doug Crescenzi"
/>

当所需数据的索引已知时，可以极快地返回该数据。因此，无论数据量多大，哈希表的插入和查找操作都非常快速。

## **Solidity 中的映射是什么？**

映射是 Solidity 中的一种哈希表，以键值对形式存储数据，其中键可以是除引用类型之外的任意内置数据类型，值的数据类型则可以是任意类型。

在 Solidity 和 Ethereum 区块链中，映射最常见的用途是将唯一的 Ethereum 地址关联到对应的值类型。

在其他编程语言中，映射相当于字典（dictionary）。

### 哈希表与 Solidity 映射有什么区别？

映射的作用类似于哈希表，由键类型与对应的值类型组成键值对，其价值在于可以容纳大量的 **_KeyType_** 到 **_ValueType_** 的映射关系。映射没有长度，也没有设置键或值这一概念。映射只适用于作为存储引用类型的状态变量。映射初始化时，会包含所有可能的键，并将其映射到字节表示全为零的值。

Solidity 中定义映射的方式与定义其他变量类型相同：

### Solidity 数组与映射有什么区别？

[**Solidity 数组**](https://www.alchemy.com/overviews/solidity-arrays)更适合遍历一组数据（例如使用 for 循环），而映射则更适合在已知键的情况下获取值（即无需遍历数据）。

由于在 Solidity 中遍历数组的成本可能比从映射中获取数据更高，而开发者有时既需要在智能合约中存储值，也需要存储对应的键，因此开发者有时会创建一个键的数组，作为引用，之后再从映射中检索对应数据。

开发者不应让 Solidity 中的数组增长过大，因为遍历一个大数组所耗费的 Solidity gas 费用可能超过交易本身的价值，这使得映射成为更[节省 gas 的智能合约实现方式](https://www.alchemy.com/overviews/solidity-gas-optimization)。

**关于映射，还有以下一些特性：**

- 映射没有长度。
- 映射也不存在“设置”键或值这一概念。
- 映射只能用于作为存储引用类型的状态变量。

## **什么是嵌套映射？**

嵌套映射是指从一个映射映射到另一个映射。例如，如果我们有用户名和年龄信息，并希望借助一个特殊的 ID 来存储这些信息，使得其他人只能通过该 ID 获取这些信息，这在 Solidity 中被称为双重映射。

下面是一个嵌套映射的示例：

在这个合约中，我们构建了一个嵌套映射，称为 User。在该映射中，我们关联了两个映射：

1. 一个用于记录特定用户 id 的相关信息；
1. 一个用于存储特定用户的姓名和年龄。

下面的代码块是一个简单的 getter 函数，用于返回用户的信息。

## **如何在 Solidity 中使用映射**

以下是在 Solidity 中使用映射的示例。以下代码片段的功能是：

- 从 address 映射到 uint，并确保映射始终返回一个值
- 如果该值从未被设置过，则返回默认值。
- 更新映射地址对应的值
- 将值重置为默认值。
- 创建从 address 到另一个映射的嵌套映射
- 即使嵌套映射未初始化，也能从中获取值

## **Solidity 映射示例**

以下是 Solidity 中映射的三个示例：

1. ERC20 代币余额
1. 使用布尔逻辑
1. 查询 DAO 成员

### **1. ERC20 用户余额**

此代码片段将用户地址映射到其地址对应的 ERC20 余额。

### **2. Solidity 布尔类型映射示例**

此代码片段旨在列出候选人名单，并返回每位候选人获得的票数。此示例在 [DAO](https://www.alchemy.com/dapps/top/daos) 中有实际应用场景，成员需要对组织事务进行投票。

### **3. 是否为成员（DAO）**

此示例来自 Dominion DAO 智能合约，其中映射了 _raisedProposals_、_stakeholderVotes_、_votedOn_、_contributors_ 和 _stakeholders_。

此代码示例列出了两个 [Solidity 结构体](https://www.alchemy.com/overviews/solidity-struct)的字段：_ProposalStruct_ 和 _VotedStruct_。

## **Solidity 字符串映射**

为了更好地理解，我们在构建映射的过程中尝试添加一些值。在以下示例中，我们将：

- 创建一个合约
- 定义一个结构体
- 声明不同的结构体元素
- 创建一个映射
- 向映射中添加值

## **Solidity 映射常见问题**

以下是关于 Solidity 映射的一些常见问题：

1. Solidity 映射的长度是多少？
1. Solidity 映射的默认值是什么？
1. 如何公开查看 Solidity 映射？

### **Solidity 映射的长度是多少？**

**映射没有长度。键的数据本身并不保存在映射中，而是使用其 keccak256 哈希值**来存储该键所指向的值。因此不存在单独意义上的“键”和“值”这一概念。

### **Solidity 映射默认值类型是什么？**

以下是 Solidity 映射的默认值类型：

- **int/uint** - 键类型 = 是；值类型 = 是
- **string** - 键类型 = 是；值类型 = 是
- **byte/bytes** - 键类型 = 是；值类型 = 是
- **address** - 键类型 = 是；值类型 = 是
- **struct** - 键类型 = 否；值类型 = 是
- **mapping** - 键类型 = 否；值类型 = 是
- **enum** - 键类型 = 否；值类型 = 是
- **contract** - 键类型 = 否；值类型 = 是
- **fixed-sized array** - 键类型 = 是；值类型 = 是
- **dynamic-sized array** - 键类型 = 否；值类型 = 是
- **multi-dimensional array** - 键类型 = 否；值类型 = 是
- **variable** - 键类型 = 否；值类型 = 否

### **如何公开查看 Solidity 映射？**

由于[该属性是 public 的](https://www.alchemy.com/overviews/solidity-function-visibility)，你可以使用 **Solidity 编译器**生成的 getter 函数来访问它。
