既然Read::chars迭代器已经被正式弃用了,那么在Reader不将整个流读入内存的情况下获取来自类似stdin 的字符的迭代器的正确方法是什么?
相应的弃用问题很好地总结了问题Read::chars并提出了建议:
可以使用不关心递增处理数据的代码
Read::read_to_string.大概需要关注的代码也希望控制其缓冲策略并使用尽可能大的切片&[u8]和&str切片,而不是一次char一个.它应该立足于str::from_utf8功能以及对valid_up_to与error_len该方法Utf8Error的类型.一个棘手的方面是处理单个char以UTF-8表示多个字节的情况,其中这些字节碰巧在不同的read调用/缓冲区块中分割.(Utf8Error::error_len返回None表明这可能是这种情况.)的utf-8板条箱解决了这一点,但为了是柔性的规定,可能有太多的表面的API被包括在标准库.当然,以上是针对始终为UTF-8的数据.如果需要支持其他字符编码,请考虑使用
encoding_rs或encoding包.
在最有效的方面解决方案的数量我/ O调用是阅读一切都变成一个巨大的缓冲String和迭代是:
use std::io::{self, Read};
fn main() {
let stdin = io::stdin();
let mut s = String::new();
stdin.lock().read_to_string(&mut s).expect("Couldn't read");
for c in s.chars() {
println!(">{}<", c);
}
}
Run Code Online (Sandbox Code Playgroud)
你可以将它与答案结合起来是否有自己的String :: chars版本?:
use std::io::{self, Read};
fn reader_chars<R: Read>(mut rdr: R) -> io::Result<impl Iterator<Item = char>> {
let mut s = String::new();
rdr.read_to_string(&mut s)?;
Ok(s.into_chars()) // from https://stackoverflow.com/q/47193584/155423
}
fn main() -> io::Result<()> {
let stdin = io::stdin();
for c in reader_chars(stdin.lock())? {
println!(">{}<", c);
}
Ok(())
}
Run Code Online (Sandbox Code Playgroud)
我们现在有一个函数,它char为任何实现的类型返回s 的迭代器Read.
一旦你有这种模式,只需要决定在哪里进行内存分配与I/O请求的权衡.这是一个类似的想法,使用行大小的缓冲区:
use std::io::{BufRead, BufReader, Read};
fn reader_chars<R: Read>(rdr: R) -> impl Iterator<Item = char> {
// We use 6 bytes here to force emoji to be segmented for demo purposes
// Pick more appropriate size for your case
let reader = BufReader::with_capacity(6, rdr);
reader
.lines()
.flat_map(|l| l) // Ignoring any errors
.flat_map(|s| s.into_chars()) // from https://stackoverflow.com/q/47193584/155423
}
fn main() {
// emoji are 4 bytes each
let data = "";
let data = data.as_bytes();
for c in reader_chars(data) {
println!(">{}<", c);
}
}
Run Code Online (Sandbox Code Playgroud)
最极端的是为每个角色执行一个I/O请求.这不会占用太多内存,但会产生大量的I/O开销.
将实现复制并粘贴Read::chars到您自己的代码中.它会像以前一样有效.
也可以看看:
正如其他几个人提到的,可以复制已弃用的实现Read::chars以在您自己的代码中使用。这是否真正理想将取决于您的用例 - 对我来说,这证明目前已经足够好了,尽管我的应用程序可能会在不久的将来超越这种方法。
为了说明如何做到这一点,让我们看一个具体的例子:
use std::io::{self, Error, ErrorKind, Read};
use std::result;
use std::str;
struct MyReader<R> {
inner: R,
}
impl<R: Read> MyReader<R> {
fn new(inner: R) -> MyReader<R> {
MyReader {
inner,
}
}
#[derive(Debug)]
enum MyReaderError {
NotUtf8,
Other(Error),
}
impl<R: Read> Iterator for MyReader<R> {
type Item = result::Result<char, MyReaderError>;
fn next(&mut self) -> Option<result::Result<char, MyReaderError>> {
let first_byte = match read_one_byte(&mut self.inner)? {
Ok(b) => b,
Err(e) => return Some(Err(MyReaderError::Other(e))),
};
let width = utf8_char_width(first_byte);
if width == 1 {
return Some(Ok(first_byte as char));
}
if width == 0 {
return Some(Err(MyReaderError::NotUtf8));
}
let mut buf = [first_byte, 0, 0, 0];
{
let mut start = 1;
while start < width {
match self.inner.read(&mut buf[start..width]) {
Ok(0) => return Some(Err(MyReaderError::NotUtf8)),
Ok(n) => start += n,
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
Err(e) => return Some(Err(MyReaderError::Other(e))),
}
}
}
Some(match str::from_utf8(&buf[..width]).ok() {
Some(s) => Ok(s.chars().next().unwrap());
None => Err(MyReaderError::NotUtf8),
})
}
}
Run Code Online (Sandbox Code Playgroud)
上面的代码也需要read_one_byte和utf8_char_width来实现。这些应该看起来像:
static UTF8_CHAR_WIDTH: [u8; 256] = [
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x1F
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x3F
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x5F
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x7F
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0x9F
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0xBF
0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // 0xDF
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, // 0xEF
4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0, // 0xFF
];
fn utf8_char_width(b: u8) -> usize {
return UTF8_CHAR_WIDTH[b as usize] as usize;
}
fn read_one_byte(reader: &mut Read) -> Option<io::Result<u8>> {
let mut buf = [0];
loop {
return match reader.read(&mut buf) {
Ok(0) => None,
Ok(..) => Some(Ok(buf[0])),
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
Err(e) => Some(Err(e)),
};
}
}
Run Code Online (Sandbox Code Playgroud)
现在我们可以使用该MyReader实现在某些读取器上生成chars 的迭代器,例如io::stdin::Stdin:
fn main() {
let stdin = io::stdin();
let mut reader = MyReader::new(stdin.lock());
for c in reader {
println!("{}", c);
}
}
Run Code Online (Sandbox Code Playgroud)
这种方法的局限性在原始问题线索中进行了详细讨论。然而,值得指出的一个特别问题是该迭代器无法正确处理非 UTF-8 编码流。