برنامه نویسی

نحوه خواندن فایل ها در Rust

خواندن فایل ها یکی از رایج ترین عملیاتی است که در توسعه نرم افزار می توانید با آن مواجه شوید. بارگذاری فایل‌های پیکربندی، پردازش فایل‌ها و موارد دیگر اغلب بخشی از کاربرد نرم‌افزاری است که می‌سازید.

مانند هر زبان برنامه نویسی دیگری، روش های متعددی برای خواندن فایل ها در Rust وجود دارد. با این حال، همه مزایا و معایب خود را دارند. به همین دلیل است که درک این نکته که در چه موردی از کدام روش استفاده کنیم بسیار مهم است.

در این مقاله با رایج ترین روش های Rust برای خواندن فایل ها آشنا می شوید.


فقط در صورتی که از ویدیوهای YouTube بیشتر از یک مقاله لذت می برید، می توانید به جای آن ویدیوی بالا را نیز تماشا کنید!


خواندن یک فایل کامل در یک رشته

خواندن یک فایل کامل در یک رشته مزایای خود را دارد. شما نیازی به نگرانی در مورد هیچ چیز دیگری جز مدیریت فایل و پردازش محتوای آن ندارید. این یک انتخاب عالی برای فایل هایی است که:

  1. حاوی محتوای رشته
  2. می توان به صورت یکجا پردازش کرد

از طرفی این روش معایبی نیز دارد:

  1. فایل‌های بیش از حد بزرگ ممکن است تأثیرات شدیدی بر عملکرد داشته باشند
  2. هرچه حجم فایل بیشتر باشد، مصرف حافظه برنامه شما بیشتر است
  3. فایل هایی که حاوی رشته نیستند اما محتوای باینری را نمی توان به این روش پردازش کرد

مثال زیر نحوه خواندن یک فایل کامل را در یک رشته نشان می دهد:

use std::fs;

fn read_file_content_as_string(path: &str) -> Result<String, Box<dyn std::error::Error>> {
    let string_content = fs::read_to_string(path)?;
    Ok(string_content)
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید


خواندن یک فایل کامل در یک بایت وکتور

اگر لزوماً مجبور نیستید با محتوای String سر و کار داشته باشید، اما باید با فرمت باینری که باید پردازش کنید، خواندن یک فایل کامل در یک بردار بایت راهی است. اگرچه این روش هنوز برای محتوای String کار می کند. به جای دریافت مستقیم یک رشته از فراخوانی متد، باید خودتان آن را نمونه برداری کنید. اگر با محتوای String سروکار ندارید، لازم نیست این کار را انجام دهید.

این روش برای فایل هایی که:

  1. حاوی هر شکلی از محتوا باشد
  2. می تواند به صورت یک فایل کامل پردازش شود

با این حال، برخی از همان اشکالاتی که بر خواندن کل فایل در یک رشته تأثیر می‌گذارند، اعمال می‌شوند. اینها عبارتند از:

  1. فایل های بیش از حد بزرگ ممکن است تأثیر شدیدی بر عملکرد داشته باشند
  2. هرچه حجم فایل بیشتر باشد، مصرف حافظه برنامه شما بیشتر است

مثال زیر نحوه خواندن یک فایل کامل را در یک بردار بایت نشان می دهد:

use std::fs;

fn read_file_as_bytes(path: &str) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
    let byte_content = fs::read(path)?;
    Ok(byte_content)
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اگر هنوز هم می خواهید بردار بایت را خودتان به رشته تبدیل کنید، می توانید این کار را به صورت زیر انجام دهید:

use std::fs;
use std::str;

fn read_file_as_bytes(path: &str) -> Result<String, Box<dyn std::error::Error>> {
    let byte_content = fs::read(path)?;
    let string_content = str::from_utf8(&byte_content)?;

    Ok(string_content.to_string())
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید


خواندن یک فایل خط به خط

همانطور که در بالا گفته شد، اگر با فایل های حجیم سر و کار داشته باشید، خواندن یک فایل کامل به یکباره می تواند منجر به مشکلاتی شود. در چنین مواردی، بهتر است این فایل ها را با رویکرد خط به خط پردازش کنید. البته این چیزی است که بیشتر برای فایل هایی با محتوای String قابل استفاده است.

Rust با کمال میل ساختار مناسبی در کتابخانه استاندارد خود دارد که برخی از جزئیات سطح پایین تر به نام BufReader را نیز حذف می کند. این روش برای فایل هایی که:

  1. حاوی محتوای رشته
  2. بیش از حد بزرگ هستند که نمی‌توانند یک‌باره پردازش شوند

اما این رویکرد دارای چند اشکال نیز می باشد که لازم به ذکر است که عبارتند از:

  1. فقط برای فایل هایی با محتوای String کار می کند
  2. پیاده سازی ها می توانند به سرعت پیچیده تر شوند
  3. بسته به قالب بندی فایل، ممکن است مجبور شوید خطوط را خودتان بافر کنید، اگر همه چیزهایی که می خواهید پردازش کنید در یک خط قرار نمی گیرند.

مثال زیر نحوه خواندن یک فایل را خط به خط نشان می دهد:

use std::fs::File;
use std::io::{BufReader, BufRead};

fn read_file_line_by_line(path: &str) -> Result<(), Box<dyn std::error::Error>> {
    let file = File::open(path)?;
    let reader = BufReader::new(file);

    for line in reader.lines() {
        match line {
            // line is a String
            Ok(line) => process_line(line),
            Err(err) => handle_error(err),
        }
    }

    Ok(())
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید


خواندن یک فایل به صورت تک بایتی

در حالی که رویکرد قبلی به شما امکان می‌داد یک فایل را خط به خط بخوانید، این روش به شما امکان می‌دهد بایت‌های جداگانه فایلی را که می‌خواهید با BufReader پردازش کنید بخوانید. این یکی از اساسی ترین رویکردها است زیرا تقریباً تمام نرده های محافظی را که کتابخانه استاندارد به شما می دهد کنار می گذارید. با این حال، برخی از انعطاف‌پذیری‌ها را به شما می‌دهد.

از این روش استفاده کنید اگر:

  1. نیاز به کنترل کامل بر روی آنچه که با محتوای یک فایل اتفاق می افتد
  2. برای پیاده سازی بسیاری از محتوا که خودتان مدیریت می کنید، کاملاً خوب است
  3. باید با فایل‌های حجیمی سر و کار داشته باشید که در صورت خواندن یک‌باره، مصرف حافظه شما را منفجر می‌کند

اگرچه قبلاً ذکر شد، اما خوب است در مورد معایب این روش نیز صحبت کنیم. معایب آن عبارتند از:

  1. شما باید با داده های خام کار کنید. در این مورد، حتی تک بایت های خام آن است
  2. احتمالاً هنوز به یک بافر برای ذخیره موقت تک بایت ها نیاز دارید تا زمانی که بتوانید چندین مورد از آنها را در چیزی معنادارتر ادغام کنید.

مثال زیر نحوه خواندن یک فایل را در مراحل تک بایت نشان می دهد:

use std::fs::File;
use std::io::{BufReader, Read};

fn read_file_as_single_bytes(path: &str) -> Result<(), Box<dyn std::error::Error>> {
    let file = File::open(path)?;
    let reader = BufReader::new(file);

    for byte in reader.bytes() {
        match byte {
            // byte is exactly one byte
            Ok(byte) => process_byte(byte),
            Err(err) => handle_error(err),
        }
    }

    Ok(())
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید


خواندن یک فایل در تکه های بایت

اگر می‌خواهید انعطاف‌پذیری بیشتری داشته باشید، می‌توانید از BufReader برای خواندن تکه‌های یک فایل استفاده کنید. اگر بخواهیم کاملاً صادق باشیم، BufReader همچنین بهینه‌سازی‌هایی را انجام می‌دهد و وقتی از متد .bytes() استفاده می‌کنید، هر بایت را جداگانه نمی‌خواند. آنها را به صورت تکه تکه می خواند و سپس تک بایت ها را از Iterator برمی گرداند.

با این حال، وقتی می‌خواهید خودتان تکه‌ها را پردازش کنید، این خیلی کمکی نمی‌کند. البته می‌توانید هنگام استفاده از () bytes، بایت‌ها را به صورت دستی بافر کنید یا به سادگی از این روش پیروی کنید.

خواندن محتوای یک فایل در تکه های بایت مانند هر روش دیگری دارای مزایا و معایبی است. مزایای آن عبارتند از:

  1. شما کنترل کاملی بر نحوه برخورد با محتویات یک فایل به دست می آورید
  2. این بیشترین انعطاف پذیری را به شما می دهد، زیرا می توانید اندازه قطعه را به صورت پویا تنظیم کنید و به شرایط خاص واکنش نشان دهید.
  3. اگر مجبورید با فایل‌های بزرگی سر و کار داشته باشید که در صورت خواندن یک‌باره باعث افزایش مصرف حافظه شما می‌شوند، می‌توانید از آن استفاده کنید

البته باز هم چند اشکال شناخته شده در این روش وجود دارد:

  1. شما باید با داده های خام کار کنید. تمام رمزگشایی و پردازش به شما بستگی دارد
  2. ممکن است چند تلاش برای بهینه سازی اندازه بافر برای سناریوهای خاص لازم باشد
  3. اگر اندازه قطعه را خیلی کوچک کنید، ممکن است در واقع به عملکرد کلی برنامه خود آسیب وارد کنید (تماس های سیستمی بیش از حد)

مثال زیر نحوه خواندن یک فایل را در تکه های بایت نشان می دهد:

use std::fs::File;
use std::io::{BufReader, BufRead}

const BUFFER_SIZE: usize = 512;

fn read_file_in_byte_chunks(path: &str) -> Result<(), Box<dyn std::error::Error>> {
    let file = File::open(path)?;

    let mut reader = BufReader::with_capacity(BUFFER_SIZE, file);

    loop {
        let buffer = reader.fill_buf()?;

        let buffer_length = buffer.len();

        // BufRead could not read any bytes.
        // The file must have completely been read.
        if buffer_length == 0 {
            break;
        }

        do_something_with(buffer);

        // All bytes consumed from the buffer
        // should not be read again.
        reader.consume(buffer_length);
    }

    Ok(())
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید


خلاصه

خواندن فایل ها یک عملیات رایج در هنگام توسعه نرم افزار است. مانند هر زبان برنامه نویسی دیگری، Rust راه های مختلفی برای مقابله با آن ارائه می دهد. این راهنما پنج روش رایج برای خواندن فایل‌ها (هم به صورت رشته و هم در فرمت باینری خام) در Rust را پوشش می‌دهد.

تمام روش های ارائه شده دارای مزایا و معایبی هستند و شما باید روشی را که مناسب شرایط خاص و مورد استفاده خود است انتخاب کنید.

اگر فایل های کوچکی دارید و با محتوای String سر و کار دارید، خواندن یک فایل کامل در یک رشته انتخاب عالی است. از طرف دیگر، اگر فایل های شما بزرگتر شوند یا اصلاً با محتوای String سروکار نداشته باشید، این روش بهترین نیست.

اگر فایل‌های کوچکی دارید و با محتوای خام دلخواه سروکار دارید، خواندن یک فایل کامل در یک وکتور بایت، انتخاب خوبی است. اگر فایل‌های شما بزرگ‌تر شوند و محدودیت‌های حافظه داشته باشید، فاقد آن است.

اگر با محتوای String سر و کار دارید و نمی خواهید حافظه شما بیش از حد رشد کند، خواندن خط به خط فایل ها یک انتخاب عالی است. اگر با محتوای String سروکار نداشته باشید و فایل‌هایی داشته باشید که محتوای مورد نظرتان را به طور همزمان در چندین خط پخش می‌کنند، این روش کوتاه است، که نیاز دارد خودتان خطوط را بافر کنید.

خواندن فایل ها به صورت تک بایتی یکی از ابتدایی ترین روش هاست. اگر می خواهید انعطاف پذیری داشته باشید و به کنترل زیادی نیاز دارید، این یک انتخاب عالی است. از سوی دیگر، شما باید با داده‌های خام کار کنید و همچنین اگر نیاز به ادغام چندین بایت در چیزی معنادارتر دارید، احتمالاً باید خودتان داده‌ها را بافر کنید.

در نهایت، خواندن یک فایل در تکه های بایت کمی انعطاف پذیرتر از خواندن هر بایت به صورت جداگانه است. کنترل کامل بر پردازش داده ها را ارائه می دهد و همچنین می تواند به صورت پویا تنظیم شود. اما یک بار دیگر، شما باید با داده های خام کار کنید، و ممکن است کمی طول بکشد تا انفصال را تنظیم کنید.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

همچنین ببینید
بستن
دکمه بازگشت به بالا