ورودی و خروجی فایل در متلب: کار با داده‌های خارجی (با کد)

فهرست مطالب

ورودی و خروجی فایل در متلب: کار با داده‌های خارجی (با کد)

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

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

تمرکز ما بر روی ارائه مثال‌های کاربردی و کدهای اجرایی خواهد بود تا مفاهیم به بهترین شکل ممکن منتقل شوند. از مباحث پایه مانند باز و بسته کردن فایل‌ها تا تکنیک‌های پیشرفته برای مدیریت فایل‌های حجیم و بهینه‌سازی عملکرد، همه جنبه‌های حیاتی پوشش داده خواهند شد. این راهنما برای کاربران متلب که به دنبال عمیق‌تر شدن در موضوع I/O فایل هستند و می‌خواهند راه‌حل‌های قوی و مقیاس‌پذیر برای چالش‌های داده‌ای خود ایجاد کنند، ایده‌آل است.

اصول بنیادین مدیریت فایل در متلب

پیش از ورود به جزئیات خواندن و نوشتن انواع مختلف فایل‌ها، ضروری است که با اصول بنیادین مدیریت فایل در متلب آشنا شویم. این اصول شامل درک مسیرهای فایل، نحوه باز و بسته کردن فایل‌ها و مدیریت خطاهای احتمالی است.

مسیرهای فایل (File Paths): مطلق و نسبی

هنگام کار با فایل‌ها، متلب نیاز دارد تا مکان دقیق فایل را بداند. این مکان با استفاده از یک مسیر فایل مشخص می‌شود. دو نوع مسیر فایل اصلی وجود دارد:

  • مسیر مطلق (Absolute Path): مسیری که مکان دقیق فایل را از ریشه سیستم فایل (مانند C:\ در ویندوز یا / در لینوکس/مک) مشخص می‌کند. این مسیر مستقل از مکان فعلی اجرای اسکریپت متلب است.
  • مسیر نسبی (Relative Path): مسیری که مکان فایل را نسبت به پوشه کاری فعلی (Current Working Directory) متلب مشخص می‌کند. استفاده از مسیرهای نسبی معمولاً برای پروژه‌هایی که قرار است در سیستم‌های مختلف اجرا شوند، انعطاف‌پذیرتر است.

می‌توانید پوشه کاری فعلی متلب را با دستور pwd مشاهده کنید و با cd('path/to/directory') آن را تغییر دهید.


% مثال مسیر مطلق در ویندوز
filePathAbsWin = 'C:\Users\Username\Documents\myData.txt';

% مثال مسیر مطلق در لینوکس/مک
filePathAbsUnix = '/home/username/documents/myData.txt';

% مثال مسیر نسبی (اگر فایل در پوشه جاری باشد)
filePathRel = 'myData.txt';

% مثال مسیر نسبی (اگر فایل در یک زیرپوشه باشد)
filePathSubDir = 'data/myData.txt';

% مشاهده پوشه کاری فعلی
currentDir = pwd;
disp(['پوشه کاری فعلی: ' currentDir]);

باز کردن و بستن فایل‌ها: fopen و fclose

برای تعامل با یک فایل، ابتدا باید آن را باز کنید. تابع fopen این کار را انجام می‌دهد و یک شناسه فایل (fileID) را برمی‌گرداند که یک عدد صحیح غیرمنفی است. در صورت بروز خطا، fopen مقدار -1 را برمی‌گرداند. پس از اتمام کار با فایل، باید آن را با استفاده از تابع fclose ببندید تا منابع سیستم آزاد شده و اطمینان حاصل شود که تمام تغییرات ذخیره شده‌اند.

fopen: باز کردن فایل

سینتکس اصلی fopen به صورت زیر است:


fileID = fopen(filename, permission);
  • filename: یک رشته حاوی مسیر فایل.
  • permission: یک رشته که نحوه دسترسی به فایل را مشخص می‌کند. متداول‌ترین حالت‌ها عبارتند از:
    • 'r': فقط برای خواندن (Read). فایل باید وجود داشته باشد.
    • 'w': فقط برای نوشتن (Write). اگر فایل وجود نداشته باشد، ایجاد می‌شود. اگر وجود داشته باشد، محتوای آن پاک می‌شود.
    • 'a': فقط برای اضافه کردن (Append). اگر فایل وجود نداشته باشد، ایجاد می‌شود. اگر وجود داشته باشد، داده‌ها به انتهای آن اضافه می‌شوند.
    • 'r+': برای خواندن و نوشتن. فایل باید وجود داشته باشد.
    • 'w+': برای خواندن و نوشتن. اگر فایل وجود نداشته باشد، ایجاد می‌شود. اگر وجود داشته باشد، محتوای آن پاک می‌شود.
    • 'a+': برای خواندن و اضافه کردن. اگر فایل وجود نداشته باشد، ایجاد می‌شود. اگر وجود داشته باشد، داده‌ها به انتهای آن اضافه می‌شوند.

علاوه بر این، می‌توانید 't' یا 'b' را به permission اضافه کنید تا نحوه باز کردن فایل را به عنوان متن (text) یا باینری (binary) مشخص کنید. به صورت پیش‌فرض، fopen فایل‌ها را به عنوان باینری باز می‌کند (به جز در سیستم‌عامل‌های خاص که 't' پیش‌فرض است). برای تضمین رفتار یکسان در پلتفرم‌های مختلف، بهتر است صراحتاً 't' یا 'b' را مشخص کنید.


% مثال: باز کردن یک فایل متنی برای نوشتن
filename = 'output.txt';
fileID = fopen(filename, 'wt'); % 'wt' برای نوشتن فایل متنی
if fileID == -1
    error('خطا در باز کردن فایل برای نوشتن.');
else
    disp(['فایل ' filename ' با موفقیت باز شد.']);
    % ادامه کار با فایل
    fprintf(fileID, 'این یک خط آزمایشی است.\n');
    fclose(fileID);
    disp(['فایل ' filename ' با موفقیت بسته شد.']);
end

% مثال: باز کردن یک فایل باینری برای خواندن
% فرض کنید یک فایل باینری به نام 'binaryData.bin' وجود دارد
% ابتدا یک فایل باینری نمونه ایجاد می‌کنیم
data = rand(10, 1, 'single'); % 10 عدد اعشاری تک دقیقه‌ای
fileID_bin_write = fopen('binaryData.bin', 'wb');
fwrite(fileID_bin_write, data, 'single');
fclose(fileID_bin_write);

% حالا فایل را برای خواندن باز می‌کنیم
fileID_bin_read = fopen('binaryData.bin', 'rb'); % 'rb' برای خواندن فایل باینری
if fileID_bin_read == -1
    error('خطا در باز کردن فایل باینری برای خواندن.');
else
    disp('فایل binaryData.bin با موفقیت باز شد.');
    % ادامه کار با فایل
    read_data = fread(fileID_bin_read, Inf, 'single');
    fclose(fileID_bin_read);
    disp('فایل binaryData.bin با موفقیت بسته شد.');
    disp('داده‌های خوانده شده:');
    disp(read_data');
end

fclose: بستن فایل

بستن فایل‌ها پس از اتمام کار، عملی حیاتی است. این کار تضمین می‌کند که:

  • تمام داده‌های بافر شده به دیسک نوشته شوند.
  • منابع سیستم آزاد شوند و فایل برای دسترسی سایر برنامه‌ها یا فرآیندها در دسترس باشد.
  • از از دست رفتن داده‌ها در صورت خرابی برنامه جلوگیری شود.

سینتکس fclose:


status = fclose(fileID); % بستن یک فایل خاص
status = fclose('all'); % بستن تمام فایل‌های باز

status: در صورت موفقیت، 0 و در صورت خطا، -1 است.

ferror: بررسی خطاهای فایل

هنگامی که عملیات فایل با شکست مواجه می‌شود، ferror می‌تواند اطلاعات مفیدی در مورد دلیل خطا ارائه دهد. این تابع رشته‌ای حاوی پیام خطا و شماره خطای سیستم را برمی‌گرداند.


fileID = fopen('nonExistentFile.txt', 'r');
if fileID == -1
    [msg, errnum] = ferror(fileID);
    disp(['خطا در باز کردن فایل: ' msg ' (شماره خطا: ' num2str(errnum) ')']);
end

استفاده از بلوک‌های try-catch همراه با onCleanup (برای تضمین بسته شدن فایل‌ها حتی در صورت بروز خطا) یک روش توصیه شده برای مدیریت خطای قوی در برنامه‌های متلب است.

ناوبری در فایل: frewind, fseek, ftell

در برخی سناریوها، ممکن است نیاز داشته باشید که در طول فایل حرکت کنید تا به بخش‌های خاصی از آن دسترسی پیدا کنید یا عملیات خواندن/نوشتن را از نقطه خاصی آغاز کنید:

  • frewind(fileID): مکان‌نمای فایل را به ابتدای فایل بازنشانی می‌کند.
  • status = fseek(fileID, offset, origin): مکان‌نمای فایل را به موقعیت جدیدی منتقل می‌کند.
    • offset: تعداد بایت‌هایی که باید حرکت کنید.
    • origin: نقطه شروع برای محاسبه افست ('bof' – ابتدا، 'cof' – مکان فعلی، 'eof' – انتها).
  • position = ftell(fileID): موقعیت فعلی مکان‌نمای فایل را برمی‌گرداند (بر حسب بایت از ابتدای فایل).

filename = 'seek_test.txt';
fileID = fopen(filename, 'wt');
fprintf(fileID, '1234567890');
fclose(fileID);

fileID = fopen(filename, 'r');
% خواندن 3 بایت اول
char1 = fread(fileID, 3, '*char')';
disp(['اولین 3 بایت: ' char1]); % خروجی: 123

% حرکت به جلو 4 بایت از مکان فعلی
fseek(fileID, 4, 'cof');
char2 = fread(fileID, 1, '*char')';
disp(['کاراکتر بعد از 4 بایت حرکت از مکان فعلی: ' char2]); % خروجی: 8

% حرکت به ابتدای فایل
frewind(fileID);
char3 = fread(fileID, 1, '*char')';
disp(['اولین کاراکتر بعد از frewind: ' char3]); % خروجی: 1

% رفتن به 2 بایت قبل از انتهای فایل
fseek(fileID, -2, 'eof');
char4 = fread(fileID, 2, '*char')';
disp(['دو کاراکتر آخر: ' char4]); % خروجی: 90

fclose(fileID);
delete(filename); % پاک کردن فایل آزمایشی

خواندن و نوشتن فایل‌های متنی: از فرمت‌بندی تا تجزیه پیشرفته

فایل‌های متنی (Text files) یکی از رایج‌ترین فرمت‌ها برای ذخیره‌سازی و تبادل داده‌ها هستند. متلب ابزارهای متنوعی برای کار با این نوع فایل‌ها ارائه می‌دهد که از نوشتن خروجی فرمت‌بندی شده تا تجزیه پیچیده داده‌های جداشده با کاراکترها را پوشش می‌دهند.

نوشتن فایل‌های متنی: fprintf

تابع fprintf برای نوشتن داده‌های فرمت‌بندی شده در یک فایل متنی استفاده می‌شود. این تابع بسیار شبیه به تابع sprintf عمل می‌کند، با این تفاوت که خروجی را به جای یک متغیر رشته‌ای، به یک فایل ارسال می‌کند.

سینتکس:


fprintf(fileID, formatSpec, A1, ..., An);
  • fileID: شناسه فایلی که توسط fopen باز شده است.
  • formatSpec: یک رشته فرمت‌بندی که نحوه نمایش داده‌ها را مشخص می‌کند (مانند %s برای رشته، %d برای عدد صحیح، %f برای عدد اعشاری، \n برای خط جدید، \t برای تب).
  • A1, ..., An: متغیرهایی که قرار است نوشته شوند.

filename = 'student_grades.txt';
fileID = fopen(filename, 'wt');

if fileID == -1
    error('خطا در باز کردن فایل برای نوشتن.');
end

names = {'علی', 'سارا', 'رضا'};
grades = [18.5, 19.0, 17.25];
studentIDs = [101, 102, 103];

% نوشتن هدر
fprintf(fileID, 'شناسه\tنام\tنمره\n');

% نوشتن داده‌ها
for i = 1:length(names)
    fprintf(fileID, '%d\t%s\t%.2f\n', studentIDs(i), names{i}, grades(i));
end

fclose(fileID);
disp(['فایل ' filename ' با موفقیت ایجاد شد:']);
type(filename); % نمایش محتویات فایل در Command Window

خروجی فایل student_grades.txt:


شناسه	نام	نمره
101	علی	18.50
102	سارا	19.00
103	رضا	17.25

خواندن فایل‌های متنی

fscanf: ورودی فرمت‌بندی شده

تابع fscanf داده‌ها را از یک فایل با استفاده از یک رشته فرمت مشخص می‌خواند. این تابع برای خواندن فایل‌هایی که ساختار کاملاً منظمی دارند، مناسب است.

سینتکس:


A = fscanf(fileID, formatSpec, sizeA);
  • fileID: شناسه فایل.
  • formatSpec: رشته فرمت‌بندی مشابه fprintf.
  • sizeA: (اختیاری) مشخص می‌کند چند مقدار خوانده شود یا ابعاد آرایه خروجی. Inf به معنی خواندن تا انتهای فایل است.

filename = 'data_matrix.txt';
fileID = fopen(filename, 'wt');
fprintf(fileID, '1.1 2.2 3.3\n');
fprintf(fileID, '4.4 5.5 6.6\n');
fclose(fileID);

fileID = fopen(filename, 'rt');
if fileID == -1
    error('خطا در باز کردن فایل برای خواندن.');
end

% خواندن تمام اعداد به عنوان یک ستون و سپس تغییر شکل به ماتریس 2x3
data_col = fscanf(fileID, '%f', [6, 1]); % خواندن 6 عدد اعشاری
data_matrix = reshape(data_col, 3, 2)'; % تغییر شکل به ماتریس 2x3

fclose(fileID);
delete(filename);
disp('داده‌های خوانده شده با fscanf:');
disp(data_matrix);

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

fgetl و fgets: خواندن خط به خط

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

  • line = fgetl(fileID): یک خط را از فایل می‌خواند و کاراکتر خط جدید (\n) را حذف می‌کند. در صورت رسیدن به انتهای فایل، -1 را برمی‌گرداند.
  • line = fgets(fileID): یک خط را از فایل می‌خواند و کاراکتر خط جدید را شامل می‌شود. در صورت رسیدن به انتهای فایل، -1 را برمی‌گرداند.

filename = 'log_file.txt';
fileID = fopen(filename, 'wt');
fprintf(fileID, '2023-10-26 10:00:01 INFO: سیستم شروع به کار کرد.\n');
fprintf(fileID, '2023-10-26 10:00:05 WARNING: حافظه کم است.\n');
fprintf(fileID, '2023-10-26 10:00:10 ERROR: عملیات ناموفق بود.\n');
fclose(fileID);

fileID = fopen(filename, 'rt');
if fileID == -1
    error('خطا در باز کردن فایل برای خواندن.');
end

lineNum = 1;
while ~feof(fileID) % تا زمانی که به انتهای فایل نرسیده‌ایم
    line = fgetl(fileID); % استفاده از fgetl که \n را حذف می‌کند
    if ischar(line) % مطمئن شوید که به انتهای فایل نرسیده‌ایم (fgetl -1 برمی‌گرداند)
        disp(['خط ' num2str(lineNum) ': ' line]);
        lineNum = lineNum + 1;
    end
end

fclose(fileID);
delete(filename);

textscan: تجزیه قدرتمند فایل‌های متنی

textscan قدرتمندترین و انعطاف‌پذیرترین تابع برای خواندن داده‌های متنی سازمان‌یافته، به خصوص فایل‌های جدا شده با کاراکترها (مانند CSV یا TSV)، است. این تابع به شما امکان می‌دهد تا انواع مختلف داده‌ها (اعداد، رشته‌ها، تاریخ‌ها) را به طور همزمان بخوانید و کنترل دقیقی بر فرآیند تجزیه داشته باشید.

سینتکس پایه:


C = textscan(fileID, formatSpec);
C = textscan(fileID, formatSpec, N); % خواندن N سطر

پارامترهای کلیدی textscan:

  • fileID: شناسه فایل.
  • formatSpec: یک رشته فرمت‌بندی که انواع داده‌ها و ترتیب ستون‌ها را مشخص می‌کند (مثلاً '%f %s %d').
  • Delimiter: کاراکتر جداکننده (مثلاً ',' برای CSV، '\t' برای TSV).
  • HeaderLines: تعداد سطرهایی که باید به عنوان هدر نادیده گرفته شوند.
  • CollectOutput: اگر true باشد، ستون‌های عددی مشابه را در یک ماتریس جمع می‌کند.
  • EmptyValue: مقدار جایگزین برای فیلدهای خالی.

filename = 'sales_data.csv';
fileID = fopen(filename, 'wt');
fprintf(fileID, 'Date,Product,Quantity,Price\n');
fprintf(fileID, '2023-01-01,Laptop,10,1200.50\n');
fprintf(fileID, '2023-01-02,Mouse,50,25.00\n');
fprintf(fileID, '2023-01-03,Keyboard,20,75.20\n');
fprintf(fileID, '2023-01-04,Monitor,15,300.00\n');
fclose(fileID);

fileID = fopen(filename, 'rt');
if fileID == -1
    error('خطا در باز کردن فایل برای خواندن.');
end

% خواندن داده‌ها با textscan
% 'HeaderLines', 1: نادیده گرفتن سطر اول (هدر)
% 'Delimiter', ',': جداکننده ویرگول
% '%s %s %f %f': فرمت ستون‌ها (رشته، رشته، عدد اعشاری، عدد اعشاری)
data = textscan(fileID, '%s %s %f %f', 'Delimiter', ',', 'HeaderLines', 1);

dates = data{1};
products = data{2};
quantities = data{3};
prices = data{4};

fclose(fileID);
delete(filename);

disp('تاریخ‌ها:'); disp(dates);
disp('محصولات:'); disp(products);
disp('تعداد:'); disp(quantities);
disp('قیمت‌ها:'); disp(prices);

textscan بسیار قدرتمند است و می‌تواند تقریبا هر فرمت متنی جداسازی شده را پردازش کند.

توابع مدرن برای فایل‌های متنی: readmatrix, writematrix, readtable, writetable

از نسخه‌های جدیدتر متلب (معمولاً از R2013b به بعد)، توابع سطح بالاتر و کاربرپسندتری برای کار با فایل‌های متنی و صفحه‌گسترده معرفی شده‌اند که کار را بسیار ساده‌تر می‌کنند:

  • readmatrix(filename): برای خواندن فایل‌های متنی حاوی فقط داده‌های عددی (مانند CSV، TSV) و برگرداندن آن‌ها به عنوان یک آرایه عددی.
  • writematrix(A, filename): برای نوشتن یک آرایه عددی به یک فایل متنی.
  • readtable(filename): برای خواندن فایل‌های متنی با ساختار ستونی پیچیده‌تر (با هدر، انواع داده مختلف) و برگرداندن آن‌ها به عنوان یک table. این تابع به طور هوشمندانه انواع داده‌ها و نام ستون‌ها را تشخیص می‌دهد.
  • writetable(T, filename): برای نوشتن یک table متلب به یک فایل متنی.

این توابع معمولاً توصیه می‌شوند زیرا کار را ساده می‌کنند و نیاز به مشخص کردن دقیق فرمت یا جداسازها را در بسیاری از موارد از بین می‌برند.


% مثال با readmatrix و writematrix
data_to_write = [1.1 2.2 3.3; 4.4 5.5 6.6];
writematrix(data_to_write, 'matrix_data.csv');
read_data = readmatrix('matrix_data.csv');
disp('داده‌های خوانده شده با readmatrix:');
disp(read_data);
delete('matrix_data.csv');

% مثال با readtable و writetable
T = table({'Alice'; 'Bob'; 'Charlie'}, [25; 30; 22], {'NY'; 'CA'; 'TX'}, ...
          'VariableNames', {'Name', 'Age', 'State'});
writetable(T, 'people_data.txt', 'Delimiter', '\t'); % ذخیره به عنوان TSV

read_T = readtable('people_data.txt', 'Delimiter', '\t');
disp('جدول خوانده شده با readtable:');
disp(read_T);
delete('people_data.txt');

% مثال readtable با فایل CSV از قبل ساخته شده
filename = 'sales_data.csv'; % از مثال textscan
fileID = fopen(filename, 'wt');
fprintf(fileID, 'Date,Product,Quantity,Price\n');
fprintf(fileID, '2023-01-01,Laptop,10,1200.50\n');
fprintf(fileID, '2023-01-02,Mouse,50,25.00\n');
fprintf(fileID, '2023-01-03,Keyboard,20,75.20\n');
fprintf(fileID, '2023-01-04,Monitor,15,300.00\n');
fclose(fileID);

sales_table = readtable(filename);
disp('جدول فروش خوانده شده با readtable:');
disp(sales_table);
delete(filename);

همانطور که مشاهده می‌شود، readtable و writetable به طور قابل توجهی کد را ساده‌تر و خواناتر می‌کنند و برای اکثر وظایف I/O فایل‌های متنی توصیه می‌شوند.

مدیریت داده‌های باینری: کارایی و دقت در I/O

فایل‌های باینری، برخلاف فایل‌های متنی، داده‌ها را به صورت مستقیم در قالب باینری ذخیره می‌کنند. این روش ذخیره‌سازی مزایای قابل توجهی دارد: کارایی بالاتر (به دلیل عدم نیاز به تبدیل بین فرمت متنی و عددی)، فضای کمتر (به دلیل عدم وجود کاراکترهای اضافی مانند جداکننده‌ها یا خطوط جدید) و حفظ دقت داده‌ها (به خصوص برای اعداد اعشاری).

با این حال، کار با فایل‌های باینری نیازمند درک دقیق‌تر ساختار داده‌ها و نحوه ذخیره‌سازی آن‌ها است. متلب توابع fread و fwrite را برای این منظور ارائه می‌دهد.

fwrite: نوشتن داده‌های باینری

تابع fwrite برای نوشتن داده‌های باینری در یک فایل استفاده می‌شود.

سینتکس:


count = fwrite(fileID, A, precision);
count = fwrite(fileID, A, precision, skip);
count = fwrite(fileID, A, precision, skip, machinefmt);
  • fileID: شناسه فایلی که توسط fopen با حالت 'b' (باینری) باز شده است.
  • A: داده‌هایی که قرار است نوشته شوند (یک ماتریس یا آرایه متلب).
  • precision: یک رشته که نوع داده و اندازه بایت هر عنصر را مشخص می‌کند (مثلاً 'single'، 'double'، 'int8'، 'uint16'). این پارامتر بسیار مهم است.
  • skip: (اختیاری) تعداد بایت‌هایی که قبل از نوشتن هر مقدار باید پرش شود (برای ساختارهای داده پیچیده‌تر).
  • machinefmt: (اختیاری) نحوه ذخیره‌سازی بایت‌ها (Endianness). (مثلاً 'ieee-le' برای Little-Endian، 'ieee-be' برای Big-Endian).
  • count: تعداد مقادیری که با موفقیت نوشته شده‌اند.

filename = 'binaryData.bin';
fileID = fopen(filename, 'wb'); % 'wb' برای نوشتن باینری

if fileID == -1
    error('خطا در باز کردن فایل باینری برای نوشتن.');
end

% داده‌های نمونه: یک ماتریس 3x2 از اعداد اعشاری تک دقیقه‌ای
data_to_write = single([1.1 2.2; 3.3 4.4; 5.5 6.6]);

% نوشتن داده‌ها به عنوان 'single' (اعداد اعشاری 4 بایتی)
bytes_written = fwrite(fileID, data_to_write, 'single');
disp(['تعداد بایت‌های نوشته شده: ' num2str(bytes_written * 4)]); % هر single 4 بایت است
disp(['تعداد مقادیر نوشته شده: ' num2str(bytes_written)]);

% نوشتن داده‌های نوع int16
int_data = int16([100; 200; 300]);
fwrite(fileID, int_data, 'int16'); % این داده‌ها به انتهای فایل اضافه می‌شوند

fclose(fileID);
disp(['فایل باینری ' filename ' با موفقیت ایجاد شد.']);

fread: خواندن داده‌های باینری

تابع fread برای خواندن داده‌های باینری از یک فایل استفاده می‌شود. برای خواندن صحیح، باید ساختار داده‌های ذخیره شده در فایل را به دقت بدانید.

سینتکس:


A = fread(fileID, sizeA, precision);
A = fread(fileID, sizeA, precision, skip);
A = fread(fileID, sizeA, precision, skip, machinefmt);
  • fileID: شناسه فایلی که توسط fopen با حالت 'b' (باینری) باز شده است.
  • sizeA: (اختیاری) مشخص می‌کند چند مقدار خوانده شود یا ابعاد آرایه خروجی.
    • Inf: خواندن تمام مقادیر تا انتهای فایل.
    • یک عدد صحیح: خواندن آن تعداد از مقادیر.
    • یک بردار [m, n]: خواندن m*n مقدار و تغییر شکل آن‌ها به یک ماتریس m x n.
  • precision: یک رشته که نوع داده مقادیر را در فایل و همچنین نوع داده‌ای که متلب باید آن‌ها را به آن تبدیل کند، مشخص می‌کند (مثلاً 'single'، 'double'، 'uint8').
  • skip، machinefmt: مشابه fwrite هستند.

filename = 'binaryData.bin'; % از مثال قبلی
fileID = fopen(filename, 'rb'); % 'rb' برای خواندن باینری

if fileID == -1
    error('خطا در باز کردن فایل باینری برای خواندن.');
end

% خواندن اولین 6 مقدار به عنوان 'single'
read_single_data = fread(fileID, [3, 2], 'single'); % انتظار 3x2 ماتریس
disp('داده‌های single خوانده شده:');
disp(read_single_data);

% خواندن 3 مقدار بعدی به عنوان 'int16'
read_int_data = fread(fileID, [3, 1], 'int16'); % انتظار 3x1 بردار
disp('داده‌های int16 خوانده شده:');
disp(read_int_data);

fclose(fileID);
delete(filename); % پاک کردن فایل آزمایشی

در این مثال، fread به طور هوشمندانه مکان‌نمای فایل را پس از خواندن اولین مجموعه داده‌ها، به بعد از آن‌ها منتقل می‌کند.

Endianness (اندین) و machinefmt

Endianness به ترتیبی اشاره دارد که بایت‌ها در حافظه ذخیره می‌شوند (مثلاً برای اعداد چند بایتی). دو نوع اصلی وجود دارد:

  • Big-Endian: پرارزش‌ترین بایت (Most Significant Byte) در آدرس حافظه پایین‌تر ذخیره می‌شود.
  • Little-Endian: کم‌ارزش‌ترین بایت (Least Significant Byte) در آدرس حافظه پایین‌تر ذخیره می‌شود.

این موضوع زمانی اهمیت پیدا می‌کند که فایل‌های باینری بین سیستم‌های مختلفی که Endianness متفاوتی دارند، مبادله شوند. متلب به شما اجازه می‌دهد تا با استفاده از پارامتر machinefmt در fopen، fread و fwrite، Endianness را مشخص کنید.

  • 'ieee-le': Little-Endian
  • 'ieee-be': Big-Endian
  • 'n' یا 'native': Endianness بومی سیستم عامل جاری.

به صورت پیش‌فرض، متلب از Endianness بومی سیستم استفاده می‌کند. اگر با فایل‌های باینری تولید شده توسط سیستم‌های دیگر کار می‌کنید، ممکن است لازم باشد machinefmt را به صراحت مشخص کنید تا از خواندن و نوشتن نادرست داده‌ها جلوگیری شود.


% مثال Endianness
val = typecast(uint32(hex2dec('AABBCCDD')), 'uint8'); % 4 بایت: [AA BB CC DD] در Big-Endian
% در Little-Endian، در حافظه به صورت [DD CC BB AA] ذخیره می‌شود

filename_le = 'little_endian.bin';
filename_be = 'big_endian.bin';

% نوشتن به صورت Little-Endian
fileID_le = fopen(filename_le, 'wb', 'ieee-le');
fwrite(fileID_le, val, 'uint32');
fclose(fileID_le);

% نوشتن به صورت Big-Endian
fileID_be = fopen(filename_be, 'wb', 'ieee-be');
fwrite(fileID_be, val, 'uint32');
fclose(fileID_be);

% خواندن هر دو فایل با Endianness بومی
fileID_le_read = fopen(filename_le, 'rb', 'native');
read_le = fread(fileID_le_read, 1, 'uint32');
fclose(fileID_le_read);

fileID_be_read = fopen(filename_be, 'rb', 'native');
read_be = fread(fileID_be_read, 1, 'uint32');
fclose(fileID_be_read);

disp(['مقدار از فایل Little-Endian (خوانده شده به صورت بومی): ' dec2hex(read_le)]);
disp(['مقدار از فایل Big-Endian (خوانده شده به صورت بومی): ' dec2hex(read_be)]);

% در یک سیستم Little-Endian، read_le با AABBCCDD برابر خواهد بود
% و read_be برعکس آن خواهد بود (DDCCBBAA) مگر اینکه صراحتاً Endianness مشخص شود.
% حالا دوباره با تعیین Endianness می‌خوانیم تا درست بخوانیم
fileID_le_read = fopen(filename_le, 'rb', 'ieee-le');
read_le_correct = fread(fileID_le_read, 1, 'uint32');
fclose(fileID_le_read);

fileID_be_read = fopen(filename_be, 'rb', 'ieee-be');
read_be_correct = fread(fileID_be_read, 1, 'uint32');
fclose(fileID_be_read);

disp(['مقدار از فایل Little-Endian (خوانده شده صحیح): ' dec2hex(read_le_correct)]);
disp(['مقدار از فایل Big-Endian (خوانده شده صحیح): ' dec2hex(read_be_correct)]);

delete(filename_le);
delete(filename_be);

کار با فرمت‌های رایج و تخصصی داده در متلب

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

فایل‌های MAT (.mat): فرمت بومی متلب

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

  • save(filename, variables): برای ذخیره یک یا چند متغیر در فایل MAT.
    • save('mydata.mat'): تمام متغیرهای فضای کاری را ذخیره می‌کند.
    • save('mydata.mat', 'A', 'B'): فقط متغیرهای A و B را ذخیره می‌کند.
    • می‌توانید از -v7.3 برای ذخیره فایل‌های MAT بسیار بزرگ (بیشتر از 2GB) استفاده کنید.
    • -ascii: برای ذخیره داده‌های عددی به صورت فایل متنی (که کمتر توصیه می‌شود).
  • load(filename, variables): برای بارگذاری متغیرها از فایل MAT به فضای کاری.
    • load('mydata.mat'): تمام متغیرها را بارگذاری می‌کند.
    • load('mydata.mat', 'A'): فقط متغیر A را بارگذاری می‌کند.
    • S = load('mydata.mat'): متغیرها را به عنوان فیلدهای یک ساختار S بارگذاری می‌کند، که برای جلوگیری از آلودگی فضای کاری مفید است.

% مثال save و load
myMatrix = rand(100, 50);
myString = 'سلام از متلب!';
myStruct.field1 = 123;
myStruct.field2 = [4 5 6];

save('session_data.mat', 'myMatrix', 'myString', 'myStruct');
disp('متغیرها در session_data.mat ذخیره شدند.');

% پاک کردن متغیرها از فضای کاری
clear myMatrix myString myStruct;

% بارگذاری مجدد متغیرها
load('session_data.mat');
disp('متغیرها از session_data.mat بارگذاری شدند.');
disp(myMatrix(1,1));
disp(myString);
disp(myStruct.field1);

% بارگذاری به عنوان یک ساختار
loaded_data = load('session_data.mat');
disp('متغیرها به عنوان یک ساختار بارگذاری شدند:');
disp(loaded_data.myMatrix(1,1));

delete('session_data.mat');

فایل‌های Excel (.xls, .xlsx)

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

  • readtable(filename, 'FileType', 'spreadsheet', Name, Value): خواندن داده‌ها از فایل Excel به یک table. می‌توانید Sheet و Range را مشخص کنید.
  • writetable(T, filename, 'FileType', 'spreadsheet', Name, Value): نوشتن یک table به فایل Excel.
  • توابع قدیمی: xlsread و xlswrite هنوز وجود دارند اما برای کارهای جدیدتر readtable/writetable توصیه می‌شوند.

% مثال با writetable و readtable برای Excel
data_table = table({'Apple'; 'Banana'; 'Cherry'}, [1.2; 0.7; 2.1], [100; 150; 80], ...
                    'VariableNames', {'Fruit', 'Weight_kg', 'Quantity'});

excel_filename = 'fruit_inventory.xlsx';
writetable(data_table, excel_filename, 'Sheet', 'InventoryData', 'Range', 'A1');
disp(['جدول به فایل Excel: ' excel_filename ' نوشته شد.']);

% خواندن از فایل Excel
read_excel_table = readtable(excel_filename, 'Sheet', 'InventoryData');
disp('جدول خوانده شده از Excel:');
disp(read_excel_table);

% خواندن یک محدوده خاص
read_specific_range = readtable(excel_filename, 'Sheet', 'InventoryData', 'Range', 'B2:C4', ...
                                'ReadVariableNames', false); % اگر محدوده شامل هدر نباشد
disp('محدوده خاص خوانده شده از Excel:');
disp(read_specific_range);

delete(excel_filename);

فایل‌های تصویری

متلب به طور گسترده‌ای در پردازش تصویر استفاده می‌شود و توابع قدرتمندی برای خواندن و نوشتن فرمت‌های مختلف تصویر دارد.

  • A = imread(filename): خواندن یک تصویر از فایل. متلب به طور خودکار فرمت فایل (مانند JPEG, PNG, TIFF, BMP, GIF) را تشخیص می‌دهد.
  • imwrite(A, filename): نوشتن یک ماتریس یا آرایه متلب به عنوان فایل تصویری.

% مثال imread و imwrite
% ایجاد یک تصویر ساده (مثلاً یک شیب رنگی)
img = zeros(100, 100, 3, 'uint8'); % یک تصویر 100x100 RGB
for i = 1:100
    img(i, :, 1) = i*2.55; % کانال قرمز
    img(:, i, 2) = i*2.55; % کانال سبز
end
img(:,:,3) = 128; % کانال آبی ثابت

% ذخیره تصویر
output_image_filename = 'gradient_image.png';
imwrite(img, output_image_filename);
disp(['تصویر به ' output_image_filename ' ذخیره شد.']);

% خواندن تصویر
read_img = imread(output_image_filename);
disp('تصویر خوانده شد. ابعاد:');
disp(size(read_img));

% (برای نمایش تصویر نیاز به Image Processing Toolbox است)
% imshow(read_img);
% title('تصویر خوانده شده');

delete(output_image_filename);

فایل‌های صوتی

برای کار با داده‌های صوتی، متلب توابع اختصاصی برای خواندن و نوشتن فایل‌های صوتی ارائه می‌دهد.

  • [y, Fs] = audioread(filename): خواندن داده‌های صوتی (y) و نرخ نمونه‌برداری (Fs) از فایل.
  • audiowrite(filename, y, Fs): نوشتن داده‌های صوتی (y) با نرخ نمونه‌برداری (Fs) به یک فایل.

% مثال audioread و audiowrite
Fs = 44100; % نرخ نمونه‌برداری
duration = 1; % مدت زمان 1 ثانیه
t = 0:1/Fs:duration-1/Fs;
frequency = 1000; % فرکانس 1000 هرتز
y = 0.5 * sin(2*pi*frequency*t); % تولید یک موج سینوسی

audio_filename = 'sine_wave.wav';
audiowrite(audio_filename, y, Fs);
disp(['فایل صوتی به ' audio_filename ' ذخیره شد.']);

% خواندن فایل صوتی
[read_y, read_Fs] = audioread(audio_filename);
disp(['فایل صوتی خوانده شد. نرخ نمونه‌برداری: ' num2str(read_Fs)]);
disp(['ابعاد داده‌های صوتی: ' num2str(size(read_y))]);

% (برای پخش صدا نیاز به Audio Toolbox است)
% sound(read_y, read_Fs);

delete(audio_filename);

XML و JSON

برای تبادل داده‌ها در وب و برنامه‌های کاربردی، فرمت‌های XML و JSON بسیار رایج هستند. متلب توابعی برای تجزیه و تولید این فرمت‌ها ارائه می‌دهد.

  • XML:
    • xmlread(filename): خواندن فایل XML به یک شیء DOM.
    • xmlwrite(filename, domNode): نوشتن شیء DOM به یک فایل XML.
  • JSON:
    • S = jsondecode(jsonText): تجزیه رشته JSON به یک ساختار یا آرایه سلولی متلب.
    • jsonText = jsonencode(S): تبدیل ساختار یا آرایه متلب به رشته JSON.

% مثال JSON
data_struct.name = 'John Doe';
data_struct.age = 30;
data_struct.isStudent = false;
data_struct.courses = {'Math', 'Physics', 'Chemistry'};

json_string = jsonencode(data_struct);
disp('رشته JSON تولید شده:');
disp(json_string);

% نوشتن به فایل JSON
json_filename = 'data.json';
fileID = fopen(json_filename, 'wt');
fprintf(fileID, '%s', json_string);
fclose(fileID);
disp(['داده‌ها به ' json_filename ' ذخیره شد.']);

% خواندن از فایل JSON و تجزیه
read_json_text = fileread(json_filename); % خواندن تمام محتویات فایل به یک رشته
read_data_struct = jsondecode(read_json_text);
disp('ساختار خوانده شده از JSON:');
disp(read_data_struct);

delete(json_filename);

HDF5 و NetCDF

برای مدیریت داده‌های علمی بزرگ و پیچیده، فرمت‌های HDF5 (Hierarchical Data Format 5) و NetCDF (Network Common Data Form) بسیار مناسب هستند. متلب پشتیبانی کاملی از این فرمت‌ها ارائه می‌دهد که به شما امکان می‌دهد داده‌ها را به صورت ساختاریافته و کارآمد ذخیره و بازیابی کنید.

  • HDF5: توابعی مانند h5read، h5write، h5info.
  • NetCDF: توابعی مانند netcdf.open، netcdf.getVar، netcdf.putVar.

کار با HDF5 و NetCDF معمولاً شامل تعریف دیتاست‌ها، گروه‌ها، و attributeها در یک ساختار درختی است و برای داده‌های با ابعاد بالا و متادیتاهای غنی ایده‌آل است. به دلیل پیچیدگی، مثال‌های جامع برای این فرمت‌ها نیازمند فضای بیشتری هستند، اما توابع متلب رابط کاربری مشابهی با توابع I/O دیگر ارائه می‌دهند.


% مثال ساده HDF5
h5_filename = 'my_h5_data.h5';
dataset_name = '/my_group/my_dataset';

% داده‌های نمونه
data_to_save = rand(10, 20);

% نوشتن داده به فایل HDF5
h5write(h5_filename, dataset_name, data_to_save);
disp(['داده به ' h5_filename ' ذخیره شد.']);

% خواندن داده از فایل HDF5
read_h5_data = h5read(h5_filename, dataset_name);
disp('داده خوانده شده از HDF5:');
disp(read_h5_data(1:2, 1:2)); % نمایش چند مقدار اول

% مشاهده اطلاعات فایل HDF5
h5_info = h5info(h5_filename);
disp('اطلاعات HDF5:');
disp(h5_info);

delete(h5_filename);

مدیریت پیشرفته فایل و دایرکتوری در متلب

علاوه بر خواندن و نوشتن محتوای فایل‌ها، متلب توابع متعددی برای مدیریت خود فایل‌ها و دایرکتوری‌ها در سیستم فایل ارائه می‌دهد. این قابلیت‌ها برای سازماندهی داده‌ها، ایجاد ساختارهای پروژه و خودکارسازی وظایف مدیریت فایل ضروری هستند.

بررسی وجود فایل و پوشه: exist, isfile, isdir

قبل از انجام عملیات روی یک فایل یا پوشه، اغلب لازم است که از وجود آن مطمئن شوید:

  • status = exist(name, type): یک تابع عمومی برای بررسی وجود متغیر، تابع، فایل، یا پوشه.
    • exist('filename', 'file'): بررسی وجود فایل (شامل اسکریپت‌ها و توابع).
    • exist('directoryname', 'dir'): بررسی وجود پوشه.
  • tf = isfile(filename): (از R2017a به بعد) بررسی می‌کند که آیا filename یک فایل است یا خیر. خروجی منطقی (true/false).
  • tf = isdir(directoryname): (از R2017a به بعد) بررسی می‌کند که آیا directoryname یک پوشه است یا خیر. خروجی منطقی.

filename = 'test_file.txt';
dirname = 'test_dir';

% ایجاد یک فایل و یک پوشه برای آزمایش
fileID = fopen(filename, 'wt'); fprintf(fileID, 'Hello'); fclose(fileID);
mkdir(dirname);

disp(['آیا ' filename ' یک فایل است؟ ' num2str(isfile(filename))]);
disp(['آیا ' dirname ' یک پوشه است؟ ' num2str(isdir(dirname))]);
disp(['آیا ' filename ' با exist یافت می‌شود؟ ' num2str(exist(filename, 'file'))]);
disp(['آیا ' dirname ' با exist یافت می‌شود؟ ' num2str(exist(dirname, 'dir'))]);

% بررسی یک فایل یا پوشه ناموجود
disp(['آیا ' 'non_existent.txt' ' یک فایل است؟ ' num2str(isfile('non_existent.txt'))]);

% پاکسازی
delete(filename);
rmdir(dirname);

لیست کردن محتوای پوشه: dir

تابع dir محتوای یک پوشه را لیست می‌کند. خروجی آن یک آرایه از ساختارها است که هر ساختار شامل اطلاعاتی مانند نام فایل، تاریخ اصلاح، حجم و وضعیت پوشه/فایل بودن است.


% ایجاد چند فایل و پوشه برای آزمایش
mkdir('temp_dir_list');
fileID = fopen('temp_dir_list/file1.txt', 'wt'); fprintf(fileID, '1'); fclose(fileID);
fileID = fopen('temp_dir_list/file2.m', 'wt'); fprintf(fileID, '2'); fclose(fileID);
mkdir('temp_dir_list/sub_dir');

% لیست کردن محتوای پوشه
dir_info = dir('temp_dir_list');

disp('اطلاعات دایرکتوری:');
for i = 1:length(dir_info)
    entry = dir_info(i);
    if ~strcmp(entry.name, '.') && ~strcmp(entry.name, '..') % نادیده گرفتن . و ..
        if entry.isdir
            disp(['[پوشه] ' entry.name]);
        else
            disp(['[فایل] ' entry.name ' (حجم: ' num2str(entry.bytes) ' بایت)']);
        end
    end
end

% پاکسازی
rmdir('temp_dir_list', 's'); % حذف پوشه و تمام محتویات آن

ایجاد و حذف پوشه‌ها: mkdir, rmdir

  • status = mkdir(parentdir, dirname): ایجاد یک پوشه جدید.
    • mkdir('my_new_folder'): ایجاد در پوشه جاری.
    • mkdir('data', 'raw_data'): ایجاد raw_data درون پوشه data.
  • status = rmdir(dirname, 's'): حذف یک پوشه.
    • 's': (فقط از R2009b به بعد) حذف پوشه و تمام محتویات آن (Subdirectories و Files). با احتیاط استفاده شود!

% مثال mkdir و rmdir
new_dir = 'project_data/input_files';

if ~exist(new_dir, 'dir')
    mkdir(new_dir);
    disp(['پوشه ' new_dir ' ایجاد شد.']);
end

% ایجاد یک فایل در داخل پوشه
fileID = fopen(fullfile(new_dir, 'sample.txt'), 'wt');
fprintf(fileID, 'این یک فایل نمونه است.');
fclose(fileID);

% تلاش برای حذف پوشه بدون 's' اگر محتویات دارد
try
    rmdir('project_data');
catch ME
    disp(['خطا در حذف: ' ME.message]);
    disp('برای حذف پوشه‌های غیر خالی، از پرچم ''s'' استفاده کنید.');
end

% حذف با 's'
rmdir('project_data', 's');
disp(['پوشه ' 'project_data' ' و محتویات آن حذف شد.']);

تابع fullfile برای ساخت مسیرهای فایل به صورت cross-platform بسیار مفید است.

کپی، جابجایی و حذف فایل‌ها: copyfile, movefile, delete

  • status = copyfile(source, destination): کپی کردن یک فایل یا پوشه.
  • status = movefile(source, destination): جابجایی (انتقال) یک فایل یا پوشه.
  • status = delete(filename): حذف یک فایل. برای حذف چندین فایل از wildcard (*) استفاده کنید.

% مثال copyfile, movefile, delete
original_file = 'original.txt';
copied_file = 'copy.txt';
moved_file = 'moved_data/moved.txt';

% ایجاد فایل اصلی
fileID = fopen(original_file, 'wt');
fprintf(fileID, 'این فایل اصلی است.');
fclose(fileID);

% کپی فایل
copyfile(original_file, copied_file);
disp(['فایل ' original_file ' به ' copied_file ' کپی شد.']);

% ایجاد پوشه برای جابجایی
mkdir('moved_data');

% جابجایی فایل
movefile(copied_file, moved_file);
disp(['فایل ' copied_file ' به ' moved_file ' جابجا شد.']);

% حذف فایل اصلی
delete(original_file);
disp(['فایل ' original_file ' حذف شد.']);

% پاکسازی
delete(moved_file);
rmdir('moved_data');

کار با فایل‌های موقت: tempname, tempdir

برای عملیاتی که نیاز به فایل‌های میانی یا موقت دارند که پس از استفاده باید پاک شوند، متلب توابعی برای ایجاد مسیرهای موقت ارائه می‌دهد:

  • tempdir: مسیر پوشه موقت سیستم را برمی‌گرداند.
  • tempname: یک نام منحصر به فرد (ولی نه تضمینی) برای یک فایل موقت در پوشه tempdir برمی‌گرداند.

% مثال tempdir و tempname
temp_folder = tempdir;
temp_file_path = tempname; % یک مسیر کامل با نام فایل موقت

disp(['پوشه موقت: ' temp_folder]);
disp(['مسیر فایل موقت پیشنهادی: ' temp_file_path]);

% استفاده از فایل موقت
fileID = fopen(temp_file_path, 'wt');
fprintf(fileID, 'داده‌های موقت.');
fclose(fileID);
disp(['فایل موقت ایجاد شد: ' temp_file_path]);

% پس از اتمام کار، فایل موقت را حذف کنید
delete(temp_file_path);
disp(['فایل موقت حذف شد.']);

بهینه‌سازی، مدیریت خطا و نکات حرفه‌ای در I/O

برای ساختن برنامه‌های متلب قوی، کارآمد و قابل اعتماد که با فایل‌ها تعامل دارند، فراتر از شناخت توابع پایه، نیاز به درک اصول بهینه‌سازی، مدیریت خطا و رعایت بهترین شیوه‌ها است.

بهینه‌سازی عملکرد (Performance Optimization)

عملیات I/O (ورودی/خروجی) معمولاً کندتر از عملیات محاسباتی در حافظه هستند. بهینه‌سازی I/O می‌تواند تأثیر قابل توجهی بر عملکرد کلی برنامه شما داشته باشد.

خوانش و نگارش دسته‌ای (Batch I/O)

به جای خواندن یا نوشتن داده‌ها عنصر به عنصر یا خط به خط، سعی کنید تا حد امکان داده‌ها را به صورت دسته‌های بزرگتر (بلوک‌ها یا آرایه‌های کامل) بخوانید یا بنویسید. هر فراخوانی تابع I/O سربار (overhead) دارد، بنابراین کاهش تعداد فراخوانی‌ها سرعت را افزایش می‌دهد.


% مثال: خواندن دسته ای در مقابل خواندن خط به خط
% فرض کنید یک فایل بزرگ با 1 میلیون عدد داریم
large_filename = 'large_numbers.txt';
N_numbers = 1e6;
fileID = fopen(large_filename, 'wt');
fprintf(fileID, '%f\n', rand(N_numbers, 1));
fclose(fileID);

% روش خط به خط (کند)
tic;
fileID = fopen(large_filename, 'rt');
data_line_by_line = zeros(N_numbers, 1);
idx = 1;
while ~feof(fileID)
    line = fgetl(fileID);
    if ischar(line)
        data_line_by_line(idx) = str2double(line);
        idx = idx + 1;
    end
end
fclose(fileID);
time_line_by_line = toc;
disp(['زمان خواندن خط به خط: ' num2str(time_line_by_line) ' ثانیه']);


% روش دسته ای با textscan (سریع)
tic;
fileID = fopen(large_filename, 'rt');
data_batch = textscan(fileID, '%f');
fclose(fileID);
data_batch = data_batch{1};
time_batch = toc;
disp(['زمان خواندن دسته ای با textscan: ' num2str(time_batch) ' ثانیه']);

delete(large_filename);
% مشاهده تفاوت زمانی: روش دسته ای به مراتب سریعتر است.

پیش‌تخصیص حافظه (Pre-allocation)

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


% مثال: پیش تخصیص برای جمع آوری داده های خوانده شده
num_records = 1000;
data_list = cell(num_records, 1); % پیش تخصیص برای ذخیره رشته ها

% فرض کنید از fgetl برای خواندن خطوط استفاده می‌کنید
for i = 1:num_records
    data_list{i} = ['Record ' num2str(i)]; % فقط برای مثال
end
% بدون پیش تخصیص: data_list = {}; data_list{i} = ... بسیار کندتر خواهد بود

کش کردن (Caching) داده‌های پرکاربرد

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

نقشه‌برداری حافظه‌ای (Memory-Mapping) با memmapfile

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

این روش برای دسترسی تصادفی (random access) به بخش‌هایی از فایل‌های بزرگ بسیار کارآمد است.


% مثال: memmapfile (نیاز به فایل باینری واقعی)
% ابتدا یک فایل باینری بزرگ ایجاد می‌کنیم
memmap_filename = 'large_binary_data.bin';
big_data_size = 1e8; % 100 میلیون double (800MB)
data_to_write_large = rand(big_data_size, 1, 'double');

fileID = fopen(memmap_filename, 'wb');
fwrite(fileID, data_to_write_large, 'double');
fclose(fileID);

% ایجاد یک شیء memmapfile
% 'Format' نوع داده در فایل را مشخص می‌کند
% 'Writable', true برای امکان نوشتن در فایل از طریق نقشه حافظه
m = memmapfile(memmap_filename, 'Format', 'double', 'Writable', true);

% دسترسی به داده‌ها مانند یک آرایه
first_10_elements = m.Data(1:10);
disp('10 عنصر اول از طریق memory-map:');
disp(first_10_elements');

% تغییر یک عنصر (اگر Writable=true باشد)
m.Data(1000) = 999.99;
disp(['عنصر 1000 تغییر یافت به: ' num2str(m.Data(1000))]);

% دسترسی تصادفی به یک عنصر در میانه فایل
random_element = m.Data(ceil(big_data_size / 2));
disp(['عنصر تصادفی در میانه فایل: ' num2str(random_element)]);

% آزاد کردن منابع
clear m;
delete(memmap_filename);

مدیریت خطا (Error Handling)

عملیات I/O مستعد خطا هستند (مانند عدم وجود فایل، عدم مجوز دسترسی، خطاهای دیسک). مدیریت خطای قوی برای ایجاد برنامه‌های پایدار حیاتی است.

بلوک‌های try-catch

از بلوک‌های try-catch-end برای محصور کردن عملیات I/O که ممکن است با شکست مواجه شوند، استفاده کنید.


filename = 'non_existent_file.txt';
try
    fileID = fopen(filename, 'r');
    if fileID == -1
        error('MyError:FileOpen', 'فایل ''%s'' برای خواندن باز نشد.', filename);
    end
    % عملیات خواندن
    data = fread(fileID, Inf, 'char');
    fclose(fileID);
    disp('فایل با موفقیت خوانده شد.');
catch ME
    switch ME.identifier
        case 'MyError:FileOpen'
            disp(['خطا: ' ME.message]);
        case 'MATLAB:FileIO:InvalidFid'
            disp('خطا: شناسه فایل نامعتبر است.');
        otherwise
            disp(['خطای غیرمنتظره: ' ME.message]);
    end
end

onCleanup: تضمین بسته شدن منابع

حتی با try-catch، ممکن است در شرایط خاصی (مانند فشردن Ctrl+C) فایل‌ها باز بمانند. onCleanup یک شیء است که وقتی از محدوده (scope) خارج می‌شود یا اسکریپت/تابع خاتمه می‌یابد (چه با خطا و چه بدون خطا)، یک تابع مشخص را اجرا می‌کند. این برای تضمین بسته شدن فایل‌ها بسیار مفید است.


filename = 'test_cleanup.txt';
fileID = -1; % مقداردهی اولیه برای مدیریت حالت خطا

try
    fileID = fopen(filename, 'wt');
    if fileID == -1
        error('Demo:FileOpenError', 'Could not open file %s', filename);
    end

    % ایجاد یک شیء onCleanup
    cleanupObj = onCleanup(@()fclose(fileID));

    fprintf(fileID, 'این فایل برای آزمایش onCleanup است.\n');
    disp('خط اول نوشته شد.');

    % فرض کنید خطایی در اینجا رخ می‌دهد
    error('Demo:SimulatedError', 'شبیه‌سازی یک خطا پس از نوشتن.');

    fprintf(fileID, 'این خط هرگز نوشته نمی‌شود.');

catch ME
    disp(['خطا رخ داد: ' ME.message]);
end

% در اینجا، حتی اگر خطا رخ دهد، fclose(fileID) فراخوانی می‌شود
% چون cleanupObj از محدوده خارج می‌شود.
% اگر fileID == -1 باشد، fclose(fileID) به طور خودکار خطا نمی‌دهد.

if isfile(filename)
    type(filename); % نمایش محتویات فایل
    delete(filename);
end

مقابله با فایل‌های بزرگ (Large Files)

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

  • خواندن/نوشتن تکه‌تکه (Chunking): به جای تلاش برای خواندن کل فایل در یک مرحله، آن را به تکه‌های کوچکتر تقسیم کرده و هر تکه را به صورت جداگانه پردازش کنید. این روش اغلب در حلقه‌ها با استفاده از fseek و fread/fwrite انجام می‌شود.
  • پردازش جریان داده (Streaming): داده‌ها را به محض در دسترس قرار گرفتن پردازش کنید و نتایج را به صورت متوالی ذخیره کنید، به جای اینکه منتظر بمانید تا کل ورودی در دسترس باشد.
  • datastore و tall arrays: (نیاز به Big Data Toolbox) متلب ابزارهای پیشرفته‌ای مانند datastore و tall arrays را برای کار با مجموعه داده‌های بسیار بزرگ که در حافظه جا نمی‌شوند، ارائه می‌دهد. این ابزارها امکان پردازش موازی و توزیع شده را فراهم می‌کنند.

امنیت و پایداری

  • اعتبارسنجی مسیرها و نام فایل‌ها: همیشه مسیرهای فایل ورودی از کاربر یا منابع خارجی را اعتبارسنجی کنید تا از حملات تزریق مسیر (path traversal) یا دسترسی به فایل‌های غیرمجاز جلوگیری شود. از fullfile برای ساخت مسیرها و isvarname برای اعتبارسنجی نام متغیرها استفاده کنید.
  • مدیریت مجوزهای دسترسی: اطمینان حاصل کنید که برنامه شما دارای مجوزهای لازم برای خواندن/نوشتن فایل‌ها در مکان‌های مورد نظر است. خطاهای مجوز می‌توانند از طریق try-catch و بررسی پیام‌های خطا مدیریت شوند.

نظم و قابلیت نگهداری

  • استفاده از توابع کمکی: برای عملیات I/O پیچیده و تکراری، توابع کمکی (helper functions) ایجاد کنید. این کار باعث خوانایی بیشتر کد و امکان استفاده مجدد می‌شود.
  • مستندسازی فرمت فایل‌های سفارشی: اگر فرمت فایل باینری یا متنی سفارشی را ایجاد می‌کنید، آن را به دقت مستند کنید تا دیگران (و خود شما در آینده) بتوانند آن را بخوانند و درک کنند.

نتیجه‌گیری

مدیریت ورودی و خروجی فایل در متلب یک مهارت اساسی برای هر کسی است که با تحلیل داده‌ها، شبیه‌سازی‌ها و توسعه ابزارهای محاسباتی سروکار دارد. در این پست، ما به تفصیل انواع مختلف عملیات I/O، از خواندن و نوشتن فایل‌های متنی و باینری گرفته تا کار با فرمت‌های رایج مانند MAT-Files، Excel، تصاویر و داده‌های علمی، پرداختیم.

شما با توابع کلیدی مانند fopen، fclose، fprintf، textscan، fread، fwrite آشنا شدید و همچنین قابلیت‌های پیشرفته‌تر متلب مانند readtable، writetable، imread، imwrite، jsonencode و h5write را بررسی کردید. فراتر از این توابع، ما بر اهمیت مدیریت قوی فایل و دایرکتوری با استفاده از mkdir، dir، copyfile و delete و همچنین بهینه‌سازی عملکرد با تکنیک‌هایی مانند Batch I/O و memmapfile تأکید کردیم.

نکات مربوط به مدیریت خطا با try-catch و onCleanup نیز ارائه شد تا برنامه‌های شما در مواجهه با شرایط غیرمنتظره مقاوم باشند. با تسلط بر این مفاهیم و توابع، شما قادر خواهید بود تا راه‌حل‌های داده‌ای پیچیده و کارآمدی را در متلب پیاده‌سازی کنید و به طور موثر با دنیای وسیع داده‌های خارجی تعامل داشته باشید.

به یاد داشته باشید که انتخاب تابع I/O مناسب به ماهیت داده‌ها و فرمت فایل شما بستگی دارد. برای سادگی، توابع مدرن مانند readtable و writetable اغلب بهترین گزینه هستند، در حالی که برای کنترل دقیق‌تر و کارایی بالا با داده‌های باینری، fread و fwrite ضروری‌اند. همواره کد خود را تست کنید و در مواجهه با فایل‌های بزرگ یا پیچیده، از تکنیک‌های بهینه‌سازی و مدیریت خطا استفاده کنید.

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

“تسلط به برنامه‌نویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”

قیمت اصلی 2.290.000 ریال بود.قیمت فعلی 1.590.000 ریال است.

"تسلط به برنامه‌نویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"

"با شرکت در این دوره جامع و کاربردی، به راحتی مهارت‌های برنامه‌نویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر می‌سازد تا به سرعت الگوریتم‌های پیچیده را درک کرده و اپلیکیشن‌های هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفه‌ای و امکان دانلود و تماشای آنلاین."

ویژگی‌های کلیدی:

بدون نیاز به تجربه قبلی برنامه‌نویسی

زیرنویس فارسی با ترجمه حرفه‌ای

۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان