映射
1是一种引用类型,存储键值对,提供根据键查找值,与其它语言中的字典,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. 映射的存储模型
由于状态变量是存储在区块链上的,所以存储空间需要预先分配,但映射的存储值是可以动态增改的,那么最终是如何支持的呢。关于状态的存储模型2里面提到,实际存储时是以哈希键值对的方式。其中哈希是由键值和映射的存储槽位序号拼接后计算的哈希值(映射只占一个槽位序号),也就是说值是存到由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. 与其它语言映射的不同
由于映射的存储模型决定了,映射实际不存在一个映射的键大小,没有一个键集合的概念。但我们可以通过扩展默认映射来实现这样的功能,官方有个扩展示例3:
/// @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
版权所有,转载注明出处
参考资料
处于某些特定的环境下,可以看到评论框,欢迎留言交流^_^。