Solidity的映射类型深入详解(十二)|入门系列

TryBlockchain   |     |   776 次阅读

映射^origin是一种引用类型,存储键值对,提供根据键查找值,与其它语言中的字典,map等类似,但也有非常大的不同,尤其它在区块链中独特的存储模型。

1. 只能是状态变量

由于在映射中键的数量是任意的,导致映射的大小也是变长的。映射只能声明为storage的状态变量,或被赋值给一个storage的对象引用。我们来看下面的示例:

pragma solidity ^0.4.0;

contract StateVariableOnly{

  //状态变量
  mapping(uint => uint) stateVar;

  function mappingTest() returns (uint){
    //可以被赋值为storage的引用
    mapping(uint => uint) storageRef = stateVar;

    storageRef[1] = uint(64);
    return storageRef[1];
  }
}

在上面的示例中,我们声明了storage的状态变量stateVar,可以对其增加新键值对;也能通过引用传递的方式赋值给storage的引用storageRef

2. 支持的类型

映射类型的支持除映射,变长数组,合约,枚举,结构体以外的任意类型。则允许任意类型,甚至是映射。下面是一个简单的例子代码:

pragma solidity ^0.4.0;

contract MappingType{

  struct s{
    string name;
    uint8 age;
  }

  mapping(bytes => s) structMapping;
  mapping(address => s) addrMapping;
  mapping(string => mapping(uint => s)) complexMapping;
}

3. setter方法

对于映射类型,也能标记为public。以让Solidity为我们自动生成访问器。

pragma solidity ^0.4.0;

contract MappingGetter{
  mapping(uint => uint) public intMapp;
  mapping(uint => mapping(uint => string)) public mapMapp;

  function set(){
    intMapp[1] = 100;
    mapMapp[2][2] = "aaa";
  }
}

在上面的例子中,如果要访问intMapp[1],输入值1。而如果要访问嵌套的映射mapMapp[2][2],则输入两个键对应的值2,2即可。

4. 映射的存储模型

由于状态变量是存储在区块链上的,所以存储空间需要预先分配,但映射的存储值是可以动态增改的,那么最终是如何支持的呢。关于状态的存储模型[^stateModel]里面提到,实际存储时是以哈希键值对的方式。其中哈希是由键值和映射的存储槽位序号拼接后计算的哈希值(映射只占一个槽位序号),也就是说值是存到由keccak256(k . p)计算的哈希串里,这里的k表示的是映射要查找的键,p表示映射在整个合约中相对序号位置。

下面我们将通过例子,先用合约给一个映射类型设置一个值,再用web3.js提供的getStorageAt()方法将值取出来。

pragma solidity ^0.4.0;

contract MappingLayout{
  //位置序号0
  mapping(string=>string) strMapping;

  function setString() {
      //aaa对应的十六进制ascii为hex"616161"
      //合约中为键aaa存一个值aaa
      strMapping["aaa"] = "aaa";
  }
}

上面的智能合约代码中,我们为strMapping的键aaa存入值aaa

let Web3 = require('web3');
let web3;

if (typeof web3 !== 'undefined') {
    web3 = new Web3(web3.currentProvider);
} else {
    // set the provider you want from Web3.providers
    web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
}

let from = web3.eth.accounts[0];

//编译合约
let source = 'pragma solidity ^0.4.0;contract MappingLayout{  /*位置序号0*/  mapping(string=>string) strMapping;  function setString() {      /*aaa对应的十六进制ascii为hex"616161"*/      /*合约中为键aaa存一个值aaa*/      strMapping["aaa"] = "aaa";  }}';
let layoutCompiled = web3.eth.compile.solidity(source);

//得到合约对象
let abiDefinition = layoutCompiled["info"]["abiDefinition"];
let layoutContract = web3.eth.contract(abiDefinition);

//2. 部署合约

//2.1 获取合约的代码,部署时传递的就是合约编译后的二进制码
let deployCode = layoutCompiled["code"];

//2.2 部署者的地址,当前取默认账户的第一个地址。
let deployeAddr = web3.eth.accounts[0];

//2.3 异步方式,部署合约
let myContractReturned = layoutContract.new({
    data: deployCode,
    from: deployeAddr,
    gas: 1000000
}, function(err, myContract) {
    if (!err) {

        // 通过判断是否有地址,来确认是第一次调用,还是第二次调用。
        if (!myContract.address) {
            console.log("contract deploy transaction hash: " + myContract.transactionHash) //部署合约的交易哈希值

            // 合约发布成功后,才能调用后续的方法
        } else {
            console.log("contract deploy address: " + myContract.address) // 合约的部署地址

            //使用transaction方式调用,写入到区块链上
            myContract.setString.sendTransaction({
                from: deployeAddr
            }, function(err, result){
              var key = "616161"
              var pos = "0000000000000000000000000000000000000000000000000000000000000000"
              let hash = web3.sha3(key + pos, {"encoding":"hex"})

              var state = web3.eth.getStorageAt(myContract.address, hash);
              //0x6161610000000000000000000000000000000000000000000000000000000006
              console.log(state);
            });
        }
    }
});

上面的代码中,getStorageAt的第一个参数是合约地址,第二个参数是键和映射所在槽序号的哈希值。通过填入这两个参数,最终获得了在合约中存储的值0x6161610000000000000000000000000000000000000000000000000000000006
[^stateModel]。

5. 与其它语言映射的不同

由于映射的存储模型决定了,映射实际不存在一个映射的键大小,没有一个键集合的概念。但我们可以通过扩展默认映射来实现这样的功能,官方有个扩展示例^IterMapp:

/// @dev Models a uint -> uint mapping where it is possible to iterate over all keys.
library IterableMapping
{
  struct itmap
  {
    mapping(uint => IndexValue) data;
    KeyFlag[] keys;
    uint size;
  }
  struct IndexValue { uint keyIndex; uint value; }
  struct KeyFlag { uint key; bool deleted; }
  function insert(itmap storage self, uint key, uint value) returns (bool replaced)
  {
    uint keyIndex = self.data[key].keyIndex;
    self.data[key].value = value;
    if (keyIndex > 0)
      return true;
    else
    {
      keyIndex = self.keys.length++;
      self.data[key].keyIndex = keyIndex + 1;
      self.keys[keyIndex].key = key;
      self.size++;
      return false;
    }
  }
  function remove(itmap storage self, uint key) returns (bool success)
  {
    uint keyIndex = self.data[key].keyIndex;
    if (keyIndex == 0)
      return false;
    delete self.data[key];
    self.keys[keyIndex - 1].deleted = true;
    self.size --;
  }
  function contains(itmap storage self, uint key) returns (bool)
  {
    return self.data[key].keyIndex > 0;
  }
  function iterate_start(itmap storage self) returns (uint keyIndex)
  {
    return iterate_next(self, uint(-1));
  }
  function iterate_valid(itmap storage self, uint keyIndex) returns (bool)
  {
    return keyIndex < self.keys.length;
  }
  function iterate_next(itmap storage self, uint keyIndex) returns (uint r_keyIndex)
  {
    keyIndex++;
    while (keyIndex < self.keys.length && self.keys[keyIndex].deleted)
      keyIndex++;
    return keyIndex;
  }
  function iterate_get(itmap storage self, uint keyIndex) returns (uint key, uint value)
  {
    key = self.keys[keyIndex].key;
    value = self.data[key].value;
  }
}

关于作者

专注基于以太坊(Ethereum)的相关区块链(Blockchain)技术,了解以太坊,Solidity,Truffle,web3.js。

个人博客: http://me.tryblockchain.org
版权所有,转载注明出处

参考资料

[^stateModel]: 状态变量的存储模型: http://www.tryblockchain.org/Solidity-LayoutOfStateVariablesInStorage-.html状态变量的存储模型

 
0 人喜欢