22.智能指针(下)

news2024/11/24 11:40:22

标题

  • 五、引用计数智能指针
    • 5.1 共享引用计数智能指针共享数据
    • 5.2 使用Box定义三个共享链表
    • 5.3 使用Rc代替Box
    • 5.4 引用计数增加实验
  • 六、RefCell和内部可变性模式
    • 6.1 通过RefCell在运行时检查借用规则
    • 6.2 内部可变性:不可变值的可变借用
      • 1)内部可变性的用例:mock对象
      • 2)创建测试场景
    • 6.3 RefCell在运行时记录借用
    • 6.4 结合Rc和RefCell拥有多个可变数据所有者
  • 七、引用循环会导致内存泄漏
    • 7.1 概念
    • 7.2 制造引用循环
    • 7.3 解决方案
      • 1)避免引用循环:将Rc变成Weak
      • 2)Strong VS Weak
      • 3)创建带有父子结点的树形数据结构

五、引用计数智能指针

  • 引用计数Rc<T>通过引入的数量记录着某个变量是否仍在被使用;
  • 如果引用计数为0,则代表没有任何有效引用,此时可以被清理;
  • Rc<T>用于堆上分配的内存被程序的多个部分读取,且无法确定哪一部分最后结束使用;
  • Rc<T>只能用于单线程;

5.1 共享引用计数智能指针共享数据

  • 创建两个共享第三个列表所有权的示例
    在这里插入图片描述
  • 列表a从5开始;
  • 列表bc分别从3和4开始,然后共享a的部分;

5.2 使用Box定义三个共享链表

enum List {
    Cons(i32, Box<List>),
    Nil,
}

use crate::List::{Cons, Nil};

fn main() {
    let a = Cons(5,Box::new(Cons(10,Box::new(Nil))));
    let b = Cons(3, Box::new(a));
    let c = Cons(4, Box::new(a));
}
  • 代码无法编译通过
  • 当创建列表b时,a的所有权被移进了b;
  • 当创建列表c时,a的所有权已经被移动,所以c无法创建成功;

5.3 使用Rc代替Box

enum List {
    Cons(i32, Rc<List>),
    Nil,
}

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

fn main() {
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
    let b = Cons(3, Rc::clone(&a));
    let c = Cons(4, Rc::clone(&a));
}
  • 在List中,使用Rc<T>代替Box<T>
  • 创建b和c时,克隆a所包含的Rc<List>,这使引用计数分别加1并允许a和b以及a和c之间共享Rc<List>中数据的所有权;
  • 也可以将Rc::clone(&a)换成a.clone()Rc::clone只会增加引用计数而不会执行深拷贝;

5.4 引用计数增加实验

  • 打印引用计数值,以便直接看着引用计数的变化;
fn main() {
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
    println!("count after creating a = {}", Rc::strong_count(&a));
    let b = Cons(3, Rc::clone(&a));
    println!("count after creating b = {}", Rc::strong_count(&a));
    {
        let c = Cons(4, Rc::clone(&a));
        println!("count after creating c = {}", Rc::strong_count(&a));
    }
    println!("count after c goes out of scope = {}", Rc::strong_count(&a));
}
  • 运行结果如下
    在这里插入图片描述
  • 初始计数为1,每调用一个克隆都会使计数加1;
  • 离开作用域后会自动减少计数;

通过不可变引用,Rc<T>允许在程序的多个部分之间只读的共享数据;

六、RefCell和内部可变性模式

  • 内部可变性(Interior mutability)是Rust的一个设计模式,可让使用者在有不可变引用时也可以改变数据(这通过是规则所禁止的);
  • 为了改变数据,该模式在数据结构中使用unsafe代码来模糊Rust通常的可变性和借用规则;
  • 当可以确保代码在运行时会遵守借用规则,即使编译器不能保证的情况,可以选择使用那些运用内部可变性模式的类型。所涉及的unsafe代码将被封装进安全的API中,而外部类型仍然是不可变的;

6.1 通过RefCell在运行时检查借用规则

  • RefCell<T>代表其数据的唯一所有权;
  • RefCell<T>的借用规则的不可变性作用于运行时
  • RefCell<T>只能用于单线程场景;

6.2 内部可变性:不可变值的可变借用

  • 当有不可变值时,不能可变地借用它;
  • 如下的编译报错
fn main() {
    let x = 5;
    let y = &mut x;
}
  • 可以在特定情况下,令一个值在其方法内部能够修改自身,而在其他代码中仍然视为不可变;
  • RefCell<T>是一个获得内部可变性的方法;

1)内部可变性的用例:mock对象

  • 测试替身(test double) 代表一个测试中替代某个类型的类型;
  • mock对象是特定类型的测试替身,它们记录测试过程中发生了什么以便可以说明操作是否正确;
  • Rust没有内建mock对象功能,可以自己创建一个与mock对象有着相同功能的结构体;

2)创建测试场景

  • 编写一个记录某个值与最大值的差距的库;
  • 根据当前值与最大值的差距来发送消息;
  • 例如:可以记录用户所允许的API调用数量限额;

src/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: &T, max: usize) -> LimitTracker<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!");
        }
    }
}
  • Message trait拥有send方法,这是定义的mock对象所需要拥有的接口;
  • set_value方法可以改变传递的value参数的值;
#[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);
        
        for item in &mock_messenger.sent_messages{
            println!("{}", item);
        }
        
        assert_eq!(mock_messenger.sent_messages.len(), 1);
    }
}
  • 测试代码定义了一个MockMessenger结构体,其要发送的字段为一个String值的Vec;
  • MockMessenger结构体实现Messenger trait,这样就可以为LimitTracker提供一个MockMessenger;
  • 测试用例中
    1. 创建一个MockMessager实例,send_message存储着空vector;
    2. 创建LimitTracker,传入MockMessenger实例和最大值100;
    3. 调用LimitTracker的set_value方法,并传入80,这将运行self.messenger.send("Warning: You've used up over 75% of your quota!");代码;
    4. 最后断言sent_messages的长度为1,打印其值;

编译报错
在这里插入图片描述

  • 由于send方法获取了self的不可变引用,因此不能修改MockMessenger来记录消息;
  • 也不能按照信息使用&mut self替代,否则send的参数与Messenger trait的参数不符合;
  • 将这两项也修改;
pub trait Messenger {
    fn send(&mut self, msg: &str);
}

impl Messenger for MockMessenger {
    fn send(&mut self, message: &str) {
        self.sent_messages.push(String::from(message));
    }
}
  • 还是如下报错;

在这里插入图片描述

  • 这就使得内部可变性有了用武之地!
  • 通过RefCell来储存sent_messages就可以解决这个问题;

修改的部分如下,采用注释进行对比

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

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

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

    impl Messenger for MockMessenger {
        fn send(&self, message: &str) {
            // self.sent_messages.push(String::from(message));
            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);

        for item in mock_messenger.sent_messages.borrow().iter(){
            println!("{}", item);
        }

        assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);
    }
}
  • sent_messages字段的类型由Vec<String>改为RefCell<Vec<String>>
  • 在new函数中新建了一个RefCell<Vec<String>>实例替代空vector;
  • 在send方法中,使用RefCell的borrow_mut方法获取RefCell中值的可变引用(是一个vector);
  • 最后的打印以及断言中,使用RefCell的borrow以获取vector的不可变引用;
  • 最后输出了vector中的字符串并通过了测试;
    在这里插入图片描述

6.3 RefCell在运行时记录借用

  • 一般情况下使用&&mut分别创建不可变和可变引用;
  • 对于RefCell<T>,分别使用borrowborrow_mut方法创建不可变和可变引用;
  • borrow方法返回Ref<T>类型的智能指针,borrow_mut方法返回RefMut<T>类型的智能指针;
  • RefCell<T>记录当前有多少个活动的Ref<T>RefMut<T>智能指针;
  • 每次调用borrow,RefCell<T>将活动的不可变借用计数加1,当Ref<T>值离开作用域时,不可变借用计数减1;
  • RefCell<T>的实现会在运行时出现panic;
impl Messenger for MockMessenger {
    fn send(&self, message: &str) {
        let mut one_borrow = self.sent_messages.borrow_mut();
        let mut two_borrow = self.sent_messages.borrow_mut();

        one_borrow.push(String::from(message));
        two_borrow.push(String::from(message));
    }
}
  • 代码在send函数里相同的作用域创建两个可变借用;
  • 编译能正常通过,运行测试时失败;

在这里插入图片描述

  • 错误提示already borrowed: BorrowMutError,就是RefCell<T>在运行时处理违反借用规则时的报错;

6.4 结合Rc和RefCell拥有多个可变数据所有者

  • RefCell<T>的一个常见用法是与Rc<T>结合;
  • Rc<T>允许对相同数据有多个所有者,只能提供数据的不可变访问;
  • 一个储存了RefCell<T>Rc<T>,可以得到有多个所有者且可以修改的值;
#[derive(Debug)]
enum List {
    Cons(Rc<RefCell<i32>>, Rc<List>),
    Nil,
}

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

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(6)), Rc::clone(&a));
    let c = Cons(Rc::new(RefCell::new(10)), Rc::clone(&a));

    *value.borrow_mut() += 10;

    println!("a after = {:?}", a);
    println!("b after = {:?}", b);
    println!("c after = {:?}", c);
}
  • 创建Rc<RefCell<i32>>实例并存储在变量value中;
  • 在a中用包含value的Cons成员创建了一个List;
  • 克隆value后a和value 以便a和value都能拥有其内部值 5 的所有权;
  • 将列表a封装进Rc<T>,当创建列表b和c时,他们都可以引用a;
  • 对value调用borrow_mut解引用Rc<T>以获取内部的RefCell<T>值;
  • borrow_mut方法返回RefMut<T>智能指针,可以对其使用解引用运算符并修改其内部值;
    在这里插入图片描述
  • 输出a,b,c时,可以看到他们都拥有修改后的值15;
  • 通过使用RefCell<T>可以拥有一个表面上不可变的List;
  • 可以使用RefCell<T>中提供内部可变性的方法来在需要时修改数据;
  • RefCell<T>的运行时借用规则检查也确实保护我们免于出现数据竞争;

标准库中也有其他提供内部可变性的类型,比如 Cell,它类似 RefCell 但有一点除外:它并非提供内部值的引用,而是把值拷贝进和拷贝出 Cell。还有 Mutex,其提供线程间安全的内部可变性。

七、引用循环会导致内存泄漏

7.1 概念

  • Rust的内存安全可以保证很难发生内存泄露,但并非完成不可能!
  • 使用Rc<T>RefCell<T>创造出循环引用,引用数量不会减少为0,就会发生内存泄漏;

7.2 制造引用循环

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

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

impl List {
    fn tail(&self) -> Option<&RefCell<Rc<List>>> {
        match self {
            Cons(_, item) => Some(item),
            Nil => None,
        }
    }
}

fn main() {
    let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));

    println!("a initial rc count = {}", Rc::strong_count(&a));
    println!("a next item = {:?}", a.tail());

    let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));

    println!("a rc count after b creation = {}", Rc::strong_count(&a));
    println!("b initial rc count = {}", Rc::strong_count(&b));
    println!("b next item = {:?}", b.tail());

    if let Some(link) = a.tail() {
        *link.borrow_mut() = Rc::clone(&b);
    }

    println!("b rc count after changing a = {}", Rc::strong_count(&b));
    println!("a rc count after changing a = {}", Rc::strong_count(&a));

    // Uncomment the next line to see that we have a cycle;
    // it will overflow the stack
    // println!("a next item = {:?}", a.tail());
}
  • Cons成员的第二个元素是RefCell<Rc<List>>,因此能够修改Cons成员所指向的List;
  • tail方法方便在有Cons成员的时候访问第二项;
  • 在main函数中创建了一个链表以及一个指向a中链表的b链表;
  • if let语句中使用a.tail()取出a的第二个元素,将它指向b,形成一个引用循环:两个List互相指向彼此;

在这里插入图片描述

  • 开始创建a时的引用数量为1,a的下一个元素是Nil;
  • b创建之后a的引用个数变为2,b的引用数量为1,b的下一个元素就是a;
  • 修改a的第二个元素的指向后,a和b都有两个引用。

在这里插入图片描述

  • 将main函数的最后一个println!的注释取消,就会发现在循环打印;

7.3 解决方案

  • 开发者采用自动化测试、代码评审等;
  • 重新组织数据结构,使得一部分引用拥有所有权而另一部分没有;

1)避免引用循环:将Rc变成Weak

  • Rc::cloneRc<T>实例的strong_count加1,Rc<T>的实例只有在strong_count为0时才会被清理;
  • Rc<T>实例通过调用Rc::downgrade方法可以创建值的Weak Reference(弱引用);
  • 调用Rc::downgrade时会得到Weak<T>类型的智能指针;
  • 调用Rc::downgrade时会将weak_count加1;
  • weak_count无需计数为0就能使Rc<T>实例被清理;

2)Strong VS Weak

  • 强引用(Strong Reference)是关于如何共享Rc<T>实例的所有权;
  • 弱引用(Weak Reference)并不表示所有权关系;
  • 当强引用数量为0时,弱引用会自动断开,因此弱引用不会创建循环引用;
  • Weak<T>实例上调用upgrade方法,返回Option<Rc<T>>,以此保证指向弱引用的值仍然存在;

3)创建带有父子结点的树形数据结构

加入子节点

use std::rc::Rc;
use std::cell::RefCell;

#[derive(Debug)]
struct Node {
    value: i32,
    children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        children: RefCell::new(vec![]),
    });

    let branch = Rc::new(Node {
        value: 5,
        children: RefCell::new(vec![Rc::clone(&leaf)]),
    });
}
  • 在Node结构体中:
    • Rc<Node>保证了所有的子结点共享所有权;
    • RefCell保证了能修改其他节点的子结点;
  • 在main函数中:
    • 创建了值为3且没有子节点的Node实例leaf
    • 创建了值为5且以leaf作为子节点的实例branch
    • 这就可以通过branch.children从branch中获得leaf,反过来不行(可以通过添加parent解决);

加入父结点

  • 要使能够从leaf中获得branch,需要再加一个parent;
  • parent的类型如果是Rc<T>则会产生循环引用;
  • 换个思路:
    • 父节点应该拥有子节点:父节点被销毁了,子节点也应该被销毁;
    • 子节点不应该拥有父节点:子节点被销毁,父节点应该依然存在;
    • 因此应该使用弱引用类型Weak<T>,具体的是RefCell<Weak<Node>>
use std::rc::{Rc, Weak};
use std::cell::RefCell;

#[derive(Debug)]
struct Node {
    value: i32,
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });

    println!("leaf parent = {:#?}", leaf.parent.borrow().upgrade());

    let branch = Rc::new(Node {
        value: 5,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![Rc::clone(&leaf)]),
    });

    *leaf.parent.borrow_mut() = Rc::downgrade(&branch);

    println!("leaf parent = {:#?}", leaf.parent.borrow().upgrade());
}
  • 对应Node结构体
    • 添加引向父节点的结构parent: RefCell::new(Weak::new()),
  • 对于main函数
    • 创建leaf结点:值为3,由于没有父结点,因此创建了空的weak引用实例;
    • 创建branch结点:值为5,父结点也为空,leaf仍作为子结点;
    • 第一个打印语句通过父结点的borrow()方法获取不可变引用,然后用upgrade()方法打印出来(为空);
    • 通过leaf.parent.borrow_mut()获取leaf的父结点的可变引用,然后通过解引用将其指向branch
    • 接着打印leaf的父结点,没有无限的输出也表示没有造成循环引用;

在这里插入图片描述

可视化strong_count和weak_count的改变

  • 创建新的内部作用域并放入branch的创建;
  • 观察Rc<Node>实例的strong_countweak_count值变化;
fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });

    println!(
        "leaf strong = {}, weak = {}",
        Rc::strong_count(&leaf),
        Rc::weak_count(&leaf),
    );

    {
        let branch = Rc::new(Node {
            value: 5,
            parent: RefCell::new(Weak::new()),
            children: RefCell::new(vec![Rc::clone(&leaf)]),
        });

        *leaf.parent.borrow_mut() = Rc::downgrade(&branch);

        println!(
            "branch strong = {}, weak = {}",
            Rc::strong_count(&branch),
            Rc::weak_count(&branch),
        );

        println!(
            "leaf strong = {}, weak = {}",
            Rc::strong_count(&leaf),
            Rc::weak_count(&leaf),
        );
    }

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
    println!(
        "leaf strong = {}, weak = {}",
        Rc::strong_count(&leaf),
        Rc::weak_count(&leaf),
    );
}

运行结果
在这里插入图片描述

  • 创建leaf之后,其Rc<Node>的强引用为1,没有弱引用;
  • 进入内部作用域
    • branch的强引用计数为1,弱引用计数也为1(leaf.parnet引向了branch);
    • leaf的弱引用计数为2(一个是本身的,另一个是branch的branch.children储存了leaf的Rc<Node>的拷贝),弱引用计数仍然为0;
  • 离开内部作用域后
    • branch的作用域也就结束了,leaf的Rc<Node>强引用减少为0,因此相应的branch被丢弃;
    • 来自leaf.parent的弱引用计数为1,这与Node是否被丢弃无关,因此没有产生任何内存泄漏!
  • 离开内部作用域后访问leaf的父节点,再次得到None。
  • 到程序结尾,由于leaf又是Rc<Node>唯一的引用了,因此最后打印leafRc<Node>的强引用计数为1,弱引用计数为0;

管理计数和值的逻辑都内建于Rc<T>Weak<T>以及它们的Drop trait实现中。通过在Node定义中指定从子节点到父节点的关系为一个Weak<T>引用,就能够拥有父节点和子节点之间的双向引用而不会造成引用循环和内存泄漏。

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

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

相关文章

Vue40 修改默认配置

修改默认配置 在官网查看各个属性的作用 ### 在vue.config.js文件中&#xff0c;修改属性的值

【数据结构与算法】图的存储(邻接矩阵,邻接表)详解

图的邻接矩阵数据结构 typedef enum { NDG, DG, NDN, DN } GraphKind;using VRType int; using InfoType int;typedef struct ArcCell {VRType adj;InfoType *info; } Arc[N][N];struct MGraph {ElemType vexs[N];Arc arc;int vexnum, arcnum;GraphKind kind; };ArcCell 结构…

Pip换源秘籍:让你的Python包飞行起来!

在Python的包管理中&#xff0c;Pip是最重要的工具之一。它允许开发者从Python Package Index (PyPI)安装包&#xff0c;但有时由于网络问题或服务器负载过高&#xff0c;直接从PyPI安装包可能会非常慢。这时&#xff0c;更换Pip源到一个更快的镜像站点是一个常见的解决方案。本…

Vue-双向数据绑定指令

v-model指令 双向数据绑定就是当数据设置给表单元素时&#xff0c;修改这个数据会修改表单元素的值&#xff0c; 修改表单元素的值同样也会修改这个数据 <body><div id"app"><input type"text" v-model"name"><p>{{name…

BookSim2 安装步骤教程 Network-on-Chips (NoCs) 片上网络模拟器 含视频

BookSim简介 BookSim2 一个用于Network-on-Chips (NoCs) 芯片上网络的周期精确模拟器。该模拟器的设计是为了实现网络组件的模拟灵活性和精确建模。 BookSim1 是一个通用的网络模拟器&#xff0c;并不专门针对片上环境。不支持在片上网络环境中提出的一些更先进的功能和拓扑…

如何实现外部编码器轴和虚轴电子齿轮比例随动(汇川AM400PLC)

1、如何添加虚轴可以参考下面文章链接: 如何添加虚轴(AM400PLC)-CSDN博客文章浏览阅读2次。EtherCAT运动控制总线启用的时候,选择EtherCAT总线任务周期。选择好后,选择点击添加。https://blog.csdn.net/m0_46143730/article/details/139898985?csdn_share_tail=%7B%22type…

《PyTorch计算机视觉实战》:一、二章

目录 第一章&#xff1a;人工神经网络基础 比较人工智能和传统机器学习 人工神经网络&#xff08;Artificial Neural Network&#xff0c;ANN&#xff09; 是一种受人类大脑运作方式启发而构建的监督学习算法。神经网络与人类大脑中神经元连接和激活的方式比较类似&#xff0…

django学习入门系列之第三点《CSS基础样式介绍3》

文章目录 浮动什么是浮动浮动的特性清除浮动 往期回顾 浮动 什么是浮动 float属性用于创建浮动框&#xff0c;将其移动到一边&#xff0c;直到左边缘或右边缘触及包含块或另一个浮动框的边缘。 浮动的特性 浮动元素会脱离标准流(脱标) 浮动的元素会一行内显示并且元素顶部对…

PostMan动态设置全局变量

1. 前言 在开发过程中调试接口&#xff0c;一般都会使用PostMan。 其中有几个变量可能是好几个接口共用的&#xff0c;就会出现频繁手动复制(ctrlc)、粘贴(ctrlv)的情况。 这个过程得非常留意&#xff0c;生怕复制错了&#xff0c;或删减了某些东西&#xff0c;导致接口报错。…

Centos7.9系统对于文件和文件夹的常用命令操作说明

一、背景描述 在我们的日常工作和生活中&#xff0c;会遇到需要连接Linux服务器操作的情况&#xff0c;最常遇到的一些操作就是文件和文件夹的操作&#xff0c;将这些常用操作记录一下&#xff0c;方便后续的使用。 二、文件常用操作指令 Linux下的指令可以通过按下【tab】键进…

RPC通信原理以及项目的技术选型

目录 1.引言 2、RPC通信原理 3.图示解析 4.再举个例子 1.引言 根据上一篇博客《单机&#xff0c;集群和分布式》的举的例子。 我们最终合理地通过对大型软件的合理划分&#xff0c;划分成不同模块&#xff0c;按需求&#xff08;硬件需求&#xff0c;高并发需求&#xff09…

【arm扩容】docker load -i tar包 空间不足

背景&#xff1a; 首先我在/home/nvidia/work下导入了一些镜像源码tar包。然后逐个load进去。当我 load -i dev-aarch64-18.04-20210423_2000.tar包的时候&#xff0c;出现 Error processing tar file(exit status 1): write /9818cf5a7cbd5a828600d9a4d4e62185a7067e2a6f2ee…

Nikto一键扫描Web服务器(KALI工具系列三十)

目录 1、KALI LINUX 简介 2、Nikto工具简介 3、信息收集 3.1 目标IP&#xff08;服务器) 3.2kali的IP 4、操作实例 4.1 基本扫描 4.2 扫描特定端口 4.3 保存扫描结果 4.4 指定保存格式 4.5 连接尝试 4.6 仅扫描文件上传 5、总结 1、KALI LINUX 简介 Kali Linux 是一…

若以框架学习(3),echarts结合后端数据展示,暂时完结。

前三天&#xff0c;参加毕业典礼&#xff0c;领毕业证&#xff0c;顿时感到空落落的失去感&#xff0c;没有工作&#xff0c;啥也没有&#xff0c;总感觉一辈子白活了。晚上ktv了一晚上&#xff0c;由于我不咋个唱歌&#xff0c;没心情&#xff0c;听哥几个唱了一晚上周杰伦&am…

gbase8s关于客户端和数据库连接的方式和应用建立连接的简单线索分工

应用和数据库的连接分为本地连接和远程连接&#xff0c;当应用程序和数据库在同一台服务器上为本地连接&#xff0c;不在一台服务器上为远程连接 1. 本地连接 本地连接三种方式&#xff1a; 通过共享内存消息系统&#xff1a;应用和数据库在同一台服务器上&#xff0c;应用程…

都2024年了,还有人不懂动态代理么?

文章目录 一、定义二、静态代理三、动态代理1. JDK代理1.1 JDK代理实现流程1.2 动态生成的类字节码 2. Cglib代理2.1 Cglib实现流程 四、总结 一、定义 静态代理和动态代理都反映了一个代理模式&#xff0c;代理模式是一种经典的设计模式&#xff0c;常用于为其他对象提供一种…

字节跳动:从梦想之芽到参天大树

字节跳动掌舵人&#xff1a;张一鸣 2012年&#xff1a;梦想的起点&#xff1a;在一个阳光明媚的早晨&#xff0c;北京的一座普通公寓里&#xff0c;一位名叫张一鸣的年轻人坐在电脑前&#xff0c;眼中闪烁着坚定的光芒。他的心中有一个梦想——通过技术改变世界&#xff0c;让…

AcWing 1801:蹄子剪刀布 ← 模拟题

【题目来源】https://www.acwing.com/problem/content/1803/【题目描述】 你可能听说过“石头剪刀布”的游戏。 这个游戏在牛当中同样流行&#xff0c;它们称之为“蹄子剪刀布”。 游戏的规则非常简单&#xff0c;两头牛相互对抗&#xff0c;数到三之后各出一个表示蹄子&#x…

32 - 判断三角形(高频 SQL 50 题基础版)

32 - 判断三角形 select *,if(xy>z and xz>y and zy > x,Yes,No) triangle fromTriangle;

webpack处理js资源10--webpack入门学习

处理 js 资源 有人可能会问&#xff0c;js 资源 Webpack 不能已经处理了吗&#xff0c;为什么我们还要处理呢&#xff1f; 原因是 Webpack 对 js 处理是有限的&#xff0c;只能编译 js 中 ES 模块化语法&#xff0c;不能编译其他语法&#xff0c;导致 js 不能在 IE 等浏览器运…