3分钟Solidity: 8.4 可迭代映射

40 阅读2分钟

欢迎订阅专栏3分钟Solidity--智能合约--Web3区块链技术必学

如需获取本内容的最新版本,请参见 Cyfrin.io 上的可迭代映射(代码示例)

在 Solidity 中,mapping 是一种高效的键值存储结构,但它不能直接迭代(无法获取所有键),因为底层实现是哈希表,没有存储键的列表。
如果需要可迭代映射,通常的做法是结合数组和映射来实现。

  1. mapping 无法直接迭代

    • Solidity 不存储 mapping 的键集合,因此不能用 for 遍历。
  2. 用数组保存键

    • 每次新增键时,将其存入 keys 数组。
  3. 用布尔映射防止重复

    • inserted[address] 用于判断该键是否已存在,避免重复添加。
  4. 遍历方法

    • 可以通过 size() 获取长度,再用 getKey(i) 逐个访问。
    • 或一次性返回所有键值对(如 getAll())。

你无法遍历一个mapping。这里有一个如何创建可迭代mapping的例子。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

library IterableMapping {
    // 从地址到无符号整数的可迭代映射;
    struct Map {
        // 辅助结构:存储所有键(地址)
        address[] keys;
        // 主映射:地址 => 值
        mapping(address => uint256) values;
        mapping(address => uint256) indexOf;
        // 记录某个地址是否已存在(避免重复添加到 keys)
        mapping(address => bool) inserted;
    }

    function get(Map storage map, address key) public view returns (uint256) {
        return map.values[key];
    }
    //按索引获取键
    function getKeyAtIndex(Map storage map, uint256 index)
        public
        view
        returns (address)
    {
        return map.keys[index];
    }
    // 获取所有键的数量
    function size(Map storage map) public view returns (uint256) {
        return map.keys.length;
    }

    function set(Map storage map, address key, uint256 val) public {
        if (map.inserted[key]) {
            map.values[key] = val;
        } else {
            map.inserted[key] = true;
            map.values[key] = val;
            map.indexOf[key] = map.keys.length;
            map.keys.push(key);
        }
    }

    function remove(Map storage map, address key) public {
        if (!map.inserted[key]) {
            return;
        }

        delete map.inserted[key];
        delete map.values[key];

        uint256 index = map.indexOf[key];
        address lastKey = map.keys[map.keys.length - 1];

        map.indexOf[lastKey] = index;
        delete map.indexOf[key];

        map.keys[index] = lastKey;
        map.keys.pop();
    }
}

contract TestIterableMap {
    using IterableMapping for IterableMapping.Map;

    IterableMapping.Map private map;

    function setInMapping(uint256 val) public {
        map.set(msg.sender, val);
    }

    function getFromMap() public view returns (uint256) {
        return map.get(msg.sender);
    }

    function getKeyAtIndex(uint256 index) public view returns (address) {
        return map.getKeyAtIndex(index);
    }

    function sizeOfMapping() public view returns (uint256) {
        return map.size();
    }

    function removeFromMapping() public {
        map.remove(msg.sender);
    }
}

Remix Lite 尝试一下

solidity-iterable_mappiing


END