فصل ۱۵: Stream Adapterها، Encoding، Compression، ZIP و Tar

فصل ۱۵: Stream Adapterها، Encoding، Compression، ZIP و Tar

فصل ۱۵: Stream Adapterها، Encoding، Compression، ZIP و Tar

شکل 15-4 — Decorator Streamهانمودار Decorator Streamهای .NET و نحوهٔ Wrap شدن Stream زیربنایی.
شکل 15-4 — Decorator Streamها

نام Typeهای .NET در شکل مطابق منبع اصلی حفظ شده‌اند.

BufferedStream و Stream Adapterها

Buffering با کم‌کردن تعداد Round-tripها به Backing Store، Performance را بهتر می‌کند. در مثال زیر یک FileStream را داخل BufferedStream با Buffer بیست KB Wrap می‌کنیم:

// Write 100K to a file:
File.WriteAllBytes ("myFile.bin", new byte [100000]);
using FileStream fs = File.OpenRead ("myFile.bin");
using BufferedStream bs = new BufferedStream (fs, 20000);  //20K buffer
bs.ReadByte();
Console.WriteLine (fs.Position);         // 20000

در این مثال، به‌دلیل Read-ahead Buffering، Stream زیربنایی پس از Read کردن فقط یک Byte، بیست‌هزار Byte جلو می‌رود. می‌توانستیم ReadByte را 19,999 بار دیگر Call کنیم تا FileStream دوباره مورد مراجعه قرار بگیرد.

وصل‌کردن BufferedStream به FileStream مانند این مثال ارزش محدودی دارد، چون FileStream خودش Buffering داخلی دارد. شاید تنها کاربردش بزرگ‌کردن Buffer روی FileStream از قبل ساخته‌شده باشد.

Close کردن BufferedStream به‌طور خودکار Backing Store Stream زیربنایی را نیز Close می‌کند.

Stream Adapterها

یک Stream فقط با Byteها سروکار دارد. برای Read یا Write کردن Data Typeهایی مثل String، Integer یا XML Element باید Adapter وصل کنید. .NET موارد زیر را ارائه می‌دهد:

Text Adapterها ــ برای String و Character Data
TextReader، TextWriter
StreamReader، StreamWriter
StringReader، StringWriter
Binary Adapterها ــ برای Typeهای Primitive مانند int، bool، string و float
BinaryReader، BinaryWriter

XML Adapterها که در فصل 11 بررسی شدند عبارت‌اند از XmlReader و XmlWriter. شکل 15-5 Relation بین این Typeها را نشان می‌دهد.

شکل 15-5 — Readerها و Writerهانمودار Relation میان Reader/Writerهای Text، Binary و XML و Stream زیربنایی.
شکل 15-5 — Readerها و Writerها

نام Classها و Typeهای .NET مطابق منبع اصلی حفظ شده‌اند.

Text Adapterها

TextReader و TextWriter Base Classهای انتزاعی Adapterهایی هستند که فقط با Character و String سروکار دارند. هرکدام در .NET دو Implementation عمومی دارند:

StreamReader / StreamWriter
از Stream به‌عنوان Raw Data Store استفاده می‌کنند و Byteهای Stream را به Character یا String ترجمه می‌کنند.
StringReader / StringWriter
TextReader/TextWriter را با Stringهای داخل Memory پیاده‌سازی می‌کنند.

جدول 15-2 Memberهای TextReader را برحسب Category فهرست می‌کند. Peek Character بعدی Stream را بدون جلو بردن Position برمی‌گرداند. هم Peek و هم نسخهٔ بدون Argument از Read اگر در End Stream باشند −1 برمی‌گردانند؛ در غیر این صورت Integerی می‌دهند که می‌توان مستقیم به char Cast کرد. Overload مربوط به Read که Buffer از نوع char[] می‌پذیرد از نظر Function با ReadBlock یکسان است. ReadLine تا رسیدن به CR ــ Character 13 ــ یا LF ــ Character 10 ــ یا Pair پشت‌سرهم CR+LF می‌خواند و سپس String را بدون Characterهای CR/LF برمی‌گرداند.

جدول 15-2 — Memberهای TextReader
CategoryMemberها
Reading one charpublic virtual int Peek(); // Cast the result to a char
public virtual int Read(); // Cast the result to a char
Reading many charspublic virtual int Read (char[] buffer, int index, int count);
public virtual int ReadBlock (char[] buffer, int index, int count);
public virtual string ReadLine();
public virtual string ReadToEnd();
Closingpublic virtual void Close();
public void Dispose(); // Same as Close
Otherpublic static readonly TextReader Null;
public static TextReader Synchronized (TextReader reader);

TextWriter Methodهای متناظر برای Writing دارد، همان‌طور که در جدول 15-3 نشان داده شده است. Methodهای Write و WriteLine Overloadهایی دارند که همهٔ Primitive Typeها و همچنین object را می‌پذیرند. این Methodها به‌سادگی ToString را روی چیزی که به آن‌ها داده می‌شود Call می‌کنند؛ به‌صورت اختیاری از IFormatProvider مشخص‌شده هنگام Call یا Construct کردن TextWriter استفاده می‌شود.

جدول 15-3 — Memberهای TextWriter، بخش نخست
CategoryMemberها
Writing one charpublic virtual void Write (char value);
Writing many charspublic virtual void Write (string value);
public virtual void Write (char[] buffer, int index, int count);
public virtual void Write (string format, params object[] arg);
public virtual void WriteLine (string value);
ادامهٔ جدول 15-3 — Memberهای TextWriter
CategoryMemberها
Closing and flushingpublic virtual void Close();
public void Dispose(); // Same as Close
public virtual void Flush();
Formatting and encodingpublic virtual IFormatProvider FormatProvider { get; }
public virtual string NewLine { get; set; }
public abstract Encoding Encoding { get; }
Otherpublic static readonly TextWriter Null;
public static TextWriter Synchronized (TextWriter writer);

WriteLine فقط Text داده‌شده را با Environment.NewLine Append می‌کند. می‌توانید با Property با نام NewLine این مقدار را تغییر دهید؛ برای Interoperability با Unix File Formatها می‌تواند مفید باشد.

StreamReader و StreamWriter

در مثال زیر StreamWriter دو Line Text را داخل File می‌نویسد و سپس StreamReader File را دوباره می‌خواند:

using (FileStream fs = File.Create ("test.txt"))
using (TextWriter writer = new StreamWriter (fs))
{
  writer.WriteLine ("Line1");
  writer.WriteLine ("Line2");
}
using (FileStream fs = File.OpenRead ("test.txt"))
using (TextReader reader = new StreamReader (fs))
{
  Console.WriteLine (reader.ReadLine());       // Line1
  Console.WriteLine (reader.ReadLine());       // Line2
}

چون Text Adapterها اغلب با File ترکیب می‌شوند، Class با نام File Methodهای استاتیک CreateText، AppendText و OpenText را برای Shortcut کردن Process ارائه می‌دهد:

using (TextWriter writer = File.CreateText ("test.txt"))
{
  writer.WriteLine ("Line1");
  writer.WriteLine ("Line2");
}
using (TextWriter writer = File.AppendText ("test.txt"))
  writer.WriteLine ("Line3");
using (TextReader reader = File.OpenText ("test.txt"))
  while (reader.Peek() > -1)
    Console.WriteLine (reader.ReadLine());     // Line1
                                               // Line2
                                               // Line3

این مثال همچنین روش Test کردن End File را با reader.Peek() نشان می‌دهد. Option دیگر این است که تا زمانی بخوانید که reader.ReadLine مقدار null برگرداند.

می‌توانید Typeهای دیگر مانند Integerها را نیز Read و Write کنید؛ اما چون TextWriter روی Type شما ToString را Invoke می‌کند، هنگام Read کردن باید String را Parse کنید:

using (TextWriter w = File.CreateText ("data.txt"))
{
  w.WriteLine (123);          // Writes "123"
  w.WriteLine (true);         // Writes the word "true"
}
using (TextReader r = File.OpenText ("data.txt"))
{
  int myInt = int.Parse (r.ReadLine());     // myInt == 123
  bool yes = bool.Parse (r.ReadLine());     // yes == true
}

Character Encodingها

TextReader و TextWriter به‌تنهایی فقط Class انتزاعی‌اند و Connectionی به Stream یا Backing Store ندارند. اما StreamReader و StreamWriter به Stream زیربناییِ Byte-oriented متصل‌اند، پس باید بین Character و Byte تبدیل انجام دهند. این کار را از طریق Class با نام Encoding در Namespace با نام System.Text انجام می‌دهند که هنگام Construct کردن StreamReader یا StreamWriter انتخاب می‌کنید. اگر چیزی انتخاب نکنید، Encoding پیش‌فرض UTF-8 استفاده می‌شود.

ساده‌ترین Encoding، ASCII است، چون هر Character با یک Byte نمایش داده می‌شود. ASCII نخستین 127 Character مجموعهٔ Unicode را در یک Byte Map می‌کند و چیزهایی را پوشش می‌دهد که روی Keyboard سبک US می‌بینید. بیشتر Characterهای دیگر، از جمله Symbolهای تخصصی و Characterهای غیرانگلیسی، قابل نمایش نیستند و به Character □ تبدیل می‌شوند.

Encoding پیش‌فرض UTF-8 می‌تواند همهٔ Characterهای Unicode تخصیص‌یافته را Map کند، اما پیچیده‌تر است. 127 Character نخست برای Compatibility با ASCII در یک Byte Encode می‌شوند؛ بقیه با تعداد متغیری Byte ــ اغلب دو یا سه ــ Encode می‌شوند. مثال:

using (TextWriter w = File.CreateText ("but.txt"))    // Use default UTF-8
  w.WriteLine ("but-");                               // encoding.
using (Stream s = File.OpenRead ("but.txt"))
  for (int b; (b = s.ReadByte()) > -1;)
    Console.WriteLine (b);

پس از Word با نام «but» به‌جای Hyphen استاندارد، Character بلندتر em dash یعنی (—) با Code Point برابر U+2014 آمده است؛ همان Dashی که Editor کتاب را ناراحت نمی‌کند! خروجی:

98     // b
117    // u
116    // t
226    // em dash byte 1       Note that the byte values
128    // em dash byte 2       are >= 128 for each part
148    // em dash byte 3       of the multibyte sequence.
13     // <CR>
10     // <LF>

چون em dash بیرون از نخستین 127 Character Unicode است، برای Encode شدن در UTF-8 به بیش از یک Byte نیاز دارد؛ در اینجا سه Byte. UTF-8 برای Alphabet غربی Efficient است چون بیشتر Characterهای رایج فقط یک Byte مصرف می‌کنند. همچنین با Ignore کردن همهٔ Byteهای بالاتر از 127 به‌آسانی به ASCII Downgrade می‌شود. عیب آن این است که Seeking در Stream دشوار است، چون Position Character با Position Byte در Stream متناظر نیست.

Alternative، UTF-16 است که در Class با نام Encoding فقط با Label «Unicode» نشان داده می‌شود. همان String را با UTF-16 چنین می‌نویسیم:

using (Stream s = File.Create ("but.txt"))
using (TextWriter w = new StreamWriter (s, Encoding.Unicode))
  w.WriteLine ("but-");
foreach (byte b in File.ReadAllBytes ("but.txt"))
  Console.WriteLine (b);

خروجی:

255    // Byte-order mark 1
254    // Byte-order mark 2
98     // 'b' byte 1
0      // 'b' byte 2
117    // 'u' byte 1
0      // 'u' byte 2
116    // 't' byte 1
0      // 't' byte 2
20     // '--' byte 1
32     // '--' byte 2
13     // <CR> byte 1
0      // <CR> byte 2
10     // <LF> byte 1
0      // <LF> byte 2

از نظر فنی UTF-16 برای هر Character دو یا چهار Byte استفاده می‌کند؛ نزدیک به یک میلیون Unicode Character تخصیص‌یافته یا Reserved وجود دارد، پس دو Byte همیشه کافی نیست. اما چون Type با نام char در C# خودش فقط 16 Bit عرض دارد، Encoding از نوع UTF-16 همیشه برای هر .NET char دقیقاً دو Byte استفاده می‌کند. این Jump کردن به Index Character مشخص داخل Stream را آسان می‌کند.

UTF-16 از Prefix دو Byte برای مشخص‌کردن اینکه Pairهای Byte با Order «Little-endian» یا «Big-endian» نوشته شده‌اند استفاده می‌کند؛ یعنی Least Significant Byte اول یا Most Significant Byte اول. Order پیش‌فرض Little-endian در Systemهای Windows استاندارد است.

StringReader و StringWriter

Adapterهای StringReader و StringWriter اصلاً Stream را Wrap نمی‌کنند؛ در عوض از String یا StringBuilder به‌عنوان Data Source زیربنایی استفاده می‌کنند. پس Byte Translation لازم نیست. در واقع این Classها کاری نمی‌کنند که نتوان با String یا StringBuilder همراه با Index Variable به‌سادگی انجام داد. مزیتشان این است که با StreamReader/StreamWriter Base Class مشترک دارند.

فرض کنید Stringی حاوی XML داریم و می‌خواهیم آن را با XmlReader Parse کنیم. Method با نام XmlReader.Create یکی از موارد زیر را می‌پذیرد:

  • URI
  • Stream
  • TextReader

پس String را چگونه XML-parse کنیم؟ چون StringReader Subclass از TextReader است، می‌توانیم آن را Instantiate و چنین Pass کنیم:

XmlReader r = XmlReader.Create (new StringReader (myString));

Binary Adapterها

BinaryReader و BinaryWriter Data Typeهای Native را Read و Write می‌کنند: bool، byte، char، decimal، float، double، short، int، long، sbyte، ushort، uint و ulong، همچنین Stringها و Arrayهای Primitive Data Typeها.

برخلاف StreamReader و StreamWriter، Binary Adapterها Primitive Data Typeها را همان‌طور که در Memory نمایش داده می‌شوند Efficient ذخیره می‌کنند؛ مثلاً int چهار Byte و double هشت Byte. Stringها با Text Encoding نوشته می‌شوند، اما Prefixی از Length دارند تا بتوان Seriesی از Stringها را بدون Delimiter خاص دوباره Read کرد.

فرض کنید Type ساده زیر را داریم:

public class Person
{
  public string Name;
  public int    Age;
  public double Height;
}

می‌توانیم Methodهای زیر را به Person اضافه کنیم تا Data آن با Binary Adapterها روی Stream Save/Load شود:

public void SaveData (Stream s)
{
  var w = new BinaryWriter (s);
  w.Write (Name);
  w.Write (Age);
  w.Write (Height);
  w.Flush();         // Ensure the BinaryWriter buffer is cleared.
                     // We won't dispose/close it, so more data
}                    // can be written to the stream.
public void LoadData (Stream s)
{
  var r = new BinaryReader (s);
  Name   = r.ReadString();
  Age    = r.ReadInt32();
  Height = r.ReadDouble();
}

BinaryReader می‌تواند داخل Byte Array هم Read کند. کد زیر کل Content یک Stream قابل Seek را می‌خواند:

byte[] data = new BinaryReader (s).ReadBytes ((int) s.Length);

این از Read مستقیم Stream راحت‌تر است، چون برای تضمین Read شدن همهٔ Data به Loop نیاز ندارد.

Close و Dispose کردن Stream Adapterها

برای Tear Down کردن Stream Adapter چهار انتخاب دارید:

  1. فقط Adapter را Close کنید.
  2. Adapter را Close و سپس Stream را Close کنید.
  3. برای Writer، Adapter را Flush و سپس Stream را Close کنید.
  4. برای Reader، فقط Stream را Close کنید.

Optionهای 1 و 2 از نظر Semantic یکسان‌اند، چون Close کردن Adapter به‌طور خودکار Stream زیربنایی را Close می‌کند. هر زمان Statementهای using را Nest می‌کنید، به‌طور ضمنی Option 2 را انتخاب کرده‌اید:

using (FileStream fs = File.Create ("test.txt"))
using (TextWriter writer = new StreamWriter (fs))
  writer.WriteLine ("Line");

چون Nest از Inside به Outside Dispose می‌کند، اول Adapter و سپس Stream Close می‌شود. علاوه بر آن اگر داخل Constructor مربوط به Adapter Exception پرتاب شود، Stream همچنان Close می‌شود. با Nested using اشتباه‌کردن سخت است.

Optionهای 3 و 4 کار می‌کنند چون Adapterها در Category غیرمعمول Objectهای «اختیاری برای Dispose» قرار می‌گیرند. نمونهٔ موقعیتی که شاید Adapter را Dispose نکنید زمانی است که کارتان با Adapter تمام شده، اما می‌خواهید Stream زیربنایی برای استفادهٔ بعدی باز بماند:

using (FileStream fs = new FileStream ("test.txt", FileMode.Create))
{
  StreamWriter writer = new StreamWriter (fs);
  writer.WriteLine ("Hello");
  writer.Flush();
  fs.Position = 0;
  Console.WriteLine (fs.ReadByte());
}

اینجا به File می‌نویسیم، Stream را Reposition می‌کنیم و پیش از Close کردن Stream، نخستین Byte را می‌خوانیم. اگر StreamWriter را Dispose می‌کردیم، FileStream زیربنایی نیز Close می‌شد و Read بعدی Failure می‌داد. شرط کار این است که Flush را Call کنیم تا Buffer در StreamWriter به Stream زیربنایی Write شود.

در StreamReader/StreamWriter Constructorی نیز وجود دارد که دستور می‌دهد پس از Disposal، Stream باز بماند. بنابراین مثال قبل را چنین بازنویسی می‌کنیم:

using (var fs = new FileStream ("test.txt", FileMode.Create))
{
  using (var writer = new StreamWriter (fs, new UTF8Encoding (false, true),
                                       0x400, true))
    writer.WriteLine ("Hello");
  fs.Position = 0;
  Console.WriteLine (fs.ReadByte());
  Console.WriteLine (fs.Length);
}

Streamهای فشرده‌سازی (Compression Streams)

دو Stream فشرده‌سازی General-purpose در Namespace با نام System.IO.Compression وجود دارند: DeflateStream و GZipStream. هر دو از Compression Algorithm محبوبی شبیه Format با نام ZIP استفاده می‌کنند. تفاوت آن‌ها این است که GZipStream Protocol اضافی در Beginning و End می‌نویسد، شامل CRC برای تشخیص Error. همچنین GZipStream با Standardی که Softwareهای دیگر می‌شناسند سازگار است.

.NET همچنین BrotliStream را دارد که Algorithm با نام Brotli را پیاده‌سازی می‌کند. BrotliStream در Compression بیش از 10 برابر از DeflateStream و GZipStream کندتر است، اما Compression Ratio بهتری می‌دهد. این Performance Hit فقط به Compression مربوط است؛ Decompression بسیار خوب عمل می‌کند.

هر سه Stream Reading و Writing را با شرط‌های زیر پشتیبانی می‌کنند:

  • هنگام Compress کردن همیشه به Stream Write می‌کنید.
  • هنگام Decompress کردن همیشه از Stream Read می‌کنید.

DeflateStream، GZipStream و BrotliStream Decorator هستند؛ Data را از Stream دیگری که هنگام Construction می‌دهید Compress یا Decompress می‌کنند. در مثال زیر Seriesی از Byteها را با FileStream به‌عنوان Backing Store Compress و Decompress می‌کنیم:

using (Stream s = File.Create ("compressed.bin"))
using (Stream ds = new DeflateStream (s, CompressionMode.Compress))
  for (byte i = 0; i < 100; i++)
    ds.WriteByte (i);
using (Stream s = File.OpenRead ("compressed.bin"))
using (Stream ds = new DeflateStream (s, CompressionMode.Decompress))
  for (byte i = 0; i < 100; i++)
    Console.WriteLine (ds.ReadByte());     // Writes 0 to 99

با DeflateStream File فشرده 102 Byte است؛ کمی بزرگ‌تر از Original. BrotliStream آن را به 73 Byte Compress می‌کرد. Compression روی Binary Data «Dense» و غیرتکراری ضعیف است و بدترین حالت Encrypted Data است که عمداً نظم ندارد. روی بیشتر Text Fileها خوب عمل می‌کند. در مثال بعد Text Stream شامل 1000 Word تصادفی از یک Sentence کوتاه را با Brotli Compress و Decompress می‌کنیم. این مثال همچنین Chain کردن Backing Store Stream، Decorator Stream و Adapter را ــ همان شکل 15-1 ــ و استفاده از Methodهای ناهمگام نشان می‌دهد:

string[] words = "The quick brown fox jumps over the lazy dog".Split();
Random rand = new Random (0);   // Give it a seed for consistency
using (Stream s = File.Create ("compressed.bin"))
using (Stream ds = new BrotliStream (s, CompressionMode.Compress))
using (TextWriter w = new StreamWriter (ds))
  for (int i = 0; i < 1000; i++)
    await w.WriteAsync (words [rand.Next (words.Length)] + " ");
Console.WriteLine (new FileInfo ("compressed.bin").Length);      // 808
using (Stream s = File.OpenRead ("compressed.bin"))
using (Stream ds = new BrotliStream (s, CompressionMode.Decompress))
using (TextReader r = new StreamReader (ds))
  Console.Write (await r.ReadToEndAsync());  // Output below:
lazy lazy the fox the quick The brown fox jumps over fox over fox The
brown brown brown over brown quick fox brown dog dog lazy fox dog brown
over fox jumps lazy lazy quick The jumps fox jumps The over jumps dog...

در این حالت BrotliStream Data را Efficient به 808 Byte Compress می‌کند؛ کمتر از یک Byte به‌ازای هر Word. برای مقایسه، DeflateStream همان Data را به 885 Byte Compress می‌کند.

Compression در Memory

گاهی لازم است همهٔ Compression در Memory انجام شود. روش استفاده از MemoryStream:

byte[] data = new byte[1000];          // We can expect a good compression
                                       // ratio from an empty array!
var ms = new MemoryStream();
using (Stream ds = new DeflateStream (ms, CompressionMode.Compress))
  ds.Write (data, 0, data.Length);
byte[] compressed = ms.ToArray();
Console.WriteLine (compressed.Length);       // 11
// Decompress back to the data array:
ms = new MemoryStream (compressed);
using (Stream ds = new DeflateStream (ms, CompressionMode.Decompress))
  for (int i = 0; i < 1000; i += ds.Read (data, i, 1000 - i));

Statement با نام using دور DeflateStream آن را طبق الگوی استاندارد Close می‌کند و هر Buffer نوشته‌نشده را Flush می‌کند. این همچنین MemoryStream Wrapped را Close می‌کند؛ بنابراین برای استخراج Data باید ToArray را Call کنیم.

Alternative زیر از Close کردن MemoryStream جلوگیری می‌کند و از Methodهای Read/Write ناهمگام استفاده می‌کند:

byte[] data = new byte[1000];
MemoryStream ms = new MemoryStream();
using (Stream ds = new DeflateStream (ms, CompressionMode.Compress, true))
  await ds.WriteAsync (data, 0, data.Length);
Console.WriteLine (ms.Length);             // 113
ms.Position = 0;
using (Stream ds = new DeflateStream (ms, CompressionMode.Decompress))
  for (int i = 0; i < 1000; i += await ds.ReadAsync (data, i, 1000 - i));

Flag اضافی که به Constructor مربوط به DeflateStream داده شده به آن می‌گوید Protocol معمولِ Dispose کردن Stream زیربنایی را دنبال نکند. یعنی MemoryStream باز می‌ماند و می‌توانیم Position را دوباره صفر و آن را Read کنیم.

Compression فایل gzip در Unix

Compression Algorithm مربوط به GZipStream در Systemهای Unix به‌عنوان File Compression Format محبوب است. هر Source File به Target File جداگانه با Extension برابر .gz Compress می‌شود.

Methodهای زیر کار Utilityهای Command-line با نام gzip و gunzip در Unix را انجام می‌دهند:

async Task GZip (string sourcefile, bool deleteSource = true)
{
  var gzip = $"{sourcefile}.gz";
  if (File.Exists (gzip))
    throw new Exception ("Gzip file already exists");
  // Compress
  using (FileStream inStream = File.Open (sourcefile, FileMode.Open))
  using (FileStream outStream = new FileStream (gzip, FileMode.CreateNew))
  using (GZipStream gzipStream = 
    new GZipStream (outStream, CompressionMode.Compress))
    await inStream.CopyToAsync (gzipStream); 
  
  if (deleteSource) File.Delete(sourcefile);
}
async Task GUnzip (string gzipfile, bool deleteGzip = true)
{
  if (Path.GetExtension (gzipfile) != ".gz") 
    throw new Exception ("Not a gzip file");
  var uncompressedFile = gzipfile.Substring (0, gzipfile.Length - 3);
  if (File.Exists (uncompressedFile)) 
    throw new Exception ("Destination file already exists");
  // Uncompress
  using (FileStream uncompressToStream = 
         File.Open (uncompressedFile, FileMode.Create))
  using (FileStream zipfileStream = File.Open (gzipfile, FileMode.Open))
  using (var unzipStream = 
         new GZipStream (zipfileStream, CompressionMode.Decompress))
    await unzipStream.CopyToAsync (uncompressToStream);
    
  if (deleteGzip) File.Delete (gzipfile);
}

دستور زیر File را Compress می‌کند:

await GZip ("/tmp/myfile.txt");      // Creates /tmp/myfile.txt.gz

و دستور زیر آن را Decompress می‌کند:

await GUnzip ("/tmp/myfile.txt.gz")  // Creates /tmp/myfile.txt

کار با ZIP Fileها

Classهای ZipArchive و ZipFile در System.IO.Compression از Compression Format با نام ZIP پشتیبانی می‌کنند. مزیت ZIP نسبت به DeflateStream و GZipStream این است که Container برای چند File نیز هست و با ZIPهایی که Windows Explorer می‌سازد Compatibility دارد.

ZipArchive با Streamها کار می‌کند؛ ZipFile سناریوی رایج‌ترِ کار با Fileها را هدف می‌گیرد. ZipFile یک Helper Class استاتیک برای ZipArchive است.

Method با نام CreateFromDirectory در ZipFile همهٔ Fileهای Directory مشخص را داخل ZIP File اضافه می‌کند:

ZipFile.CreateFromDirectory (@"d:\MyFolder", @"d:\archive.zip");

ExtractToDirectory کار عکس را انجام می‌دهد و ZIP را داخل Directory استخراج می‌کند:

ZipFile.ExtractToDirectory (@"d:\archive.zip", @"d:\MyFolder");

از .NET 8 می‌توانید به‌جای Path مربوط به ZIP File یک Stream نیز بدهید.

هنگام Compression می‌توانید تعیین کنید Optimization برای File Size باشد یا Speed و اینکه Name مربوط به Source Directory داخل Archive قرار بگیرد یا نه. فعال‌کردن Option دوم در مثال ما Subdirectoryای به نام MyFolder داخل Archive می‌سازد و Fileهای Compressشده آنجا قرار می‌گیرند.

ZipFile Method با نام Open برای Read/Write کردن Entryهای منفرد دارد. این Method Object از نوع ZipArchive برمی‌گرداند؛ همان Object را می‌توانید با Instantiate کردن ZipArchive با Stream بگیرید. هنگام Open باید Filename و Mode از نوع Read، Create یا Update را مشخص کنید. سپس می‌توانید Entryهای موجود را با Property با نام Entries Enumerate یا File خاصی را با GetEntry پیدا کنید:

using (ZipArchive zip = ZipFile.Open (@"d:\zz.zip", ZipArchiveMode.Read))
  foreach (ZipArchiveEntry entry in zip.Entries)
    Console.WriteLine (entry.FullName + " " + entry.Length);

ZipArchiveEntry Method با نام Delete، Method با نام ExtractToFile ــ در واقع Extension Method در Class با نام ZipFileExtensions ــ و Method با نام Open دارد که Stream قابل Read/Write برمی‌گرداند. با CreateEntry یا Extension Method با نام CreateEntryFromFile روی ZipArchive می‌توانید Entry جدید بسازید.

مثال زیر Archive با نام d:\zz.zip را می‌سازد و foo.dll را در ساختار Directory با نام bin\X86 داخل Archive اضافه می‌کند:

byte[] data = File.ReadAllBytes (@"d:\foo.dll"); 
using (ZipArchive zip = ZipFile.Open (@"d:\zz.zip", ZipArchiveMode.Update))
  zip.CreateEntry (@"bin\X64\foo.dll").Open().Write (data, 0, data.Length);

می‌توانید همین کار را کاملاً در Memory با Construct کردن ZipArchive روی MemoryStream انجام دهید.

کار با Tar Fileها

Typeهای Namespace با نام System.Formats.Tar ــ از .NET 7 ــ از Archive Format با نام .tar پشتیبانی می‌کنند که در Unix برای Bundle کردن چند File محبوب است. برای ساخت File از نوع .tar یا Tarball، TarFile.CreateFromDirectory را Call کنید:

TarFile.CreateFromDirectory ("/tmp/testfolder", "/tmp/test.tar", false);

Argument سوم تعیین می‌کند Base Directory Name در Archive Entryها قرار بگیرد یا نه.

برای Extract کردن Tarball، TarFile.ExtractToDirectory را Call کنید:

TarFile.ExtractToDirectory ("/tmp/test.tar", "/tmp/testfolder", true);

Argument سوم تعیین می‌کند Fileهای موجود Overwrite شوند یا نه.

هر دو Method اجازه می‌دهند به‌جای File Path از نوع .tar یک Stream بدهید. در مثال زیر Tarball را داخل Memory Stream می‌نویسیم و سپس با GZipStream آن را به File از نوع .tar.gz Compress می‌کنیم:

var ms = new MemoryStream();
TarFile.CreateFromDirectory ("/tmp/testfolder", ms, false);
ms.Position = 0;    // So that we can re-use the stream for reading.
using (var fs = File.Create ("/tmp/test.tar.gz"))
using (var gz = new GZipStream (fs, CompressionMode.Compress))
  ms.CopyTo (gz);

Compress کردن .tar به .tar.gz مفید است چون Format با نام .tar برخلاف .zip خودش Compression ندارد. File از نوع .tar.gz را چنین Extract می‌کنیم:

using (var fs = File.OpenRead ("/tmp/test.tar.gz"))
using (var gz = new GZipStream (fs, CompressionMode.Decompress))
  TarFile.ExtractToDirectory (gz, "/tmp/testfolder", true);

با Classهای TarReader و TarWriter می‌توانید در Level ریزتری به API دسترسی داشته باشید. مثال استفاده از TarReader:

using (FileStream archiveStream = File.OpenRead ("/tmp/test.tar "))
using (TarReader reader = new (archiveStream))
  while (true)
  {
    TarEntry entry = reader.GetNextEntry();
    if (entry == null) break;   // No more entries
    Console.WriteLine (
      $"Entry {entry.Name} is {entry.DataStream.Length} bytes long");
    entry.ExtractToFile (
      Path.Combine ("/tmp/testfolder", entry.Name), true);
  }

پایان محتوای تخصیص‌یافته از فایل PDF برای این مقاله.

امتیاز کاربران به این مقاله

☆☆☆☆☆

0 نفر امتیاز داده اند. میانگین: 0.0 از 5

 

0 نظر

نظر محترم شما در مورد مقاله های وب سایت برنامه نویسی و پایگاه داده

نظرات محترم شما در خدمات رسانی بهتر ما را یاری می نمایند. لطفا اگر مایل بودید یک نظر ما را مهمان فرمائید. آدرس ایمیل و وب سایت شما نمایش داده نخواهد شد.

0 / 500

اطلاعات تماس

  • آدرس:اصفهان-خیابان ام کلثوم غربی - بعد خیابان تخم چی - بیست متر بعد از پیتزا ننه شب - کوچه تعمیر گاه سمار زغالی - پلاک 354 - درب مشکی - طبقه هفتم
  • آدرس ایمیل:najafzade@gmail.com
  • وب سایت:http://www.a00b.com/
  • تلفن ثابت:(+98)9131253620
  • تلفن همراه:09131253620