1

我试图找出在 Rust中使用缓冲区和snappy的最佳方法。之前,我正在使用BufWriter. 但现在我也想添加压缩。上面 snappy crate 的compress函数需要 a&[u8]作为参数,但BufWriter不会让我访问它的缓冲区以传递给 snappy。我已经研究了两种方法来解决这个问题。

在第一种方式中,我使用向量 (with_capacity) 而不是BufWriter我的缓冲区,并创建了一个写入函数,以确保写入向量不会导致它重新分配。如果可以,我会压缩缓冲区中当前的内容,然后将其写入文件并释放向量(排水功能)。我是根据什么来写的BufWriter。缺点是因为它是一个向量,如果缓冲区超出范围,它不会自动将缓冲区刷新到文件中。我必须在编写文件的范围内手动执行此操作,这是我不喜欢的。

另一方面,我或多或少地复制了BufWriter源代码,只是更改了flush函数以在将其输出到文件之前压缩其缓冲区(向量)。这种方式似乎是最好的,但我只是不喜欢复制代码的想法。

继续使用这两个选项或其他选项的最佳方法是什么?

如果相关,我写入缓冲区的对象总是相同的大小,我的缓冲区大小是对象大小的倍数。

4

1 回答 1

1

由于看起来 snappy 需要一次性压缩所有内容,因此您只需将所有内容缓冲到最后。然后,您可以在最后冲洗和压缩:

use std::io::{self, Write, Cursor};

fn compress(_data: &[u8]) -> Vec<u8> {
    // The best compression ever
    b"compressed".as_ref().into()
}

struct SnappyCompressor<W> {
    inner: W,
    buffer: Vec<u8>,
}

impl<W> SnappyCompressor<W>
    where W: Write
{
    fn new(inner: W) -> Self {
        SnappyCompressor {
            inner: inner,
            buffer: vec![],
        }
    }
}

impl<W> Write for SnappyCompressor<W>
    where W: Write
{
    fn write(&mut self, data: &[u8]) -> io::Result<usize> {
        self.buffer.extend(data);
        Ok(data.len())
    }

    fn flush(&mut self) -> io::Result<()> {
        let compressed = compress(&self.buffer);
        self.inner.write_all(&compressed)
    }
}

fn main() {
    let mut output = Cursor::new(vec![]);
    {
        let mut compressor = SnappyCompressor::new(output.by_ref());
        assert_eq!(5, compressor.write(b"hello").unwrap());
        assert_eq!(5, compressor.write(b"world").unwrap());
        compressor.flush().unwrap();
    }
    let bytes = output.into_inner();
    assert_eq!(&b"compressed"[..], &bytes[..]);
}

这个解决方案有一个很大的问题——我们用它flush来标记流的结束,但这并不是该方法的真正含义。使用纯流式压缩器可能会好得多,但有时你必须做你必须做的事情。

还有一些地雷:

  1. 您必须明确调用flush
  2. 你不能打电话flush两次。

为了让用户简单地放下压缩器并完成它,您可以实现Drop

impl<W> Drop for SnappyCompressor<W>
    where W: Write
{
    fn drop(&mut self) {
        self.flush().unwrap();
    }
}

为了防止尝试刷新两次,您需要添加一个标志来跟踪它:

fn write(&mut self, data: &[u8]) -> io::Result<usize> {
    if self.is_flushed {
        return Err(Error::new(ErrorKind::Other, "Buffer has already been compressed, cannot add more data"));
    }

    self.buffer.extend(data);
    Ok(data.len())
}

fn flush(&mut self) -> io::Result<()> {
    if self.is_flushed {
        return Ok(())
    }

    self.is_flushed = true;
    let compressed = compress(&self.buffer);
    self.inner.write_all(&compressed)
}

总之,最终版本如下所示:

use std::io::{self, Write, Cursor, Error, ErrorKind};

fn compress(_data: &[u8]) -> Vec<u8> {
    // The best compression ever
    b"compressed".as_ref().into()
}

struct SnappyCompressor<W>
    where W: Write
{
    inner: W,
    buffer: Vec<u8>,
    is_flushed: bool,
}

impl<W> SnappyCompressor<W>
    where W: Write
{
    fn new(inner: W) -> Self {
        SnappyCompressor {
            inner: inner,
            buffer: vec![],
            is_flushed: false,
        }
    }

    // fn into_inner
}

impl<W> Write for SnappyCompressor<W>
    where W: Write
{
    fn write(&mut self, data: &[u8]) -> io::Result<usize> {
        if self.is_flushed {
            return Err(Error::new(ErrorKind::Other, "Buffer has already been compressed, cannot add more data"));
        }

        self.buffer.extend(data);
        Ok(data.len())
    }

    fn flush(&mut self) -> io::Result<()> {
        if self.is_flushed {
            return Ok(())
        }

        self.is_flushed = true;
        let compressed = compress(&self.buffer);
        self.inner.write_all(&compressed)
    }
}

impl<W> Drop for SnappyCompressor<W>
    where W: Write
{
    fn drop(&mut self) {
        self.flush().unwrap();
    }
}

fn main() {
    let mut output = Cursor::new(vec![]);
    {
        let mut compressor = SnappyCompressor::new(output.by_ref());
        assert_eq!(5, compressor.write(b"hello").unwrap());
        assert_eq!(5, compressor.write(b"world").unwrap());
        compressor.flush().unwrap();
    }
    let bytes = output.into_inner();
    assert_eq!(&b"compressed"[..], &bytes[..]);
}
于 2015-10-14T13:16:51.067 回答