Потоци и файлове Георги Иванов



Дата25.07.2016
Размер92.34 Kb.
Потоци и файлове

Георги Иванов

File I/O

Тъй като паметта в която данните на вашата програма са записани е непостоянна (с други думи, изтрита когато компютъра ви се изключи), ние се нуждаем от начин за записване на данните на постояннен носител, какъвто е хард диска. Има 3 най-основн начини за запис на данни : вие можете да я запишете във файл на диска, във база от данни (която също е записана на хард диска), или вие можете да я изпратите по мрежата на друг компютър да я обработи.

Потоци

Потока е оснополагаща абстракция в файловете в .NET Framwork. Тази абстракция е проста но много силна. Тя е също използвана за мрежови конекции, изолирани файлове (isolated files) и разбира се паметта. Просто казано, потока представлява подренени серии от байтове. Потоците имат три основни операции: четене, писане и позициониране на курсора в този поток. Не всички потоци поддържат и трите операции, мрежовиа поток е read-only или write-only, и вие не можете да промените позицията в потока чрез сеекване (seeking) – с други думи, тои е последователен поток, а не с случаен достъп. Но файловите и потоците в паметта поддърват случаен достъп: от тях двата може да се чете и пише, и вие можете да позиционирате курсора в потока.



Има 2 типа потоци. Базовите потоци (base streams) четат и пишат директно от/към потоянна памет (backing store) – вероятно памет, файл, или мрежова конекция. Както казах преди, междинните потоци четат или пишат в друг поток, изпълнявайки някаква допълнителна функция като криптиране или буфериране. Нека отбележим че може да имаме повече от 1 междинен поток, свързани помежду си с друти потоци, които от своя страна могат да бъдат свързани с базов поток.

Конкретни потоци

Понеже Stream е абстрактен, вие не можете да го инстанцирате. Въпреки това има набор от класове, наследници на Stream които вие можете да използвате.

System.IO.FileStream, System.IO.IsolatedStorage.IsolatedStorageFileStream, System.IO.MemoryStream, и System.Net.Sockets.NetworkStream за базови класове. Те използват файлове, блокове памет (по специално масиви от байтове), и мрежовия сокет. Всички освен NetworkStream поддържат случаен достъп (random access), NetworkStream поддържа само последователен достъп. Вие давате на конструкторите на тези класове информация необходима да се свържем към постоянната памет (backing store) – път към файл или handle към файл за FileStream, относителен път за IsolatedStorageFileStream, набор от байтове за MemoryStream или сокет за NetworkStream.

BufferedStream предоставя буфер между вашето приложение и съответния базов поток, позволявайки ви да правите много четения и писания в буфера, без да предизвиквате никаква I/O дейност. Единственото условие е базовия поток да има достатъчно информация или място в своя буфер да задоволи вашите заявки към него.

CryptoStream позволява да криптирате или декриптирате поток, като използвайте обект, който имплементрира ICryptoTransform интерфейс (System.Security.Cryptography namespace). Стандартен начин да се сдобиете с инстанция на криптографичен обект който имплементира ICryptoTransform е да извикате една от имплементацията на CreateEncryptor и CreateDecryptor. Има и набор то класове които директно имплементират ICryptoTransform директно. Всичко на всичко, има към 20 различни криптиращи начини в .NET Framework – проверете System.Security.Cryptography Namespace за повече.

Стандартна задача за всеки програмист е да чете и пише данни от или към фаилове, мрежови сокети (network sockets) или към други уреди. BCL обхваща тази функционалност с помощта на абстрактния клас Stream (namespace System.IO). таблицата показва някои често използвани методи на този клас.





Метод

Описание

CanSeek

Можем ли да изместваме файловия маркер в този поток?

CanRead

Можем ли да четем от този поток?

CanWrite

Можем ли да пишем в този поток?

Length

Дължината на потока.

Position

Текущата позиция на файловия курсор.

Seek

Преместване на файловия курсор на друга позиция в потока.

Read

Четене на байтове от потока.

Write

Писане на байтове в потока.

Flush

Flush-ване на всичката буферирана информация в съответния hardware.

Close

Затвори потока и освободи всички ресурси (като socket-и и file handles). Данните се записват преди да се затвори потока.

Класът Stream също предлага методи за асинхронно четене и писане.

Тук вероятно се чудите защо Stream класът не предлага никакви методи за отваряне на поток. Начина на отваряне на поток от данни зависи от специфичния device. Stream е само абстактен клас. Таблицата показва някои често употребявани класове които наследяват Stream .

Име

Описание

FileStream


Буфериран поток, базиран на файл записан на хард диска.

NetworkStream

Небуфериран поток базиран на socket.

BufferedStream

Wrapper class който предоставя възможността да добави буфериране към съществуващ небуфериран поток.

MemoryStream

Поток, базиран на оперативна памет.

Как се създава един поток зависи от начина по който се създава съответния клас който се грижи за него. Например, обект от тип FileStream може да бъде създаден със статичниа метод OpenRead (за четене) и OpenWrite (за писане), които се предоставят от класа File от BCL (namespace System.IO).

Ето един пример за отваряне на файлов поток и записване на данни в него:

public static void WriteToFile()

{

// store ASCII "Hello" in a buffer and write it down in a file

Byte [] data = new Byte[] {104, 101, 108, 108, 111};
// open a new file for writing. Write the data

using ( FileStream fStream = File.OpenWrite("output.dat") )

{

fStream.Write( data, 0, data.Length );

fStream.Close();

}

}

Нека отбележим че FileStream обектът е създаден в using блок, което гарантира че фаиловия handle ще бъде правилно освободен независимо от изпълнението на на кода в блока след него. Това се прави да се подсигури че дори при наличие на грешка този файл ще бъде корентно завторен.


Кодиране на данните (Data Encoding)

Stream.Read и Stream.Write ви позволяват да четете и пишете данни във вид на байтове. Въпреки това като цяло програмистите предпочитат да работят със стрингове, пред масиви от байтове. Би било чудесно ако има начин да конвертираме стрингове към списъци от байтове и обратно.

От такава гледна точка, важно е да изясним разликите между байтове, символи и стрингове във .Net. Например в С#, System.Byte е unsigned byte (8-bit), System.Char е дву-байтов символ в UNICODE, и System.String е списък от UNICODE символи.

BCL предоставя набор от класове под System.Text namespace, с които можем да конвертираме между символи, байтове и стрингове използвайки най-различни кодиращи методи. Тези класове включват ASCIIEncoding, UnicodeEncoding, UTF7Encoding и UTF8Encoding да се занимават с ASCII, Unicode, UTF7 и UTF8 респективно. Sytem.Text namespace също разполага с друг клас Encoding, който expose-ва различните класове на кодиране на символите чрез стаични пропертита (properties). Следващия програмен код демострира употребата му:
public static void ConvertStrings()

{

// convert ASCII-encoded bytes to a UNICODE string

Byte [] byteBuffer = new Byte[] {104, 101, 108, 108, 111};

String UnicodeString = Encoding.ASCII.GetString( byteBuffer );

Console.WriteLine( UnicodeString );
// convert the string back to ASCII-encoded bytes

Byte [] convASCIIBuffer = Encoding.ASCII.GetBytes(UnicodeString);

foreach ( Byte curByte in convASCIIBuffer)

{

Console.Write( curByte );

}

Console.WriteLine("converted the string back to ASCII-encoded bytes");
// convert the string to UNICODE-encoded bytes

Byte [] convUnicodeBuff = Encoding.Unicode.GetBytes(UnicodeString );

foreach( Byte currByte in convUnicodeBuff )

{

Console.Write( currByte );

}

Console.WriteLine("converted the string to UNICODE-encoded bytes");

}



Асинхронно четене и писане в потоци (Asynchronous Stream I/O)

Нормално, четене/писане в поток става в синхронен режим за .NET Framework. Това означава че когато извикате входно-изходен метод за четене или писане, изпълнението на програмата спира докато четенето или писането е завършено.

Възможно е вашата програма да продължи изполнението си докато заявката за вход/изход (I/O request) се изпълни. Такъв начин на вход/изход се нарича асинхронен. Това може да бъде полезно в някои ситуации, където четете или пишете много големи количества от данни, но е доста по полезен когато става дума за четен или писане от мрежов сокет.

Ето как става асинхронният вход/изход. Вие започвате асинхронното I/O чрез извикване на BeginRead or BeginWrite, давайки масив от байтове, индиксите на байтовете за четене или писане, делегат който засяга незадължителен метод за обратна връзка (callback method), и обект да различава това четене/писане от другите. Тези методи връщат обект който имплементира IasyncResult – вашият ключ да видите резултатите от входно/изходната операция. За да се сдобиете с резултата от тази операция и да завършите асинхронната операция, в някакъв етап тр ябва да извикате EndRead или EndWrite, давайки им този IAsyncResult който сте получили от BeginRead / BeginWrite. EndRead връща броя на прочетените байтове, докато EndWrite просто финализира и изчиства обекта след тази заявка..

Ако I/O операцията не е все още завършила, когато извикате EndRead/ EndWrite, call-а ще бъде блокиран докато тази операция завърши. Извикването на тези методи НЕ прекратява операцията, дори напротив, те се блокират докато операцията завърши нормално.

Антернативен метод е да дадете метод за обратна връзка (callback method) който да бъде извикан когато I/O операцията е завършила. Този метод всема само 1 параметър от тип IasyncResult. В този случай, в метода трябва да се извика EndRead or EndWrite, като отново се дава референция към IasyncResult обект, и се добавя друга подходяща имплементация.



“Четци” и “Писачи” (Readers and Writers)


Използвайки класовете за кодиране, сега е възможно да ковертираме байтове към стрингове всеки път когато те са прочетени от FileStream обект и да конвертираме стринг в масив от байтове когато искаме да пишем в даден файл. За предпочитане е да не се занимавате с чисти байтове като входни или изходни данни като цяло.

BCL предоставя два класа, StreamReader и StreamWriter, да ви помогнат. StreamReader чете данни от даден поток от данни във вид на символи или цели редове. StreamWriter пише данни в даден поток във вид на символи или стрингове. Желаният начин на кодиране на данните може да се специфицира още в конструктора, като по този начин всички данни ще бъдат конвертирани във този вид. Ето и един пример в който се чете един ASCII стринг от един входен файл и се записва обратно във UNICODE формат в друг изходен файл:



public static void ReadASCIIWriteUnicode()

{

String sReadLine;

using (FileStream inputFile = File.OpenRead( "input.txt" ))

{

// initialize a reader to read ASCII byte stream

using (StreamReader asciiReader =

new StreamReader( inputFile, Encoding.ASCII ))

{

// read a line from the opened file as a ascii byte stream

sReadLine = asciiReader.ReadLine();

asciiReader.Close();

}
// close the input file

inputFile.Close();

}
using (FileStream outputFile = File.OpenWrite("output.txt") )

{

// initialize the stream as a UNICODE

using (StreamWriter unicodeWriter =

new StreamWriter( outputFile, Encoding.Unicode ))

{

unicodeWriter.WriteLine( sReadLine );

unicodeWriter.Close();

}
// close the output file

outputFile.Close();

}

}
Tрябва да отбележим употребата на use statements за StreamReader и StreamWriter. И двата класа имплементират интерфейса IDisposable и освобождават всички unmanaged resources чрез Dispose() метода. Също така е добре да отбележим че и двата класа дефинират конструктрор който взема като параметър име на файл да бъде отворен (вместо да се използва Stream обект, както е в текущия пример). Ако използваме класовете по този начин, това ни спестява работата със Stream обектите.

StreamReader ни предоставя уникална функционалност, с която вие трябва да се запознаете. Ако вие не сте убедени какъв начин на кодиране е използван в даден входен файл, вие можете да инструктирате StreamReader обекта да установи начина на кодиране на данните в него по време на самото създаване на обекта. Ако параметъра detect-encoding е сложен на true, “четеца” поглежда първите 3 байта на потока (наречени Byte Ordered Masks - BOM) за да установи начина на кодиране. С този клас можете да четете UTF8 и Unicode схеми за кодиране, ако файла започва с правилно форматирани Byte Ordered Masks.

StreamReader и StreamWriter са перфектни за работа със символен вход и изход, но какво ще правим ако искате да четете директно други типове от данни като integer, Boolean, float и т.н. За да реши този проблем BCL предлага още два класа, BinaryReader и BinaryWriter под System.IO. Тези два класа предлагат методи за четене и писане на много базови типове във и от даден поток от данни. Ето и един пример който показва употребата на тези два класа:

public static void BinaryReaderBinaryWriterUsage()

{

int intValue;

double dbValue;
// open the input file

using (FileStream inputFile = File.OpenRead("input.txt") )

{

using (BinaryReader binaryRead =

new BinaryReader( inputFile, Encoding.ASCII ))

{

intValue = binaryRead.ReadInt32(); // read int

dbValue = binaryRead.ReadDouble();// read double
binaryRead.Close();// close the reader

}
// close the input file

inputFile.Close();

}
// open the output file

using (FileStream outputFile = File.OpenWrite( "output.txt" ))

{

using (BinaryWriter binaryWriter =

new BinaryWriter( outputFile, Encoding.Unicode ))

{

binaryWriter.Write( intValue ); // write int value

binaryWriter.Write( dbValue );// write double value



binaryWriter.Close(); // close the writer

}
// close the output file

outputFile.Close();

}

}

Нека само да отбележим че класът BinaryWriter може да бъде използван и за писане на стрингове. Един от overloaded Write() методи на класа взема стринг като агрумент и го пише като поток от байтове (базиран на кодидрането което използва класът BinaryWriter).


XML readers and writers

Разбира се концепцията за четците и писачите е далече по силна и удобна комбинирана с нещо толкова силно като XML.

Така че ние имаме клас “писач’, System.Xml.XmlTextWriter, който форматира вашите данни във XML, и после ги пише в поток. Този клас почти винаги се използва заедно с класа System.Xml.XmlConvert, който има методи за конвертиране на базовите класове за .NET Framework във правилно форматирани и изградени XML стрингове.

За четене на XML, .NET Framework има 3 класа:



For reading XML, the .NET Framework has three classes:

  • System.Xml.XmlTextReader

  • System.Xml.XmlValidatingReader

  • System.Xml.XmlNodeReader

XmlNodeReader взема XmlNode като вход за своя контруктор, и се използва предимно за работа с XML DOM поддърветa.Другите два класа, XmlTextReader и XmlValidatingReader, могат да бъдат използвани с входни потоци. Основната разлика е че XmlValidatingReader може да валидира входа според дадена схема, която вие предоставяте.


База данных защищена авторским правом ©obuch.info 2016
отнасят до администрацията

    Начална страница