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

  • A+
所属分类:区块链技术

映射[^origin](http://solidity.readthedocs.io/en/develop/types.html#mappings)是一种引用类型,存储键值对,提供根据键查找值,与其它语言中的字典,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. 映射的存储模型

由于状态变量是存储在区块链上的,所以存储空间需要预先分配,但映射的存储值是可以动态增改的,那么最终是如何支持的呢。关于状态的存储模型1里面提到,实际存储时是以哈希键值对的方式。其中哈希是由键值和映射的存储槽位序号拼接后计算的哈希值(映射只占一个槽位序号),也就是说值是存到由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的第一个参数是合约地址,第二个参数是键和映射所在槽序号的哈希值。通过填入这两个参数,最终获得了在合约中存储的值0x61616100000000000000000000000000000000000000000000000000000000061

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

由于映射的存储模型决定了,映射实际不存在一个映射的键大小,没有一个键集合的概念。但我们可以通过扩展默认映射来实现这样的功能,官方有个扩展示例[^IterMapp](%E5%AE%98%E6%96%B9%E6%8F%90%E4%BE%9B%E7%9A%84%E6%98%A0%E5%B0%84%E6%89%A9%E5%B1%95%E7%A4%BA%E4%BE%8B%EF%BC%9Ahttps://github.com/ethereum/dapp-bin/blob/master/library/iterable_mapping.sol):

/// @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 版权所有,转载注明出处

参考资料


  1. 状态变量的存储模型: http://www.tryblockchain.org/Solidity-LayoutOfStateVariablesInStorage-.html状态变量的存储模型 
weinxin
共识社
用手机扫一扫,加入组织,时刻关注组织动态。
daodaoliang

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: