从字符串使用看Golang和Rust对内存使用的区别

从字符串使用看Golang和Rust对内存使用的区别

​ 今天从Rust偶然回到Golang的世界,怎么写代码怎么别扭,总是忍不住在句子结尾加个分号...看到golang的字符串使用起来特别爽可以到处复制疯狂乱用,有一种从部队宿舍豆腐块被子的生活回归到居家肥宅的随意感,想起好久之前看的golang底层有关的内容,就写点东西来比较一下golang和rust对string的使用。

Go的字符串

在 Go 中,每个字符串本质上是一个结构体,其定义好了就不可用索引进行修改,包含两个字段:

  1. 指向字符串内容的指针 (*byte):8 字节(在64 位架构下)。
  2. 字符串的长度 (len):8 字节。

其内存布局是这个这样的结构体:

golang 复制代码
struct string {
    data uintptr // 指向字符串内容的指针
    len  int     // 字符串的长度
}

所以golang里面的一个string事实上占用的正真大小为:

golang 复制代码
package main

import (
	"fmt"
	"unsafe"
)

func main() {
	str := "hello"
	fmt.Printf("len(): %d bytes (content size)\n", len(str))
	fmt.Printf("Sizeof string struct: %d bytes\n", unsafe.Sizeof(str))
	fmt.Printf("Total estimated memory: %d bytes\n", unsafe.Sizeof(str)+uintptr(len(str)))
}

output:

bash 复制代码
String: hello
len(): 5 bytes (content size)
Sizeof string struct: 16 bytes
Total estimated memory: 21 bytes

Rust的字符串

String

rust里面有两种常用的字符串,一个是String,另一个是&str。

在Rust中,String是一个可变的、堆分配的类型,底层实现是一个Vec<u8>

rust 复制代码
pub struct String {
    vec: Vec<u8>,
}

所以一个String本质上还包含着vector的结构,也就是:

  1. 指向堆分配数据的指针:8 字节(在64 位系统上)。
  2. 字符串的长度 (usize):8 字节。
  3. 堆分配容量 (usize):8 字节。

所以说一个rust的string所占用的内存就至少是24字节,而且其本质由于就是一个vector,可以根据索引修改vector里面的值

rust 复制代码
fn main() {
    let s = String::from("hello");
    println!("Size of String struct: {} bytes", std::mem::size_of::<String>());
    println!("Content length: {} bytes", s.len());
}

output:

bash 复制代码
Size of String struct: 24 bytes
Content length: 5 bytes

&str

​ 另一个是&str,在Rust中,&str是一个字符串切片 类型,它是对字符串数据的不可变引用。相比于String,&str更轻量级,因为它只是一个指向实际字符串数据的引用,而不是负责管理字符串数据本身。简单来说&str就是一个对静态内存或者堆内存的一个引用 。一个&str的大小是固定的,包含两个部分:

  1. 指向字符串内容的指针 (*const u8):8 字节(在 64 位系统上)。
  2. 字符串的长度 (usize):8 字节。

所以一个&str至少就是16字节。

rust 复制代码
fn main() {
    let s = "hello"; // &str 类型
    println!("Size of &str: {} bytes", std::mem::size_of_val(&s));
    println!("Content length: {} bytes", s.len());
}

output:

复制代码
Size of &str: 16 bytes
Content length: 5 bytes

why

&str和String的关系

  1. String转换为&str

    • &str是对String数据的不可变引用。

    • 通过&操作可以将String转换为&str,这并不是简单的取地址,而是生成一个指向String内部数据的引用。

    • 示例:

      复制代码
      let s = String::from("hello");
      let slice: &str = &s; //将 String 转为&str
      println!("{}", slice);
  2. &str转换为String

    • 如果你需要一个拥有所有权的字符串,可以通过.to_string()或String::from()将&str转换为String。

    • 示例:

      复制代码
      let slice: &str = "hello";
      let s: String = slice.to_string(); // 将 &str 转为 String
      println!("{}", s);

怎么要两个字符串?

都有String了,为什么还要个这种&str,有时候看别人的代码都只创建&str而不是String,这是为什么呢?而且String还可以修改可以直接克隆。

  1. 轻量级和高效
  • 内存开销更小:

    • &str是不可变的引用,不需要额外的堆分配。
    • 它只包含一个指针和一个长度,总大小为16 字节 ,比String的24 字节更小。
  • 数据共享:

    • &str是对现有字符串数据的引用,不会创建新数据或重新分配内存。适用于只读场景,避免不必要的性能开销。
    • 例如,字符串字面量("hello")是静态分配的,用&str表示效率更高。
  • 性能优越:

    • 在函数参数中使用&str而不是String,避免堆分配和拷贝。

    • 示例:

      rust 复制代码
      fn greet(name: &str) {
          println!("Hello, {}!", name);
      }
      let name = String::from("Alice");
      greet(&name); // 传递不可变引用,避免拷贝,类似于golang里面传递&string
  1. 安全性
  • &str的不可变性提供了额外的安全保障,确保引用的数据不会意外被修改。

3.适配静态字符串

  • 如果数据是静态的(如程序中的字符串字面量),选择&str是合适的,经常作为全局静态变量使用

    复制代码
    let s: &str = "hello world"; // 静态字符串

结尾

总结对比

特性 RustString &str Golangstring
大小 24字节 16字节 16字节
内存管理 动态分配堆内存 引用已有数据 堆分配
是否可变 可变 不可变 不可变
用途 动态字符串管理,修改内容 高效只读,数据共享 很多
典型场景 动态构建和管理字符串 静态字符串,函数参数,全局变量 很多
相关推荐
悠仁さん5 分钟前
【C++】vector 类
开发语言·c++
_upupup15 分钟前
智能指针的原理及简单模拟实现(C++)
开发语言·c++
en.en..23 分钟前
C语言---宏
c语言·开发语言
郝学胜-神的一滴31 分钟前
C++ Templates 01:拨开迷雾,读懂C++模板经典著作
开发语言·c++·程序人生·游戏·游戏引擎·软件构建
言乐61 小时前
Python实现关税自动计算模型
开发语言·python·django·virtualenv·pygame
朝朝辞暮i1 小时前
C++ 第 14 课:引用 &
开发语言·c++·算法
梅雅达编程笔记1 小时前
04-Python CSV数据保存与翻页抓取
开发语言·爬虫·python·pandas·数据采集·csv
C++ 老炮儿的技术栈1 小时前
有符号变量与无符号变量的区别
服务器·开发语言·前端·数据结构·c++·算法·c
估值探索者2 小时前
【Python量化策略实战 #04】Donchian 通道假突破太多?用 ATR 阈值过滤跑通真实突破信号
开发语言·python·接口·api接口·数据api接口·股票数据api接口
个 人 练 习 生2 小时前
C++:模板初阶详解
开发语言·c++·经验分享·学习