【Rust自学】15.6. RefCell与内部可变性:“摆脱”安全性限制

news2025/1/31 6:27:17

题外话,这篇文章一共4050字,是截止到目前为止最长的文章,如果你能坚持读完并理解,那真的很强!
请添加图片描述
喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
请添加图片描述

15.6.1. 什么是内部可变性

内部可变性(interior mutability)是Rust的设计模式之一,它允许程序员在只持有不可变引用的前提下对数据进行修改。

通常而言,这样的行为会被借用规则(详见 4.4. 引用与借用)所禁止,但是为了能够改变数据,内部可变性模式在代码的数据结构里使用了unsafe代码来绕过Rust正常的可变性和借用规则。

不安全代码向编译器表明我们正在手动检查规则,而不是依赖编译器为我们检查规则。不安全代码相关的概念将在以后的文章中涉及。

15.6.2. RefCell<T>

Rc<T>不同,RefCell<T>类型代表了其持有数据的唯一所有权

为了了解RefCell<T>Box<T>的区别,我们得回顾一下借用规则(详见 4.4. 引用与借用):

  • 在任何给定时间,你可以拥有(但不能同时拥有)一个可变引用或任意数量的不可变引用。
  • 引用总是保持有效。

PS:给定时间可以理解为给定的作用域内

RefCell<T>Box<T>的区别如下:

类型检查阶段规则违背后果
Box<T>编译阶段检查借用规则编译时报错
RefCell<T>运行时检查借用规则触发 panic

借用规则在不同阶段进行检查有不同的特点:

  • 编译阶段:

    • 尽早暴露问题
    • 没有任何运行时的开销
    • 是大多数场景的最佳选择
    • 是Rust的默认行为
  • 运行时:

    • 问题暴露延后,甚至到生产环境
    • 因借用计数产生些许性能损失
    • 实现某些特定的内存安全场景(比如在不可变环境中修改自身数据)

该在什么时候使用`RefCell

Rust编译器在编译阶段会检查所有的代码,其中大部分代码它都能够分析明白,如果没有问题就通过编译,如果有问题就报错。

Rust编译器是非常保守的,某些代码并不能在编译阶段就能分析明白,针对这类无法在编译阶段完成分析的代码Rust会直接拒绝掉,哪怕这些代码本质上没有任何问题。

Rust这么保守是为了保证程序的安全性。虽然拒绝掉某些本身没有问题的代码会对开发者造成不便,但是至少不会产生任何灾难性的后果。

针对这些编译器无法分析的代码,如果开发者能够保证这段代码满足借用规则,那么就可以使用RefCell<T>

RefCell<T>类似,Rc<T>只适用于单线程场景。

15.6.3. 如何在Box<T>Rc<T>RefCell<T>中进行选择

根据下表列出的三者的特性就可以进行选择:

特性Box<T>Rc<T>RefCell<T>
同一数据的所有者一个多个一个
可变性、借用检查可变、不可变借用(编译时检查)不可变借用(编译时检查)可变、不可变借用(运行时检查)

额外说一句,由于RefCell<T>在运行时才会被检查,所以即使RefCell<T>本身是不可变的,但我们仍然可以修改里面储存的值

15.6.4. 内部可变形:可变的借用一个不可变的值

这个小标题有一点绕,意思是对一个没有声明为mut的类型使用&mut引用。看个例子就明白了:

fn main() {
    let x = 5;
    let y = &mut x;
}

借用规则的一个推论是,当你有一个不可变的值时,你就不能可变地借用它。所以这么写会报错:

error[E0596]: cannot borrow `x` as mutable, as it is not declared as mutable
 --> src/main.rs:3:13
  |
3 |     let y = &mut x;
  |             ^^^^^^ cannot borrow as mutable
  |
help: consider changing this to be mutable
  |
2 |     let mut x = 5;
  |         +++

For more information about this error, try `rustc --explain E0596`.
error: could not compile `borrowing` (bin "borrowing") due to 1 previous error

然而在某些特定情况下,我们会需要这样一个值——它对外部保持不可变,但它同时能在方法内部修改自身的值,除了这个值本身的方法,其余的代码都不能修改这个值,这叫做内部可变性RefCell<T>就是为了这种情况而存在的。

但是RefCell<T>并没有完全地绕开借用规则,编译阶段的检查虽然能够通过,但是在运行阶段如果违反了借用规则就会造成程序恐慌。

下面看一个例子(lib.rs):

功能:用于跟踪某个值与最大值的接近程度,并在该值达到特定级别时发出警告

pub trait Messenger {
    fn send(&self, msg: &str);
}

pub struct LimitTracker<'a, T: Messenger> {
    messenger: &'a T,
    value: usize,
    max: usize,
}

impl<'a, T> LimitTracker<'a, T>
where
    T: Messenger,
{
    pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {
        LimitTracker {
            messenger,
            value: 0,
            max,
        }
    }

    pub fn set_value(&mut self, value: usize) {
        self.value = value;

        let percentage_of_max = self.value as f64 / self.max as f64;

        if percentage_of_max >= 1.0 {
            self.messenger.send("Error: You are over your quota!");
        } else if percentage_of_max >= 0.9 {
            self.messenger
                .send("Urgent warning: You've used up over 90% of your quota!");
        } else if percentage_of_max >= 0.75 {
            self.messenger
                .send("Warning: You've used up over 75% of your quota!");
        }
    }
}

这个例子的逻辑并不重要,看一下它的写法:

  • 程序开头定义了Messenger trait,里面有send方法的签名:接收不可变引用&self和一个字符串切片类型&str的形参msg作为参数。

  • 下面定义了一个结构体叫LimitTracker,它是一个泛型类型,生命周期为'a,泛型参数为T,要求T的生命周期为'a并实现Messenger这个在程序开头定义的trait。LimitTracker里面有三个字段:

    • messenger:类型为&str字符串切片类型,生命周期为'a
    • value:类型为usize
    • max:类型为usize
  • 往下看,通过impl块在LimitTracker上写了关联函数new,其参数是类型为泛型引用&T的形参messenger和类型为usize的形参max,返回值是LimitTracker类型。这个函数用于创建LimitTracker实例,这个实例:

    • messenger字段是形参menssenger的值
    • value字段值为0
    • max字段值为形参max的值
  • LimitTracker还有一个方法叫做set_value,其第一个参数是self的可变引用&mut self,第二个参数是value,类型为usize
    方法内部的代码逻辑很简单。把selfvalue字段值和参数value的值相除(还要转换成f64避免丢失精度)得到一个百分比,存储在percentage_of_max内。根据percentage_of_max的大小使用Messenger trait下的send方法发送不同的警告。

使用测试替代(test double)进行测试

这里有一个问题,如果我们要对这个set_value方法进行测试,就需要这个方法得输出些什么东西以供断言。但是set_value方法实际上并没有返回任何的值,所以说它不会提供任何的结果来进行断言。

我们要测试的是当某一个实现了Messenger trait的值和一个max值来创建LimitTracker实例时,传入不同的value就能够触发Messenger发送不同的消息。

为了解决这个问题,这里要介绍test double,它的中文叫测试替代,是一个通用的变成概念,代表了测试工作中被用作其他类型的替代品。test double中有一个特定的类型,叫模拟对象Mock Object),它会承担记录测试过程中的工作。我们就可以利用这些记录来断言这个测试工作运行是否正确。

Rust里没有类似的概念,在标准库里也没有模拟对象(Mock Object),但是我们可以自定义一个结构体来实现和Mock Object相同的功能。

接着上文的代码来写:

#[cfg(test)]
mod tests {
    use super::*;

    struct MockMessenger {
        sent_messages: Vec<String>,
    }

    impl MockMessenger {
        fn new() -> MockMessenger {
            MockMessenger {
                sent_messages: vec![],
            }
        }
    }

    impl Messenger for MockMessenger {
        fn send(&self, message: &str) {
            self.sent_messages.push(String::from(message));
        }
    }

    #[test]
    fn it_sends_an_over_75_percent_warning_message() {
        let mock_messenger = MockMessenger::new();
        let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);

        limit_tracker.set_value(80);

        assert_eq!(mock_messenger.sent_messages.len(), 1);
    }
}
  • 在测试模块的最开头先声明了MockMessenger结构体,里面有1个字段sent_message,表示发送的消息,其类型是Vec<String>

  • MockMessenger在下文又通过impl块创建了了new函数用于创建MockMessenger的实例,实例的sent_messages字段的值是一个空的Vector

  • 后面又为MockMessenger结构体实现了整个代码一开头的Messenger trait。实现了这个trait之后MockMessenger就可以用来创建LimitTracker(因为LimitTracker要求泛型类型实现Messenger trait)。
    使用send方法时这个消息会存储在MockMessenger下字段sent_message这个Vector里。

  • 最后是it_sends_an_over_75_percent_warning_message这个测试函数,它测试的是超过75%的这部分。
    首先创建了MockMessenger的实例叫mock_messenger,然后创建了一个LimitTracker的实例叫limit_tracker,接着在LimitTracker的实例上(就是limit_tracker)调用。
    最后通过mock_messengersent_message这个Vector里元素的数量来断言。

此时的代码逻辑有问题,但是运行会报错:

error[E0596]: cannot borrow `self.sent_messages` as mutable, as it is behind a `&` reference
  --> src/lib.rs:58:13
   |
58 |             self.sent_messages.push(String::from(message));
   |             ^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable
   |
help: consider changing this to be a mutable reference in the `impl` method and the `trait` definition
   |
2  ~     fn send(&mut self, msg: &str);
3  | }
...
56 |     impl Messenger for MockMessenger {
57 ~         fn send(&mut self, message: &str) {
   |

For more information about this error, try `rustc --explain E0596`.
error: could not compile `limit-tracker` (lib test) due to 1 previous error

错误在为MockMessenger实现Messenger trait时定义send方法的过程:

impl Messenger for MockMessenger {
    fn send(&self, message: &str) {
        self.sent_messages.push(String::from(message));
    }
}

无法修改MockMessenger来跟踪消息,因为send方法的函数签名参数是对self不可变引用。我们也无法使用&mut self来代替,因为这样send的签名将与Messenger trait定义中的签名&self不匹配。

针对这种需要内部可变性的情况,就可以使用RefCell<T>,只需要把MockMessengersent_messages字段用RefCell<T>再包装一下即可:

struct MockMessenger {
    sent_messages: RefCell<Vec<String>>,
}

由于RefCell<T>不在预导入模块中,所以在使用它之前得先把它引入当前作用域

use std::cell::RefCell;

这样改了之后使用了sent_messages字段的代码都需要使用RefCell<T>再包装一下:

impl MockMessenger {
    fn new() -> MockMessenger {
        MockMessenger {
            sent_messages: RefCell::new(vec![]),
        }
    }
}

RefCell到底是怎么用的呢?其实就是用RefCell创建的数据,可以用borrow_mut方法来修改,对实参调用borrow_mut方法即可获得一个可变引用,所以为MockMessenger实现Messenger trait时定义send方法就可以使用borrow_mut

impl Messenger for MockMessenger {
    fn send(&self, message: &str) {
        self.sent_messages.borrow_mut().push(String::from(message));
    }
}

这样即使send的参数是不可变引用,在函数体里也可以通过borrow_mut来修改其值。

最后把测试函数的断言部分改一下:

fn it_sends_an_over_75_percent_warning_message() {
    let mock_messenger = MockMessenger::new();
    let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);

    limit_tracker.set_value(80);

    assert_eq!(mock_messenger.sent_messages..borrow().len(), 1);
}

mock_messenger使用borrow函数即可获取对该变量的不可变引用用于断言。

这时候运行就没有问题了,整体代码如下:

pub trait Messenger {
    fn send(&self, msg: &str);
}

pub struct LimitTracker<'a, T: Messenger> {
    messenger: &'a T,
    value: usize,
    max: usize,
}

impl<'a, T> LimitTracker<'a, T>
where
    T: Messenger,
{
    pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {
        LimitTracker {
            messenger,
            value: 0,
            max,
        }
    }

    pub fn set_value(&mut self, value: usize) {
        self.value = value;

        let percentage_of_max = self.value as f64 / self.max as f64;

        if percentage_of_max >= 1.0 {
            self.messenger.send("Error: You are over your quota!");
        } else if percentage_of_max >= 0.9 {
            self.messenger
                .send("Urgent warning: You've used up over 90% of your quota!");
        } else if percentage_of_max >= 0.75 {
            self.messenger
                .send("Warning: You've used up over 75% of your quota!");
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::cell::RefCell;

    struct MockMessenger {
        sent_messages: RefCell<Vec<String>>,
    }

    impl MockMessenger {
        fn new() -> MockMessenger {
            MockMessenger {
                sent_messages: RefCell::new(vec![]),
            }
        }
    }

    impl Messenger for MockMessenger {
        fn send(&self, message: &str) {
            self.sent_messages.borrow_mut().push(String::from(message));
        }
    }

    #[test]
    fn it_sends_an_over_75_percent_warning_message() {
        let mock_messenger = MockMessenger::new();
	    let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);

	    limit_tracker.set_value(80);

        assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);
    }
}

15.6.5. 使用RefCell<T>在运行时记录借用信息

实际上,上文所使用的borrow_mutborrow方法相当于提供给用户的两个安全接口:

  • borrow:返回智能指针Ref<T>,它实现了Deref trait
  • borrow_mut:返回智能指针RefMut<T>,实现了Deref trait

RefCell<T>会记录当前存在多少活跃的Ref<T>RefMut<T>

  • 每次调用borrow:不可变借用计数加1。
    任何一个Ref<T>的值离开作用域被释放:不可变借用计数减1
  • 每次调用borrow_mut:可变借用计数加1
    任何一个RefMut<T>的值离开作用域被释放:可变借用计数减1

与编译时借用规则(详见 4.4. 引用与借用)一样, RefCell<T>允许我们在任何时间点拥有许多不可变借用或一个可变借用。

如果我们尝试违反这些规则, RefCell<T>的实现将在运行时出现恐慌(因为RefCell<T>在运行时才会进行借用规则检查)。出现恐慌 already borrowed: BorrowMutError 就是RefCell<T>在运行时处理违反借用规则的方式。

15.6.6. 将Rc<T>RefCell<T>结合使用的例子

Rc<T>允许某些数据被多个所有者持有,但它只提供对该数据的不可变访问。如果您有一个包含RefCell<T>Rc<T> ,你可以获得一个可以拥有多个所有者并且可变的值。

下面看一个将Rc<T>RefCell<T>结合使用来实现多重数据所有权的可变数据:

#[derive(Debug)]
enum List {
    Cons(Rc<RefCell<i32>>, Rc<List>),
    Nil,
}

use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;

fn main() {
    let value = Rc::new(RefCell::new(5));

    let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));

    let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a));
    let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a));

    *value.borrow_mut() += 10;

    println!("a after = {a:?}");
    println!("b after = {b:?}");
    println!("c after = {c:?}");
}

还记得上一篇文章的Cons列表示例吗?其中我们使用了Rc<T>允许多个列表共享另一个列表的所有权。因为Rc<T>仅保存不可变值,一旦创建了列表中的任何值,我们就无法更改它们。通过这篇文章的内容,让我们添加RefCell<T>以获得更改列表中的值的能力:

  • 首先在生命枚举类型List时把Cons关联的i32类型用RefCell<>包裹,由于Rust编译器无法确定RefCell<T>大小,得用Rc<>包裹在外,其余保持不变
  • 记得引入RcRefCell到当前作用域
  • 下面通过Rc::new()RefCell::new()来创建实例,a通过Rc::clone()来共享value的值,bc通过Rc::clone()来共享a的值(前提是aRc<>包裹)。
  • 最后通过RefCell<T>上的borrow_mut获得value的可变引用,其类型时&i32,然后通过解引用符号*变为i32来进行加10的操作。

输出:

a after = Cons(RefCell { value: 15 }, Nil)
b after = Cons(RefCell { value: 3 }, Cons(RefCell { value: 15 }, Nil))
c after = Cons(RefCell { value: 4 }, Cons(RefCell { value: 15 }, Nil))

跟预期一样,没有问题。

15.6.7. 其他可以实现内部可变性的类型

  • Cell<T>:通过复制来访问数据
  • Mutex<T>:用于实现跨线程情况下的内部可变性模5

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

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

相关文章

Luzmo 专为SaaS公司设计的嵌入式数据分析平台

Luzmo 是一款嵌入式数据分析平台&#xff0c;专为 SaaS 公司设计&#xff0c;旨在通过直观的可视化和快速开发流程简化数据驱动决策。以下是关于 Luzmo 的详细介绍&#xff1a; 1. 背景与定位 Luzmo 前身为 Cumul.io &#xff0c;专注于为 SaaS 公司提供嵌入式分析解决方案。…

HTML<label>标签

例子 三个带标签的单选按钮&#xff1a; <form action"/action_page.php"> <input type"radio" id"html" name"fav_language" value"HTML"> <label for"html">HTML</label><br&…

【技术洞察】2024科技绘卷:浪潮、突破、未来

涌动与突破 2024年&#xff0c;科技的浪潮汹涌澎湃&#xff0c;人工智能、量子计算、脑机接口等前沿技术如同璀璨星辰&#xff0c;方便了大家的日常生活&#xff0c;也照亮了人类未来的道路。这一年&#xff0c;科技的突破与创新不断刷新着人们对未来的想象。那么回顾2024年的科…

【Linux】gdb——Linux调试器

gdb使用背景 程序的发布方式有两种&#xff0c;debug模式和release模式 Linux gcc/g出来的二进制程序&#xff0c;默认是release模式 要使用gdb调试&#xff0c;必须在源代码生成二进制程序的时候, 加上 -g 选项 gdb使用方法 首先进入gdb gdb test_glist显示代码 断点 b 行…

fpga系列 HDL:XILINX Vivado Vitis 高层次综合(HLS) 实现 EBAZ板LED控制(上)

目录 创建工程创建源文件并编写C代码C仿真综合仿真导出RTL CG导出RTL错误处理&#xff1a; 创建工程 创建源文件并编写C代码 创建源文件(Souces下的hlsv.h和hlsv.cpp&#xff0c;Test Bench下的test_hlsv1.cpp)&#xff1a; hlsv1.h #ifndef HLSV1 #define HLSV1 #include &l…

卡特兰数学习

1&#xff0c;概念 卡特兰数&#xff08;英语&#xff1a;Catalan number&#xff09;&#xff0c;又称卡塔兰数&#xff0c;明安图数。是组合数学中一种常出现于各种计数问题中的数列。它在不同的计数问题中频繁出现。 2&#xff0c;公式 卡特兰数的递推公式为&#xff1a;f(…

Python-基于PyQt5,json和playsound的通用闹钟

前言&#xff1a;刚刚结束2024年秋季学期的学习&#xff0c;接下来我们继续来学习PyQt5。由于之前我们已经学习了PyQt5以及PyUIC,Pyrcc和QtDesigner的安装&#xff0c;配置。所以接下来我们一起深入PyQt5&#xff0c;学习如何利用PyQt5进行实际开发-基于PyQt5&#xff0c;json和…

关于数字地DGND和模拟地AGND隔离

文章目录 前言一、1、为什么要进行数字地和模拟地隔离二、隔离元件1.①0Ω电阻&#xff1a;2.②磁珠&#xff1a;3.电容&#xff1a;4.④电感&#xff1a; 三、隔离方法①单点接地②数字地与模拟地分开布线&#xff0c;最后再PCB板上一点接到电源。③电源隔离④、其他隔离方法 …

DeepSeek R1学习

0.回顾&#xff1a; https://blog.csdn.net/Together_CZ/article/details/144431432?ops_request_misc%257B%2522request%255Fid%2522%253A%25226574a586f0850d0329fbb720e5b8d5a9%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id…

12 款开源OCR发 PDF 识别框架

2024 年 12 款开源文档解析框架的选型对比评测&#xff1a;PDF解析、OCR识别功能解读、应用场景分析及优缺点比较 这是该系列的第二篇文章&#xff0c;聚焦于智能文档处理&#xff08;特别是 PDF 解析&#xff09;。无论是在模型预训练的数据收集阶段&#xff0c;还是基于 RAG…

【反悔堆】【hard】力扣871. 最低加油次数

汽车从起点出发驶向目的地&#xff0c;该目的地位于出发位置东面 target 英里处。 沿途有加油站&#xff0c;用数组 stations 表示。其中 stations[i] [positioni, fueli] 表示第 i 个加油站位于出发位置东面 positioni 英里处&#xff0c;并且有 fueli 升汽油。 假设汽车油…

为什么应用程序是特定于操作系统的?[计算机原理]

你把WINDOWS程序复制到MAC上使用&#xff0c;会发现无法运行。你可能会说&#xff0c;MAC是arm处理器&#xff0c;而WINDWOS是X86 处理器。但是在2019年&#xff0c;那时候MAC电脑还全是Intel处理器&#xff0c;在同样的X86芯片上&#xff0c;运行MAC和WINDOWS 程序还是无法互相…

多项日常使用测试,带你了解如何选择AI工具 Deepseek VS ChatGpt VS Claude

多项日常使用测试&#xff0c;带你了解如何选择AI工具 Deepseek VS ChatGpt VS Claude 注&#xff1a;因为考虑到绝大部分人的使用&#xff0c;我这里所用的模型均为免费模型。官方可访问的。ChatGPT这里用的是4o Ai对话&#xff0c;编程一直以来都是人们所讨论的话题。Ai的出现…

什么是循环神经网络?

一、概念 循环神经网络&#xff08;Recurrent Neural Network, RNN&#xff09;是一类用于处理序列数据的神经网络。与传统的前馈神经网络不同&#xff0c;RNN具有循环连接&#xff0c;可以利用序列数据的时间依赖性。正因如此&#xff0c;RNN在自然语言处理、时间序列预测、语…

Flink运行时架构

一、系统架构 1&#xff09;作业管理器&#xff08;JobManager&#xff09; JobManager是一个Flink集群中任务管理和调度的核心&#xff0c;是控制应用执行的主进程。也就是说&#xff0c;每个应用都应该被唯一的JobManager所控制执行。 JobManger又包含3个不同的组件。 &am…

网络工程师 (6)操作系统概述

一、操作系统的定义 &#xff08;一&#xff09;基本定义 操作系统&#xff08;Operating System&#xff0c;简称OS&#xff09;是计算机系统中至关重要的基础性系统软件。它是计算机硬件与上层软件之间的桥梁&#xff0c;负责管理和控制整个计算机系统的硬件和软件资源&…

【2025年数学建模美赛C题】第1-5问F奖解题思路+高级绘图+可运行代码

基于多模型分析的奥运会奖牌预测与影响因素研究 解题思路一、问题重述二、问题分析三、模型假设与符号说明四、数据预处理五、奖牌榜预测5.1 基于LSTM长短期记忆循环神经网络的预测模型的建立5.2 模型预测结果 六、首枚奖牌预测6.1 BP神经网络的建立6.2 模型预测结果 七、各国奖…

RoboMaster- RDK X5能量机关实现案例(一)识别

作者&#xff1a;SkyXZ CSDN&#xff1a;https://blog.csdn.net/xiongqi123123 博客园&#xff1a;https://www.cnblogs.com/SkyXZ 在RoboMaster的25赛季&#xff0c;我主要负责了能量机关的视觉方案开发&#xff0c;目前整体算法已经搭建完成&#xff0c;实际方案上我使用的上…

检测到联想鼠标自动调出运行窗口,鼠标自己作为键盘操作

联想鼠标会自动时不时的调用“运行”窗口 然后鼠标自己作为键盘输入 然后打开这个网页 &#xff08;不是点击了什么鼠标外加按键&#xff0c;这个鼠标除了左右和中间滚轮&#xff0c;没有其他按键了&#xff09;

星火大模型接入及文本生成HTTP流式、非流式接口(JAVA)

文章目录 一、接入星火大模型二、基于JAVA实现HTTP非流式接口1.配置2.接口实现&#xff08;1&#xff09;分析接口请求&#xff08;2&#xff09;代码实现 3.功能测试&#xff08;1&#xff09;测试对话功能&#xff08;2&#xff09;测试记住上下文功能 三、基于JAVA实现HTTP流…