本文介绍了Solidity中处理文本和字节数据的三种主要类型:定长字节数组(bytes1-bytes32)、动态字节数组(bytes)和字符串(string)。详细阐述了它们的特点、操作、类型转换以及Gas消耗对比,并提供了实际应用场景和优化建议,以帮助开发者选择合适的类型来提高智能合约的效率和可维护性。
在智能合约开发中,大多数时候,使用多的整型、地址、合约类型,但有时也需要处理文本数据和原始字节数据。Solidity 提供了多种字节相关的类型来满足不同的使用场景。
Solidity 提供了三种主要的字节相关类型:
bytes1, bytes2, bytes3, ..., bytes32bytesstring定长字节数组使用 bytes1 到 bytes32 表示,数字表示字节的长度。例如 bytes1 表示1个字节,bytes32 表示32个字节。
pragma solidity ^0.8.0;
contract BytesExample {
bytes1 public a = 0x01; // 1字节
bytes2 public b = 0x0102; // 2字节
bytes4 public c = 0x01020304; // 4字节
bytes32 public d = 0x0102030405060708091011121314151617181920212223242526272829303132;
}
pragma solidity ^0.8.0;
contract BytesAccess {
bytes4 public data = 0x01020304;
// 访问第一个字节
function getFirstByte() public view returns (bytes1) {
return data[0]; // 返回 0x01
}
// 获取长度
function getLength() public pure returns (uint) {
bytes4 temp = 0x01020304;
return temp.length; // 返回 4
}
}
定长字节数组的索引访问是只读的,不能通过
data[0] = 0xff这样的方式修改。
定长字节数组支持以下运算符:
==, !=, <, <=, >, >=&(与), |(或), ^(异或), ~(取反)<<(左移), >>(右移)[index](只读)pragma solidity ^0.8.0;
contract BytesOperations {
function bitwiseOperations() public pure returns (bytes1, bytes1, bytes1) {
bytes1 a = 0xf0; // 11110000
bytes1 b = 0x0f; // 00001111
bytes1 andResult = a & b; // 00000000 = 0x00
bytes1 orResult = a | b; // 11111111 = 0xff
bytes1 xorResult = a ^ b; // 11111111 = 0xff
return (andResult, orResult, xorResult);
}
function shiftOperations() public pure returns (bytes1, bytes1) {
bytes1 data = 0x0f; // 00001111
bytes1 leftShift = data << 4; // 11110000 = 0xf0
bytes1 rightShift = data >> 2; // 00000011 = 0x03
return (leftShift, rightShift);
}
}
bytes 是一个动态大小的字节数组,适合存储任意长度的原始字节数据。
pragma solidity ^0.8.0;
contract DynamicBytes {
bytes public data;
function setData(bytes memory _data) public {
data = _data;
}
function getLength() public view returns (uint) {
return data.length;
}
function getByteAt(uint index) public view returns (bytes1) {
require(index < data.length, "Index out of bounds");
return data[index];
}
function appendByte(bytes1 _byte) public {
data.push(_byte); // 在末尾添加一个字节
}
function removeLast() public {
require(data.length > 0, "Array is empty");
data.pop(); // 删除最后一个字节
}
}
memory, storage, calldata)push() 和 pop()bytes1[] 数组,bytes 更加紧凑,节省 Gaspragma solidity ^0.8.0;
contract BytesModification {
bytes public data = hex"010203";
function modifyByte(uint index, bytes1 value) public {
require(index < data.length, "Index out of bounds");
data[index] = value;
}
function concatenateBytes(bytes memory a, bytes memory b)
public
pure
returns (bytes memory)
{
return bytes.concat(a, b);
}
}
提示: 当需要存储任意长度的字节数据时,优先使用
bytes而不是bytes1[],因为bytes更加紧凑,能节省 Gas 费用。
string 是 Solidity 中用于处理文本数据的类型,本质上是 UTF-8 编码的动态字节数组。
pragma solidity ^0.8.0;
contract StringExample {
string public name = "Hello, Solidity!";
string public emoji = "🚀";
function setName(string memory _name) public {
name = _name;
}
function getName() public view returns (string memory) {
return name;
}
}
警告:
string类型没有length属性,也不支持索引访问。如果需要操作字符串的字节,需要先转换为bytes。
由于 Solidity 原生对字符串的操作支持有限,通常需要转换为 bytes 进行操作。
pragma solidity ^0.8.0;
contract StringOperations {
// 获取字符串长度(字节数)
function getStringLength(string memory str) public pure returns (uint) {
return bytes(str).length;
}
// 连接两个字符串
function concatenate(string memory a, string memory b)
public
pure
returns (string memory)
{
return string(bytes.concat(bytes(a), bytes(b)));
}
// 比较两个字符串是否相等
function compareStrings(string memory a, string memory b)
public
pure
returns (bool)
{
return keccak256(bytes(a)) == keccak256(bytes(b));
}
// 修改字符串的某个字节
function modifyStringByte(string memory str, uint index, bytes1 newByte)
public
pure
returns (string memory)
{
bytes memory strBytes = bytes(str);
require(index < strBytes.length, "Index out of bounds");
strBytes[index] = newByte;
return string(strBytes);
}
}
说明: 字符串比较不能直接使用
==,而是通过比较两者的keccak256哈希值来判断是否相等。
pragma solidity ^0.8.0;
contract TypeConversion {
// string 转 bytes
function stringToBytes(string memory str) public pure returns (bytes memory) {
return bytes(str);
}
// bytes 转 string
function bytesToString(bytes memory data) public pure returns (string memory) {
return string(data);
}
}
pragma solidity ^0.8.0;
contract Bytes32StringConversion {
// bytes32 转 string
function bytes32ToString(bytes32 data) public pure returns (string memory) {
// 找到实际的字符串长度(去除尾部的零字节)
uint length = 0;
while(length < 32 && data[length] != 0) {
length++;
}
bytes memory bytesArray = new bytes(length);
for(uint i = 0; i < length; i++) {
bytesArray[i] = data[i];
}
return string(bytesArray);
}
// string 转 bytes32(字符串长度不能超过32字节)
function stringToBytes32(string memory source) public pure returns (bytes32 result) {
bytes memory tempBytes = bytes(source);
require(tempBytes.length <= 32, "String too long");
assembly {
result := mload(add(tempBytes, 32))
}
}
}
pragma solidity ^0.8.0;
contract UserProfile {
struct Profile {
string username;
string bio;
bytes32 avatar; // IPFS 哈希的一部分
}
mapping(address => Profile) public profiles;
function setProfile(string memory _username, string memory _bio) public {
profiles[msg.sender] = Profile({
username: _username,
bio: _bio,
avatar: 0x0
});
}
}
pragma solidity ^0.8.0;
contract DataStorage {
// 使用 bytes32 存储哈希值(节省 Gas)
mapping(uint => bytes32) public dataHashes;
function storeDataHash(uint id, bytes memory data) public {
dataHashes[id] = keccak256(data);
}
function verifyData(uint id, bytes memory data) public view returns (bool) {
return dataHashes[id] == keccak256(data);
}
}
pragma solidity ^0.8.0;
contract SimpleNFT {
mapping(uint256 => string) private _tokenURIs;
function tokenURI(uint256 tokenId) public view returns (string memory) {
return _tokenURIs[tokenId];
}
function setTokenURI(uint256 tokenId, string memory uri) internal {
_tokenURIs[tokenId] = uri;
}
}
不同的字节类型在 Gas 消耗上有显著差异:
| 类型 | 使用场景 | Gas 效率 | 备注 |
|---|---|---|---|
bytes32 |
固定长度哈希、ID | ⭐⭐⭐⭐⭐ | 最省 Gas,推荐用于固定长度数据 |
bytes |
变长原始数据 | ⭐⭐⭐⭐ | 比 bytes1[] 省 Gas |
bytes1[] |
动态字节数组 | ⭐⭐ | 较耗 Gas,不推荐 |
string |
文本数据 | ⭐⭐⭐ | 与 bytes 类似,用于 UTF-8 文本 |
Gas 优化建议:
- 固定长度数据优先使用定长类型:如果数据长度确定(如哈希值),使用
bytes32而不是bytes或string- 变长数据使用 bytes:需要存储变长原始数据时,使用
bytes而不是bytes1[]- 短字符串使用 bytes32:如果字符串长度不超过32字节且长度相对固定,可以考虑使用
bytes32- 链下存储:长文本数据(如文章、大段描述)应该存储在链外(IPFS),合约只存储哈希或 URI
尝试实现一个字符串工具合约,包含以下功能:
pragma solidity ^0.8.0;
contract StringUtils {
// TODO: 实现字符串拼接
function concat(string memory a, string memory b)
public
pure
returns (string memory)
{
// 你的代码
}
// TODO: 判断字符串是否为空
function isEmpty(string memory str) public pure returns (bool) {
// 你的代码
}
// TODO: 获取字符串长度
function length(string memory str) public pure returns (uint) {
// 你的代码
}
}
实现一个字节数组操作合约:
pragma solidity ^0.8.0;
contract BytesUtils {
bytes public data;
// TODO: 添加多个字节
function appendBytes(bytes memory newData) public {
// 你的代码
}
// TODO: 清空数组
function clear() public {
// 你的代码
}
// TODO: 反转字节数组
function reverse() public {
// 你的代码
}
}
bytes1 - bytes32):固定长度,Gas 效率最高,适合存储哈希、ID 等固定长度数据bytes):可变长度,适合存储原始字节数据,比 bytes1[] 更省 Gasstring):UTF-8 编码的文本,操作受限,需要转换为 bytes 后才能进行复杂操作string 和 bytes 可以相互转换,bytes32 需要特殊处理字节类型和字符串是处理数据的重要工具。在实际开发中,根据具体场景选择合适的类型,可以提高合约的效率和可维护性。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!