Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Модуль fs

Доступно только с флагом функции fs.

Асинхронные файловые утилиты.

Этот модуль содержит служебные методы для работы с файловой системой асинхронно. Это включает чтение/запись файлов и работу с каталогами.

Важное замечание

Имейте в виду, что большинство операционных систем не предоставляют асинхронные API файловой системы. Из-за этого Tokio будет использовать обычные блокирующие файловые операции "за кулисами". Это делается с использованием пула потоков spawn_blocking для их выполнения в фоновом режиме.

Модуль tokio::fs следует использовать только для обычных файлов. Попытка использовать его с, например, именованным каналом в Linux может привести к неожиданному поведению, такому как зависания при завершении работы среды выполнения. Для специальных файлов следует использовать специальные типы, такие как tokio::net::unix::pipe или AsyncFd.

В настоящее время Tokio всегда использует spawn_blocking на всех платформах, но в будущем это может быть изменено на использование асинхронных API файловой системы, таких как io_uring.

Использование

Самый простой способ использовать этот модуль — использовать служебные функции, которые работают с целыми файлами:

  • tokio::fs::read
  • tokio::fs::read_to_string
  • tokio::fs::write

Две функции чтения считывают весь файл и возвращают его содержимое. Функция записи принимает содержимое файла и записывает это содержимое в файл. Она перезаписывает существующий файл, если он есть.

Например, чтобы прочитать файл:

#![allow(unused)]
fn main() {
let contents = tokio::fs::read_to_string("my_file.txt").await?;

println!("Файл содержит {} строк.", contents.lines().count());
}

Чтобы перезаписать файл:

#![allow(unused)]
fn main() {
let contents = "Первая строка.\nВторая строка.\nТретья строка.\n";

tokio::fs::write("my_file.txt", contents.as_bytes()).await?;
}

Использование File

Основной тип для взаимодействия с файлами — File. Он может использоваться для чтения и записи данного файла. Это делается с использованием трейтов AsyncRead и AsyncWrite. Этот тип обычно используется, когда вы хотите сделать что-то более сложное, чем просто прочитать или записать все содержимое за один раз.

Примечание: Важно использовать flush при записи в файл Tokio. Это связано с тем, что вызовы write возвращаются до завершения записи, а flush будет ждать завершения записи. (Запись произойдет, даже если вы не вызываете flush; это просто произойдет позже.) Это отличается от std::fs::File и связано с тем, что File использует spawn_blocking за кулисами.

Например, чтобы подсчитать количество строк в файле без загрузки всего файла в память:

#![allow(unused)]
fn main() {
use tokio::fs::File;
use tokio::io::AsyncReadExt;

let mut file = File::open("my_file.txt").await?;

let mut chunk = vec![0; 4096];
let mut number_of_lines = 0;
loop {
    let len = file.read(&mut chunk).await?;
    if len == 0 {
        // Длина ноль означает конец файла.
        break;
    }
    for &b in &chunk[..len] {
        if b == b'\n' {
            number_of_lines += 1;
        }
    }
}

println!("Файл содержит {} строк.", number_of_lines);
}

Например, чтобы записать файл построчно:

#![allow(unused)]
fn main() {
use tokio::fs::File;
use tokio::io::AsyncWriteExt;

let mut file = File::create("my_file.txt").await?;

file.write_all(b"Первая строка.\n").await?;
file.write_all(b"Вторая строка.\n").await?;
file.write_all(b"Третья строка.\n").await?;

// Не забудьте вызвать `flush` после записи!
file.flush().await?;
}

Настройка производительности файлового ввода-вывода

Файлы Tokio используют spawn_blocking за кулисами, и это имеет серьезные последствия для производительности. Чтобы добиться хорошей производительности при файловом вводе-выводе в Tokio, рекомендуется объединять ваши операции в как можно меньше вызовов spawn_blocking.

Один пример этого различия можно увидеть, сравнив два приведенных выше примера чтения. Первый пример использует tokio::fs::read, который считывает весь файл за один вызов spawn_blocking, а затем возвращает его. Второй пример будет читать файл частями, используя множество вызовов spawn_blocking. Это означает, что второй пример, скорее всего, будет более затратным для больших файлов. (Конечно, использование частей может быть необходимо для очень больших файлов, которые не помещаются в памяти.)

Следующие примеры покажут некоторые стратегии для этого:

Запись всего содержимого за один раз

При создании файла записывайте данные в String или Vec<u8>, а затем записывайте весь файл одним вызовом spawn_blocking с помощью tokio::fs::write.

#![allow(unused)]
fn main() {
let mut contents = String::new();

contents.push_str("Первая строка.\n");
contents.push_str("Вторая строка.\n");
contents.push_str("Третья строка.\n");

tokio::fs::write("my_file.txt", contents.as_bytes()).await?;
}

Использование буферизации

Используйте BufReader и BufWriter для буферизации множества мелких операций чтения или записи в несколько крупных. Этот пример, скорее всего, выполнит только один вызов spawn_blocking.

#![allow(unused)]
fn main() {
use tokio::fs::File;
use tokio::io::{AsyncWriteExt, BufWriter};

let mut file = BufWriter::new(File::create("my_file.txt").await?);

file.write_all(b"Первая строка.\n").await?;
file.write_all(b"Вторая строка.\n").await?;
file.write_all(b"Третья строка.\n").await?;

// Из-за BufWriter фактическая запись и вызов spawn_blocking
// происходят при вызове flush.
file.flush().await?;
}

Ручное использование std::fs внутри spawn_blocking

#![allow(unused)]
fn main() {
use std::fs::File;
use std::io::{self, Write};
use tokio::task::spawn_blocking;

spawn_blocking(move || {
    let mut file = File::create("my_file.txt")?;

    file.write_all(b"Первая строка.\n")?;
    file.write_all(b"Вторая строка.\n")?;
    file.write_all(b"Третья строка.\n")?;

    // В отличие от файла Tokio, файлу std::fs
    // не нужен flush.

    io::Result::Ok(())
}).await.unwrap()?;
}

Также полезно знать о File::set_max_buf_size, который управляет максимальным количеством байт, которое файл Tokio будет читать или записывать за один вызов spawn_blocking. По умолчанию это два мегабайта, но это может измениться.

Структуры

ИмяОписание
DirBuilderПостроитель для создания каталогов различными способами.
DirEntryЗаписи, возвращаемые потоком ReadDir.
FileСсылка на открытый файл в файловой системе.
OpenOptionsОпции и флаги, которые можно использовать для настройки открытия файла.
ReadDirЧтение записей в каталоге.

Функции

ИмяОписание
canonicalizeВозвращает каноническую, абсолютную форму пути с нормализованными промежуточными компонентами и разрешенными символическими ссылками.
copyКопирует содержимое одного файла в другой. Также копирует биты разрешений исходного файла в файл назначения. Перезаписывает содержимое файла назначения.
create_dirСоздает новый пустой каталог по указанному пути.
create_dir_allРекурсивно создает каталог и все его родительские компоненты, если они отсутствуют.
hard_linkСоздает новую жесткую ссылку в файловой системе.
metadataПолучает информацию о файле, каталоге и т.д. по указанному пути.
readЧитает все содержимое файла в вектор байтов.
read_dirВозвращает поток записей в каталоге.
read_linkЧитает символическую ссылку, возвращая файл, на который она указывает.
read_to_stringСоздает future, который откроет файл для чтения и прочитает все содержимое в строку.
remove_dirУдаляет существующий пустой каталог.
remove_dir_allУдаляет каталог по этому пути, предварительно удалив все его содержимое. Используйте осторожно!
remove_fileУдаляет файл из файловой системы.
renameПереименовывает файл или каталог в новое имя, заменяя исходный файл, если to уже существует.
set_permissionsИзменяет разрешения файла или каталога.
symlinkUnix: Создает новую символическую ссылку в файловой системе.
symlink_dirWindows: Создает новую символическую ссылку на каталог.
symlink_fileWindows: Создает новую символическую ссылку на файл.
symlink_metadataПолучает метаданные файловой системы для пути.
try_existsВозвращает Ok(true), если путь указывает на существующую сущность.
writeСоздает future, который откроет файл для записи и запишет все содержимое contents в него.