Rust - 字符串:str 与 String

在其他语言中,字符串通常都会比较简单,例如 “hello, world” 就是字符串章节的几乎全部内容了。

但是Rust中的字符串与其他语言有所不同,若带着其他语言的习惯来学习Rust字符串,将会波折不断。

所以最好先忘记脑中已有的关于字符串的知识,从头开始学习Rust字符串。

首先来看段逻辑简单的代码:

fn main() {let my_name = "Pascal";greet(my_name);
}fn greet(name: String) {println!("Hello, {}!", name);
}

greet 函数接受一个字符串类型的 name 参数,然后打印到终端控制台中,非常好理解,那么这段代码能不能通过编译呢?

error[E0308]: mismatched types--> src/main.rs:3:11|
3 |     greet(my_name);|           ^^^^^^^|           ||           expected struct `std::string::String`, found `&str`|           help: try using a conversion method: `my_name.to_string()`error: aborting due to previous error

编译后发现报错了。编译器提示 greet 函数需要一个 String 类型的字符串,却传入了一个 &str 类型的字符串。思考分析这个错误,它表明的信息是:我们写下的 let my_name = “Pascal”; 语句,my_name并不是 String 类型,而是 “&str” 类型。

相信如果按照其他编程语言的思路,此时就像遇到了无法理解的现象,我们需要抛开这种成见,从头开始学习Rust字符串的原理。

在学习字符串之前,先来看看什么是切片。

(一)Slice(切片)

如果你有其他编程语言的经验更好理解切片。

切片并不是 Rust 独有的概念,在 Go 语言中就非常流行。Rust切片它允许我们引用集合中部分连续的元素序列,而不是引用整个集合。即“从一个大蛋糕中切出一小块来”。

我们以数组为例,从a数组中引用“ [2, 3] ”,语句是这么写的:

let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
println!("{:?}", slice); // Rust 使用 {:?} 来打印数组切片
//执行结果:[2, 3]

请先不要纠结切片的语法,接下来我们会详细说明。

而对于 Rust 字符串的切片而言,从原理上讲即 “对 String 类型中某一部分的引用”,语句是这么写的:

let s = String::from("hello world");let hello = &s[0..5];
let world = &s[6..11];

hello 没有引用整个 String s,而是引用了 s 的一部分内容,通过 [0…5] 的方式来指定。

这就是创建切片的语法,使用方括号包括的一个序列:[开始索引…终止索引],其中开始索引是切片中第一个元素的索引位置,而终止索引是最后一个元素后面的索引位置,也就是这是一个 右半开区间 [a, b)

在切片数据结构内部会保存开始的位置和切片的长度,其中长度是通过 终止索引 - 开始索引 的方式计算得来的。

对于 let world = &s[6…11]; 来说,world 是一个切片,该切片的指针指向 s 的第 7 个字节(索引从 0 开始, 6 是第 7 个字节),且该切片的长度是 5 个字节。

在使用 Rust 的 …range 序列语法 时,如果我们想从索引 0 开始,可以使用如下的方式,这两个写法是等效的:

let s = String::from("hello");let slice = &s[0..2];
let slice = &s[..2];

同样的,如果我们的的切片想要包含 String 的最后一个字节,则可以这样使用:

let s = String::from("hello");let len = s.len();let slice = &s[4..len];
let slice = &s[4..];

我们也可以截取完整的 String 切片:

let s = String::from("hello");let len = s.len();let slice = &s[0..len];
let slice = &s[..];

在对字符串使用切片语法时需要格外小心,切片的索引必须落在字符之间的边界位置,也就是 UTF-8 字符的边界。

例如中文在 UTF-8 中占用三个字节,下面的代码就会崩溃:

 let s = "中国人";let a = &s[0..2];println!("{}",a);  //索引是一个右半开区间 [a, b),[0..2]显然拿不到完整的字符

注意:slice 是一种引用,所以它没有所有权。

这点并不难理解,结合上面我们已经提到的例子,slice 它允许我们引用集合中部分连续的元素序列,而不是引用整个集合。

而引用并不涉及 所有权转移,所以它并没有所有权。

(二)字符串Slice:&str

现在我们已经了解了Rust中的切片。接下来我们要引出Rust中很重要的一个概念:在Rust中,字符串切片的类型标识就是 &str,而我们说的“字符串字面量”类型标识也是 &str

现在,我们就能回过头再审视一下我们最初写下的这段报错的代码:

fn main() {let my_name = "Pascal";greet(my_name);
}fn greet(name: String) {println!("Hello, {}!", name);
}

编译器提示 greet 函数需要一个 String 类型的字符串,却传入了一个 &str 类型的字符串。

即:my_name的本质上其实是字符串字面量(&str类型),他并不是一个 动态字符串(String类型)。

也就是说,下面两行代码是写法不同但本质相同的代码:

let my_name = "Pascal";
let my_name: &str = "Pascal";

也许你会有一个问题,既然 my_name 是一个字符串切片引用,它引用的来源是谁呢,是从谁身上切的?

在 Rust 中,字符串字面量(如我们的 "Pascal")是在编译时由编译器处理的。当我们在代码中写入一个字符串字面量时,编译器会将其视为一个静态分配的字符串。这些字符串字面量通常存储在程序的只读数据段(在内存中)。

当你执行 let my_name = "Pascal"; 时,my_name 是一个对字符串字面量的引用。这个引用是一个字符串切片(&str),它指向存储在只读内存段中的 “Pascal” 字符串。

字符串切片 &str 是一个引用,它不拥有数据,只是指向数据。

切片指向了程序可执行文件中的某个点,这也是为什么字符串字面量是不可变的,因为 &str 是一个引用,引用是不能改变它的数据的。

根据这些内容,我们小小修改一下代码,就可以执行成功了:

fn main() {let my_name = "Pascal";greet(my_name);
}fn greet(name: &str) {println!("Hello, {}!", name);
}
//Hello, Pascal!

所有权、引用、借用、slice 这些概念都是为了让 Rust 程序在编译时确保了内存安全。

(三)Rust字符串的定义

顾名思义,字符串是由字符组成的连续集合,但是在上一节中我们提到过,Rust 中的字符是 Unicode 类型,因此每个字符占据 4 个字节内存空间,但是在字符串中不一样,字符串是 UTF-8 编码,也就是字符串中的字符所占的字节数是变化的(1 - 4),这样有助于大幅降低字符串所占用的内存空间。

Rust 在语言级别上,只有一种字符串类型: str

而它通常是以引用类型出现 &str,也就是上文提到的字符串切片。虽然语言级别只有上述的 str 类型,但是在标准库里,还有多种不同用途的字符串类型,其中使用最广的即是 String 类型,即动态字符串。

str 类型是硬编码进可执行文件,也无法被修改,但是 String 则是一个可增长、可改变且具有所有权的 UTF-8 编码字符串(动态字符串),当 Rust 用户提到字符串时,往往指的就是 Strin 类型和 &str 字符串切片类型,这两个类型都是 UTF-8 编码

除了 String 类型的字符串,Rust 的标准库还提供了其他类型的字符串,例如 OsString,OsStr,CsString 和CsStr 等,注意到这些名字都以 String 或者 Str 结尾了吗?

  • OsString 拥有字符串数据,而 OsStr 是其对应的借用类型。
  • CsString 拥有字符串数据,而 CsStr 是其对应的借用类型。

(四)动态字符串:String

(1)String 与 &str 的转换

在之前的代码中,已经见到好几种从 &str 类型生成 String 类型的操作:

let s = String::from("hello,world")
let s = "hello,world".to_string()

那么如何将 String 类型转为 &str 类型呢?答案很简单,取引用即可:

fn main() {let s = String::from("hello,world!");say_hello(&s);say_hello(&s[..]);say_hello(s.as_str()); //s.as_str() 创建了一个s字符串的引用
}fn say_hello(s: &str) {println!("{}",s);
}//结果:
//hello,world!
//hello,world!
//hello,world!
(2)字符串索引

在其它语言中,使用索引的方式访问字符串的某个字符或者子串是很正常的行为,但是在 Rust 中就会报错:

let s1 = String::from("hello");
let h = s1[0];
//报错:`String` cannot be indexed by `{integer}`

这是因为 Rust 强调内存安全和数据完整性。在 Rust 中,字符串是以 UTF-8 编码存储的,这意味着一个字符可能由多个字节组成。直接通过字节索引访问可能会导致字符串在逻辑上被错误地分割,从而破坏字符的完整性。

而要在 Rust 中安全地访问字符串中的字符或子串,我们可以使用以下几种方法,简单了解一下:

  1. 使用 chars() 方法:这个方法返回一个迭代器,迭代器中的每个元素都是字符串中的一个 Unicode 字符。你可以通过迭代器来访问每个字符。
let s = "hello";
for c in s.chars() {println!("{}", c);
}
  1. 使用 char_indices() 方法:这个方法返回一个迭代器,迭代器中的元素是 (byte_index, char) 形式的元组,其中 byte_index 是字符在字符串中的字节索引。
let s = "hello";
for (i, c) in s.char_indices() {println!("{}: {}", i, c);
}
  1. 使用 get() 方法:这个方法允许你通过字节索引来安全地访问字符串中的字符。如果索引有效,它会返回 Some(&char),否则返回 None。
let s = "hello";
if let Some(c) = s.get(0) {println!("{}", c);
} else {println!("Index out of bounds");
}
  1. 使用切片语法:如果你需要访问子串,可以使用切片语法 &s[start…end]。这里的 start 和 end 是字节索引,但 Rust 会确保切片操作不会在字符边界上切割。
let s = "hello";
let slice = &s[0..2]; // "he"
println!("{}", slice);
  1. 使用 split_off() 方法:如果你需要从字符串中分离出一部分,可以使用 split_off() 方法,它会返回一个新的 String,包含从指定索引开始的所有字符。
let mut s = String::from("hello");
let part = s.split_off(2); // s 现在是 "he", part 是 "llo"
println!("s: {}, part: {}", s, part);
(3)字符串操作

由于 String 是可变字符串,下面介绍 Rust String字符串的修改,添加,删除等常用方法。

(a)追加

在字符串尾部可以使用 push() 方法追加字符 char,也可以使用 push_str() 方法追加字符串字面量。这两个方法都是在原有的字符串上追加,并不会返回新的字符串

由于字符串追加操作要修改原来的字符串,则该字符串必须是可变的,即字符串变量必须由 mut 关键字修饰。

fn main() {let mut s = String::from("Hello ");s.push_str("rust");println!("追加字符串 push_str() -> {}", s);s.push('!');println!("追加字符 push() -> {}", s);
}//运行结果:
//追加字符串 push_str() -> Hello rust
//追加字符 push() -> Hello rust!
(b)插入

可以使用 insert() 方法插入单个字符 char,也可以使用 insert_str() 方法插入字符串字面量。与 push() 方法不同,这俩方法都需要传入两个参数,第一个参数是字符(串)插入位置的索引,第二个参数是要插入的字符(串)。

索引从 0 开始计数,如果越界则会发生错误。由于字符串插入操作要修改原来的字符串,则该字符串必须是可变的,即字符串变量必须由 mut 关键字修饰

fn main() {let mut s = String::from("Hello rust!");s.insert(5, ',');println!("插入字符 insert() -> {}", s);s.insert_str(6, " I like");println!("插入字符串 insert_str() -> {}", s);
}//运行结果:
//插入字符 insert() -> Hello, rust!
//插入字符串 insert_str() -> Hello, I like rust!
(c)替换

如果想要把字符串中的某个字符串替换成其它的字符串,那可以使用 replace() 方法。与替换有关的方法有三个。

  • replace
  • replacen
  • replace_range

replace 方法可用于 String&str 类型。

replace() 方法接收两个参数,第一个参数是要被替换的字符串,第二个参数是新的字符串。该方法会替换所有匹配到的字符串。

该方法是返回一个新的字符串,而不是操作原来的字符串

fn main() {let string_replace = String::from("I like rust. Learning rust is my favorite!");let new_string_replace = string_replace.replace("rust", "RUST");println!("{}",new_string_replace);
}//运行结果:
//I like RUST. Learning RUST is my favorite!

replacen 方法可用于 String&str 类型。

replacen() 方法接收三个参数,前两个参数与 replace() 方法一样,第三个参数则表示替换的个数。

该方法是返回一个新的字符串,而不是操作原来的字符串

fn main() {let string_replace = "I like rust. Learning rust is my favorite!";let new_string_replacen = string_replace.replacen("rust", "RUST", 1);println!("{}",new_string_replace);
}//运行结果:
//I like RUST. Learning rust is my favorite!

replace_range 方法仅适用于 String 类型。

replace_range 接收两个参数,第一个参数是要替换字符串的范围(Range),第二个参数是新的字符串。

该方法是直接操作原来的字符串,不会返回新的字符串。该方法需要使用 mut 关键字修饰

fn main() {let mut string_replace_range = String::from("I like rust!");string_replace_range.replace_range(7..8, "R");println!("{}",new_string_replace);
}
//运行结果:
//I like Rust!
(d)删除

与字符串删除相关的方法有 4 个,他们分别是 pop()remove()truncate()clear()。这四个方法仅适用于 String 类型。

pop —— 删除并返回字符串的最后一个字符

该方法是直接操作原来的字符串。但是存在返回值,其返回值是一个 Option (枚举) 类型,如果字符串为空,则返回 None

fn main() {let mut string_pop = String::from("rust pop 中文!");let p1 = string_pop.pop();let p2 = string_pop.pop();dbg!(p1);dbg!(p2);dbg!(string_pop);
}/*
p1 = Some('!',
)
p2 = Some('文',
)
string_pop = "rust pop 中"
*/

PS:dbg!() 宏是一个非常有用的调试工具。它允许你在运行时打印变量的值,而不需要添加任何额外的打印语句。

remove —— 删除并返回字符串中指定位置的字符

该方法是直接操作原来的字符串。但是存在返回值,其返回值是删除位置的字符串,只接收一个参数,表示该字符起始索引位置。remove 方法是按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误。

fn main() {let mut string_remove = String::from("测试remove方法");println!("string_remove 占 {} 个字节",std::mem::size_of_val(string_remove.as_str()));// 删除第一个汉字string_remove.remove(0);// 下面代码会发生错误// string_remove.remove(1);// 直接删除第二个汉字// string_remove.remove(3);dbg!(string_remove);
}//string_remove 占 18 个字节
//string_remove = "试remove方法"

truncate —— 删除字符串中从指定位置开始到结尾的全部字符

该方法是直接操作原来的字符串。无返回值。该方法 truncate() 方法是按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误。

fn main() {let mut string_truncate = String::from("测试truncate");string_truncate.truncate(3);dbg!(string_truncate);
}//string_truncate = "测"

clear —— 清空字符串

该方法是直接操作原来的字符串。调用后,删除字符串中的所有字符,相当于 truncate() 方法参数为 0 的时候。

fn main() {let mut string_clear = String::from("string clear");string_clear.clear();dbg!(string_clear);
}//string_clear = ""
(e)连接

使用 + 或者 += 连接字符串

使用 + 或者 += 操作符连接字符串时,要求操作符右边的参数必须为字符串的切片引用(Slice)类型。

其实当调用 + 的操作符时,相当于调用了 std::string 标准库中的 add() 方法,这里 add() 方法的第二个参数是一个引用的类型。因此我们在使用 + 时, 必须传递切片引用类型,不能直接传递 String 类型。

+ 是返回一个新的字符串,所以变量声明可以不需要 mut 关键字修饰

fn main() {let string_append = String::from("hello ");let string_rust = String::from("rust");let result = string_append + &string_rust; // &string_rust会自动解引用为&strlet result = result + "!"; // `result + "!"` 中的 `result` 是不可变的println!("连接字符串 + -> {}", result);
}//连接字符串 + -> hello rust!

使用 format! 连接字符串

format! 这种方式适用于 String&strformat! 的用法与 print! 的用法类似。

fn main() {let s1 = "hello";let s2 = String::from("rust");let s = format!("{} {}!", s1, s2);println!("{}", s);
}//hello rust!

(五)String与&str总结

在 Rust 中,String&str 都是处理字符串数据的类型,但它们之间的关系和用途有所不同。以下是它们之间的关系和区别:

  1. 所有权与借用
    • String 拥有其数据,它是一个动态分配在堆上的可变字符串类型。这意味着 String 可以增长和缩小,并且可以被修改。
    • &str 是一个字符串切片,它是一个对字符串数据的不可变引用。它不拥有数据,而是借用了其他字符串数据的一部分或全部。
  2. 内存管理
    • String 需要管理其在堆上的内存,因此它需要负责分配和释放内存。这使得 String 可以动态地改变其大小。
    • &str 只是一个引用,它不需要管理内存,因为它指向的数据可能来自 String、字符串字面量或其他数据源。
  3. 可变性
    • String 是可变的,你可以更改其内容,如添加、删除或修改字符。
    • &str 是不可变的,你不能更改它指向的数据。如果你需要修改数据,你必须在 String 上进行操作。
  4. 使用场景
    • 当你需要一个可以修改的字符串时,使用 String
    • 当你需要读取或处理字符串数据,但不需要修改它时,使用 &str
  5. 转换
    • 你可以从 String 创建一个 &str 引用,只需通过引用 String 实例即可,如 &my_string
    • 你也可以从字符串字面量创建一个 &str 引用,因为字符串字面量本身就是 &str 类型。
    • &str 创建 String 可以通过 to_string() 方法或 String::from() 函数,这涉及到从不可变引用到可变数据的转换,可能需要复制数据。
  6. 生命周期
    • String 拥有其数据,因此它不受生命周期的限制。
    • &str 是一个引用,它依赖于数据的生命周期。如果 &str 引用的数据超出了作用域,那么 &str 引用将不再有效。

总的来说,String&str 在 Rust 中提供了灵活的字符串处理机制,允许你根据需要选择最合适的类型。String 提供了可变性和动态内存管理,而 &str 提供了对字符串数据的不可变引用,这两者在不同的场景下都非常有用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/148716.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【一起学NLP】Chapter2-学习神经网络

目录 学习神经网络损失函数Tip:One-hot向量导数与梯度Tip:严格地说链式法则计算图反向传播其他典型的运算结点乘法结点分支节点Repeat节点Sum节点MatMul节点 Tip:浅拷贝和深拷贝的差异梯度的推导和反向传播的实现Sigmoid层Affine层Softmax with Loss层 权重的更新——随机梯度下…

机械手末端快换技术:工业自动化的强大新动力

在飞速发展的工业自动化领域,机械手无疑是生产线上的关键成员,其性能与效率对整个生产流程的顺畅性与高效性起着至关重要的作用。而机械手末端快换技术,作为这一领域的创新性突破,正以其卓越的优势引领着工业生产的巨大变革。 机…

迷雾大陆免费辅助:强流派推荐攻略!VMOS云手机自动辅助挂机教程!

使用VMOS云手机辅助《迷雾大陆》游戏,让你的游戏体验更轻松高效。VMOS云手机专为《迷雾大陆》提供了定制版的云手机,内置游戏安装包,无需再次下载安装。同时,VMOS云手机支持免费的辅助工具,可以24小时不间断地辅助游戏…

多肽合成的一般步骤 -- 固相合成篇

1.1 溶剂的处理 DMF、甲醇在使用前用G3孔的分子筛浸泡过夜除杂质和水。 1.2 树脂的充分溶胀 称取2.0 g 空白Wang树脂于洁净干燥的反应管中,加入15 mL DMF,室温活化30 min左右。 1.3 接第一个氨基酸 室温下,通过沙芯抽滤掉上步溶剂&#xf…

中电金信 :基于开放架构的私有云建设实践

01开放架构私有云诞生背景 随着国产化创新建设的深化,产业侧行业软件持续进行云原生改造,金融机构拥抱云和容器技术,实现数智化转型已是大势所趋。近年,云原生技术以及架构发展速度更是惊人,私有云开始有了新架构、有了…

<刷题笔记> 力扣105/106题 使用中序+前(后)序构造二叉树

在曾经的博客中,曾经记录过这样一题: 二叉树遍历_牛客题霸_牛客网 (nowcoder.com) 这是一个只需要前序就能构造二叉树的题,因为一旦遇到空,就有"#"作为返回的标志,能够立刻返回。 1. 中序前序 完全可以借鉴…

select查询表单

select查询语法: select 【1】from 【2】where 【3】 1若为*表示显示全部数据列,若为某一列列名则只显示本列内容(也可为多列列名)。若在1后面加as ‘c’,则表示把查询的列名换成c。 2为要查询的表表名。 3为查询的…

众数信科 AI智能体智慧文旅解决方案——智能旅行助手

智慧文旅解决方案 智能旅行助手方案 利用先进的AI算法 提供个性化旅游体验的智能服务 众数信科AI智能体 产品亮点 旅游路线智能规划 旅游景点智能问答 旅行游记智能生成等 构建旅行实用指南 让旅游更加便捷、高效、智能化 关于我们 众数信科成立于2021年,由…

CentOS Linux教程(6)--CentOS目录

文章目录 1. 根目录2. cd目录切换命令3. CentOS目录介绍4. pwd命令介绍5. ls命令介绍5.1 ls5.2 ls -a5.3 ls -l 1. 根目录 Windows电脑的根目录是计算机(我的电脑),然后C盘、D盘。 Linux系统的根目录是/,我们可以使用cd /进入根目录,然后使…

【mysql】case...when...、group...by、sum() 聚合函数... 企业组合使用实际场景示例

文章目录 查询需求场景预设查询结果SQL实现查询 查询需求场景 -- 统计当月不同流程类型的审批通过流程数、审批终止流程数、审批驳回流程数、新增流程数(归档终止退回)、申请总条目数(关联任务单申请条目数总数)-- 查询思路&…

蘑菇成熟待收检测系统源码分享

蘑菇成熟待收检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer…

最适配达梦、人大金仓的sql工具是什么?

SQLynx是一款功能强大的数据库管理工具,它不仅支持Oracle、MySQL等国际主流数据库,还很好地支持了武汉达梦、人大金仓等国产数据库。这款工具具有以下几个特点: 1.广泛支持:SQLynx支持多种数据库系统,包括PostgreSQL、…

ADC 位的作用

示波器的横轴表示时间基准(秒/格或 s/p),而纵轴表示电压(伏/格或 V/p)。垂直精度是指示波器显示信号电压的准确程度,这对于视觉呈现和测量至关重要。示波器屏幕上的电压读数越接近实际信号电压,…

专为工程地质领域安全监测而设计,BWII型广播预警遥测系统助您实现全面监测!

专为工程地质领域安全监测而设计,BWII型广播预警遥测系统助您实现全面监测! BWII型广播预警遥测系统是一款新型的雨量预警监测仪,具备多通道和多类型传感器接入功能。该系统能够定时采集和发送电压、电流、数字和脉冲等信息,同时结…

Day4-C语言高级编程

1. gcc和gdb的用法 GNU工具:编译工具:把一个源程序编译为一个可执行程序调试工具:能对执行程序 进行源码或汇编调试软件工程工具:用于协助多人开发或大型软件项目的管理,如make、CVS、Subvision其他工具:用…

Informer模型复现项目实战

加入会员社群,免费获取本项目数据集和代码:点击进入>> 1. 项目简介 A034-Informer模型复现项目实战的目标是通过复现Informer模型,帮助理解其在时间序列预测中的实际应用和效果。该项目基于深度学习模型Informer,这是一种针…

科研绘图系列:R语言连线点图(linechart dotplot)

文章目录 介绍加载R包导入数据数据预处理画图组合图形导出数据系统信息介绍 不同物种的强度和微生物的组成情况 加载R包 library("here") library("tidyverse") library("reshape2") library("vegan")

如何获取一个返回Promise<T>的函数的“T“的类型

type ThenArg<T> T extends Promise<infer U> ? U : never export type GenByStaTypeAndHfRsType ThenArg<ReturnType<typeof genByStaTypeAndHf>> 这样一来&#xff0c;虽然返回了Promise<T>&#xff0c;我们没定义T的情况下&#xff0c;也可…

揭秘大模型背后的神秘力量:算力、数据与算法的“黄金三角”

目录 揭秘大模型背后的神秘力量:算力、数据与算法的“黄金三角” 一、算力:大模型的超级引擎 二、数据:大模型的智慧源泉 三、高性能算法:大模型的智慧大脑 结语:黄金三角的共鸣 揭秘大模型背后的神秘力量:算力、数据与算法的“黄金三角” 在人工智能的浩瀚星空中,…

基于Ambari搭建hadoop生态圈+Centos7安装教程V2.0优化版(本篇博客写的较为详细,可能比较多,请耐心看)

当我们学习搭建hadoop的时候&#xff0c;未免也会遇见很多繁琐的事情&#xff0c;比如很多错误&#xff0c;需要解决。在以后公司&#xff0c;也不可能让你一个一个搭建hadoop&#xff0c;成千上万的电脑&#xff0c;你再一个个搭建&#xff0c;一个个报错&#xff0c;而且每台…