将`Vec <u16>`内容写入文件的正确方法是什么?

时间:2015-06-15 06:08:00

标签: rust

我无法将Vec<u16>内容写入文件:

use std::fs::File;
use std::io::{Write, BufWriter};
use std::mem;

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum ImageFormat {
    GrayScale,
    Rgb32,
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub struct ImageHeader {
    pub width: usize,
    pub height: usize,
    pub format: ImageFormat,
}

pub struct Image {
    pub header: ImageHeader,
    pub data: Vec<u16>,
}

fn write_to_file(path: &str, img: &Image) -> std::io::Result<()> {
    let f = try!(File::create(path));
    let mut bw = BufWriter::new(f);
    let slice = &img.data[..];
    println!("before length: {}", slice.len());
    let sl: &[u8];
    unsafe {
        sl = mem::transmute::<&[u16], &[u8]>(slice);
    }
    println!("after length: {}", sl.len());
    try!(bw.write_all(sl));
    return Ok(());
}

fn main() {}

由于write_all()要求&[u8],我正在进行&[u16]&[u8]的不安全转换。由于转换不会更改切片长度(slice.len()sl.len()具有相同的值),因此只有一半的图像数据会输出到文件中。

如果我不需要任何不安全的转换或复制,那会更好。

2 个答案:

答案 0 :(得分:7)

要直接执行此操作,您需要使用std::slice::from_raw_parts()

use std::slice;
use std::mem;

fn main() {
    let slice_u16: &[u16] = &*vec![1, 2, 3, 4, 5, 6];
    println!("u16s: {:?}", slice_u16);

    let slice_u8: &[u8] = unsafe {
        slice::from_raw_parts(
            slice_u16.as_ptr() as *const u8,
            slice_u16.len() * mem::size_of::<u16>(),
        )
    };

    println!("u8s: {:?}", slice_u8);
}

确实需要unsafe因为from_raw_parts()不能保证你只能传递一个有效的指针,它也可以创建任意生命周期的切片。

然而,这种方法不仅可能不安全,而且也不便携。使用大于一个字节的整数时,会立即出现字节序问题。如果您在x86计算机上以这种方式编写文件,则会在ARM计算机上读取垃圾。正确的方法是使用像byteorder这样的库,它们允许您明确指定字节顺序:

extern crate byteorder;

use byteorder::{WriteBytesExt, LittleEndian};

fn main() {
    let slice_u16: &[u16] = &*vec![1, 2, 3, 4, 5, 6];
    println!("u16s: {:?}", slice_u16);

    let mut result: Vec<u8> = Vec::new();
    for &n in slice_u16 {
        let _ = result.write_u16::<LittleEndian>(n);
    }

    println!("u8s: {:?}", result);
}

请注意,我在这里使用了Vec<u8>,但它实现了Writewrite_u16()WriteBytesExt特征中的其他方法在任何Write上定义,所以你可以直接在BufWriter上使用这些方法,例如。

虽然这可能比重新解释一块内存效率稍低,但它是安全且便携的。

答案 1 :(得分:5)

我建议使用现有的库进行序列化,例如serdebincode

extern crate bincode;
extern crate serde;
#[macro_use]
extern crate serde_derive;

use std::error::Error;

#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)]
pub enum ImageFormat {
    GrayScale,
    Rgb32,
}

#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)]
pub struct ImageHeader {
    pub width: usize,
    pub height: usize,
    pub format: ImageFormat,
}

#[derive(Serialize, Deserialize)]
pub struct Image {
    pub header: ImageHeader,
    pub data: Vec<u16>,
}

impl Image {
    fn save_to_disk(&self, path: &str) -> Result<(), Box<Error>> {
        use std::fs::File;
        use std::io::Write;
        let bytes: Vec<u8> = try!(bincode::serialize(self, bincode::Infinite));
        let mut file = try!(File::create(path));
        file.write_all(&bytes).map_err(|e| e.into())
    }
}

fn main() {
    let image = Image {
        header: ImageHeader {
            width: 2,
            height: 2,
            format: ImageFormat::GrayScale,
        },
        data: vec![0, 0, 0, 0],
    };

    match image.save_to_disk("image") {
        Ok(_) => println!("Saved image to disk"),
        Err(e) => println!("Something went wrong: {:?}", e.description()),
    }
}