Оба класса File и FileInfo предоставляют методы OpenRead() и OpenWrite(), которые облегчают создание объектов FileStream. Первый открывает метод с режимом доступа "только чтение", а второй, кроме того, разрешает осуществлять запись. Это позволяет упростить обращение к файлу, так что вам теперь не придется указывать всю информацию, как в предыдущих примерах. Например, следующая строка открывает файл Data.txt в режиме "только чтение":
FileStream aFile = File.OpenRead("Data.txt");
Обратите внимание на то, что следующий код выполняет те же самые действия:
FileInfo aFile = new FileInfo("Data.txt") ; | |
FileStream aFile = aFile OpenRead() ; |
8.1.7.Позиция внутри файла
Класс FileStream поддерживает внутренний указатель файла, ссылающийся на то место в файле, в котором будет производиться очередная операция чтения или записи. В большинстве случаев при открытии файла указатель устанавливается в начало файла, однако такое поведение указателя может быть изменено, что позволяет приложению осуществлять чтение или запись в любой точке файла. Можно также организовать произвольный доступ к файлу и осуществлять поиск конкретной позиции в файле. Такой подход позволяет сэкономить массу времени при работе с очень большими файлами, поскольку можно постоянно отыскивать необходимую позицию.
Метод, который реализует данную функциональную возможность, носит название Seek ():
public long Seek(long offset, SeekOrigin origin);
Первый параметр определяет, насколько далеко вперед в байтах должен быть передвинут указатель. Второй параметр определяет, с какой точки вести отсчет. Перечислимый тип SeekOrigin состоит из трех значений: Begin (начало), current (текущая позиция) и End (конец). Следующая строка передвигает указатель файла на восьмой байт, считая с самого первого байта данного файла:
aFile.Seek(8, SeekOrigin.Begin);
Следующая строка кода приведет к перемещению указателя файла на два байта вперед относительно его текущей позиции. Если эта строка будет выполнена непосредственно сразу после предыдущей строки, то указатель будет ссылаться на десятый байт файла:
aFile.Seek(2, SeekOrigin.Current);
Следует обратить внимание на то, что как при чтении из файла, так и при записи в файл происходит изменение позиции указателя файла. В случае считывания, например, десяти байтов, указатель файла будет ссылаться на байт, расположенный после десятого считанного байта.
Существует также возможность указывать отрицательные значения для определения требуемой позиции, что в сочетании с использованием значения перечислимого типа SeekOrigin.End может быть использовано для поиска необходимой позиции вблизи конца файла. Следующая строка позволит перейти к пятому с конца байту файла:
aFile.Seek(-5, SeekOrigin.End);
Файлы, доступ к которым может осуществляться подобным образом, зачастую называют файлами с произвольным доступом, поскольку у приложения имеется возможность получить доступ к любой позиции в файле. Классы Stream позволяют осуществлять последовательный доступ к файлам, и в них не предусмотрена возможность работы с указателем.
8.1.8.Чтение данных
Чтение данных с использованием класса FileStream оказывается не таким простым, как с использованием классов StreamReader и StreamWriter, к рассмотрению которых мы вернемся ниже в данной главе. Это происходит потому, что класс FileStream имеет дело исключительно с байтами, что делает класс полезным при работе с файлами произвольного типа, а не только с текстовыми файлами. Побайтное считывание данных позволяет использовать этот класс при работе с графическими и звуковыми файлами. Однако платой за подобную гибкость является невозможность с помощью класса FileStream считывать данные непосредственно в переменные типа строки. Это позволяют осуществлять классы StreamWriter и StreamReader. Существует, однако, несколько конверсионных классов, которые существенно облегчают выполнение преобразования массива байтов в символьные массивы и обратно.
Метод FileStream.Read() является основным средством, позволяющим осуществлять доступ к данным, содержащимся в файле, на который ссылается объект FiieStream:
public int Read(byte[] array, int offset, int count);
Первый параметр представляет собой массив байтов, передаваемый для размещения в нем данных, получаемых от объекта FileStream. Второй параметр определяет ту позицию в массиве байтов, начиная с которой должна осуществляться операция записи данных. Обычно этот параметр имеет значение, равное нулю, в результате чего данные из файла заносятся в самое начало массива. Последний параметр определяет, какое количество байтов должно быть считано из файла.
Следующий пример демонстрирует считывание данных из файла с произвольным доступом. В качестве файла, из
которого будет производиться чтение, используется файл, специально созданный для данного примера. ПРАКТИКУМ. Чтение данных из файла с произвольным доступом
1. Откройте Visual Studio.NET.
2. Откройте новый проект, выбрав File | New j Project. Выберите пункт С# Console Application и назовите его ReadFile.
3. Добавьте два следующих объявления пространств имен в самое начало файла Class1.cs. Пространство имен System.IO необходимо для класса FiieStream, а пространство Имен System.Text потребуется для осуществления преобразования, которое нам придется выполнять над байтами, считанными из файла.
using System; using System.IO; using System.Text;
4. Добавьте следующий код в метод Main():
static void Main(string[] args)
{
byte[] byData = new byte[100]; char[] charData = new Char[100]; try { FileStream aFile = new FileStream("../../Classl.cs",FileMode.Open); aFile. Seek (55, SeekOngin.Begin) ; aFile.Read(byData,0,100); }
catch(IOException e) {
Console.WriteLine("An IO exception has been thrown!");
Console.WriteLine{e.ToString ()) ;
Console.ReadLine(); return; }
Decoder d = Encoding. UTF8.GetDecoder();
d.GetChars(byData, 0, byData.Length, charData, 0);
Console.WriteLine(charData); Console.ReadLine(); return;
}
5. Запустите приложение. Нажмите клавишу Enter для закрытия консоли
Как это работает
Чтение данных из файла с произвольным доступом
Данное приложение открывает свой собственный файл с расширением .cs, из которого затем осуществляется чтение.
Для этого оно осуществляет поиск по файловой структуре с использованием двух точек в следующей строке:
FileStream aFile = new FileStream(" ./. ./Classl.cs", FileMode.Open) ;
Две строки кода, которые в действительности осуществляют поиск по файлу и считывание из него, имеют следующий вид: try { aFile.Seek(55,SeekOrigin.Begin); aFile. Read (byData, 0,100); }
catch(IOException e) {
Console.WriteLine("An 10 exception has been thrown!");
Console.WriteLine(e.ToString());
Console.ReadLine(); return; }
В результате выполнения первой из них указатель файла перемещается на 55-й байт файла. Это символ 'n' из слова namespace; 54 предыдущих символа находятся в строках с директивами using. В результате выполнения второй строки следующие 100 байтов считываются в байтовый массив byData.
Обратите внимание на то, что нами используется конструкция для обработки исключительных ситуаций try-catch. Практически любая операция, связанная с вводом/выводом, может привести к возникновению исключительной ситуации типа IOException. Любой исполнимый код должен включать в себя обработку исключительных ситуаций, особенно если в нем ведется работа с файловой системой.
После того как информация считана из файла в байтовый массив, возникает необходимость преобразовать ее в символьный массив с тем, чтобы получить возможность выводить ее на консоль. С этой целью воспользуемся классом Decoder из пространства имен System.Text. Этот класс предназначен для того, чтобы преобразовывать исходные байты в какие-либо более полезные объекты, например, в символы:
Decoder d = Encoding.UTF8.GetDecoder(); | |
d.GetChars(byData, 0, byData.Length, charData, 0) |
;
В этих строках осуществляется создание объекта Decoder, который основывается на кодировке UTF8. Это схема кодирования Unicode. После этого вызывается метод GetChars (), который принимает массив байтов и преобразовывает его в массив символов. После завершения этой процедуры массив символов оказывается готовым к выводу на консоль.
8.1.9.Запись данных
Процесс записи данных в файл с произвольным доступом совершенно аналогичен. Сначала необходимо создать байтовый массив; наиболее простой способ достигнуть этого – создать сначала символьный массив, который мы собираемся записывать в файл. Затем следует воспользоваться объектом Encoder, позволяющим преобразовать его в массив байтов; использование этого объекта очень напоминает использование объекта Decoder. В завершение необходимо вызывать метод Write (), который и запишет информацию в файл.
Давайте создадим простой пример, который позволит продемонстрировать, каким образом это выполняется.
ПРАКТИКУМ. Запись данных в файл с произвольным доступом
1. Создайте новый проект. Выберите пункт меню С# Console Application и назовите его WriteFile.
2. Как и в предыдущем случае, вставьте в начало файла Class1.cs следующие два описания пространств имен: using System; using System.IO; using System.Text;
3. Добавьте в метод Main() следующий код:
static void Main(string[] args) { byte[] byData = new byte[100]; char[] charData = new Char [100] ; try {
FileStream aFile = new FileStream("Temp. txt" . FileMode.OpenOrCreate) ; charData = "Hello World".ToCharArray(); Encoder e = Encoding.UTF8.GetEncoder();
e.GetBytes (charData, 0,charData.Length, byData, 0 , true); // Перемещение указателя файла в самое начало файла aFile.Seek(0,SeekOrigin.Begin); aFile.Write(byData,0,byData.Length); }
catch(IOException ex) {
Console.WriteLine("An 10 exception has been thrown!") ;
Console.WriteLine(ex.ToString());
Console.ReadLine(); return; }
return; }
4. Запустите приложение. Оно будет выполняться некоторое время, а затем завершится.
5. Перейдите в директорию приложения – файл будет сохранен именно в ней, поскольку мы используем относительный путь. Файл будет помещен в папку WriteFile\bin\Debug. Откройте файл Temp.txt.