مقدمه
در درس قبل، با نحوهی مدیریت فایلها و پوشهها با استفاده از کلاسهای فضای نام System.IO آشنا شدیم. اما آن کلاسها تنها ساختار سیستم فایل را مدیریت
میکردند. برای کار با محتوای یک فایل، .NET از یک مفهوم انتزاعی و
قدرتمند به نام استریم (Stream) استفاده میکند. یک استریم، دنبالهای از بایتها
را نشان میدهد که میتوان از آن خواند، در آن نوشت، یا هر دو کار را انجام داد. زیبایی استریمها
در این است که منبع اصلی دادهها را از دید ما پنهان میکنند. شما با یک API یکسان
میتوانید با یک فایل روی دیسک، یک جریان داده از شبکه، یا حتی یک بلوک حافظه کار کنید. در این درس،
با کلاس پایهی Stream و کلاسهای کمکی StreamReader و StreamWriter برای کار با دادههای
متنی آشنا خواهیم شد.
System.IO.Stream چیست؟
کلاس System.IO.Stream یک کلاس انتزاعی (abstract) است که به عنوان
کلاس پایه برای تمام انواع استریم در .NET عمل میکند. این کلاس یک نمای استاندارد از یک
دنبالهی بایت را فراهم کرده و متدهای پایهای مانند Read، Write، Seek (برای جابجایی در
استریم) و Close را تعریف میکند.
برخی از پیادهسازیهای مشخص این کلاس عبارتند از:
- FileStream: برای خواندن و نوشتن در یک فایل فیزیکی روی دیسک.
- MemoryStream: برای کار با دادهها مستقیماً در حافظهی RAM.
- NetworkStream: برای ارسال و دریافت داده از طریق یک سوکت شبکه.
کار مستقیم با بایتها میتواند پیچیده باشد، به خصوص زمانی که با دادههای متنی سروکار داریم. برای
سادهسازی این فرآیند، .NET کلاسهای کمکی (helper classes) را فراهم کرده که بر روی یک
استریم پایه قرار گرفته و API سطح بالاتری را برای کار با انواع دادهی مشخص ارائه
میدهند.
خواندن فایلهای متنی با StreamReader
کلاس StreamReader برای خواندن کاراکترها از یک استریم طراحی شده است.
این کلاس مسئولیت پیچیدهی تبدیل بایتها به کاراکترها را بر اساس یک انکدینگ مشخص (مانند
UTF-8) بر عهده میگیرد.
از آنجایی که StreamReader با منابع مدیریتنشده کار میکند، این کلاس اینترفیس
IDisposable را پیادهسازی میکند و ما باید همیشه آن را در یک بلوک using قرار دهیم تا از بسته شدن صحیح فایل و آزاد شدن منابع اطمینان
حاصل کنیم.
Program.cs
using System.IO;
File.WriteAllText("mydata.txt", "Line 1\nLine 2\nLine 3");
using (StreamReader reader = new StreamReader("mydata.txt"))
{
string line;
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
در این مثال، ما یک StreamReader برای فایل mydata.txt ایجاد میکنیم. سپس در یک حلقهی
while، با استفاده از متد ReadLine() فایل را خط به خط میخوانیم تا به انتهای آن برسیم (در
این حالت ReadLine مقدار null را برمیگرداند). بلوک using تضمین میکند که پس از اتمام کار،
متد Dispose خواننده فراخوانی شده و فایل بسته میشود.
نوشتن در فایلهای متنی با StreamWriter
به طور مشابه، کلاس StreamWriter برای نوشتن کاراکترها در یک استریم
به کار میرود. این کلاس نیز مسئولیت تبدیل کاراکترها به بایتها را بر عهده دارد و باید در یک بلوک
using استفاده شود.
Program.cs
using (StreamWriter writer = new StreamWriter("log.txt"))
{
writer.WriteLine("Log started at: " + DateTime.Now);
writer.WriteLine("This is the first log entry.");
writer.Write("This is a");
writer.Write(" partial entry.");
}
Console.WriteLine("\nlog.txt has been created.");
سازندهی StreamWriter به طور پیشفرض، اگر فایل وجود داشته باشد، محتوای آن را پاک کرده و از اول
مینویسد. اگر بخواهید به انتهای یک فایل موجود اضافه کنید (append)، باید یک پارامتر دوم با مقدار
true را به سازنده ارسال کنید:
using (StreamWriter writer = new StreamWriter("log.txt", append: true))
{
writer.WriteLine("This is an appended log entry.");
}
متدهای کمکی در کلاس File
برای کارهای ساده و سریع، کلاس استاتیک File متدهایی را فراهم میکند که تمام مراحل ایجاد استریم،
خواندن/نوشتن و بستن آن را در یک فراخوانی واحد انجام میدهند. این متدها برای کار با فایلهای کوچک
بسیار راحت هستند.
- File.ReadAllText(path): کل محتوای یک فایل متنی را خوانده و به
صورت یک رشتهی واحد برمیگرداند.
- File.WriteAllText(path, content): یک رشته را در یک فایل
مینویسد (و اگر فایل وجود داشته باشد، آن را بازنویسی میکند).
- File.ReadAllLines(path): تمام خطوط یک فایل متنی را خوانده و
آنها را به صورت یک آرایه از رشتهها برمیگرداند.
- File.WriteAllLines(path, lines): یک کالکشن از رشتهها را در یک
فایل مینویسد و هر عنصر را در یک خط جداگانه قرار میدهد.
هرچند این متدها بسیار راحت هستند، اما برای فایلهای بسیار بزرگ مناسب نیستند، زیرا کل محتوای فایل
را یکجا در حافظه بارگذاری میکنند. برای فایلهای بزرگ، استفاده از StreamReader و خواندن خط به
خط یا تکهتکه، رویکرد بسیار بهینهتری از نظر مصرف حافظه است.