当我翻遍搜索引擎,找不到Rust怎么读取末尾几千条记录时
如题,最近有个需求想要获取最近的日志文件记录,又怕日志文件太大,太多我也要不起,那么就折中取最近的2k条记录,返回给前端意思下。
我以为这是个简单的搬砖任务,结果竟然找不到砖厂。这么简单的需求,竟然找不到地方搬砖,rust体验着实太差了。
对着sdk翻了半天,也没找到一个正经的能从末尾开始读数据的姿势。没办法,只能求助gpt了。
以下便是问答
ask: rust 读取文件的最后5000条记录
answer:
要读取一个文件的最后5000条记录,你可以使用Rust的标准库和文件操作来实现。下面是一个简单的示例代码,展示了如何读取文件的最后5000行:
rustCopy code
use std::fs::File;
use std::io::{self, BufRead, Seek, SeekFrom};
fn read_last_lines(filename: &str, num_lines: usize) -> io::Result<Vec<String>> {
let file = File::open(filename)?;
let mut reader = io::BufReader::new(file);
let mut lines = Vec::with_capacity(num_lines);
let mut buffer = String::new();
let mut offset = 0;
// 从文件末尾开始逐行读取,直到达到指定的行数或文件开始
loop {
let position = reader.seek(SeekFrom::End(offset))?;
if position == 0 {
break;
}
// 读取一行并将其添加到结果向量中
buffer.clear();
reader.read_line(&mut buffer)?;
lines.push(buffer.clone());
// 如果达到了指定的行数,则退出循环
if lines.len() >= num_lines {
break;
}
// 更新偏移量以读取下一行
offset += 1;
}
// 将结果向量反转,使最后一行成为向量的第一个元素
lines.reverse();
Ok(lines)
}
请确保将 filename
替换为实际的文件路径。这个例子使用了 BufReader
和 seek
方法来逐行读取文件的最后5000行。读取的行存储在一个向量中,并在读取完成后进行反转,以使最后一行成为向量的第一个元素。最后,你可以遍历向量并对每行执行你想要的操作。
ps: 答案一本正经,看起来像那么回事,but随便一跑,什么乱七八糟。 不过至少给了个思路reader.seek
ask: seek的用法貌似不对
answer:
fn read_last_lines(filename: &str, num_lines: usize) -> io::Result<Vec<String>> {
let file = File::open(filename)?;
let reader = BufReader::new(file);
let mut lines = Vec::with_capacity(num_lines);
let mut buffer = String::new();
// 逐行读取文件,并将每行添加到向量中
for line in reader.lines().flatten() {
if lines.len() >= num_lines {
// 如果向量中的行数已达到指定的行数,则删除第一个元素,以保持最后的 num_lines 行
lines.remove(0);
}
lines.push(line);
}
Ok(lines)
}
ps: 从上往下读,这尼玛效率是不是太低了。
ask: 这个方法如果文件大的话效率太低了
answer:
你对于大文件的效率问题提出了合理的关注。读取大文件的最后几行确实可能导致效率问题,因为需要逐行读取并保留最后的行数。如果文件非常大,这种方法可能会占用大量的内存并且运行时间较长。
use std::fs::File;
use std::io::{self, BufRead, BufReader, Seek, SeekFrom};
fn read_last_lines(filename: &str, num_lines: usize) -> io::Result<Vec<String>> {
let file = File::open(filename)?;
let mut reader = BufReader::new(file);
// 获取文件的总字节数
let file_size = reader.seek(SeekFrom::End(0))?;
let mut lines = Vec::with_capacity(num_lines);
let mut buffer = Vec::new();
let mut lines_count = 0;
// 从文件末尾开始逐行读取,直到达到指定的行数或文件开始
for offset in (0..file_size).rev() {
reader.seek(SeekFrom::Start(offset))?;
reader.read_until(b'\n', &mut buffer)?;
if lines_count >= num_lines {
// 如果向量中的行数已达到指定的行数,则退出循环
break;
}
// 如果读取到行末尾,则将行添加到结果向量中
if buffer.ends_with(&[b'\n']) {
let line = String::from_utf8_lossy(&buffer).into_owned();
lines.push(line);
lines_count += 1;
}
buffer.clear();
}
// 将结果向量反转,使最后一行成为向量的第一个元素
lines.reverse();
Ok(lines)
}
ps: 看着像那么回事。代码一跑,取到是末尾一行的数据 ["\n","\rn","e\r\n", ...],不过至少看着这个seek加read_util的方式可以从末尾取数了。思路可行,继续求教。
然后就没有更加像那么回事的代码了~,~
long times later!
算了,自己撸吧。
最终方案
pub async fn read_last_lines(&self, num_lines: usize) -> RetAny<Vec<String>> {
let file = File::open(&self.0).await?;
let mut reader = BufReader::new(file);
// 获取文件的总字节数
let file_size = reader.seek(SeekFrom::End(0)).await?;
let mut lines = Vec::new();
let mut pre_buffer: Option<Vec<u8>> = None;
let mut buffer = Vec::new();
let mut lines_count = 0;
for offset in (0..file_size).rev() {
reader.seek(SeekFrom::Start(offset)).await?;
let result = reader.read_until(b'\n', &mut buffer).await?;
if offset == 0 {
lines.push(String::from_utf8_lossy(&buffer).into_owned());
break;
}
if result == 1 && pre_buffer.is_some() {
let line = String::from_utf8_lossy(&pre_buffer.unwrap()).into_owned();
lines.push(line);
lines_count += 1;
if lines_count >= num_lines {
break;
}
}
pre_buffer = Some(buffer.clone());
buffer.clear();
}
lines.reverse();
Ok(lines)
}
大致讲下思路:
其实就是得到了文件的总字节数。然后从后往前判断字符。 比如总字节数2000,那么1999-2000是否为字节'\n',第一次循环肯定是,但是pre_buffer是空的,所以不用加入。那么丢入pre_buffer,等下一次找到'\n'时,把pre_buffer取出来,加入结果集。然后继续循环。
本文仅图一乐,若后来人搬砖搬到这,也算是为改善搬砖环境做了贡献吧。
转载自:https://juejin.cn/post/7247010613741568055