013-Solidity 映射类型全面技术指南

Solidity 映射类型全面技术指南

映射(Mapping)是 Solidity 中最强大且最常用的引用类型之一,为智能合约提供类似哈希表的键值存储功能。本文将深入探讨映射类型的各个方面,包括基础用法、高级技术、优化策略和实际应用案例。

目录

  1. 映射基础概念
  2. 映射类型的声明与操作
  3. 映射的特殊属性
  4. 映射的数据位置与存储
  5. 嵌套映射
  6. 映射与数组/结构体的组合
  7. 映射的迭代技术
  8. 使用映射的常见模式
  9. 映射的优化技巧
  10. 映射的安全考量
  11. 映射的高级应用案例
  12. 映射的局限性与替代方案

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 中,不能存在于 memorycalldata 中。

存储映射

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];
    }
}