Solidity 映射类型全面技术指南
映射(Mapping)是 Solidity 中最强大且最常用的引用类型之一,为智能合约提供类似哈希表的键值存储功能。本文将深入探讨映射类型的各个方面,包括基础用法、高级技术、优化策略和实际应用案例。
目录
- 映射基础概念
- 映射类型的声明与操作
- 映射的特殊属性
- 映射的数据位置与存储
- 嵌套映射
- 映射与数组/结构体的组合
- 映射的迭代技术
- 使用映射的常见模式
- 映射的优化技巧
- 映射的安全考量
- 映射的高级应用案例
- 映射的局限性与替代方案
1. 映射基础概念
映射是 Solidity 中用于存储键值对关系的数据结构,类似于其他编程语言中的哈希表、字典或关联数组。
映射的特性
- 键值存储: 每个键映射到唯一的值
- 快速查找: O(1) 时间复杂度的查询操作
- 自动初始化: 所有可能的键都已存在并映射到默认值
- 不可迭代: 无法直接获取所有键或值
- 仅限存储: 映射只能作为状态变量存储在区块链上
基本用法
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract MappingBasics {
// 基本映射: 地址 => 余额
mapping(address => uint256) public balances;
// 设置余额
function setBalance(uint256 _balance) public {
balances[msg.sender] = _balance;
}
// 获取余额
function getBalance(address _account) public view returns (uint256) {
return balances[_account];
}
// 增加余额
function addToBalance(uint256 _amount) public {
balances[msg.sender] += _amount;
}
// 减少余额
function deductFromBalance(uint256 _amount) public {
require(balances[msg.sender] >= _amount, "Insufficient balance");
balances[msg.sender] -= _amount;
}
// 删除映射条目 (重置为默认值)
function resetBalance() public {
delete balances[msg.sender]; // 将余额重置为 0
}
}
2. 映射类型的声明与操作
声明语法
映射的声明使用以下语法:
solidity
mapping(KeyType => ValueType) mappingName;
其中:
KeyType
: 可以是除映射、动态数组、合约、枚举、结构体外的任何内置类型ValueType
: 可以是任何类型,包括映射、数组和结构体
支持的键类型
以下是常见的键类型:
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract MappingKeyTypes {
// 地址键
mapping(address => uint256) public addressBalances;
// uint 键
mapping(uint256 => string) public idToName;
// 字符串键
mapping(string => uint256) public nameToAge;
// 字节键
mapping(bytes32 => bool) public hashActivated;
// 布尔键
mapping(bool => string) public boolToString;
// 地址和 uint 组合键 (使用 bytes32 哈希)
function getCompositeKey(address _addr, uint256 _id) public pure returns (bytes32) {
return keccak256(abi.encodePacked(_addr, _id));
}
mapping(bytes32 => uint256) public compositeBalances;
// 使用组合键
function setCompositeBalance(address _addr, uint256 _id, uint256 _balance) public {
bytes32 key = getCompositeKey(_addr, _id);
compositeBalances[key] = _balance;
}
// 获取组合键对应的值
function getCompositeBalance(address _addr, uint256 _id) public view returns (uint256) {
bytes32 key = getCompositeKey(_addr, _id);
return compositeBalances[key];
}
}
常见操作
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract MappingOperations {
mapping(address => uint256) public userScores;
// 插入或更新
function setScore(address _user, uint256 _score) public {
userScores[_user] = _score;
}
// 读取
function getScore(address _user) public view returns (uint256) {
return userScores[_user];
}
// 检查是否存在特定值 (映射总是返回默认值,所以需要额外的逻辑)
function hasScore(address _user) public view returns (bool) {
// 这种方法仅适用于当我们确定有效分数不会是 0 时
return userScores[_user] != 0;
}
// 删除/重置
function deleteScore(address _user) public {
delete userScores[_user];
}
// 更新现有值
function incrementScore(address _user, uint256 _points) public {
userScores[_user] += _points;
}
// 有条件更新
function updateScoreIfHigher(address _user, uint256 _newScore) public {
if (_newScore > userScores[_user]) {
userScores[_user] = _newScore;
}
}
// 批量更新
function batchUpdateScores(address[] memory _users, uint256[] memory _scores) public {
require(_users.length == _scores.length, "Arrays must have same length");
for (uint256 i = 0; i < _users.length; i++) {
userScores[_users[i]] = _scores[i];
}
}
}
3. 映射的特殊属性
映射在 Solidity 中具有一些独特的特性,将其与其他数据类型区分开来。
预初始化
映射中的所有可能键都被视为存在,初始值为该值类型的默认值(如 uint256 为 0,bool 为 false,address 为 address(0)等)。
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract MappingSpecialProperties {
mapping(uint256 => bool) public boolMap;
mapping(uint256 => uint256) public uintMap;
mapping(uint256 => address) public addressMap;
mapping(uint256 => string) public stringMap;
// 演示默认值
function demonstrateDefaultValues(uint256 _key) public view returns (
bool defaultBool,
uint256 defaultUint,
address defaultAddress,
string memory defaultString
) {
// 所有这些都将返回类型的默认值,即使从未设置过
return (
boolMap[_key], // false
uintMap[_key], // 0
addressMap[_key], // address(0)
stringMap[_key] // ""
);
}
// 由于默认值的存在,无法直接判断键是否已明确设置
// 一种常见的解决方案是使用额外的映射来跟踪存在性
mapping(uint256 => bool) public keyExists;
mapping(uint256 => uint256) public valuesWithExistenceCheck;
// 设置值并跟踪其存在性
function setValue(uint256 _key, uint256 _value) public {
valuesWithExistenceCheck[_key] = _value;
keyExists[_key] = true;
}
// 检查键是否存在
function checkKeyExists(uint256 _key) public view returns (bool) {
return keyExists[_key];
}
// 获取键值,同时返回是否存在
function getValueAndExistence(uint256 _key) public view returns (uint256, bool) {
return (valuesWithExistenceCheck[_key], keyExists[_key]);
}
}
键的排序和内部存储
虽然在概念上映射是无序的,但在区块链上它们使用特定的哈希函数和存储方式:
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract MappingStorage {
mapping(uint256 => uint256) public simpleMapping;
function set(uint256 _key, uint256 _value) public {
simpleMapping[_key] = _value;
}
// 获取映射变量在存储中的位置
function getMappingSlot() public pure returns (uint256) {
// 这是映射变量在合约存储中的槽位置
// 实际值取决于合约的状态变量顺序
return 0; // 假设这是第一个状态变量
}
// 获取特定键值对在存储中的位置
function getKeyStorageLocation(uint256 _key) public pure returns (bytes32) {
// 在实际中,映射的每个键存储在通过以下公式计算的位置:
// keccak256(abi.encode(_key, uint256(mappingSlot)))
bytes32 mappingSlot = bytes32(uint256(0)); // 简化的槽位置
return keccak256(abi.encode(_key, mappingSlot));
}
}
映射的 keccak256 索引机制
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract MappingHash {
mapping(address => uint256) public balances;
function setBalance(uint256 _balance) public {
balances[msg.sender] = _balance;
}
// 理解映射如何计算存储位置
function getStoragePosition(address _user) public pure returns (bytes32) {
uint256 mappingSlot = 0; // 假设映射是合约的第一个状态变量
// 映射内的每个条目存储在 keccak256(key . mappingSlot) 计算的位置
return keccak256(abi.encode(_user, mappingSlot));
}
// 使用 assembly 查看存储值(仅作教育目的)
function getStorageValue(bytes32 _position) public view returns (uint256 value) {
assembly {
value := sload(_position)
}
}
}
4. 映射的数据位置与存储
映射的一个重要限制是它们只能存储在 storage
中,不能存在于 memory
或 calldata
中。
存储映射
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract MappingDataLocations {
// 状态变量映射 - 存储在 storage 中
mapping(address => uint256) public balances;
// 存储映射引用
function workWithStorageMapping() public {
// 创建对存储映射的引用
mapping(address => uint256) storage balancesRef = balances;
// 使用引用修改映射
balancesRef[msg.sender] = 100;
// 现在 balances[msg.sender] 也为 100
}
// 结构体中的映射
struct User {
mapping(uint256 => bool) permissions;
string name;
uint256 age;
}
// 包含映射的结构体数组
User[] public users;
// 添加新用户
function addUser(string memory _name, uint256 _age) public {
// 创建新用户
users.push(); // 创建空的用户项
// 获取最后添加的用户的存储引用
User storage newUser = users[users.length - 1];
// 设置基本字段
newUser.name = _name;
newUser.age = _age;
// 设置默认权限
newUser.permissions[0] = true; // 基本权限
}
// 管理用户权限
function setUserPermission(uint256 _userIndex, uint256 _permissionId, bool _value) public {
require(_userIndex < users.length, "User does not exist");
// 获取用户的存储引用
User storage user = users[_userIndex];
// 设置权限
user.permissions[_permissionId] = _value;
}
// 检查用户权限
function checkUserPermission(uint256 _userIndex, uint256 _permissionId) public view returns (bool) {
require(_userIndex < users.length, "User does not exist");
// 返回权限状态
return users[_userIndex].permissions[_permissionId];
}
}
映射不能在内存中创建
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract MappingMemoryRestriction {
struct UserWithoutMapping {
string name;
uint256 age;
}
struct UserWithMapping {
string name;
mapping(uint256 => bool) permissions; // 包含映射的结构体
}
// 有效 - 不包含映射的结构体可以在内存中创建
function createMemoryStruct() public pure returns (UserWithoutMapping memory) {
UserWithoutMapping memory user = UserWithoutMapping({
name: "Alice",
age: 30
});
return user;
}
// 以下函数将无法编译,因为映射不能存在于内存中
/*
function createMemoryMapping() public pure {
// 错误: 不能在内存中创建映射
mapping(uint256 => bool) memory permissions;
// 错误: 包含映射的结构体不能在内存中创建
UserWithMapping memory user;
}
*/
// 相反,必须使用存储映射
mapping(address => mapping(uint256 => bool)) public userPermissions;
function setPermission(uint256 _permissionId, bool _value) public {
userPermissions[msg.sender][_permissionId] = _value;
}
// 使用引用来处理存储映射
function updatePermissions(address _user) public {
// 获取映射的存储引用
mapping(uint256 => bool) storage permissions = userPermissions[_user];
// 修改权限
permissions[1] = true; // 权限 ID 1
permissions[2] = true; // 权限 ID 2
permissions[3] = false; // 权限 ID 3
}
}
5. 嵌套映射
嵌套映射(一个映射作为另一个映射的值类型)是 Solidity 中一种非常强大的数据结构,可用于表示多维或分层的数据。
基本嵌套映射
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract NestedMappings {
// 基本嵌套映射 - 用户对代币的余额
// 外层键: 用户地址,内层键: 代币地址,值: 余额
mapping(address => mapping(address => uint256)) public tokenBalances;
// 设置代币余额
function setTokenBalance(address _token, uint256 _balance) public {
tokenBalances[msg.sender][_token] = _balance;
}
// 增加代币余额
function addTokenBalance(address _token, uint256 _amount) public {
tokenBalances[msg.sender][_token] += _amount;
}
// 获取特定用户的代币余额
function getTokenBalance(address _user, address _token) public view returns (uint256) {
return tokenBalances[_user][_token];
}
// 转移代币余额
function transferTokenBalance(address _to, address _token, uint256 _amount) public {
require(tokenBalances[msg.sender][_token] >= _amount, "Insufficient balance");
tokenBalances[msg.sender][_token] -= _amount;
tokenBalances[_to][_token] += _amount;
}
// 删除特定代币的余额
function resetTokenBalance(address _token) public {
delete tokenBalances[msg.sender][_token];
}
}
三层嵌套映射示例
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract TripleNestedMapping {
// 三层嵌套映射示例:
// 第一层: 组织 ID
// 第二层: 部门 ID
// 第三层: 员工 ID
// 值: 员工薪资
mapping(uint256 => mapping(uint256 => mapping(uint256 => uint256))) public employeeSalaries;
// 设置员工薪资
function setSalary(
uint256 _orgId,
uint256 _deptId,
uint256 _empId,
uint256 _salary
) public {
employeeSalaries[_orgId][_deptId][_empId] = _salary;
}
// 获取员工薪资
function getSalary(
uint256 _orgId,
uint256 _deptId,
uint256 _empId
) public view returns (uint256) {
return employeeSalaries[_orgId][_deptId][_empId];
}
// 删除特定组织内所有部门的特定员工记录
// 注意: 这不实际删除所有记录,因为我们不知道有多少部门
function removeEmployeeFromOrg(uint256 _orgId, uint256 _empId, uint256[] memory _knownDeptIds) public {
for (uint256 i = 0; i < _knownDeptIds.length; i++) {
delete employeeSalaries[_orgId][_knownDeptIds[i]][_empId];
}
}
}
嵌套映射的访问控制
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract NestedMappingWithAccess {
address public owner;
// 管理员映射
mapping(address => bool) public admins;
// 用户 => 代币 => 额度
mapping(address => mapping(address => uint256)) public allowances;
constructor() {
owner = msg.sender;
admins[msg.sender] = true;
}
// 仅限管理员
modifier onlyAdmin() {
require(admins[msg.sender], "Not an admin");
_;
}
// 添加管理员
function addAdmin(address _admin) public {
require(msg.sender == owner, "Only owner can add admins");
admins[_admin] = true;
}
// 设置用户的代币额度(仅限管理员)
function setAllowance(address _user, address _token, uint256 _amount) public onlyAdmin {
allowances[_user][_token] = _amount;
}
// 用户减少自己的额度
function decreaseAllowance(address _token, uint256 _amount) public {
require(allowances[msg.sender][_token] >= _amount, "Insufficient allowance");
allowances[msg.sender][_token] -= _amount;
}
// 批量设置额度
function batchSetAllowances(
address[] memory _users,
address _token,
uint256[] memory _amounts
) public onlyAdmin {
require(_users.length == _amounts.length, "Array length mismatch");
for (uint256 i = 0; i < _users.length; i++) {
allowances[_users[i]][_token] = _amounts[i];
}
}
}
6. 映射与数组/结构体的组合
映射经常与数组和结构体结合使用,形成强大的数据组织结构。
映射与结构体
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract MappingWithStruct {
// 用户资料结构体
struct UserProfile {
string name;
uint256 age;
address wallet;
bool isActive;
}
// 用户地址到用户资料的映射
mapping(address => UserProfile) public userProfiles;
// 创建或更新用户资料
function updateProfile(string memory _name, uint256 _age) public {
// 获取或初始化用户资料
UserProfile storage profile = userProfiles[msg.sender];
// 更新字段
profile.name = _name;
profile.age = _age;
profile.wallet = msg.sender;
profile.isActive = true;
}
// 停用用户资料
function deactivateProfile() public {
require(userProfiles[msg.sender].isActive, "Profile already inactive");
userProfiles[msg.sender].isActive = false;
}
// 检查用户是否有资料
function hasProfile(address _user) public view returns (bool) {
// 检查一个非空字段来判断资料是否存在
return bytes(userProfiles[_user].name).length > 0;
}
// 获取完整资料信息
function getProfile(address _user) public view returns (
string memory name,
uint256 age,
address wallet,
bool isActive
) {
UserProfile storage profile = userProfiles[_user];
return (profile.name, profile.age, profile.wallet, profile.isActive);
}
}
映射与数组
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract MappingWithArray {
// 每个用户拥有的项目列表
mapping(address => uint256[]) public userItems;
// 项目详情
mapping(uint256 => string) public itemDetails;
// 项目计数器
uint256 private nextItemId = 1;
// 添加新项目
function addItem(string memory _details) public returns (uint256) {
uint256 itemId = nextItemId++;
// 保存项目详情
itemDetails[itemId] = _details;
// 将项目添加到用户的项目列表中
userItems[msg.sender].push(itemId);
return itemId;
}
// 获取用户拥有的项目数量
function getUserItemCount(address _user) public view returns (uint256) {
return userItems[_user].length;
}
// 获取用户特定索引的项目 ID
function getUserItemAtIndex(address _user, uint256 _index) public view returns (uint256) {
require(_index < userItems[_user].length, "Index out of bounds");
return userItems[_user][_index];
}
// 获取用户的所有项目 ID
function getAllUserItems(address _user) public view returns (uint256[] memory) {
return userItems[_user];
}
// 从用户的项目列表中移除特定项目
function removeItem(uint256 _itemId) public {
uint256[] storage items = userItems[msg.sender];
bool found = false;
uint256 index = 0;
// 查找项目索引
for (uint256 i = 0; i < items.length; i++) {
if (items[i] == _itemId) {
found = true;
index = i;
break;
}
}
require(found, "Item not found");
// 通过将最后一个元素移到要删除的位置来移除项目
if (index != items.length - 1) {
items[index] = items[items.length - 1];
}
items.pop();
// 不删除项目详情,只从用户列表中移除
}
}
映射、结构体和数组的组合
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract MappingStructArray {
// 产品结构体
struct Product {
uint256 id;
string name;
uint256 price;
bool available;
}
// 订单结构体
struct Order {
uint256 id;
uint256[] productIds;
uint256 totalAmount;
uint256 timestamp;
OrderStatus status;
}
// 订单状态枚举
enum OrderStatus { Pending, Completed, Cancelled }
// 用户到其产品的映射
mapping(address => Product[]) public userProducts;
// 用户到其订单的映射
mapping(address => Order[]) public userOrders;
// 产品ID到产品所有者的映射
mapping(uint256 => address) public productOwners;
// 产品计数器
uint256 private nextProductId = 1;
// 订单计数器
uint256 private nextOrderId = 1;
// 添加新产品
function addProduct(string memory _name, uint256 _price) public returns (uint256) {
uint256 productId = nextProductId++;
Product memory newProduct = Product({
id: productId,
name: _name,
price: _price,
available: true
});
userProducts[msg.sender].push(newProduct);
productOwners[productId] = msg.sender;
return productId;
}
// 获取用户的所有产品
function getUserProducts() public view returns (Product[] memory) {
return userProducts[msg.sender];
}
// 更新产品可用性
function updateProductAvailability(uint256 _productId, bool _available) public {
require(productOwners[_productId] == msg.sender, "Not the product owner");
Product[] storage products = userProducts[msg.sender];
for (uint256 i = 0; i < products.length; i++) {
if (products[i].id == _productId) {
products[i].available = _available;
break;
}
}
}
// 创建订单
function createOrder(uint256[] memory _productIds) public returns (uint256) {
require(_productIds.length > 0, "No products specified");
uint256 totalAmount = 0;
// 验证产品并计算总金额
for (uint256 i = 0; i < _productIds.length; i++) {
uint256 productId = _productIds[i];
address productOwner = productOwners[productId];
require(productOwner != address(0), "Product does not exist");
// 查找产品以获取价格
Product[] storage ownerProducts = userProducts[productOwner];
bool found = false;
for (uint256 j = 0; j < ownerProducts.length; j++) {
if (ownerProducts[j].id == productId) {
require(ownerProducts[j].available, "Product not available");
totalAmount += ownerProducts[j].price;
found = true;
break;
}
}
require(found, "Product not found");
}
// 创建新订单
uint256 orderId = nextOrderId++;
Order memory newOrder = Order({
id: orderId,
productIds: _productIds,
totalAmount: totalAmount,
timestamp: block.timestamp,
status: OrderStatus.Pending
});
userOrders[msg.sender].push(newOrder);
return orderId;
}
// 获取用户的所有订单
function getUserOrders() public view returns (Order[] memory) {
return userOrders[msg.sender];
}
// 获取订单详情
function getOrderDetails(uint256 _orderId) public view returns (
uint256 id,
uint256[] memory productIds,
uint256 totalAmount,
uint256 timestamp,
OrderStatus status
) {
Order[] storage orders = userOrders[msg.sender];
for (uint256 i = 0; i < orders.length; i++) {
if (orders[i].id == _orderId) {
return (
orders[i].id,
orders[i].productIds,
orders[i].totalAmount,
orders[i].timestamp,
orders[i].status
);
}
}
revert("Order not found");
}
// 更新订单状态
function updateOrderStatus(uint256 _orderId, OrderStatus _status) public {
Order[] storage orders = userOrders[msg.sender];
bool found = false;
for (uint256 i = 0; i < orders.length; i++) {
if (orders[i].id == _orderId) {
orders[i].status = _status;
found = true;
break;
}
}
require(found, "Order not found");
}
}
7. 映射的迭代技术
由于 Solidity 中的映射不能直接迭代,我们需要使用额外的数据结构和技术来实现迭代功能。
使用数组跟踪键
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract IterableMapping {
// 用户余额映射
mapping(address => uint256) public balances;
// 跟踪所有用户的数组
address[] public users;
// 检查用户是否已存在的映射
mapping(address => bool) private userExists;
// 添加或更新余额
function updateBalance(uint256 _balance) public {
// 如果用户不存在,添加到用户数组
if (!userExists[msg.sender]) {
userExists[msg.sender] = true;
users.push(msg.sender);
}
// 更新余额
balances[msg.sender] = _balance;
}
// 获取用户数量
function getUserCount() public view returns (uint256) {
return users.length;
}
// 获取所有用户
function getAllUsers() public view returns (address[] memory) {
return users;
}
// 获取所有用户的余额
function getAllBalances() public view returns (address[] memory, uint256[] memory) {
uint256 userCount = users.length;
uint256[] memory balanceValues = new uint256[](userCount);
for (uint256 i = 0; i < userCount; i++) {
balanceValues[i] = balances[users[i]];
}
return (users, balanceValues);
}
// 删除用户
function removeUser(address _user) public {
require(userExists[_user], "User does not exist");
// 从数组中移除用户
for (uint256 i = 0; i < users.length; i++) {
if (users[i] == _user) {
// 将最后一个元素移到此位置,并移除最后一个元素
users[i] = users[users.length - 1];
users.pop();
break;
}
}
// 更新用户存在性和余额
userExists[_user] = false;
delete balances[_user];
}
}
双向映射技术
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract TwoWayMapping {
// 名称到地址的映射
mapping(string => address) private nameToAddress;
// 地址到名称的映射
mapping(address => string) private addressToName;
// 所有名称的数组
string[] private allNames;
// 检查名称是否已注册
mapping(string => bool) private nameExists;
// 注册名称
function registerName(string memory _name) public {
require(bytes(_name).length > 0, "Name cannot be empty");
require(!nameExists[_name], "Name already registered");
require(bytes(addressToName[msg.sender]).length == 0, "Address already has a name");
// 更新双向映射
nameToAddress[_name] = msg.sender;
addressToName[msg.sender] = _name;
// 添加到名称列表
nameExists[_name] = true;
allNames.push(_name);
}
// 查找地址
function getAddress(string memory _name) public view returns (address) {
return nameToAddress[_name];
}
// 查找名称
function getName(address _address) public view returns (string memory) {
return addressToName[_address];
}
// 获取所有名称
function getAllNames() public view returns (string[] memory) {
return allNames;
}
// 更新名称
function updateName(string memory _newName) public {
require(bytes(_newName).length > 0, "Name cannot be empty");
require(!nameExists[_newName], "Name already registered");
string memory oldName = addressToName[msg.sender];
require(bytes(oldName).length > 0, "No name registered for this address");
// 更新双向映射
delete nameToAddress[oldName];
nameToAddress[_newName] = msg.sender;
addressToName[msg.sender] = _newName;
// 更新名称列表
nameExists[oldName] = false;
nameExists[_newName] = true;
// 更新数组中的名称
for (uint256 i = 0; i < allNames.length; i++) {
if (keccak256(bytes(allNames[i])) == keccak256(bytes(oldName))) {
allNames[i] = _newName;
break;
}
}
}
// 注销名称
function unregisterName() public {
string memory name = addressToName[msg.sender];
require(bytes(name).length > 0, "No name registered for this address");
// 从双向映射中删除
delete nameToAddress[name];
delete addressToName[msg.sender];
// 更新名称列表
nameExists[name] = false;
// 从数组中移除
for (uint256 i = 0; i < allNames.length; i++) {
if (keccak256(bytes(allNames[i])) == keccak256(bytes(name))) {
allNames[i] = allNames[allNames.length - 1];
allNames.pop();
break;
}
}
}
}
使用枚举集合实现映射迭代
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract EnumerableMap {
// 迭代集合库
struct Set {
// 存储值的数组
bytes32[] _values;
// 值到下标的映射
mapping(bytes32 => uint256) _indexes;
}
// 添加元素到集合
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
set._indexes[value] = set._values.length;
return true;
} else {
return false;
}
}
// 从集合中移除元素
function _remove(Set storage set, bytes32 value) private returns (bool) {
uint256 valueIndex = set._indexes[value];
if (valueIndex != 0) {
uint256 toDeleteIndex = valueIndex - 1;
uint256 lastIndex = set._values.length - 1;
// 如果要删除的元素不是最后一个,则将最后一个元素移到要删除的位置
if (toDeleteIndex != lastIndex) {
bytes32 lastValue = set._values[lastIndex];
set._values[toDeleteIndex] = lastValue;
set._indexes[lastValue] = valueIndex;
}
// 删除最后一个元素
set._values.pop();
delete set._indexes[value];
return true;
} else {
return false;
}
}
// 检查集合是否包含元素
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._indexes[value] != 0;
}
// 获取集合大小
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
// 获取指定索引的值
function _at(Set storage set, uint256 index) private view returns (bytes32) {
require(index < set._values.length, "Index out of bounds");
return set._values[index];
}
// 获取所有值
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// 主映射结构
struct Map {
Set _keys;
mapping(bytes32 => bytes32) _values;
}
// 在映射中设置键值对
function _set(Map storage map, bytes32 key, bytes32 value) private returns (bool) {
map._values[key] = value;
return _add(map._keys, key);
}
// 从映射中移除键
function _remove(Map storage map, bytes32 key) private returns (bool) {
delete map._values[key];
return _remove(map._keys, key);
}
// 检查映射是否包含键
function _contains(Map storage map, bytes32 key) private view returns (bool) {
return _contains(map._keys, key);
}
// 获取映射大小
function _length(Map storage map) private view returns (uint256) {
return _length(map._keys);
}
// 获取指定索引的键值对
function _at(Map storage map, uint256 index) private view returns (bytes32, bytes32) {
bytes32 key = _at(map._keys, index);
return (key, map._values[key]);
}
// 获取键对应的值
function _get(Map storage map, bytes32 key) private view returns (bytes32) {
return map._values[key];
}
// 获取所有键
function _keys(Map storage map) private view returns (bytes32[] memory) {
return _values(map._keys);
}
// 应用示例
Map private addressToUintMap;
// 设置地址对应的数值
function set(address key, uint256 value) public {
_set(addressToUintMap, bytes32(uint256(uint160(key))), bytes32(value));
}
// 获取地址对应的数值
function get(address key) public view returns (uint256) {
return uint256(_get(addressToUintMap, bytes32(uint256(uint160(key)))));
}
// 检查地址是否存在
function contains(address key) public view returns (bool) {
return _contains(addressToUintMap, bytes32(uint256(uint160(key))));
}
// 移除地址
function remove(address key) public {
_remove(addressToUintMap, bytes32(uint256(uint160(key))));
}
// 获取映射大小
function length() public view returns (uint256) {
return _length(addressToUintMap);
}
// 获取指定索引的键值对
function at(uint256 index) public view returns (address, uint256) {
(bytes32 key, bytes32 value) = _at(addressToUintMap, index);
return (address(uint160(uint256(key))), uint256(value));
}
// 获取所有键
function keys() public view returns (address[] memory) {
bytes32[] memory keyBytes = _keys(addressToUintMap);
address[] memory keyAddresses = new address[](keyBytes.length);
for (uint256 i = 0; i < keyBytes.length; i++) {
keyAddresses[i] = address(uint160(uint256(keyBytes[i])));
}
return keyAddresses;
}
}
8. 使用映射的常见模式
以下是一些在智能合约中使用映射的常见模式和技术:
访问控制模式
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract RoleBasedAccess {
// 角色到账户的映射
mapping(bytes32 => mapping(address => bool)) private roles;
// 角色常量
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN");
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR");
bytes32 public constant USER_ROLE = keccak256("USER");
// 事件
event RoleGranted(bytes32 indexed role, address indexed account);
event RoleRevoked(bytes32 indexed role, address indexed account);
constructor() {
// 授予合约部署者管理员角色
_grantRole(ADMIN_ROLE, msg.sender);
}
// 角色检查修饰符
modifier onlyRole(bytes32 role) {
require(roles[role][msg.sender], "Account does not have role");
_;
}
// 检查账户是否有特定角色
function hasRole(bytes32 role, address account) public view returns (bool) {
return roles[role][account];
}
// 授予角色(仅管理员)
function grantRole(bytes32 role, address account) public onlyRole(ADMIN_ROLE) {
_grantRole(role, account);
}
// 撤销角色(仅管理员)
function revokeRole(bytes32 role, address account) public onlyRole(ADMIN_ROLE) {
_revokeRole(role, account);
}
// 内部授予角色函数
function _grantRole(bytes32 role, address account) internal {
if (!roles[role][account]) {
roles[role][account] = true;
emit RoleGranted(role, account);
}
}
// 内部撤销角色函数
function _revokeRole(bytes32 role, address account) internal {
if (roles[role][account]) {
roles[role][account] = false;
emit RoleRevoked(role, account);
}
}
// 仅管理员可调用的函数
function adminOnlyFunction() public onlyRole(ADMIN_ROLE) {
// 管理员特定逻辑
}
// 仅操作员可调用的函数
function operatorOnlyFunction() public onlyRole(OPERATOR_ROLE) {
// 操作员特定逻辑
}
// 管理员或操作员可调用的函数
function protectedFunction() public {
require(
roles[ADMIN_ROLE][msg.sender] || roles[OPERATOR_ROLE][msg.sender],
"Requires admin or operator role"
);
// 受保护的逻辑
}
}
计数映射模式
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract CounterMapping {
// 用户到计数器的映射
mapping(address => uint256) public counters;
// 增加计数器
function increment() public returns (uint256) {
counters[msg.sender] += 1;
return counters[msg.sender];
}
// 增加特定量
function incrementBy(uint256 _value) public returns (uint256) {
counters[msg.sender] += _value;
return counters[msg.sender];
}
// 重置计数器
function reset() public {
delete counters[msg.sender];
}
// 获取当前计数
function getCount() public view returns (uint256) {
return counters[msg.sender];
}
// 批量查询计数
function batchGetCounts(address[] memory _users) public view returns (uint256[] memory) {
uint256[] memory counts = new uint256[](_users.length);
for (uint256 i = 0; i < _users.length; i++) {
counts[i] = counters[_users[i]];
}
return counts;
}
// 对特定计数采取行动
function actionAtCount(uint256 _targetCount) public {
require(counters[msg.sender] >= _targetCount, "Count not reached");
// 执行某些基于计数的逻辑
}
}
存在性检查模式
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract ExistenceCheckPattern {
// 实体结构
struct Entity {
uint256 id;
string name;
uint256 creationDate;
bool isActive;
}
// 实体映射
mapping(uint256 => Entity) private entities;
// 用于检查实体是否存在
mapping(uint256 => bool) private entityExists;
// 创建新实体
function createEntity(uint256 _id, string memory _name) public {
require(!entityExists[_id], "Entity already exists");
entities[_id] = Entity({
id: _id,
name: _name,
creationDate: block.timestamp,
isActive: true
});
entityExists[_id] = true;
}
// 检查实体是否存在
function exists(uint256 _id) public view returns (bool) {
return entityExists[_id];
}
// 获取实体详情
function getEntity(uint256 _id) public view returns (
string memory name,
uint256 creationDate,
bool isActive
) {
require(entityExists[_id], "Entity does not exist");
Entity storage entity = entities[_id];
return (entity.name, entity.creationDate, entity.isActive);
}
// 更新实体状态
function updateEntityStatus(uint256 _id, bool _isActive) public {
require(entityExists[_id], "Entity does not exist");
entities[_id].isActive = _isActive;
}
// 删除实体
function deleteEntity(uint256 _id) public {
require(entityExists[_id], "Entity does not exist");
delete entities[_id];
entityExists[_id] = false;
}
// 通过状态筛选实体 (需要有跟踪所有实体的方法,这里省略)
// 实际使用时,您会需要一个额外的数组来跟踪所有实体 ID
}
委托模式
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract DelegationPattern {
// 委托映射
mapping(address => mapping(address => bool)) private delegations;
// 授权映射
mapping(address => mapping(bytes4 => mapping(address => bool))) private functionDelegations;
// 事件
event DelegationChanged(address indexed delegator, address indexed delegate, bool value);
event FunctionDelegationChanged(address indexed delegator, bytes4 indexed functionSig, address indexed delegate, bool value);
// 设置全局委托
function setDelegate(address _delegate, bool _value) public {
delegations[msg.sender][_delegate] = _value;
emit DelegationChanged(msg.sender, _delegate, _value);
}
// 设置特定函数的委托
function setFunctionDelegate(bytes4 _functionSig, address _delegate, bool _value) public {
functionDelegations[msg.sender][_functionSig][_delegate] = _value;
emit FunctionDelegationChanged(msg.sender, _functionSig, _delegate, _value);
}
// 检查全局委托
function isDelegate(address _delegator, address _delegate) public view returns (bool) {
return delegations[_delegator][_delegate];
}
// 检查特定函数的委托
function isFunctionDelegate(address _delegator, bytes4 _functionSig, address _delegate) public view returns (bool) {
return functionDelegations[_delegator][_functionSig][_delegate];
}
// 检查委托访问
function checkDelegateAccess() public view returns (bool) {
return isDelegate(msg.sender, tx.origin) || isFunctionDelegate(msg.sender, msg.sig, tx.origin);
}
// 基于委托检查函数
function doSomethingWithDelegation(address _delegator) public view returns (bool) {
require(
msg.sender == _delegator ||
isDelegate(_delegator, msg.sender) ||
isFunctionDelegate(_delegator, this.doSomethingWithDelegation.selector, msg.sender),
"Not authorized"
);
return true;
}
}
9. 映射的优化技巧
优化映射使用可以显著减少 gas 消耗并提高智能合约效率。
紧凑存储
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract StorageOptimization {
// 场景 1: 不优化的结构体映射
struct UserDataUnoptimized {
bool isActive; // 1 字节但占用一个完整的槽
uint256 balance; // 32 字节
bool hasVoted; // 1 字节但占用一个完整的槽
}
mapping(address => UserDataUnoptimized) public unoptimizedUsers;
// 场景 2: 优化的结构体映射
struct UserDataOptimized {
uint256 balance; // 32 字节
bool isActive; // 1 字节
bool hasVoted; // 1 字节 (可以与 isActive 打包在同一个槽中)
}
mapping(address => UserDataOptimized) public optimizedUsers;
// 场景 3: 对于简单值,使用位打包
// 单个映射存储多个标志
mapping(address => uint8) public flags;
// 标志位
uint8 constant IS_ACTIVE = 1; // 00000001
uint8 constant HAS_VOTED = 2; // 00000010
uint8 constant IS_ADMIN = 4; // 00000100
uint8 constant IS_BLACKLISTED = 8; // 00001000
// 设置标志位
function setFlag(uint8 _flag, bool _value) public {
if (_value) {
flags[msg.sender] |= _flag; // 设置位
} else {
flags[msg.sender] &= ~_flag; // 清除位
}
}
// 检查标志位
function checkFlag(address _user, uint8 _flag) public view returns (bool) {
return (flags[_user] & _flag) != 0;
}
// 场景 4: 优化存储布局
// 在优化版本中,将相关的映射分组在一起,
// 这可能会导致更好的溢出槽位用法
// 示例函数:初始化用户数据
function initUser(uint256 _balance, bool _isActive, bool _hasVoted) public {
// 不优化版本
unoptimizedUsers[msg.sender] = UserDataUnoptimized({
isActive: _isActive,
balance: _balance,
hasVoted: _hasVoted
});
// 优化版本
optimizedUsers[msg.sender] = UserDataOptimized({
balance: _balance,
isActive: _isActive,
hasVoted: _hasVoted
});
// 位打包版本
uint8 userFlags = 0;
if (_isActive) userFlags |= IS_ACTIVE;
if (_hasVoted) userFlags |= HAS_VOTED;
flags[msg.sender] = userFlags;
}
}
批量操作
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract BatchOperations {
// 用户余额映射
mapping(address => uint256) public balances;
// 单个操作 - 成本高
function individualTransfer(address[] memory _recipients, uint256[] memory _amounts) public {
require(_recipients.length == _amounts.length, "Array length mismatch");
for (uint256 i = 0; i < _recipients.length; i++) {
// 每个转账都是单独的存储操作
balances[msg.sender] -= _amounts[i];
balances[_recipients[i]] += _amounts[i];
}
}
// 批量操作 - 优化版本
function batchTransfer(address[] memory _recipients, uint256[] memory _amounts) public {
require(_recipients.length == _amounts.length, "Array length mismatch");
// 计算总转移金额
uint256 totalAmount = 0;
for (uint256 i = 0; i < _amounts.length; i++) {
totalAmount += _amounts[i];
}
// 一次性从发送者余额中减去
balances[msg.sender] -= totalAmount;
// 增加接收者余额
for (uint256 i = 0; i < _recipients.length; i++) {
balances[_recipients[i]] += _amounts[i];
}
}
// 用于批量查询的函数
function batchGetBalances(address[] memory _users) public view returns (uint256[] memory) {
uint256[] memory result = new uint256[](_users.length);
for (uint256 i = 0; i < _users.length; i++) {
result[i] = balances[_users[i]];
}
return result;
}
// 用于批量更新的函数 (需要权限控制在实际应用中)
function batchUpdateBalances(address[] memory _users, uint256[] memory _balances) public {
require(_users.length == _balances.length, "Array length mismatch");
for (uint256 i = 0; i < _users.length; i++) {
balances[_users[i]] = _balances[i];
}
}
}
减少存储访问
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract ReduceStorageAccess {
// 嵌套映射
mapping(address => mapping(uint256 => uint256)) public userAssets;
// 非优化版本: 多次存储访问
function updateAssetsUnoptimized(uint256[] memory _ids, uint256[] memory _values) public {
require(_ids.length == _values.length, "Array length mismatch");
for (uint256 i = 0; i < _ids.length; i++) {
// 每次迭代都会访问存储两次 (读和写)
userAssets[msg.sender][_ids[i]] = _values[i];
}
}
// 优化版本: 使用局部变量存储引用
function updateAssetsOptimized(uint256[] memory _ids, uint256[] memory _values) public {
require(_ids.length == _values.length, "Array length mismatch");
// 获取内层映射的存储引用
mapping(uint256 => uint256) storage userMapping = userAssets[msg.sender];
// 现在使用这个引用
for (uint256 i = 0; i < _ids.length; i++) {
userMapping[_ids[i]] = _values[i];
}
}
// 非优化版本: 多次读取相同数据
function sumAssets(uint256[] memory _ids) public view returns (uint256) {
uint256 total = 0;
for (uint256 i = 0; i < _ids.length; i++) {
// 每次迭代都会读取相同的映射
total += userAssets[msg.sender][_ids[i]];
}
return total;
}
// 优化版本: 减少存储读取
function sumAssetsOptimized(uint256[] memory _ids) public view returns (uint256) {
// 获取内层映射的引用
mapping(uint256 => uint256) storage userMapping = userAssets[msg.sender];
uint256 total = 0;
for (uint256 i = 0; i < _ids.length; i++) {
total += userMapping[_ids[i]];
}
return total;
}
}
映射键优化
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract MappingKeyOptimization {
// 1. 使用简单类型作为键
// 优化: 使用 uint256 代替字符串
mapping(uint256 => string) public idToName;
mapping(string => uint256) public nameToId; // 用于反向查找
// 2. 使用 bytes32 代替字符串
mapping(bytes32 => uint256) public keyToValue;
// 转换字符串为 bytes32
function stringToBytes32(string memory _str) public pure returns (bytes32) {
return keccak256(bytes(_str));
}
// 使用 bytes32 键设置值
function setValue(string memory _key, uint256 _value) public {
bytes32 key = stringToBytes32(_key);
keyToValue[key] = _value;
}
// 使用 bytes32 键获取值
function getValue(string memory _key) public view returns (uint256) {
bytes32 key = stringToBytes32(_key);
return keyToValue[key];
}
// 3. 使用组合键
// 优化: 使用一个映射带有计算键,而不是嵌套映射
// 未优化的嵌套映射
mapping(address => mapping(uint256 => uint256)) public userTokenBalances;
// 优化的单个映射与组合键
mapping(bytes32 => uint256) public combinedKeyBalances;
// 创建组合键
function createKey(address _user, uint256 _tokenId) public pure returns (bytes32) {
return keccak256(abi.encodePacked(_user, _tokenId));
}
// 设置余额使用组合键
function setBalanceOptimized(uint256 _tokenId, uint256 _balance) public {
bytes32 key = createKey(msg.sender, _tokenId);
combinedKeyBalances[key] = _balance;
}
// 获取余额使用组合键
function getBalanceOptimized(address _user, uint256 _tokenId) public view returns (uint256) {
bytes32 key = createKey(_user, _tokenId);
return combinedKeyBalances[key];
}
}