从字符串使用看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

&strString的关系

  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 字节 ,比String24 字节更小。
  • 数据共享

    • &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字节
内存管理 动态分配堆内存 引用已有数据 堆分配
是否可变 可变 不可变 不可变
用途 动态字符串管理,修改内容 高效只读,数据共享 很多
典型场景 动态构建和管理字符串 静态字符串,函数参数,全局变量 很多
相关推荐
蚂蚁背大象6 小时前
Rust 所有权系统是为了解决什么问题
后端·rust
布列瑟农的星空6 小时前
前端都能看懂的rust入门教程(五)—— 所有权
rust
Java水解1 天前
Rust嵌入式开发实战——从ARM裸机编程到RTOS应用
后端·rust
Pomelo_刘金1 天前
Rust:所有权系统
rust
Ranger09291 天前
鸿蒙开发新范式:Gpui
rust·harmonyos
DongLi014 天前
rustlings 学习笔记 -- exercises/05_vecs
rust
花酒锄作田5 天前
Gin 框架中的规范响应格式设计与实现
golang·gin
郑州光合科技余经理5 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
feifeigo1235 天前
matlab画图工具
开发语言·matlab
dustcell.5 天前
haproxy七层代理
java·开发语言·前端