【问题标题】:Can I avoid cloning Strings by holding a reference instead?我可以通过保留引用来避免克隆字符串吗?
【发布时间】:2019-03-06 20:33:36
【问题描述】:

我有一个数据结构,我在其中提供了一个围绕读取缓冲区的包装器,以自动处理读出中的重复语句。

这是通过存储剩余重复次数和要重复的行的内部状态来完成的。

use std::fs::File;
use std::path::Path;
use std::io::BufReader;
use std::io::prelude::*;
use std::io::Error;
use std::num::NonZeroU32;
use std::mem;

pub struct Reader {
    handle: BufReader<File>,
    repeat_state: Option<(NonZeroU32, String)>,
}

impl Reader {
    pub fn new<P: AsRef<Path>>(path: P) -> Result<Reader, Error> {
        let file = File::open(path)?;
        let handle = BufReader::new(file);

        Ok(Reader {
            handle,
            repeat_state: None,
        })
    }

    /// get next line, respecting repeat instructions
    pub fn next_line(&mut self) -> Option<String> {
        if self.repeat_state.is_some() {
            let (repeats_left, last_line) = mem::replace(&mut self.repeat_state, None).unwrap();

            self.repeat_state = NonZeroU32::new(repeats_left.get() - 1)
                .map(|repeats_left| (repeats_left, last_line.clone()));

            Some(last_line)
        } else {
            let mut line = String::new();
            if self.handle.read_line(&mut line).is_err() || line.is_empty() {
                return None
            }

            if line.starts_with("repeat ") {
                let repeats: Option<u32> = line.chars().skip(7)
                    .take_while(|c| c.is_numeric())
                    .collect::<String>().parse().ok();

                self.repeat_state = repeats
                    .and_then(|repeats| NonZeroU32::new(repeats - 1))
                    .map(|repeats_left| (repeats_left, line.clone()))
            }

            Some(line)
        }
    }
}

#[test]
fn test_next_line() {
    let source = "
line one
repeat 2    line two and line three
line four
repeat 11   lines 5-15
line 16
line 17
last line (18)
    ".trim();
    let mut input = File::create("file.txt").unwrap();
    write!(input, "{}", source);


    let mut read = Reader::new("file.txt").unwrap();
    assert_eq!(
        read.next_line(),
        Some("line one\n".to_string())
    );
    assert_eq!(
        read.next_line(),
        Some("repeat 2    line two and line three\n".to_string())
    );
    assert_eq!(
        read.next_line(),
        Some("repeat 2    line two and line three\n".to_string())
    );
    assert_eq!(
        read.next_line(),
        Some("line four\n".to_string())
    );

    for _ in 5..=15 {
        assert_eq!(
            read.next_line(),
            Some("repeat 11   lines 5-15\n".to_string())
        );
    }

    assert_eq!(
        read.next_line(),
        Some("line 16\n".to_string())
    );
    assert_eq!(
        read.next_line(),
        Some("line 17\n".to_string())
    );
    assert_eq!(
        read.next_line(),
        Some("last line (18)".to_string())
    );
}

Playground

问题是我必须每次都克隆保留的重复值,以便既保留它又返回它。我想通过返回(并且可能存储)&amp;str 来避免这些昂贵的克隆。我已经尝试了几件事,但无法让它工作:

  • 存储String,返回&amp;str:“寿命不够长”生命周期错误
  • 存储&amp;str,返回&amp;str:相同的生命周期错误
  • Cow&lt;&amp;str&gt;
  • Box&lt;&amp;str&gt;

根据 CodeXL 基于时间的采样分析器,在使用调试信息构建发布模式后,这些克隆目前是我的程序的瓶颈。现在,我的程序已经足够快了,但我想知道是否有办法避免它们。

【问题讨论】:

  • mem::replace(&amp;mut self.repeat_state, None) — 那是Option::take

标签: memory-management rust


【解决方案1】:

您可以通过将字符串包装在 Rc 中并进行克隆来避免克隆字符串。克隆一个Rc 很便宜,因为它包括递增一个计数器:

pub struct Reader {
    handle: BufReader<File>,
    repeat_state: Option<(NonZeroU32, Rc<String>)>,
}

impl Reader {
    pub fn new<P: AsRef<Path>>(path: P) -> Result<Reader, Error> {
        let file = File::open(path)?;
        let handle = BufReader::new(file);

        Ok(Reader {
            handle,
            repeat_state: None,
        })
    }

    /// get next line, respecting repeat instructions
    pub fn next_line(&mut self) -> Option<Rc<String>> {
        if self.repeat_state.is_some() {
            let (repeats_left, last_line) = mem::replace(&mut self.repeat_state, None).unwrap();

            self.repeat_state = NonZeroU32::new(repeats_left.get() - 1)
                .map(|repeats_left| (repeats_left, last_line.clone()));

            Some(last_line)
        } else {
            let mut line = Rc::new (String::new());
            if self.handle.read_line(Rc::make_mut (&mut line)).is_err() || line.is_empty() {
                return None
            }

            if line.starts_with("repeat ") {
                let repeats: Option<u32> = line.chars().skip(7)
                    .take_while(|c| c.is_numeric())
                    .collect::<String>().parse().ok();

                self.repeat_state = repeats
                    .and_then(|repeats| NonZeroU32::new(repeats - 1))
                    .map(|repeats_left| (repeats_left, line.clone()))
            }

            Some(line)
        }
    }
}

playground

请注意,Rc 不能在多个线程之间共享。如果你想在线程之间共享字符串,你可以使用Arc来代替。

【讨论】:

  • 我觉得值得一提的是Rc使得数据结构不是线程安全的,而线程安全的替代方案是Arc,它更昂贵。
  • @Phoenix Rc 在多线程设置中使用是完全安全的,因为编译器会阻止你用它做任何不安全的事情,但这当然意味着编译器会将您限制为单个线程。我已经为此添加了注释。
  • 谢谢。我的意思是,如果你可以跨线程使用它是不安全的,但编译器认识到这是不安全的,所以它不允许你这样做。
猜你喜欢
  • 2011-04-27
  • 2014-08-31
  • 2018-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-09
  • 1970-01-01
  • 2021-04-27
相关资源
最近更新 更多