本文详细介绍了Solidity中的映射数据结构,包括映射的定义、工作原理及其与数组的区别。作者提供了丰富的代码示例,以帮助开发者更好地理解如何在以太坊及Solidity兼容区块链上实现映射,特别是在智能合约开发中的应用。
在 Solidity 中,映射是存储数据的哈希表,数据以键值对的形式存储,其中键可以是 Ethereum 支持的任何内置数据类型。理解映射是学习 Solidity 开发 的一个基本概念。
本文解释了映射是什么、映射如何工作、映射和数组之间的区别,并提供了映射的示例,以便你能够在 Ethereum 及其支持的区块链(如 Optimism 和 Arbitrum)上开发最佳智能合约。
哈希表是一种以关联方式存储数据的数据结构。数据以数组格式保存在哈希表中,每个数据值都有其唯一的索引值。 哈希表使用数组作为存储介质,并采用哈希技术来建立索引,从中可以插入或定位元素。
来源: Solidity 中的映射解释图
当需要数据的索引已知时,可以非常迅速地返回该数据。因此,哈希表是一种数据结构,在其中插入和搜索操作都非常快速,无论数据量的大小如何。
映射是 Solidity 中的哈希表,存储为键值对,键可以是任何内置数据类型(不包括引用类型),值的数据类型可以是任何类型。
映射通常在 Solidity 和 Ethereum 区块链中用于将唯一的 Ethereum 地址连接到相应的值类型。
在其他编程语言中,映射相当于一个字典。
映射作为哈希表运行,具有键类型和相应的值类型对,映射的价值在于它们可以容纳大量的 _KeyTypes 到 _ValueTypes。映射没有长度,也没有设置键或值的概念。映射仅适用于作为存储引用类型的状态变量。当映射被初始化时,它们包含所有可能的键,并映射到其字节表示全部为零的值。
映射在 Solidity 中的定义与任何其他变量类型相同:
mapping(address => uint) public userLevel;
Solidity 数组 在迭代数据组(例如,使用 for 循环)方面更好,而映射在能够根据已知键获取值时更好(即不需要遍历数据)。
由于在 Solidity 中遍历数组可能比从映射获取数据更昂贵,开发者可能希望在智能合约中同时存储值和其键,因此开发者有时会创建一个键的数组,以作为能从映射中检索数据的引用。
开发者不应让 Solidity 中的数组增长过大,因为遍历一个大型数组可能会导致 Solidity Gas费用高于交易值,从而使映射成为更 Gas高效的智能合约实现。
以下是一些关于映射的附加特性:
映射没有长度。
映射也不理解设置键或值的概念。
映射只能用于作为存储引用类型的状态变量。
嵌套映射是从一个映射到另一个映射。 例如,如果我们有一个用户名和年龄,并希望借助一个特殊的 ID 来存储此信息,以便他人只能通过该 ID 获取,这在 Solidity 中称为 双映射。
这是一个嵌套映射的示例:
pragma solidity >=0.7.0 <0.9.0;
contract Nestmap
{
mapping(uint256=>mapping(string=>uint256)) public User;
function adduser(uint256 _Id,string memory _name, uint256 _age) public {
User[_Id][_name] = _age;
}
}
在这个合约中,我们构建了一个嵌套映射,称为 User。在这个映射中,我们连接了两个映射:
一个用于记录特定用户的 id 信息
一个用于存储特定用户的姓名和年龄。
下面的代码块是一个简单的获取器函数,返回用户的信息。
function user(uint256 _Id, string memory _name) public view returns(uint256)
{
return User[_Id][_name];
}
以下是一个在 Solidity 中使用映射的示例。以下代码片段的功用有:
从地址到 uint 的映射,确保映射始终返回一个值
如果值从未设置,将返回默认值。
更新映射地址处的值
将值重置为默认值。
从地址创建到另一个映射的嵌套映射
即使嵌套映射未初始化,也能获取值。
pragma solidity ^0.8.13;
contract Mapping {
// 从地址到 uint 的映射
mapping(address => uint) public myMap;
function get(address _addr) public view returns (uint) {
// 映射始终返回一个值。
// 如果值从未设置,将返回默认值。
return myMap[_addr];
}
function set(address _addr, uint _i) public {
// 更新此地址处的值
myMap[_addr] = _i;
}
function remove(address _addr) public {
// 将值重置为默认值。
delete myMap[_addr];
}
}
contract NestedMapping {
// 嵌套映射(从地址到另一个映射)
mapping(address => mapping(uint => bool)) public nested;
function get(address _addr1, uint _i) public view returns (bool) {
// 你可以从嵌套映射中获取值
// 即使它未初始化
return nested[_addr1][_i];
}
function set(
address _addr1,
uint _i,
bool _boo
) public {
nested[_addr1][_i] = _boo;
}
function remove(address _addr1, uint _i) public {
delete nested[_addr1][_i];
}
}
以下是 Solidity 中映射的三个示例:
ERC20 代币余额
使用布尔逻辑
查询 DAO 的成员
此代码片段将用户地址与其地址的 ERC20 余额映射。
contract ERC20 is Context, IERC20 {
using SafeMath for uint256;
using Address for address;
mapping (address => uint256) private _balances; ...}
此代码片段旨在列出候选人的名字并返回每位候选人获得的投票数。该示例在 DAO 中有用例,其中预期成员对于组织决策进行投票。
contract Voting {
mapping (bytes32 => uint8) public votesReceived;
mapping (bytes32 => bool) public candidateList;
function Voting(bytes32[] candidateNames) {
for(uint i = 0; i < candidateNames.length; i++) {
candidateList[candidateNames[i]] = true;
}
}
function totalVotesFor(bytes32 candidate) constant returns (uint8) {
require(validCandidate(candidate));
return votesReceived[candidate];
}
function voteForCandidate(bytes32 candidate) {
require(validCandidate(candidate) == true);
votesReceived[candidate] += 1;
}
function validCandidate(bytes32 candidate) constant returns (bool) {
return candidateList[candidate];
}
}
此示例来自 Dominion DAO 智能合约,映射了 raisedProposals、stakeholderVotes、votedOn、contributors 和 stakeholders。
mapping(uint256 => ProposalStruct) private raisedProposals;
mapping(address => uint256[]) private stakeholderVotes;
mapping(uint256 => VotedStruct[]) private votedOn;
mapping(address => uint256) private contributors;
mapping(address => uint256) private stakeholders;
此代码示例列出了两个 Solidity 结构体:ProposalStruct 和 VotedStruct。
struct ProposalStruct {
uint256 id;
uint256 amount;
uint256 duration;
uint256 upvotes;
uint256 downvotes;
string title;
string description;
bool passed;
bool paid;
address payable beneficiary;
address proposer;
address executor;
}
struct VotedStruct {
address voter;
uint256 timestamp;
bool choosen;
}
让我们尝试在构建映射时添加一些值,以便更好地理解。在以下示例中,我们:
创建一个合约
定义一个结构
声明不同的结构元素
创建一个映射
向映射中添加值
pragma solidity ^0.4.18;
// 创建合约
contract mapping_example {
//定义结构
struct student {
//声明不同的
// 结构元素
string name;
string subject;
uint8 marks;
}
// 创建映射
mapping (
address => student) result;
address[] public student_result;
// 函数向映射中添加值
function adding_values() public {
var student
= result[0xDEE7796E89C82C36BAdd1375076f39D69FafE252];
student.name = "John";
student.subject = "Chemistry";
student.marks = 88;
student_result.push(
0xDEE7796E89C82C36BAdd1375076f39D69FafE252) -1;
}
}
以下是一些关于 Solidity 映射的常见问题:
Solidity 映射的长度是什么?
Solidity 映射的默认值是什么?
如何公开查看 Solidity 映射?
映射没有长度。 键的数据不会保存在映射中,而是使用 keccak256 哈希 来存储键数据所引用的值。没有键和值“单独存在”的概念。
以下是 Solidity 映射的默认值类型:
int/uint - 键类型 = 是;值类型 = 是
string - 键类型 = 是;值类型 = 是
byte/bytes - 键类型 = 是;值类型 = 是
address - 键类型 = 是;值类型 = 是
struct - 键类型 = 否;值类型 = 是
mapping - 键类型 = 否;值类型 = 是
enum - 键类型 = 否;值类型 = 是
contract - 键类型 = 否;值类型 = 是
固定大小数组 - 键类型 = 是;值类型 = 是
动态大小数组 - 键类型 = 否;值类型 = 是
多维数组 - 键类型 = 否;值类型 = 是
变量 - 键类型 = 否;值类型 = 否
因为 该属性是公共的,你可以使用由 Solidity 编译器 创建的获取器函数来访问它。
const myContract = new web3.eth.Contract(abiJson, contractAddress); // 返回映射键 `0` 的值
const info = await myContract.methods.locks(0).call();
- 原文链接: alchemy.com/overviews/so...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!