|
|
|
crate rust-embed - файлы встраиваются в бинарный файл вашего приложения во время компиляции
|
Используя rust-embed, вам не нужно будет считывать файлы с диска через std::fs::File или другие методы чтения файловой системы.
Вместо этого, файлы встраиваются в бинарный файл вашего приложения во время компиляции, и вы получаете доступ к ним напрямую через методы, предоставляемые rust-embed
Crate rust-embed используется в языке программирования Rust для встраивания статических файлов (например, HTML, CSS, JavaScript, изображений, шрифтов и т.д.) прямо в бинарный файл во время компиляции. Это позволяет удобно включать статические ресурсы в приложение, не требуя их отдельной загрузки с файловой системы или сервера.
use rust_embed::RustEmbed;
#[derive(RustEmbed)]
#[folder = "assets/"] // Указываем папку, содержащую статические файлы
struct Asset;
fn main() {
let index_html = Asset::get("index.html").unwrap();
println!("File contents: {}", String::from_utf8_lossy(index_html.as_ref()));
}
|
|
|
Основные возможности
-
Создание временного файла (NamedTempFile) или анонимного временного файла (tempfile()), который автоматически удалится, когда объект выйдет из области видимости (RAII).
-
Создание временной директории (TempDir) с таким же автоматическим удалением.
-
Работает кроссплатформенно (Windows, Linux, macOS).
-
Безопасность: создаёт файлы с уникальными именами и с правильными правами доступа.
|
|
|
|
|
|
|
|
|
Не кодируйте жестко, откуда вы читаете данные - используйте:
// ✅
fn parse(reader: impl std::io::Read) {
}
// ❌ вместо
fn parse(filename: &str){
...
}
|
|
|
Struct std::io::BufReader и std::io::BufWriter реализуют Trait std::io::BufRead для буферизованного чтения.
|
|
|
Trait std::io::BufRead — это надстройка над std::io::Read, которая добавляет удобные методы для построчного и побайтового чтения с буферизацией.
Trait std::io::BufRead реализуется источниками данных, которые могут эффективно буферизовать ввод.
Обычно используется через struct std::io::BufReader<File>, Cursor<&[u8]> и т.п.
Позволяет читать данные по строкам или до разделителя.
Методы Trait std::io::BufRead
- fill_buf() - Возвращает ссылку на внутренний буфер с данными, которые уже прочитаны, но ещё не потреблены. Не продвигает указатель, можно многократно читать один и тот же кусок.
- consume() - Сообщает, что amt байт уже использованы из буфера. Уменьшает буфер и продвигает указатель.
- lines - Возвращает итератор по строкам (
Result<String>). Убирает символ новой строки \n (и \r\n на Windows).
- read_line - Читает одну строку (включая
\n, если есть). Добавляет в конец buf.
- read_until - Читает данные в буфер до указанного байта включительно. Удобно для чтения до
\n или любого другого разделителя.
- split - Возвращает итератор по кускам, разделённым указанным байтом. Похож на
str::split, только работает на потоках байтов.
use std::fs::File;
use std::io::{self, BufRead, BufReader};
fn main() -> io::Result<()> {
let file = File::open("example.txt")?;
let mut reader = BufReader::new(file);
// read_line
let mut line = String::new();
reader.read_line(&mut line)?;
println!("Первая строка: {}", line);
// lines (итератор по строкам)
for l in reader.lines() {
println!("Строка: {}", l?);
}
// read_until
let mut buffer = Vec::new();
let mut reader = BufReader::new(File::open("example.txt")?);
reader.read_until(b' ', &mut buffer)?;
println!("До пробела: {:?}", String::from_utf8_lossy(&buffer));
// split
let mut reader = BufReader::new(File::open("example.txt")?);
for chunk in reader.split(b' ') {
println!("Фрагмент: {:?}", String::from_utf8_lossy(&chunk?));
}
Ok(())
}
|
|
Методы Trait std::io::BufRead
- fill_buf() - Возвращает ссылку на внутренний буфер с данными, которые уже прочитаны, но ещё не потреблены. Не продвигает указатель, можно многократно читать один и тот же кусок.
- consume() - Сообщает, что amt байт уже использованы из буфера. Уменьшает буфер и продвигает указатель.
|
use std::io::{self, BufRead, BufReader, Cursor};
fn main() -> io::Result<()> {
let data = b"Hello, Rust!\nThis is BufReader.";
let cursor = Cursor::new(&data[..]);
let mut reader = BufReader::new(cursor);
loop {
// Получаем ссылку на буфер
let buffer = reader.fill_buf()?;
if buffer.is_empty() {
break; // EOF
}
// Выводим содержимое буфера как строку
let text = std::str::from_utf8(buffer).unwrap();
println!("Буфер: {}", text);
// Говорим, что весь буфер мы использовали
let len = buffer.len();
reader.consume(len);
}
Ok(())
}
|
|
Методы Struct std::io::BufReader
|
Методы Struct std::io::BufReader
- new() - Создаёт BufReader с внутренним буфером по умолчанию (8 КБ).
- with_capacity() - Создаёт BufReader с буфером указанного размера cap.
- get_ref() - Возвращает ссылку на внутренний поток (для чтения без изменения).
- get_mut() - Возвращает изменяемую ссылку на внутренний поток, чтобы можно было напрямую работать с ним.
- into_inner() - Извлекает внутренний поток, уничтожая BufReader.
- buffer() - Возвращает содержимое внутреннего буфера, которое ещё не было прочитано.
- capacity() - Возвращает размер внутреннего буфера.
- peek() - Позволяет посмотреть на следующий доступный кусок данных, не продвигая указатель.
- seek_relative() - Перемещает указатель относительно текущей позиции, учитывая буфер.
use std::io::{BufReader, Cursor, Read};
fn main() -> std::io::Result<()> {
let data = b"Hello, BufReader!";
let cursor = Cursor::new(&data[..]);
let mut reader = BufReader::with_capacity(8, cursor);
// peek: посмотрим на буфер
let buf = reader.peek()?;
println!("Peek: {:?}", std::str::from_utf8(buf).unwrap());
// buffer: текущий непрочитанный буфер
let buf2 = reader.buffer();
println!("Buffer: {:?}", std::str::from_utf8(buf2).unwrap());
// чтение и продвижение
let mut output = Vec::new();
reader.read_to_end(&mut output)?;
println!("Read to end: {:?}", String::from_utf8(output).unwrap());
// доступ к внутреннему Cursor
let inner = reader.into_inner();
println!("Inner position: {}", inner.position());
Ok(())
}
|
|
Методы Struct std::io::BufWriter
|
Методы Struct std::io::BufWriter
- new() - Создаёт BufWriter с внутренним буфером по умолчанию (8 КБ).
- with_capacity() - Создаёт BufWriter с буфером указанного размера cap.
- get_ref() - Возвращает ссылку на внутренний поток, без влияния на запись.
- get_mut() - Возвращает изменяемую ссылку на внутренний поток, чтобы можно было писать напрямую.
- into_inner() - Извлекает внутренний поток, сбрасывая буфер перед возвратом. Если не удалось сбросить буфер (ошибка записи), возвращает ошибку.
- into_parts() - Разделяет BufWriter на: внутренний поток W оставшийся буфер
Vec<u8>, который ещё не был записан
- buffer() - Возвращает неотправленные данные из внутреннего буфера.
- capacity() - Возвращает размер внутреннего буфера.
use std::io::{BufWriter, Write, Cursor};
fn main() -> std::io::Result<()> {
let cursor = Cursor::new(Vec::new());
let mut writer = BufWriter::with_capacity(8, cursor);
// Пишем данные
writer.write_all(b"Hello, ")?;
writer.write_all(b"Rust!")?;
// buffer: посмотрим, что ещё не записано
println!("Buffer: {:?}", writer.buffer());
// capacity: размер буфера
println!("Capacity: {}", writer.capacity());
// flush: сброс буфера в Cursor
writer.flush()?;
// Получаем внутренний поток
let cursor = writer.into_inner()?;
let data = cursor.into_inner();
println!("Записанные данные: {:?}", String::from_utf8(data).unwrap());
Ok(())
}
|
|
Небуферизированные IO медленные
Используя BufReader и BufWriter, мы можем амортизировать стоимость системных вызовов.
unbuffered-io-slows-rust-programs
|
То есть по умолчанию операции чтения и записи файлов не буферизуются.
Программы не могут напрямую читать с или записи файлов на диске, они должны попросить операционную систему, чтобы сделать это с помощью системного вызова.
Характерной чертой системных вызовов Linux является то, что они вызываются медленнее, чем обычные функции.
Это связано с тем, что они должны переключаться с выполнения в пользовательском режиме на выполнение в режиме ядра, а этот переход стоит дорого.
Чтобы обеспечить хорошую производительность, наши программы должны избегать чрезмерных системных вызовов.
Пример без буферизации
(Эта программа не использует преимущества буферизации, что означает, что будет один write() системный вызов для каждого из трех вызовов f.write() метода)
use std::fs;
use std::io::{self, Write};
fn main() -> io::Result<()> {
let mut f = fs::File::create("/tmp/unbuffered.txt")?;
f.write(b"foo")?;
f.write(b"\n")?;
f.write(b"bar\nbaz\n")?;
return Ok(());
}
Запустив эту программу strace, мы видим, что действительно существует три write() системных вызова:
$ strace --trace=write ./target/release/01_unbuffered
write(3, "foo", 3) = 3
write(3, "\n", 1) = 1
write(3, "bar\nbaz\n", 8) = 8
Пример с буферизаций
К счастью, мы можем улучшить нашу программу, изменив ее в одну строку.
После открытия файла мы можем обернуть его внутри BufWriter объекта
use std::fs;
use std::io::{self, BufWriter, Write};
fn main() -> io::Result<()> {
let mut f = BufWriter::new(fs::File::create("x.txt")?);
f.write(b"foo")?;
f.write(b"\n")?;
f.write(b"bar\nbaz\n")?;
return Ok(());
}
Теперь, когда мы вызываем f.write(), мы не write()вызываем системный вызов, мы просто добавляем байты в массив внутри буферизованной оболочки. Это происходит полностью в пользовательском режиме, поэтому это очень дешево. Только когда буфер заполняется или когда мы закрываем файл, выполняется системный вызов для передачи байтов на диск. Мы можем подтвердить, что это правда, с помощью strace.
$ strace --trace=write ./target/release/02_buffered
write(3, "foo\nbar\nbaz\n", 12) = 12
|
|
lines()
Такой подход более эффективен, чем создание String в памяти, особенно при работе с большими файлами.
|
Метод lines() возвращает итератор, проходящий через все строки файла.
use std::fs::File;
use std::io::{self, BufRead};
use std::path::Path;
fn main() {
// Файл `hosts` должен существовать в текущей директории
if let Ok(lines) = read_lines("./hosts") {
// Получает итератор, который возвращает Option
for line in lines {
if let Ok(ip) = line {
println!("{}", ip);
}
}
}
}
// File::open работает с чем-то, что реализует типаж `AsRef<Path>`. Поэтому read_lines() будет ожидать это же.
// Для обработки ошибок, возвращаемое значение оборачивается в Result
// Возвращаем `Iterator` для построчного чтения файла.
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>> where P: AsRef<Path>, {
let file = File::open(filename)?;
Ok(io::BufReader::new(file).lines())
}
|
|
Построчное и буферизированное чтение файла
(лучший пример построчного чтения ниже см. Cursor)
BufReader
BufWriter
|
use std::io::prelude::*;
use std::io::BufReader;
use std::fs::File;
fn main() -> std::io::Result<()> {
let mut file_vtt = File::open("examples/Sub.vtt")?;
let mut reader = BufReader::new(file_vtt);
let mut line = String::new();
{
let len = reader.read_line(&mut line)?;
println!("First line is {len} bytes long");
print!("{line}");
}
line.clear();
{
let len = reader.read_line(&mut line)?;
println!("First line is {len} bytes long");
print!("{line}");
}
// ....
Ok(())
}
fn main(){
let f = fs::File::open("source/video.mp4")?;
let mut reader = BufReader::new(f);
let mut buffer = [0; 128];
loop{
if let Ok(n) = reader.read(&mut buffer[..]){
println!("N:{n}");
println!("{:?}",buffer);
}else{
println!("Err");
break;
}
}
}
|
|
|
По умолчанию ввод-вывод файла Rust не буферизуется.
Если у вас много мелких и повторяющихся вызовов чтения или записи в файл или сетевой сокет, используйте BufReader или BufWriter.
Они поддерживают буфер в памяти для ввода и вывода, сводя к минимуму количество требуемых системных вызовов.
❌ Например, измените этот небуферизованный выходной код:
use std::io::Write;
let mut out = std::fs::File::create("test.txt").unwrap();
for line in lines {
writeln!(out, "{}", line)?;
}
✅ к этому:
fn blah() -> Result<(), std::io::Error> {
use std::io::{BufWriter, Write};
let lines = vec!["one", "two", "three"];
let mut out = std::fs::File::create("test.txt")?;
let mut buf = BufWriter::new(out);
for line in lines {
writeln!(buf, "{}", line)?;
}
buf.flush()?; // для явного выброса ошибки , иначе ошибки будут прогнорированны
Ok(())
}
Обратите внимание, что буферизация также работает с stdout, поэтому вы можете комбинировать ручную блокировку и буферизацию при выполнении большого количества операций записи в stdout
fn blah() -> Result<(), std::io::Error> {
use std::io::{BufWriter, Write};
let lines = vec!["one", "two", "three"];
let mut stdout = std::io::stdout();
let mut lock = stdout.lock();
let mut buf = BufWriter::new(lock);
for line in lines {
writeln!(buf, "{}", line)?;
}
buf.flush()?;
Ok(())
}
blah();
|
|
|
Встроенный тип String использует UTF-8 внутри, что добавляет небольшие, но ненулевые накладные расходы, вызванные проверкой UTF-8, когда вы читаете вводимые в него данные. Если вы просто хотите обрабатывать входные байты, не беспокоясь о UTF-8 (например, если вы обрабатываете текст ASCII), вы можете использовать BufRead::read_until.
Существуют также специальные ящики linereader для чтения байтовых строк данных и работы с байтовыми строками bstr.
extern crate linereader;
use linereader::LineReader;
fn main(){
let mut file = File::open(myfile).expect("open");
// Defaults to a 64 KiB buffer and b'\n' delimiter; change with one of:
// * LineReader::with_capacity(usize);
// * LineReader::with_delimiter(u8);
// * LineReader::with_delimiter_and_capacity(u8, usize)
let mut reader = LineReader::new(file);
while let Some(line) = reader.next_line() {
let line = line.expect("read error");
// line is a &[u8] owned by reader.
}
}
use std::error::Error;
use std::io::{self, Write};
use bstr::{ByteSlice, io::BufReadExt};
fn main() -> Result<(), Box> {
let stdin = io::stdin();
let mut stdout = io::BufWriter::new(io::stdout());
stdin.lock().for_byte_line_with_terminator(|line| {
if line.contains_str("Dimension") {
stdout.write_all(line)?;
}
Ok(true)
})?;
Ok(())
}
|
|
Пример вывода содержимого файла в stdout с ограничением размера
|
fn file_content(path_file: &PathBuf) -> Result<(), Error> {
let metadata = fs::metadata(&path_file).map_err(|e| Error::FileNotFound(e))?;
if !metadata.is_file() {
return Err(Error::NotFile);
}
let file = File::open(path_file).map_err(|e| Error::FileNotFound(e))?;
let reader = BufReader::new(file);
let stdout = io::stdout();
let mut handle = stdout.lock();
let mut total_bytes_read = 0;
for line in reader.lines() {
let line = line.map_err(|e| Error::FileContentInvalidUtf8(e))?;
total_bytes_read += line.len() as u64;
if total_bytes_read > MAX_FILE_SIZE_BYTES {
return Err(Error::FileSizeExceeded(MAX_FILE_SIZE_BYTES));
}
handle
.write_all(line.as_bytes())
.map_err(|e| Error::IoError(e))?;
handle.write_all(b"\n").map_err(|e| Error::IoError(e))?;
}
Ok(())
}
|
|
|
Внутри std::path::Path и PathBuf существуют две платформенные реализации путей:
-
std::path::posix::Path — реализация для Unix-подобных систем (Linux, macOS, *BSD …)
- POSIX-пути: разделитель /, корень — /, никаких дисков (C:)
-
std::path::windows::Path — реализация для Windows.
- Windows-пути: поддержка \ и / как разделителей, префиксы (C:, \server\share, \?\ и т. д.), понятие дисков, UNC-пути.
Если нужно «сшить несколько частей пути»:
use std::path::PathBuf;
fn main() {
let path: PathBuf = ["usr", "local", "bin"].iter().collect();
println!("{:?}", path); // "usr/local/bin" (на Windows — с '\')
// `join` объединяет путь с байтовым контейнером, используя конкретную ОС разделитель и возвращает новый путь
let new_path = path.join("a").join("b");
}
|
|
|
use std::path::Path;
fn main(){
let source:&Path = Path::new("./hello.handlebars");
}
|
|
|
Методы std::path::Path:
- new — создаёт пустой
Path.
- as_mut_os_str — даёт изменяемую ссылку на представление пути как
OsStr.
- as_os_str — даёт ссылку на путь как
OsStr.
- canonicalize — возвращает абсолютный, нормализованный путь (с учётом ссылок и
..).
- components — разбивает путь на структурированные компоненты (
RootDir, Normal, и т. д.).
- display — даёт объект для удобного форматирования пути как строки.
- ends_with — проверяет, оканчивается ли путь на указанный компонент или путь.
- has_root — проверяет, начинается ли путь с корня (
/ или C:\).
- is_absolute — проверяет, является ли путь абсолютным.
- is_dir — проверяет, указывает ли путь на директорию.
- is_file — проверяет, указывает ли путь на файл.
- is_relative — проверяет, является ли путь относительным.
- is_symlink — проверяет, является ли путь символической ссылкой.
- starts_with — проверяет, начинается ли путь с указанного.
- strip_prefix — удаляет префикс пути, если он совпадает.
- ancestors — возвращает итератор по пути и всем его предкам.
- read_dir — возвращает итератор по содержимому директории.
- try_exists — проверяет существование пути, не паникуя при ошибках.
- exists — проверяет, существует ли объект файловой системы по этому пути.
- extension — возвращает расширение файла (если есть).
- file_name — возвращает имя файла или последнего компонента пути.
- file_stem — возвращает имя файла без расширения.
- into_path_buf — превращает
&Path во владение PathBuf.
- iter — возвращает итератор по компонентам пути как
OsStr.
- join — добавляет к пути под-путь или компонент.
- metadata — возвращает метаданные объекта по пути (размер, права и т. д.).
- parent — возвращает родительский путь (если есть).
- read_link — читает символическую ссылку и возвращает путь, на который она указывает.
- symlink_metadata — возвращает метаданные, не разыменовывая символическую ссылку.
- to_path_buf — клонирует путь во владение
PathBuf.
- to_str — пытается преобразовать путь в
&str (может вернуть None).
- to_string_lossy — преобразует путь в строку, заменяя недопустимые байты.
- with_extension — возвращает копию пути с заменённым расширением.
- with_file_name — возвращает копию пути с заменённым именем файла.
|
|
|
fn read<P: AsRef<std::path::Path>>(path: P){}
fn read (path: &std::path::Path){}
pub struct FileRepository{
file: PathBuf
}
impl FileRepository{
pub fn new(file: &(impl AsRef<Path> + ?Sized)) -> std::io::Result<Self>{
let path:&Path = file.as_ref();
if !path.exists(){
return Err(std::io::Error::other("File not found"));
}
Ok(Self{file: path.to_path_buf()})
}
fn test(&self) -> &Path{
self.file.as_path()
}
}
pub struct FileRepository{
file: String
}
impl FileRepository{
pub fn new(file: &(impl AsRef<Path> + ?Sized)) -> std::io::Result<Self>{
let path:&Path = file.as_ref();
if !path.exists(){
return Err(std::io::Error::other("File not found"));
}
Ok(Self{file: format!("{}",path.display())})
}
}
|
|
|
use std::path::Path;
use std::fs;
fn main() -> std::io::Result<()> {
let path = Path::new("/tmp/example.txt");
// Проверка существования и типа
println!("exists: {}", path.exists());
println!("is_file: {}", path.is_file());
println!("is_dir: {}", path.is_dir());
// Разбор компонентов
println!("file_name: {:?}", path.file_name()); // "example.txt"
println!("extension: {:?}", path.extension()); // "txt"
println!("file_stem: {:?}", path.file_stem()); // "example"
println!("parent: {:?}", path.parent()); // "/tmp"
// Манипуляции
let new = path.with_extension("log");
println!("with_extension: {:?}", new); // "/tmp/example.log"
let joined = Path::new("/tmp").join("nested/file.rs");
println!("join: {:?}", joined); // "/tmp/nested/file.rs"
// Метаданные
if let Ok(metadata) = fs::metadata(path) {
println!("file size: {} bytes", metadata.len());
}
Ok(())
}
|
|
|
use std::fs;
use std::io::{Read, SeekFrom, Write};
use std::path::Path;
mod SPFile {
use super::*;
use std::ops::{Deref, DerefMut};
#[derive(Debug)]
pub struct File<'a, T> {
pub file: T,
path: &'a Path,
}
impl<'a, T> Deref for File<'a, T> {
type Target = T;
fn deref(&self) -> &T {
&self.file
}
}
impl<'a, T> DerefMut for File<'a, T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.file
}
}
impl<'a, T> Drop for File<'a, T> {
fn drop(&mut self) {
if let Some(file_name) = &self.path.file_name() {
std::fs::remove_file(file_name);
println!("File is being dropped");
}
}
}
impl<'a> File<'a, fs::File> {
pub fn open(path: &'a Path) -> Option> {
let name = path.to_str()?;
let file = fs::File::open(name).ok()?;
Some(File::new(file, path))
}
pub fn create(path: &'a Path) -> Option> {
let name = path.to_str()?;
let file = fs::File::create(name).ok()?;
Some(File::new(file, path))
}
}
impl<'a, T> File<'a, T> {
fn new(file: T, path: &'a Path) -> Self {
File {
file: file,
path: path,
}
}
}
}
fn main() {
use SPFile::File;
let path = Path::new("file.txt");
if let Some(_file) = File::create(path) {
let b: &[u8] = "some bytes".as_bytes();
let mut file = &*_file;
file.write(b);
};
}
|
|
|
Владеющий измененный путь (похожий на String) в отличии от разделяемого/ссылочного Path, поэтому у него есть методы для мутирования пути (push, pop, set_file_name, set_extension) и работы с памятью (reserve, shrink, capacity).
Path идеально подходит для случаев, когда нужно просто прочитать или проанализировать путь без необходимости его изменения. Он обеспечивает эффективное и безопасное представление пути.
PathBuf используется, когда необходимо динамически создавать или изменять путь. Он предоставляет методы для безопасного и удобного изменения пути.
|
|
|
Методы std::path::PathBuf
- as_mut_os_string — получить изменяемую ссылку на внутренний
OsString.
- as_path — получить ссылку на
Path.
- into_boxed_path — превратить
PathBuf во владение Box<Path>.
- into_os_string — превратить
PathBuf во владение OsString.
- leak — утечка памяти: превращает в
'static ссылку, никогда не освобождается.
- new — создать пустой
PathBuf.
- with_capacity — создать
PathBuf с выделенной ёмкостью.
- set_extension — заменить расширение файла.
- set_file_name — заменить имя файла.
- pop — удалить последний компонент пути.
- push — добавить новый компонент пути.
- capacity — узнать текущую ёмкость буфера.
- clear — очистить путь (оставить пустым).
- reserve — зарезервировать дополнительное место в буфере.
- reserve_exact — зарезервировать ровно указанное количество.
- shrink_to — уменьшить ёмкость до указанного значения (не меньше длины).
- shrink_to_fit — уменьшить ёмкость до текущей длины.
- try_reserve — попытаться зарезервировать место (с обработкой ошибок).
- try_reserve_exact — то же, но с точным количеством.
|
|
|
use std::path::PathBuf;
fn main() {
// Создание
let mut path = PathBuf::new();
path.push("/usr"); // push: добавляем компонент
path.push("local");
path.push("bin");
println!("path = {:?}", path); // "/usr/local/bin"
// Преобразование
let p_ref = path.as_path(); // as_path: ссылка на Path
println!("as_path = {:?}", p_ref);
let os_str = path.as_mut_os_string(); // as_mut_os_string: доступ к OsString
os_str.push("-custom");
println!("as_mut_os_string = {:?}", path);
// Работа с именем и расширением
path.set_file_name("program"); // set_file_name: заменяет имя файла
println!("set_file_name = {:?}", path); // "/usr/local/program"
path.set_extension("exe"); // set_extension: меняет расширение
println!("set_extension = {:?}", path); // "/usr/local/program.exe"
// Модификация пути
path.pop(); // pop: убирает последний компонент
println!("pop = {:?}", path); // "/usr/local"
// Работа с ёмкостью
let mut p2 = PathBuf::with_capacity(50); // резервируем место
p2.push("hello");
println!("p2 = {:?}, capacity = {}", p2, p2.capacity());
p2.reserve(20); // увеличить capacity
println!("reserve = {}", p2.capacity());
p2.shrink_to_fit(); // уменьшить до длины
println!("shrink_to_fit = {}", p2.capacity());
// Полное преобразование
let boxed = path.clone().into_boxed_path(); // Box
let os_string = path.clone().into_os_string(); // OsString
println!("into_boxed_path = {:?}", boxed);
println!("into_os_string = {:?}", os_string);
}
|
|
это структура, которую мы получаем при обходе директории через std::fs::read_dir
std::fs::DirEntry
|
|
|
|
Методы std::fs::DirEntry
path — возвращает полный путь (PathBuf) к элементу.
file_name — возвращает имя файла (последний компонент пути) как OsString.
metadata — возвращает Metadata (размер, время модификации, права).
file_type — возвращает FileType (директория, файл или симлинк).
file_type() + is_dir() / is_file() / is_symlink() — проверка типа.
into_path — забирает путь во владение (PathBuf).
|
|
|
use std::fs;
fn main() -> std::io::Result<()> {
for entry in fs::read_dir(".")? {
let entry = entry?; // DirEntry
let path = entry.path(); // полный путь
let name = entry.file_name(); // имя файла
let ftype = entry.file_type()?; // тип
println!("Имя: {:?}", name);
println!("Путь: {:?}", path);
if ftype.is_dir() {
println!(" -> это директория");
} else if ftype.is_file() {
println!(" -> это файл");
} else if ftype.is_symlink() {
println!(" -> это символическая ссылка");
}
if let Ok(metadata) = entry.metadata() {
// Now let's show our entry's permissions!
println!("{:?}: {:?}", entry.path(), metadata.permissions());
} else {
println!("Couldn't get metadata for {:?}", entry.path());
}
println!();
}
Ok(())
}
|
|
|
File - это объект, через который можно читать, писать, управлять правами и блокировками файла, а также синхронизировать изменения с диском.
|
|
|
Методы Struct std::fs::File
- create — создаёт новый файл, перезаписывая существующий.
- create_new — создаёт новый файл, выдаёт ошибку, если файл уже существует.
- open — открывает существующий файл для чтения (или чтения и записи через
Options).
- options — возвращает объект
OpenOptions для детальной настройки открытия файла.
- metadata — возвращает struct
Metadata файла (размер, права, время модификации).
- set_len — изменяет размер файла.
- set_modified — изменяет время последней модификации.
- set_permissions — изменяет права доступа к файлу.
- set_times — изменяет время создания и модификации (включает и время доступа).
- sync_all — сбрасывает все буферы в файловую систему (включая метаданные).
- sync_data — сбрасывает буферы данных (без метаданных).
- try_clone — создаёт новый дескриптор для того же файла. Чтение, запись и поиск будут влиять одновременно на оба экземпляра файлов.
- lock — ставит эксклюзивную блокировку на файл.
- lock_shared — ставит блокировку только на чтение.
- try_lock — пытается поставить эксклюзивную блокировку без ожидания.
- try_lock_shared — пытается поставить блокировку на чтение без ожидания.
- unlock — снимает блокировку.
|
|
File::open() - Попытка открыть файл в режиме только для чтения.
FIle::from(name)
trait.Read.read_to_string() - запись в переменную содержимого файла
|
use std::fs::File;
use std::io::Read;
fn read() -> std::io::Result<()> {
let mut f = File::open("file.txt")?;//открыть только для чтения
// let mut f1 = File::from("file.txt");
let mut contents = String::new();
f.read_to_string(&mut contents)?;// запись в переменную содержимого файла
println!("{}",contents);
Ok(())
}
fn main(){
match read() {
Ok(r)=> println!("{:?}",r),
Err(e) => println!("Error:{:?}",e)
}
}
|
|
create() - Открывает файл в режиме только для записи.
Эта функция создаст файл, если он не существует, и обрезает его, если он существует.
trait.Write.write_all() - запись буфера в файл или std::fs::write("foo.txt", b"Lorem ipsum")?;
|
use std::fs::File;
use std::io::Write;
fn write() -> std::io::Result<()> {
let mut f = File::create("file2.txt")?;//открыть/создать
let mut file_copy = f.try_clone()?;// вторая ссылка на дескриптор
f.write_all(b"Hello, world!")?;// запись в файл
file_copy.write_all(b"Hello, world!")?;
f.set_len(30)?;//установить размер/дополнить 0s
f.sync_all()?;//синхронизировать с диском
Ok(())
}
fn main(){
match write() {
Ok(r)=> println!("{:?}",r),
Err(e) => println!("Error:{:?}",e)
}
}
|
|
|
use std::fs::{File, OpenOptions};
use std::io::{Write, Read};
use std::time::{SystemTime, UNIX_EPOCH};
fn main() -> std::io::Result<()> {
// -------------------
// Создание файла
// -------------------
let mut file = File::create("example.txt")?;
file.write_all(b"Hello, Rust!")?;
println!("Файл создан и записан.");
// -------------------
// Метаданные и изменение размера
// -------------------
let metadata = file.metadata()?;
println!("Размер файла: {}", metadata.len());
// методы Metadata
//println!("metadata={:#?}",file.metadata()?);
println!("{:?}",metadata.file_type());
println!("{:?}",metadata.is_dir());
println!("{:?}",metadata.is_file());
println!("{:?}",metadata.len());
println!("{:?}",metadata.permissions().readonly());
println!("{:#?}",std::fs::metadata("file.txt"));//или ф-цию
// Устанавливаем новые permissions
let mut perms = file.metadata()?.permissions();
perms.set_readonly(true);
file.set_permissions(perms)?;
// Устанавливаем новый размер
file.set_len(5)?;
println!("Файл усечён до 5 байт.");
// -------------------
// Синхронизация
// -------------------
file.sync_all()?; // сброс данных и метаданных
println!("Данные синхронизированы с диском.");
// -------------------
// Чтение файла
// -------------------
let mut content = String::new();
file.reopen()?.read_to_string(&mut content)?;
println!("Содержимое файла: {:?}", content);
// -------------------
// Открытие с опциями
// -------------------
let file2 = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open("example2.txt")?;
println!("Файл example2.txt открыт с OpenOptions.");
// -------------------
// Блокировки (только на Unix/Windows с поддержкой)
// -------------------
#[cfg(unix)]
{
use fs2::FileExt; // для lock/lock_shared/unlock (через внешнюю crate fs2)
file2.lock_shared()?;
println!("Файл заблокирован для чтения.");
file2.unlock()?;
println!("Блокировка снята.");
}
Ok(())
}
|
|
Формирование файла с данными
|
fn main(){
let mut inp_file = File::create(Path::new("RGraph/data2.js")).unwrap();
let mut f = File::open("source/12345.WAV").unwrap();
inp_file.write( b"var dataarr = [").unwrap();
let take=50;
for (index,byte) in f.bytes().skip(44).take(take).enumerate() {
inp_file.write(format!("{}",byte.unwrap()).as_bytes()).unwrap();
if index
|
|
std::include_str! - содержимое файла в строку
std::file!() - макрос возвращает имя файла в котором вызван
std::line!() - возвращает номер строки в котором вызван
std::module_path!() - путь к модулю в котором вызван
std::write!() - Запишите отформатированные данные в буфер
std::writeln!() - Запишите отформатированные данные в буфер с добавлением новой строки
macro.writeln
|
fn main(){
let s = include_str!("rayon2.rs");
println!("{:?}",s);
}
mod my_module {
pub fn print_location() {
println!("Файл: {}", file!()); // имя файла, где вызван макрос
println!("Строка: {}", line!()); // номер строки, где вызван макрос
println!("Модуль: {}", module_path!()); // путь к модулю, где вызван макрос
}
}
fn main() {
println!("Вызов из main:");
println!("Файл: {}", file!());
println!("Строка: {}", line!());
println!("Модуль: {}", module_path!());
println!("\nВызов из my_module:");
my_module::print_location();
}
fn main(){
// Записать отформатированные данные в буфер
use std::io::Write;
let mut w:Vec = Vec::new();
write!(&mut w, "test").unwrap();
write!(&mut w, "formatted {}", "arguments").unwrap();
//assert_eq!(w, b"testformatted arguments");
let mut w = String::new();
write!(&mut w, "test").unwrap();
write!(&mut w, "formatted {}", "arguments").unwrap();
assert_eq!(w, "testformatted arguments");
let mut w:Vec= Vec::new();
writeln!(&mut w)?;
writeln!(&mut w, "test")?;
writeln!(&mut w, "formatted {}", "arguments")?;
assert_eq!(&w[..], "\ntest\nformatted arguments\n".as_bytes());
}
|
|
|
|
|
|
Работа с путями и файлами
- std::fs::canonicalize(path) — возвращает абсолютный, нормализованный путь (с учётом ссылок и
..).
- std::fs::copy(src, dst) — копирует файл из
src в dst.
- std::fs::rename(src, dst) — переименовывает или перемещает файл/директорию.
- std::fs::exists(path) — проверяет существование файла или директории (через
Path::exists).
Создание
- std::fs::create_dir(path) — создаёт одну директорию, ошибка если уже существует.
- std::fs::create_dir_all(path) — создаёт директорию и все родительские директории, если их нет.
Чтение
- std::fs::read(path) — читает файл в
Vec<u8>.
- std::fs::read_to_string(path) — читает файл в
String.
- std::fs::read_dir(path) — возвращает итератор по элементам директории (
DirEntry).
- std::fs::read_link(path) — возвращает путь, на который указывает символическая ссылка.
- std::fs::symlink_metadata(path) — возвращает метаданные файла/директории, не разыменовывая симлинк.
- std::fs::metadata(path) — возвращает метаданные, разыменовывая симлинк.
Запись и права
- std::fs::write(path, contents) — записывает данные (
&[u8]) в файл, создаёт/перезаписывает файл.
- std::fs::set_permissions(path, permissions) — устанавливает права доступа к файлу/директории.
Удаление
- std::fs::remove_file(path) — удаляет файл.
- std::fs::remove_dir(path) — удаляет пустую директорию.
- std::fs::remove_dir_all(path) — удаляет директорию рекурсивно со всем содержимым.
Ссылки
- std::fs::hard_link(src, dst) — создаёт жёсткую ссылку на файл.
|
|
Чтение
- std::fs::read(path) — читает файл в
Vec<u8>.
- std::fs::read_to_string(path) — читает файл в
String.
- std::fs::read_dir(path) — возвращает итератор по элементам директории (
DirEntry).
- std::fs::read_link(path) — возвращает путь, на который указывает символическая ссылка.
- std::fs::symlink_metadata(path) — возвращает метаданные файла/директории, не разыменовывая симлинк.
- std::fs::metadata(path) — возвращает метаданные, разыменовывая симлинк.
Trait Read
trait.Read.read_to_string - Запись содержимого в буфер
|
// std::fs::read_to_string
fn test()-> Result<(), Box<std::error::Error + 'static>>{
let s:String = std::fs::read_to_string("file2.txt")?;// Прочтите все содержимое файла в строку
println!("{}",s);
Ok(())
}
// trait.Read
fn test2() -> io::Result<()> {
let mut f = File::open("file2.txt")?;
let mut buffer = String::new();
f.read_to_string(&mut buffer)?;
Ok(())
}
use std::fs;
use std::io;
fn main() -> io::Result<()> {
// Чтение всего файла в Vec<u8>
let bytes = fs::read("example.txt")?;
println!("Bytes: {:?}", bytes);
// Чтение файла в строку
let text = fs::read_to_string("example.txt")?;
println!("Text: {}", text);
// Итератор по директории
for entry in fs::read_dir(".")? {
let entry = entry?;
println!("Name: {:?}", entry.file_name());
}
// Чтение симлинка
#[cfg(unix)]
{
let target = fs::read_link("symlink.txt")?;
println!("Symlink points to: {:?}", target);
}
Ok(())
}
|
|
Запись и права
- std::fs::write(path, contents) — записывает данные (
&[u8]) в файл, создаёт/перезаписывает файл.
- std::fs::set_permissions(path, permissions) — устанавливает права доступа к файлу/директории.
|
use std::fs;
use std::io;
fn main() -> io::Result<()> {
// Запись данных в файл (создаёт/перезаписывает)
fs::write("example_write.txt", b"Hello, Rust!")?;
// Установка прав доступа (на Unix)
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let perms = fs::Permissions::from_mode(0o644);
fs::set_permissions("example_write.txt", perms)?;
}
Ok(())
}
|
|
Создание
- std::fs::create_dir(path) — создаёт одну директорию, ошибка если уже существует.
- std::fs::create_dir_all(path) — создаёт директорию и все родительские директории, если их нет.
|
use std::fs;
use std::io;
fn main() -> io::Result<()> {
// Создаёт одну директорию
fs::create_dir("dir1")?;
// Создаёт все необходимые родительские директории
fs::create_dir_all("dir2/subdir")?;
Ok(())
}
|
|
Удаление
- std::fs::remove_file(path) — удаляет файл.
- std::fs::remove_dir(path) — удаляет пустую директорию.
- std::fs::remove_dir_all(path) — удаляет директорию рекурсивно со всем содержимым.
|
use std::fs;
use std::io;
fn main() -> io::Result<()> {
fs::remove_file("example_write.txt")?; // удалить файл
fs::remove_dir("dir1")?; // удалить пустую директорию
fs::remove_dir_all("dir2")?; // удалить директорию рекурсивно
Ok(())
}
|
|
Ссылки
- std::fs::hard_link(src, dst) — создаёт жёсткую ссылку на файл.
|
use std::fs;
use std::io;
fn main() -> io::Result<()> {
// Создание жёсткой ссылки
fs::hard_link("example.txt", "example_hardlink.txt")?;
#[cfg(unix)]
{
// Чтение симлинка (уже показано выше)
let target = fs::read_link("example_symlink.txt")?;
println!("Symlink points to: {:?}", target);
}
Ok(())
}
|
|
Напишите функцию, которая по заданному каталогу находит все файлы с данным файлом.
расширение, подсчитывает количество строк в файле и выводит его на стандартный вывод.
Улучшение. Каждую папку можно запускать в отдельном потоке
|
use std::ffi::{OsString, OsStr};
use std::fs::File;
use std::io::{self, BufRead};
use std::path::Path;
fn find_ex(dir: &std::path::Path, ex:&OsStr)-> std::io::Result<()>{
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries {
if let Ok(entry) = entry {
if entry.path().is_dir(){
find_ex(&entry.path(),ex);
}else{
if Some(ex)==entry.path().extension(){
if let Ok(lines) = read_lines(entry.path()) {
println!("File {:?} contains {} lines",entry.path(), lines.into_iter().count());
}
}
}
}
}
Ok(())
}else{
Err(std::io::Error::new(std::io::ErrorKind::Other, "The path is not valid"))
}
}
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where P: AsRef<Path>, {
let file = File::open(filename)?;
Ok(io::BufReader::new(file).lines())
}
fn main() {
find_ex(Path::new("examples"),OsStr::new("rs"));
}
|
|
обход рекурсивный директории
|
extern crate failure;
use failure::Error;
fn walk(root: impl AsRef, callback: impl Fn(&Path)) -> Result<(), Error> {
let root = root.as_ref().to_path_buf();
let mut tasks = vec![root];
while let Some(path) = tasks.pop() {
if path.is_file() {
callback(&path);
} else if path.is_dir() {
for dir in std::fs::read_dir(&path)? {
tasks.push(dir?.path());
}
}
}
Ok(())
}
fn main() {
walk("test",|path| {println!("{:?}", path.display())});
}
|
|
обход рекурсивный директории
|
fn rec(dir: &std::path::Path)-> std::io::Result<()>{
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries {
if let Ok(entry) = entry {
// Here, `entry` is a `DirEntry`.
if entry.path().is_dir(){
println!("DIR:{:?}", entry.path());
rec(&entry.path());
std::fs::remove_dir(entry.path())?;
}else{
println!("FILE:{:?}", entry.path());
if let Ok(metadata) = entry.metadata() {
// Now let's show our entry's permissions!
println!("{:?}", metadata.permissions());
} else {
println!("Couldn't get metadata for {:?}", entry.path());
}
std::fs::remove_file(entry.path())?;
}
}
}
Ok(())
}else{
Err(std::io::Error::new(std::io::ErrorKind::Other, "oh no!"))
}
}
fn main() {
let path = std::path::Path::new("test");
match rec(path) {
Ok(n) => { std::fs::remove_dir(path);println!("Ok");},
Err(err) => { println!("Error: {}", err);}
};
}
|
|
|
|
|
|
Seek — это trait для объектов, которые поддерживают позиционирование внутри потока данных (например, файлов, буферов).
Он позволяет перемещать указатель чтения/записи и узнавать текущую позицию.
Типичный пример: File, Cursor<Vec<u8>> и другие потоковые структуры поддерживают Seek.
Enum std::io::SeekFrom - задаёт откуда и на сколько смещать указатель потока.
|
|
|
Методы std::io::Seek
- seek() - Метод seek перемещает указатель потока (например, в файле или буфере) относительно позиции Enum std::io::SeekFrom.
- rewind() - Перемещает указатель потока в начало (0). Эквивалентно
seek(SeekFrom::Start(0)).
- seek_relative(offset: i64) - Сдвигает позицию потока относительно текущей на offset байт. Поддерживает как положительные, так и отрицательные смещения.
- stream_len() - Возвращает длину потока в байтах, не меняя текущей позиции. Полезно для проверки размера файла.
- stream_position() - Возвращает текущую позицию указателя в потоке. Эквивалентно
seek(SeekFrom::Current(0)).
|
|
|
use std::fs::File;
use std::io::{Seek, SeekFrom, Write, Read};
fn main() -> std::io::Result<()> {
let mut file = File::create("example_seek.txt")?;
file.write_all(b"Hello, Rust!")?;
// Перемещаемся в начало
file.rewind()?;
// Читаем первые 5 байт
let mut buffer = [0; 5];
file.read_exact(&mut buffer)?;
println!("Read: {:?}", std::str::from_utf8(&buffer).unwrap());
// Сместить на 2 байта вперёд относительно текущей позиции
file.seek_relative(2)?;
// Позиция сейчас
println!("Current position: {}", file.stream_position()?);
// Общая длина файла
println!("Stream length: {}", file.stream_len()?);
Ok(())
}
|
|
pub enum SeekFrom {
Start(u64), // смещение от начала потока
End(i64), // смещение от конца потока
Current(i64), // смещение от текущей позиции
}
|
use std::fs::File;
use std::io::{Seek, SeekFrom, Read};
fn main() -> std::io::Result<()> {
let mut file = File::open("example_seek.txt")?;
// Перейти на 5 байт от начала
let pos = file.seek(SeekFrom::Start(5))?;
println!("Current position: {}", pos);
// Перейти на 2 байта вперёд от текущей позиции
let pos = file.seek(SeekFrom::Current(2))?;
println!("Current position: {}", pos);
// Перейти на 3 байта назад от конца файла
let pos = file.seek(SeekFrom::End(-3))?;
println!("Current position: {}", pos);
Ok(())
}
|
|
|
Write — это трейд для потоков данных, в которые можно записывать байты.
Например: File, Vec<u8>, TcpStream поддерживают Write.
Он предоставляет методы для записи данных, буферизации и форматированного вывода.
|
|
|
Методы Trait std::io::Write
- write - Пишет часть данных из
buf в поток. Возвращает количество реально записанных байт (может быть меньше длины buf).
- write_all - Пишет все данные из
buf. Блокирует до тех пор, пока не будут записаны все байты или не произойдёт ошибка.
- write_fmt - Форматированная запись (аналогично
println!, но в поток). Использует format_args!.
- write_vectored - Записывает несколько буферов за один системный вызов. Оптимально для высокопроизводительной записи нескольких кусков данных.
- flush - Сбрасывает внутренние буферы, чтобы данные точно попали в устройство/файл. Полезен для адаптеров и явных буферов для обеспечения того, чтобы все буферизованные данные были вытолкнуты в «истинный приемник»
- by_ref - Возвращает ссылку на объект для удобного цепочного вызова методов без владения.
|
|
Макросы
std::write!() - Запишите отформатированные данные в буфер
std::writeln!() - Запишите отформатированные данные в буфер с добавлением новой строки
macro.writeln
|
fn main(){
// Записать отформатированные данные в буфер
use std::io::Write;
let mut w:Vec = Vec::new();
write!(&mut w, "test").unwrap();
write!(&mut w, "formatted {}", "arguments").unwrap();
//assert_eq!(w, b"testformatted arguments");
let mut w = String::new();
write!(&mut w, "test").unwrap();
write!(&mut w, "formatted {}", "arguments").unwrap();
assert_eq!(w, "testformatted arguments");
let mut w:Vec= Vec::new();
writeln!(&mut w)?;
writeln!(&mut w, "test")?;
writeln!(&mut w, "formatted {}", "arguments")?;
assert_eq!(&w[..], "\ntest\nformatted arguments\n".as_bytes());
}
|
|
|
use std::fs::File;
use std::io::{Write, IoSlice};
fn main() -> std::io::Result<()> {
let mut file = File::create("example_write.txt")?;
// write
let bytes_written = file.write(b"Hello, ")?;
println!("Записано {} байт", bytes_written);
// by_ref мы можем использовать ссылку так же, как наш исходный буфер
let reference = file.by_ref();
reference.write_all(b"some bytes")?;
// write_all
file.write_all(b"Rust!")?;
// flush
file.flush()?;
// write_fmt
file.write_fmt(format_args!("\nNumber: {}", 42))?;
// write_vectored
let data = [1; 8];
let bufs = [IoSlice::new(io_slice), IoSlice::new(b"\nBuf2")];
file.write_vectored(&bufs)?;
Ok(())
}
|
|
Реализация Trait std::io::Write для своей структуры
|
pub struct WriteAdaptor<'a> {
fmt_write: &'a mut dyn std::fmt::Write,
}
impl<'a> WriteAdaptor<'a> {
pub fn new(fmt_write: &'a mut dyn std::fmt::Write) -> Self {
Self { fmt_write }
}
}
impl<'a> std::io::Write for WriteAdaptor<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result {
let s = std::str::from_utf8(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
self.fmt_write
.write_str(s)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
Ok(s.as_bytes().len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
fn main(){
// Можно использовать:
let mut serializer = serde_json::Serializer::new(WriteAdaptor::new(&mut writer));
let mut serializer = serializer.serialize_map(None)?;
serializer.serialize_entry("level", &meta.level().as_serde())?;
serializer.end()
}
struct A{
buff:std::fs::File
}
type Result = std::result::Result;
impl std::io::Write for A{
fn write(&mut self, buf: &[u8])->Result{
println!("{:?}",buf);
self.buff.write_all(buf);
Ok(buf.len() as usize)
}
fn flush(&mut self) -> Result<()>{Ok(())}
}
fn main(){
let file:std::fs::File = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open("test.log")
.unwrap();
let f:A = A{buff:file};
}
|
|
write() Пишет &[u8] в буфер, вернув количество байтов.
|
use std::io::Write;
use std::io::BufWriter;
fn test() -> std::io::Result<()> {
let mut file = std::fs::File::create("foo.txt")?;
let b:&[u8] = b"some bytes"; // let b:Vec =vec![115, 111, 109, 101, 32, 98, 121, 116, 101, 115]; // let b:&[u8] = "some bytes".as_bytes();
file.write(b)?;
file.flush()?;// Сбросьте этот выходной поток, гарантируя, что все содержимое с промежуточным буфером достигнет места назначения.
Ok(())
}
fn main(){
test();
}
fn main(){
// Запись в файл массива
let mut payload:Vec = std::fs::read("store/test.mp3").unwrap().into_iter().collect();
let mut file = std::fs::File::create("store/array_mp3.raw").unwrap();
file.write(b"unsigned char test_mp3[] = {\n\t").unwrap();
for (c,v) in payload.iter().take(127284).enumerate() {// 127284
file.write(&format!("{:<3},",v).as_bytes()[..]).unwrap();
if (c+1)%12==0 {
file.write(b"\n\t").unwrap();
}
}
file.write(b"\n};\n\nunsigned int test_mp3_len = 127284;").unwrap();
file.flush().unwrap();
}
|
|
|
Read — это трейд для потоков, из которых можно читать байты.
Примеры: File, TcpStream, &[u8], Cursor<Vec<u8>> реализуют Read.
|
|
|
Методы Trait std::io::Read
- read - Читает данные в буфер
buf. Возвращает количество реально прочитанных байт.
- read_exact - Читает ровно столько байт, сколько в
buf. Возвращает ошибку std::io::ErrorKind::UnexpectedEof , если данных меньше.
- read_to_end - Читает поток до конца и записывает в вектор.
- read_to_string - Читает поток до конца в строку (валидный UTF-8 обязателен).
- read_vectored - Читает в несколько буферов за один системный вызов. Оптимизация для ввода-вывода.
- by_ref - Удобный метод, чтобы использовать
Read в итераторах/цепочках без потери владения.
- bytes - Возвращает итератор по байтам (
Result<u8>). Удобно для побайтового чтения.
- chain - Объединяет два источника чтения в один. Сначала читает из
self когда он закончится (EOF), автоматически продолжается чтение из next.
- take - Создает адаптер, который будет читать не более limit байт, после чего он ведёт себя как пустой (EOF).
|
|
|
use std::fs::File;
use std::io::{Read, BufReader};
fn main() -> std::io::Result<()> {
let file = File::open("example.txt")?;
let mut reader = BufReader::new(file);
// read_exact
let mut buf = [0; 5];
reader.read_exact(&mut buf)?;
println!("First 5 bytes: {:?}", buf);
// read_to_string
let mut content = String::new();
reader.read_to_string(&mut content)?;
println!("Content: {}", content);
// bytes() iterator
for byte in "Hi".as_bytes().bytes() {
println!("Byte: {:?}", byte);
}
// take: ограничиваем чтение 10 байт
let mut limited = reader.take(10);
let mut small_buf = Vec::new();
limited.read_to_end(&mut small_buf)?;
println!("First 10 bytes after position: {:?}", small_buf);
Ok(())
}
|
|
read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize>
Подобно read(), за исключением того, что он считывается в срез буферов.
Данные копируются для заполнения каждого буфера по порядку, при этом последний записываемый буфер может быть заполнен лишь частично.
read_vectored
std::io::IoSliceMut
|
use std::fs::File;
use std::io::Read;
use std::io::SeekFrom;
use std::io::Seek;
use std::io::IoSliceMut;
fn read_exam() -> std::io::Result<()> {
let mut f = File::open("src/sentence.wav")?;//открыть только для чтения
//f.seek(SeekFrom::Start(44))?;
let mut buffer:[u8;6] = [0; 6];
// читать до 6 байт
f.read(&mut buffer[..])?;
println!("{:?}",buffer);
f.seek(std::io::SeekFrom::Start(0));
// Семантически это оболочка вокруг &mut [u8],
// но гарантируется совместимость ABI с этим iovec типом на платформах Unix и WSABUF в Windows
let mut buf = std::io::IoSliceMut::new(&mut buffer);
let mut buf1 = [0; 6];
let mut bufs = &mut [
IoSliceMut::new(&mut buf1),
][..];
f.read_vectored(&mut bufs[..])?;
println!("{:?}",bufs.first().unwrap());
assert_eq!(buffer.to_vec(),bufs.first().unwrap().to_vec());
Ok(())
}
|
|
read() Вытяните несколько байтов из этого источника в указанный буфер, возвращая количество байтов.
Считает из источника столько сколько было байт в буфере не больше или ничего если он был 0 байт длины.
read
|
use std::io::Read;
fn test() -> std::io::Result<()> {
let mut f = std::fs::File::open("src/foo.txt")?;
let mut buffer = [0; 10];
// читать до 10 байт
f.read(&mut buffer[..])?;
Ok(())
}
fn test() -> std::io::Result<()> {
let mut b:&[u8] = "This string will be read".as_bytes(); // &[u8]
let mut buffer = [0; 10];
// читать до 10 байт
b.read(&mut buffer)?;
// и т. д. ... он работает точно так же, как файл!
Ok(())
}
|
|
by_ref() - Нужен для того, чтобы использовать адаптеры (chain, take, bytes, итераторы и т.д.) без потери владения объектом.
даёт временную ссылку, чтобы работать с методом как с итератором или адаптером, а после можно продолжить использовать оригинальный объект.
|
use std::io::{self, Read};
fn main() -> io::Result<()> {
let data = b"Hello, world!";
let mut reader = &data[..]; // &[u8] реализует Read
// Читаем только часть через by_ref + take
let mut limited = String::new();
reader.by_ref().take(5).read_to_string(&mut limited)?;
println!("Ограниченное чтение: {}", limited); // "Hello"
// Теперь можно продолжить читать из того же reader
let mut rest = String::new();
reader.read_to_string(&mut rest)?;
println!("Остальное: {}", rest); // ", world!"
Ok(())
}
fn main() -> io::Result<()> {
let mut f = File::open("foo.txt")?;
let mut buffer = Vec::new();
let mut other_buffer = Vec::new();
{
let reference = f.by_ref();
// read at most 5 bytes
reference.take(5).read_to_end(&mut buffer)?;
} // drop our &mut reference so we can use f again
// original file still usable, read the rest
f.read_to_end(&mut other_buffer)?;
Ok(())
}
|
|
chain() - Создает адаптер, который будет связывать этот поток с другим.
Возвращенный экземпляр Read сначала будет считывать все байты с этого объекта до тех пор, пока не встретится EOF. Впоследствии вывод эквивалентен выходу следующего.
|
fn main() -> io::Result<()> {
let mut f1 = File::open("foo.txt")?;
let mut f2 = File::open("bar.txt")?;
let mut handle = f1.chain(f2);
let mut buffer = String::new();
// Считать значение в строку. Здесь можно использовать любой метод Read
handle.read_to_string(&mut buffer)?;
Ok(())
}
use std::io::{self, Read};
fn main() -> io::Result<()> {
let a = b"Hello, ";
let b = b"world!";
// Объединим два источника
let mut chained = a.as_ref().chain(b.as_ref());
let mut result = String::new();
chained.read_to_string(&mut result)?;
println!("Результат: {}", result); // "Hello, world!"
Ok(())
}
|
|
|
Через OpenOptions можно гибко комбинировать режимы: чтение, запись, добавление, обрезка и т.д.
|
|
|
Методы std::fs::OpenOptions
- new() - Создаёт новый пустой конфиг
OpenOptions. С него всегда начинают.
- read() - Разрешает (
true) или запрещает (false) чтение файла.
- write() - Разрешает или запрещает запись.
- append() - Если включено, новые данные всегда будут добавляться в конец файла, не перезаписывая существующее содержимое.
- truncate() - Если файл открыт на запись и этот флаг установлен, его содержимое будет обрезано до нуля при открытии.
- create() - Если файл не существует — он будет создан. Если существует — откроется.
- create_new() - Создаёт новый файл только если он не существует. Если существует — возвращает ошибку.
- open() - Применяет все заданные опции и открывает файл.
|
|
|
use std::fs::OpenOptions;
use std::io::{Write, Read};
fn main() -> std::io::Result<()> {
// 1. Запишем в новый файл (создание + запись + truncate)
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open("example.txt")?;
writeln!(file, "Hello, Rust!")?;
// 2. Добавим в конец (append)
let mut file = OpenOptions::new()
.append(true)
.open("example.txt")?;
writeln!(file, "New line!")?;
// 3. Прочитаем файл
let mut file = OpenOptions::new()
.read(true)
.open("example.txt")?;
let mut content = String::new();
file.read_to_string(&mut content)?;
println!("Содержимое:\n{}", content);
Ok(())
}
|
|
Создаем файл на чтение и запись
|
use std::fs::OpenOptions;
fn main(){
let file = OpenOptions::new()
.create_new(true)
.read(true)
.write(true)
.open("foo.txt");
}
|
|
Открывает файл для продолжения записи
|
use std::fs::OpenOptions;
fn main(){
let file = OpenOptions::new()
.append(true)
.write(true)
.create(false)
.open("foo.txt");
}
|
|
Пишем и читаем передвигая указатель файла
|
use std::io::Seek;
use std::fs::OpenOptions;
fn main(){
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(name).unwrap();
file.seek(std::io::SeekFrom::End(2));
let b:&[u8] = "some bytes".as_bytes();
file.write(b);
file.seek(std::io::SeekFrom::Start(0));
let mut buffer = String::new();
file.read_to_string(&mut buffer);
println!("{}", buffer);
}
|
|
|
std::io::Cursor — это удобная обёртка, которая превращает любой буфер (например, Vec<u8> или &[u8], String) в объект, реализующий:
- std::io::Read
- std::io::Write
- std::io::Seek
Иными словами, Cursor позволяет работать с массивом байт как с файлом: читать, писать, перемещать указатель.
|
|
|
Методы std::io::Cursor
- new - Создаёт
Cursor поверх буфера. Пример: Cursor::new(vec![0; 10]).
- position - Возвращает текущую позицию курсора (смещение внутри буфера).
- set_position - Устанавливает новую позицию курсора (аналог
seek у файлов).
- get_mut - Возвращает изменяемую ссылку на буфер, чтобы напрямую его менять.
- get_ref - Возвращает ссылку на внутренний буфер (только чтение).
- into_inner - Извлекает буфер, уничтожая
Cursor.
|
|
lines()
Такой подход более эффективен, чем создание String в памяти, особенно при работе с большими файлами.
|
|
|
|
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
fn main() -> std::io::Result<()> {
// Создаём курсор поверх вектора
let mut cursor = Cursor::new(vec![0; 10]);
// Пишем в буфер
cursor.write_all(b"Hi")?;
println!("Буфер после записи: {:?}", cursor.get_ref());
// Узнаём и меняем позицию
println!("Позиция: {}", cursor.position());
cursor.set_position(0);
// Читаем из начала
let mut buf = [0; 2];
cursor.read_exact(&mut buf)?;
println!("Прочитали: {:?}", buf);
// Доступ к внутреннему буферу
let inner = cursor.into_inner();
println!("Внутренний буфер: {:?}", inner);
Ok(())
}
|
|
std::io::BufReader и std::io::Cursor можно совместить, потому что оба реализуют трейт std::io::Read
|
Пример: читаем строки из Vec<u8>
use std::io::{self, BufRead, BufReader, Cursor};
fn main() -> io::Result<()> {
// Буфер в памяти
let data = b"line1\nline2\nline3\n";
// Cursor превращает &[u8] в поток
let cursor = Cursor::new(&data[..]);
// BufReader добавляет построчные методы
let mut reader = BufReader::new(cursor);
// Чтение строк
let mut line = String::new();
while reader.read_line(&mut line)? > 0 {
print!("Строка: {}", line);
line.clear(); // очищаем буфер для следующей строки
}
Ok(())
}
|
|
std::io::BufWriter и std::io::Cursor можно совместить, потому что оба реализуют трейт std::io::Write
BufWriter вместе с Cursor<Vec<u8>>
|
Пример: запись в память с буферизацией
use std::io::{BufWriter, Write, Cursor};
fn main() -> std::io::Result<()> {
// Создаём Cursor поверх вектора в памяти
let cursor = Cursor::new(Vec::new());
// Оборачиваем его в BufWriter для буферизованной записи
let mut writer = BufWriter::new(cursor);
// Пишем данные
writeln!(writer, "Hello, Rust!")?;
writeln!(writer, "Writing to memory with BufWriter")?;
// flush() — сбрасываем буфер в Cursor
writer.flush()?;
// Получаем внутренний вектор из Cursor
let cursor = writer.into_inner()?; // writer возвращает Cursor>
let data = cursor.into_inner(); // Cursor возвращает Vec
// Превращаем байты в строку для проверки
let result = String::from_utf8(data).unwrap();
println!("Результат записи:\n{}", result);
Ok(())
}
|
|
|
use std::io::prelude::*;
use std::io::{self, SeekFrom};
use std::fs::File;
// библиотечная функция, которую мы написали
fn write_ten_bytes_at_end<W: Write + Seek>(writer: &mut W) -> io::Result<()> {
writer.seek(SeekFrom::End(-10))?;
for i in 0..10 {
writer.write(&[i])?;
}
// all went well
Ok(())
}
// Вот код, использующий эту библиотечную функцию.
//
// Возможно, для эффективности нам понадобится BufReader, но давайте
// сосредоточимся на этом примере.
let mut file = File::create("foo.txt")?;
write_ten_bytes_at_end(&mut file)?;
// теперь давайте напишем тест
#[test]
fn test_writes_bytes() {
// Создание настоящего файла гораздо медленнее, чем создание буфера в памяти,
// Давайте вместо этого используем курсор
use std::io::Cursor;
let mut buff = Cursor::new(vec![0; 15]);
write_ten_bytes_at_end(&mut buff).unwrap();
assert_eq!(&buff.get_ref()[5..15], &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
}
|
|
|
Мотивация использования асинхронных операций для файлового потока ввода-вывода:
- Если выполнять блокирующее чтение или запись через std::fs, поток runtime просто замрёт, и все остальные async задачи тоже остановятся.
- Даже если чтение последовательное, async позволяет параллельно обрабатывать другие задачи, например: сетевые запросы, таймеры, обработку событий GUI.
Использовать асинхронные библиотеки для потоков и буферов
- Tokio
- async-compression — асинхронное сжатие/распаковка файлов.
- tokio-util::codec::FramedRead — читать файлы как поток структурированных сообщений.
Можно оборачивать BufReader или Cursor в async адаптеры с tokio::io::AsyncReadExt
|
|
|
|
|
Асинхронный вариант File и BufWriter от Tokio
tokio::fs::File
async-i-o-with-tokio-rust
|
Trait tokio::io::AsyncRead, tokio::io::AsyncWrite - эти две черты предоставляют возможности для асинхронного чтения и записи в байтовые потоки.
Методы этих признаков обычно не вызываются напрямую, вместо этого вы будете использовать их с помощью AsyncReadExt, AsyncWriteExt
При традиционном блокировании ввода-вывода приложение останавливается, ожидая завершения каждой операции ввода-вывода, прежде чем продолжить работу.
Это может привести к проблемам с производительностью и ограничить масштабируемость приложения. Чтобы решить эти проблемы, мы обратимся к асинхронному вводу-выводу.
Write
use tokio::io::{self, BufWriter, AsyncWriteExt};
use tokio::fs::File;
#[tokio::main]
async fn main() -> io::Result<()> {
let f = File::create(""foo.txt"").await?;
{
let mut writer = BufWriter::new(f);
// Записать байт в буфер
writer.write(&[42u8]).await?;
// Очистить буфер до того, как он выйдет за пределы области действия.
writer.flush().await?;
} // Если он не очищен или не выключен, содержимое буфера удаляется при drop
Ok(())
}
Read
use tokio::io::{BufReader, AsyncBufReadExt};
use tokio::fs::File;
#[tokio::main]
async fn main() -> io::Result<()> {
let f = File::open("foo.txt").await?;
let mut reader = BufReader::new(f);
let mut buffer = String::new();
// Read a line into the buffer
reader.read_line(&mut buffer).await?;
println!("{}", buffer);
Ok(())
}
|
|
|
async fn async_test() -> std::io::Result<()> {
use async_std::fs::File;
use futures::io::AsyncReadExt;
let mut file = File::open("src/foo.txt").await?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer).await?;
println!("buffer:{:?}",buffer);
Ok(())
}
fn main(){
async_test().await;
}
|
|
|
Смысл в том, что mmap делает копирование практически мгновенным для больших файлов. mmap вообще не читает файл сразу! Он просто говорит ядру: "если программа обратится к этим адресам, подгрузи данные с диска". А когда write читает эту память, ядро уже само разбирается с диском.
По сравнению с обычным копированием (read/write):
- Данные копируются 4 раза: диск → ядро (read) → пользовательский буфер → ядро (write) → диск
- Нужны системные вызовы для каждого блока
- Для файла размером 1 МБ (1,048,576 байт)
- 1,048,576 / 4096 = 256 итераций
- 256 read + 256 write = 512 системных вызовов
- Для файла 1 ГБ (1,073,741,824 байт):
- read/write: ~524,288 системных вызовов
char buf[4096];
while((n = read(fd_in, buf, sizeof(buf))) > 0)
write(fd_out, buf, n);
mmap копирование:
- mmap экономит тысячи/миллионы переключений между пользователем и ядром.
- Данные копируются 1 раз: диск → память процесса (через mmap)
- Ядро и процесс используют одну и ту же физическую память
- Нет копирования между ядром и пользователем
- Для файла 1 ГБ:
addr = mmap(...); // 1 вызов
write(stdout, addr, size); // 1 вызов
munmap(addr, size); // 1 вызов
|
|
|
Mmap::map — unsafe, потому что может привести к undefined behavior, если файл изменяется во время отображения. Но для чтения это безопасно.
Еще есть mmap-rs — более новая и безопасная альтернатива.
use std::env;
use std::fs::File;
use std::io::{self, Write};
use memmap2::Mmap;
fn main() -> io::Result<()> {
let args: Vec<String> = env::args().collect();
if args.len() != 2 {
eprintln!("Использование: {} <имя_файла>", args[0]);
std::process::exit(1);
}
// Открываем файл
let file = File::open(&args[1])?;
// Отображаем в память
let mmap = unsafe { Mmap::map(&file)? };
// Пишем в stdout
io::stdout().write_all(&mmap[..])?;
// mmap автоматически освободится при выходе из области видимости
Ok(())
}
[package]
name = "mmapcopy"
version = "0.1.0"
edition = "2021"
[dependencies]
memmap2 = "0.9"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|