• 0
أحمد أبو عبد البر

الدرس الثامن في لغة Object pascal : الملفات(2)

سؤال

بسم الله الرحمن الرحيم

ملفات الوصول العشوائي Random access files

 

كما سبق ذكره فإن النوع الثاني من الملفات من حيث الوصول هي ملفات الوصول العشوائي أو كما تسمى أحياناً بملفات الوصول المباشر. وهي تتميز بطول معروف للسجل ويمكن الكتابة والقراءة من الملف في آن واحد، كذلك يمكن الوصول مباشرة إلى أي سجل بغض النظر عن موقع القراءة أو الكتابة الحالي.

توجد طريقتين للتعامل مع الملفات ذات الوصول العشوائي في لغة باسكال: الطريقة الأولى هي إستخدام الملفات ذات النوع typed files، أما الطريقة الثانية فهي إستخدام الملفات الغير محددة النوع untyped files.

 

الملفات ذات النوع typed file

 

في هذه الطريقة يكون الملف المراد قراءته أو كتابته مرتبط بنوع معين، والنوع المعين يمثله سجل، فمثلاً يمكن أن يكون ملف من النوع الصحيح Byte، في هذه الحال نقول أن السجل هو عبارة عن عدد صحيح Byte وفي هذه الحالة يكون طول السجل1 بايت.

 

المثال التالي يوضح طريقة كتابة ملف لأعداد صحيحة:

 

برنامج تسجيل درجات الطلاب

 

var  F: file of Byte;  Mark: Byte;begin  AssignFile(F, 'marks.dat');  Rewrite(F); // Create file  Writeln('Please input students marks, write 0 to exit');  repeat    Write('Input a mark: ');    Readln(Mark);    if Mark <> 0 then  // Don't write 0 value      Write(F, Mark);  until Mark = 0;  CloseFile(F);  Write('Press enter key to close..');  Readln;end. 

 

 

نلاحظ في البرنامج أننا قمنا بتعريف نوع الملف بهذه الطريقة:

 

   

 

 

F: file of Byte;

 

 

 

وهي تعني أن الملف هو من نوع Byte أو أن سجلاته عبارة هي قيم للنوع Byte والذي يحتل في الذاكرة وفي القرص 1 بايت، ويمكنه تخزين القيم من 0 إلى 255.

 

كذلك قمنا بإنشاء الملف وتجهيزه للكتابة بإستخدام الأمر:

 

 

Rewrite(F); // Create file

 

 

 

وقد قمنا بإستخدام الإجراء Write للكتابة في الملف:

 

 

Write(F, Mark);

 

 

 

حيث أن Writeln لاتصلح لهذا النوع من الملفات لأنها تقوم بإضافة علامة نهاية السطر CR/LF كما سبق ذكره، أما Write فهي تكتب السجل كما هو بدون أي زيادات، لذلك نجد أننا في المثال السابق يمكننا معرفة حجم الملف، فإذا قمنا بإدخال 3 درجات فإن عدد السجلات يكون 3 وبما أن طول السجل هو 1 بايت فإن حجم الملف يكون 3 بايت.

يمكن تغيير النوع إلى Integer الذي يحتل 4 خانات، ورؤية حجم الملف الناتج.

 

في المثال التالي نقوم بقراءة محتويات الملف السابق، فلاننسى إرجاع نوع الملف في البرنامج السابق إلى النوع Byte، لأن القراءة والكتابة لابد أن تكون لملف من نفس النوع.

 

برنامج قراءة ملف الدرجات

program ReadMarks;{$mode objfpc}{$H+}uses  {$IFDEF UNIX}{$IFDEF UseCThreads}  cthreads,  {$ENDIF}{$ENDIF}  Classes, SysUtils  { you can add units after this };var  F: file of Byte;  Mark: Byte;begin  AssignFile(F, 'marks.dat');  if FileExists('marks.dat') then  begin    Reset(F); // Open file    Writeln('Please input students marks, write 0 to exit');    while not Eof(F) do    begin      Read(F, Mark);      Writeln('Mark: ', Mark);    end;    CloseFile(F);  end  else    Writeln('File (marks.dat) not found');  Write('Press enter key to close..');  Readln;end.

 

نلاحظ أن البرنامج السابق قام بإظهار الدرجات الموجودة في الملف. وأظن أن البرنامج واضح ولايحتاج لشرح.

 

في البرنامج التالي نقوم بفتح الملف السابق ونضيف إليه درجات جديدة بدون حذف الدرجات السابقة:

 

 

برنامج إضافة درجات الطلاب

program AppendMarks;{$mode objfpc}{$H+}uses  {$IFDEF UNIX}{$IFDEF UseCThreads}  cthreads,  {$ENDIF}{$ENDIF}  Classes, SysUtils  { you can add units after this };var  F: file of Byte;  Mark: Byte;begin  AssignFile(F, 'marks.dat');  if FileExists('marks.dat') then  begin    FileMode:= 2; // Open file for read/write    Reset(F); // open file    Seek(F, FileSize(F)); // Go to after last record    Writeln('Please input students marks, write 0 to exit');    repeat      Write('Input a mark: ');      Readln(Mark);      if Mark <> 0 then  // Don't write 0 value        Write(F, Mark);    until Mark = 0;    CloseFile(F);  end  else    Writeln('File marks.dat not found');  Write('Press enter key to close..');  Readln;end.

 

بعد تنفيذ البرنامج السابق نقوم بتشغيل برنامج قراءة ملف الدرجات وذلك لرؤية أن قيم السجلات السابقة والجديدة موجودة معاً في نفس الملف.

نلاحظ في هذا البرنامج أننا إستخدمنا الإجراء Reset للكتابة بدلاً من Rewrite. وفي هذا النوع من الملفات يمكن إستخدام كليهما للكتابة، والفرق الرئيسي بينهما يكمن في أن Rewrite تقوم بإنشاء الملف إذا لم يكن موجود وحذف محتوياته إذا كان موجود، أما Reset فهي تفترض وجود الملف، فإذا لم يكون موجود حدث خطأ. لكن عبارة Reset تقوم بفتح الملف حسب قيمة FileMode :

فإذا كانت قيمتها 0 فإن الملف يفتح للقراءة فقط، وإذا كانت قيمته 1 يفتح للكتابة فقط، وإذا كانت قيمته 2 -وهي القيمة الإفتراضية- فإنه يفتح للقراءة والكتابة معاً:

 

    FileMode:= 2; // Open file for read/write    Reset(F); // open file

 

كذلك فإن الدالة Reset تقوم بوضع مؤشر القراءة والكتابة في أول سجل، لذلك إذا باشرنا الكتابة فإن البرنامج السابق يقوم بالكتابة فوق محتويات السجلات السابقة، لذلك وجب تحريك هذا المؤشر للسجل الأخير، وذلك بإستخدام الإجراء Seek الذي يقوم بتحريك الموشر، وبهذه الطريقة تمت تسمية هذا النوع من الملفات بالملفات العشوائية أو ملفات الوصول المباشر، حيث أن الإجراء Seek يسمح لنا بالتنقل لأي سجل مباشرة إذا علمنا رقمه، شريطة أن يكون هذا الرقم موجود، فسوف يحدث خطأ مثلاً إذا قمنا بمحاولة توجيه المؤشر إلى السجل رقم 100 في حين أن الملف يحتوي على عدد سجلات أقل من 100.

استخدمنا الدالة FileSize والتي تقوم بإرجاع عدد السجلات:

 

        Seek(F, FileSize(F)); // Go to after last record

 

نلاحظ أن المثال السابق يصلح فقط في حالة وجود ملف الدرجات، أما إذا لم يكن موجود وجب إستخدام البرنامج الأول لكتابة الدرجات.

يمكننا المزج بين الطريقتين، بحيث أن البرنامج يقوم بفحص وجود الملف، فإذا كان موجود يقوم بفتحه للإضافة عن طريق Reset وإذا لم يكن موجود يقوم بإنشائه بإستخدام Rewrite:

 

 

برنامج إنشاء وإضافة درجات الطلاب

 

program ReadWriteMarks;{$mode objfpc}{$H+}uses  {$IFDEF UNIX}{$IFDEF UseCThreads}  cthreads,  {$ENDIF}{$ENDIF}  Classes, SysUtils  { you can add units after this };var  F: file of Byte;  Mark: Byte;begin  AssignFile(F, 'marks.dat');  if FileExists('marks.dat') then  begin    FileMode:= 2; // Open file for read/write    Reset(F); // open file    Writeln('File already exist, opened for append');    // Display file records    while not Eof(F) do    begin      Read(F, Mark);      Writeln('Mark: ', Mark);    end  end  else // File not found, create it  begin    Rewrite(F);    Writeln('File does not exist, created');  end;  Writeln('Please input students marks, write 0 to exit');  Writeln('File pointer position at record # ', FilePos(f));  repeat    Write('Input a mark: ');    Readln(Mark);    if Mark <> 0 then  // Don't write 0 value      Write(F, Mark);  until Mark = 0;  CloseFile(F);  Write('Press enter key to close..');  Readln;end.

 

 

بعد تشغيل البرنامج نجد أن القيم السابقة تم عرضها في البداية قبل الشروع في إضافة درجات جديدة.

نلاحظ أننا في هذه الحالة لم نستخدم الإجراء Seek وذلك لأننا قمنا بقراءة كل محتويات الملف، ومن المعروف أن القراءة تقوم بتحريك مؤشر الملف إلى الأمام، لذلك بعد الفراغ من قراءة كافة سجلاته يكون المؤشر في الخانة الأخيرة في الملف، لذلك يمكن الإضافة مباشرة.

استخدمنا الدالة FilePos التي تقوم بإرجاع الموقع الحالي لمؤشر الملفات.

 

 

في المثال التالي سوف نستخدم سجل Record لتسجيل بيانات سيارة، نلاحظ أننا نقوم بكتابة وقراءة السجل كوحدة واحدة:

 

برنامج سجل السيارات

 

program CarRecords;{$mode objfpc}{$H+}uses  {$IFDEF UNIX}{$IFDEF UseCThreads}  cthreads,  {$ENDIF}{$ENDIF}  Classes, SysUtils  { you can add units after this };type  TCar = record    ModelName: string[20];    Engine: Single;    ModelYear: Integer;  end;var  F: file of TCar;  Car: TCar;begin  AssignFile(F, 'cars.dat');  if FileExists('cars.dat') then  begin    FileMode:= 2; // Open file for read/write    Reset(F); // open file    Writeln('File already exist, opened for append');    // Display file records    while not Eof(F) do    begin      Read(F, Car);      Writeln;      Writeln('Car # ', FilePos(F), ' --------------------------');      Writeln('Model : ', Car.ModelName);      Writeln('Year  : ', Car.ModelYear);      Writeln('Engine: ', Car.Engine);    end  end  else // File not found, create it  begin    Rewrite(F);    Writeln('File does not exist, created');  end;  Writeln('Please input car informaion, ',         'write x in model name to exit');  Writeln('File pointer position at record # ', FilePos(f));  repeat    Writeln('--------------------------');    Write('Input car Model Name : ');    Readln(car.ModelName);    if Car.ModelName <> 'x' then    begin      Write('Input car Model Year : ');      Readln(car.ModelYear);      Write('Input car Engine size: ');      Readln(car.Engine);      Write(F, Car);    end;  until Car.ModelName = 'x';  CloseFile(F);  Write('Press enter key to close..');  Readln;end.

 

في البرنامج السابق إستخدمنا سجل لمعلومات السيارة، والحقل الأول في السجل ModelName هو من النوع المقطعي، إلا أننا في هذه الحالة قمنا بتحديد طول المقطع بالعدد 20 وهو يمثل 20 حرف:

 

  ModelName: string[20];

 

 

وبهذه الطريقة يكون طول المقطع معروف ومحدد ويأخذ مساحة معروفة من القرص، أما طريقة إستخدام النوع string مطلقاً والذي يسمى AnsiString فهي طريقة لها تبعاتها في طريقة تخزينها في الذاكرة، وسوف نتكلم عنها في كتاب لاحق إن شاء الله.

 

نسخ الملفات Files copy

 

الملفات بكافة أنواعها سواءً كانت ملفات نصية أو ثنائية، صور ، برامج، أو غيرها فإنها تشترك في أن الوحدة الأساسية فيها هي البايت Byte، حيث أن أي ملف هو عبارة عن مجموعة من البايتات، تختلف في محتوياتها، لكن البايت يحتوي على أرقام من القيمة صفر إلى القيمة 255، لذلك فإذا قرأنا أي ملف فنجد أن رموزه لاتخرج عن هذه اﻹحتمالات (0 - 255).

عملية نسخ الملف هي عملية بسيطة، فنحن نقوم بنسخ الملف حرفاً حرفاً بإستخدام متغير يحتل بايت واحد من الذاكرة مثل البايت Byte أو الرمز Char. في هذه الحالة لايهم نوع الملف، لأن النسخ بهذه الطريقة يكون الملف المنسوخ صورة طبق الأصل من الملف الأصلي:

 

 

برنامج نسخ الملفات عن طريق البايت

 

program FilesCopy;{$mode objfpc}{$H+}uses  {$IFDEF UNIX}{$IFDEF UseCThreads}  cthreads,  {$ENDIF}{$ENDIF}  Classes, SysUtils  { you can add units after this };var  SourceName, DestName: string;  SourceF, DestF: file of Byte;  Block: Byte;begin  Writeln('Files copy');  Write('Input source file name: ');  Readln(SourceName);  Write('Input destination file name: ');  Readln(DestName);  if FileExists(SourceName) then  begin    AssignFile(SourceF, SourceName);    AssignFile(DestF, DestName);    FileMode:= 0;   // open for read only    Reset(SourceF); // open source file    Rewrite(DestF); // Create destination file    // Start copy    Writeln('Copying..');    while not Eof(SourceF) do    begin      Read(SourceF, Block); // Read Byte from source file      Write(DestF, Block);  // Write this byte into new                                             // destination file     end;    CloseFile(SourceF);    CloseFile(DestF);  end  else // Source File not found    Writeln('Source File does not exist');  Write('Copy file is finished, press enter key to close..');  Readln;end.

 

عند تشغيل هذا البرنامج يجب كتابة إسم الملف المراد النسخ منه والملف الجديد كاملاً مثلا في نظام لينكس نكتب:

 

Input source file name: /home/motaz/quran/mishari/32.mp3

Input destination file name: /home/motaz/Alsajda.mp3

 

وفي نظام وندوز:

 

Input source file name: c:\photos\mypphoto.jpg

Input destination file name: c:\temp\copy.jpg

 

أما إذا كان البرنامج FileCopy موجود في نفس الدليل للملف المصدر والنسخة، فيمكن كتابة إسمي الملف بدون كتابة إسم الدليل مثلاً:

 

Input source file name: test.pas

Input destination file name: testcopy.pas

 

 

نلاحظ أن برنامج نسخ الملفات يأخذ وقت طويل عند نسخ الملفات الكبيرة مقارنة بنسخها بواسطة نظام التشغيل نفسه، وذلك يعني أن نظام التشغيل يستخدم طريقة مختلفة لنسخ الملفات. فهذه الطريقة بطيئة جداً بسبب قراءة حرف واحد في الدورة الواحدة ثم نسخة في الملف الجديد، فلو كان حجم الملف مليون بايت فإن الحلقة تدور مليون مرة، تتم فيها القراءة مليون مرة والكتابة مليون مرة.

وكانت هذه الطريقة للشرح فقط، أما الطريقة المثلى لنسخ الملفات فهي عن طريق استخدام الملفات غير محددة النوع untyped files.

 

 

الملفات غير محددة النوع untyped files

 

وهي ملفات ذات وصول عشوائي، إلا أنها تختلف عن الملفات محددة النوع في أنها لاترتبط بنوع محدد، كذلك فإن القراءة والكتابة غير مرتبطة بعدد سجلات معين، حيث أن للمبرمج كامل الحرية في تحديد عدد السجلات التي يرغب في كتابتها أو قراءتها في كل مرة.

 

 

برنامج نسخ الملفات بإستخدام الملفات غير محددة النوع

 

program FilesCopy2;{$mode objfpc}{$H+}uses  {$IFDEF UNIX}{$IFDEF UseCThreads}  cthreads,  {$ENDIF}{$ENDIF}  Classes, SysUtils  { you can add units after this };var  SourceName, DestName: string;  SourceF, DestF: file;  Block: array [0 .. 1023] of Byte;  NumRead: Integer;begin  Writeln('Files copy');  Write('Input source file name: ');  Readln(SourceName);  Write('Input destination file name: ');  Readln(DestName);  if FileExists(SourceName) then  begin    AssignFile(SourceF, SourceName);    AssignFile(DestF, DestName);    FileMode:= 0;   // open for read only    Reset(SourceF, 1); // open source file    Rewrite(DestF, 1); // Create destination file    // Start copy    Writeln('Copying..');    while not Eof(SourceF) do    begin      // Read Byte from source file      BlockRead(SourceF, Block, SizeOf(Block), NumRead);       // Write this byte into new destination file      BlockWrite(DestF, Block, NumRead);      end;    CloseFile(SourceF);    CloseFile(DestF);  end  else // Source File not found    Writeln('Source File does not exist');  Write('Copy file is finished, press enter key to close..');  Readln;end.

 

 

في المثال السابق نجد أن هناك أشياء جديدة وهي:

 

  1. طريقة تعريف الملفات، وهي بتعريفها أن المتغير مجرد ملف:

     

      SourceF, DestF: file;

     

  1. المتغير الذي يستخدم في نسخ البيانات بين الملفين:

 

Block: array [0 .. 1023] of Byte;

 

 

نجد أنه في هذه الحالة عبارة مصفوفة من نوع البايت تحتوي على كيلو بايت، ويمكن تغييرها إلى أي رقم يريده المبرمج.

 

  1. طريقة فتح الملف إختلفت قليلاً:

     

      Reset(SourceF, 1); // open source file  Rewrite(DestF, 1); // Create destination file

     

فقد زاد مُدخل جديد وهو طول السجل، وفي حالة نسخ ملفات هذه يجب أن يكون دائماً يحمل القيمة واحد وهو يعني أن طول السجل واحد بايت. والسبب يكمن في أن الرقم واحد يقبل القسمة على جميع قيم حجم الملفات، مثلاً يمكن أن يكون حجم الملف 125 بايت، أو 23490 بايت وهكذا.

 

  1. طريقة القراءة:

     

      BlockRead(SourceF, Block, SizeOf(Block), NumRead); 

     

يستخدم الإجراء BlockRead مع الملفات غير محددة النوع، حيث يعتبر أن القيمة المراد قراءتها هي عبارة عن كومة أو رزمة غير معلومة المحتويات. والمدخلات لهذا الإجراء هي:

 

SourceF: وهو متغير الملف المراد القراءة منه.

 

Block : وهو المتغير أو المصفوفة التي يراد وضع محتويات القراءة الحالية فيها.

 

SizeOf(Block): وهو عدد السجلات المراد قراءتها في هذه اللحظة، ونلاحظ أننا استخدمنا الدالة SizeOf التي ترجع حجم المتغير من حيث عدد البايتات، وفي هذه الحالة هو الرقم 1024.

 

NumRead: عندما نقول أننا نريد قراءة 1024 بايت ربما ينجح الإجراء بقراءتها جميعاً في حالة أن تكون هناك بيانات متوفر في الملف، أما إذا كانت محتويات الملف أقل من هذه القيمة أو أن مؤشر القراءة وصل قرب نهاية الملف، ففي هذه الحالة لايستطيع قراءة 1024 بايت، وتكون القيمة التي قرأها أقل من ذلك وتخزن القيمة في المتغير NumRead. فمثلاً إذا كان حجم المف 1034 بايت، فيقوم الإجراء بقراءة 1024 بايت في المرة الأولى، أما في المرة الثانية فيقوم بقراءة 10 بايت فقط ويرجع هذه القيمة في المتغير NumRead حتى يتسنى إستخدامها مع الإجراء BlockWrite.

 

  1. طريقة الكتابة:

     

      BlockWrite(DestF, Block, NumRead);  

     

 

وأظن أنها واضحة، والمتغير الأخيرة NumRead في هذه المرة عدد البايتات المراد كتابتها في الملف المنسوخ، وهي تعني عدد البياتات من بداية المصفوفة Block. وعند تشغيل هذا اﻹجراء فإن المتغير NumRead يحمل قيمة عدد البايتات التي تمت قراءتها عن طريق BlockRead

 

وعند تشغيل البرنامج سوف نلاحظ السرعة الكبيرة في نسخ الملفات، فمثلاً إذا كان طول الملف مليون بايت، فيلزم حوالي أقل من ألف دورة فقط للقراءة ثم الكتابة في الملف الجديد.

 

 

في المثال التالي، يقوم البرنامج بإظهار محتويات الملف بالطريقة المستخدمة في التخزين في الذاكرة أو الملف، فكما سبق ذكره فإن الملف هو عبارة عن سلسلة من الحروف أو البايتات.

 

 

برنامج عرض محتويات ملف بالبايت

 

program ReadContents;{$mode objfpc}{$H+}uses  {$IFDEF UNIX}{$IFDEF UseCThreads}  cthreads,  {$ENDIF}{$ENDIF}  Classes, SysUtils  { you can add units after this };var  FileName: string;  F: file;  Block: array [0 .. 1023] of Byte;  i, NumRead: Integer;begin  Write('Input source file name: ');  Readln(FileName);  if FileExists(FileName) then  begin    AssignFile(F, FileName);    FileMode:= 0;   // open for read only    Reset(F, 1);    while not Eof(F) do    begin      BlockRead(F, Block, SizeOf(Block), NumRead);      // display contents in screen      for i:= 0 to NumRead - 1 do        Writeln(Block[i], ':', Chr(Block[i]));    end;    CloseFile(F);  end  else // File does not exist    Writeln('Source File does not exist');  Write('press enter key to close..');  Readln;end.

 

 

بعد تنفيذ البرنامج يمكن للمستخدم كتابة إسم ملف نصي حتى نقوم بعرض محتوياته.

نلاحظ أن بعد كل سطر نجد علامة الـ Line Feed LF وقيمتها بالبايت 10 في نظام لينكس، أما في نظام وندوز فنجد علامتي Carriage Return/Line Feed CRLF وقيمتهما على التوالي 13 و 10، وهي الفاصل الموجود بين السطور في الملف النصي. يمكن كذلك إستخدام البرنامج في فتح ملفات من نوع آخر لمعرفة طريقة تكوينها.

إستخدمنا في البرنامج الدالة Chr التي تقوم بتحويل البايت إلى حرف، مثلاً نجد أن البايت الذي قيمته 97 يمثل الحرف a وهكذا.

 

==================================================

من كتاب الأستاذ معتز عبد العظيم جزاه الله خيرا

 

1

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

1 إجابات على هذا السؤال .


  • يستعرض القسم حالياً   0 members

    لا يوجد أعضاء مسجلين يشاهدون هذه الصفحة .