Программный код большинства примеров данной лекции можно найти в проектах, доступных для просмотра: DocOne14.
Как правило, приложения Office имеют дело с файлами, хранящими документы этих приложений. Это файлы с документами Word (с расширениями.doc), рабочими книгами Excel (с расширениями.xls), базами данных Access (с расширениями.mdb). Операции по созданию, открытию и записи этих файлов выполняются с помощью соответствующих методов приложений, которые запускаются командами меню File. Но офисные системы должны уметь работать и с файлами других форматов. Например, часто требуется считывать данные из обычных текстовых файлов и заносить информацию, в них хранящуюся, в таблицы Excel или базы данных Access. Возможна и обратная ситуация, когда, используя данные из приложений Office, требуется сохранять полученную информацию в файлах. В общем, есть некоторые ситуации, когда предпочтительнее работать с собственными файлами, а не со стандартными базами данных. По этой причине в VBA включены средства для работы с внешними файлами, позволяющие их создавать, открывать для работы и осуществлять ввод-вывод данных. Файлы VBA делятся на три группы.
Input ), записи ( Output ), или присоединения ( Append ). Специальных средств поиска для них нет, и чтобы получить данные из такого файла, его нужно открыть и прочесть последовательно все данные до нужного места. Изменения (запись данных) в таком файле всегда происходят в его конце. Существенной особенностью этого файла является редактирование данных в процессе записи и чтения. В файл можно записывать данные разных типов, При вводе они будут отредактированы и преобразованы в строку. Таким образом, Можно заметить, что среди допустимых моделей файлов VBA нет наиболее общей, когда разрешен прямой доступ к элементам файла, когда элементы могут иметь произвольную длину, когда ключ может быть произвольным и не обязательно должен задавать номер записи. Отсутствие такого типа файлов действительно представляет серьезное ограничение, но этому есть и не менее серьезное оправдание. В таких сложных ситуациях рекомендуется переходить на работу с базой данных. Это тем более оправдано тем, что база данных Access доступна из любого приложения Office 2000.
Типичный цикл работы программы с файлом любого типа начинается с открытия или создания файла оператором Open. При этом задается
Вот какие операторы обычно применяются для записи и чтения данных из файлов в зависимости от режима доступа.
| Тип доступа | Запись данных | Чтение данных |
|---|---|---|
| Последовательный | Print#, Write# |
Input#, Line Input# |
| Произвольный | Put |
Get |
| Бинарный | Put |
Get |
Файлы перечисленных видов открываются и могут создаваться оператором Open, который вызывается так:
Open имя-файла For режим [Access доступ] [блокировка] As [#]номерФайла [Len=длина-записи]
имя-файла - строковое выражение, задающее имя открываемого (создаваемого) файла, в него может входить также путь ѕ имя диска и имена каталогов (папок) на пути к файлу.режим - ключевое слово, которое для файлов последовательного доступа может принимать одно из значений: Input (ввод), Output (вывод), Append (присоединение). Для файлов с произвольным доступом в качестве режима нужно указать Random, а для бинарных файлов - Binary.доступ необязателен, он ограничивает набор операций, которые разрешено выполнять над открываемым файлом; возможные значения: Read (только чтение), Write (только запись), Read Write (и то и другое).блокировка позволяет ограничить набор операций над открываемым файлом, выполняемых другими процессами; возможные значения: Shared (разделяемый, другим процессам разрешается работать с файлом без всяких ограничений), Lock Read (запретить чтение), Lock Write (запретить запись), Lock Read Write (запретить чтение и запись). Здесь требуется некоторые пояснения. С одним и тем же физическим файлом одновременно могут работать разные приложения. Возможна и ситуация, когда одно приложение одновременно работает с несколькими экземплярами файла, присваивая каждому экземпляру свой уникальный номер. Параметр блокировка позволяет регулировать отношения между клиентами, получающими доступ к файлу.номерФайла - целое число в интервале от 1 до 511, идентифицирующее файл для других операций. Для каждой операции открытия файла номер-файла должен отличаться от номеров всех открытых в текущий момент файлов. Узнать очередной свободный номер позволяет функция FreeFile.длина-записи - число не более 32767 - обязателен для файлов произвольного доступа и задает для них длину одной записи файла в байтах. Для Если Output ), существует, его содержимое будет утеряно, и данные будут записываться в его начало. Если его нет, создается новый файл. Точно так же будет создан новый файл, если файл, определяемый параметром имя-файла, не существует, а режим открытия задан как Append, Random или Binary. При попытке открыть несуществующий файл в режиме Input будет получено сообщение об ошибке.
Последовательные файлы в режиме чтения, файлы произвольного доступа и бинарные можно открывать по несколько раз с различными номерами. С каждым из номеров будет связан свой буфер, что может повысить эффективность работы с файлом. Файл, открытый для чтения, должен быть закрыт перед его открытием в режимах записи Output или Append.
Рассмотрим детали открытия и создания файлов. В первом примере открывается файл последовательного доступа "read.me" для чтения:
Open "read.me" For Input As #1
Имя можно передавать и через переменную или более сложное строковое выражение:
Const Path = "e:\O2000\CD2000\Ch14\" Public Sub Openfile() Dim NewFile As String, NewNum As Integer NewFile = "read.me" NewNum = FreeFile ' очередной незанятый номер файла Open Path NewFile For Output As NewNum End Sub
Заметьте, при первом запуске этой программы был создан новый пустой файл с заданным именем, поскольку файл не существовал в момент открытия. Поскольку при открытии указан полный путь, то файл был создан в указанном каталоге, в противном случае он был бы создан в том же каталоге, где находится документ, инициировавший его создание.
Файл с последовательным доступом особенно удобен, когда при работе с ним требуется последовательно прочесть и обработать все записи файла. Если же данные из файла обрабатываются в произвольном порядке, или файл открывается для того, чтобы получить доступ к небольшому числу его записей, крайне желательно иметь возможность произвольного (прямого) доступа к нужному элементу файла. Такую возможность и обеспечивают файлы VBA с произвольным доступом, хотя у них есть серьезное ограничение, все записи должны иметь одинаковый размер. Это крайне неудобно в наиболее типичном случае, когда элементами файла с произвольным доступом являются записи, длины которых, естественно, могут быть не постоянны из-за, например, наличия в них строк переменной длины. В таких ситуациях одним из выходов является переход к строкам постоянной длины. Радикальное решение, как мы уже говорили, состоит в том, чтобы в подобных ситуациях переходить на работу с базами данных. Рассмотрим пример открытия двух файлов - файла с произвольным доступом для хранения записей пользовательского типа и бинарного файла для хранения картинки. Вначале дадим объявление пользовательского типа:
Type Person ' пользовательский тип записи. Возраст As Integer Фамилия As String * 20 Имя As String * 20 End Type
Обратили внимание на то, что фамилия и имя заданы, как строки постоянной длины. В данном случае это принципиально, поскольку предполагается возможность хранения переменных этого типа в файле с произвольным доступом. Вот процедура открытия двух файлов:
Public Sub OpenTwoFiles() Dim attr As Integer, Num As Integer Dim MyFriend As Person ' объявление переменной ' Открываем файл с произвольным доступом для записи: Num = FreeFile Open "Friends" For Random Access Read Write As Num Len = Len(MyFriend) PrintAttr (Num) Debug.Print "Длина Записи:", Len(MyFriend) ' Открываем двоичный файл Num = FreeFile Open "pict.bmp" For Binary Access Read Lock Read As Num PrintAttr (Num) End Sub Public Sub PrintAttr(ByVal Num As Integer) 'Эта процедура по номеру файла определяет его атрибуты 'и выводит соответствующее сообщение в окно отладки Dim Msg As String, attr As Integer attr = FileAttr(Num, 1) Debug.Print "файл # ", Num Select Case attr Case 1: Msg = "Input" Case 2: Msg = "Output" Case 4: Msg = "Random" Case 8: Msg = "Append" Case 32: Msg = "Binary" Case Else: Msg = "Таких файлов не бывает" End Select Debug.Print "Открыт для ", Msg End Sub
В этом примере для определения свободного номера файла использовалась функция FreeFile, а для определения длины записи ѕ функция Len. Файл произвольного доступа открыт как для чтения, так и для записи, а бинарный файл только для чтения. Заметьте, что бинарный файл закрыт для чтения другими процессами. Поэтому если повторно запустить процедуру открытия, то файл произвольного доступа будет повторно открыт с другим номером, но возникнет ошибка при повторном открытии бинарного файла. Возможность одновременного открытия под разными номерами нескольких логических экземпляров одного и того же физического файла бывает крайне полезной в ряде ситуаций.
Функция
FileAttr(номер-файла, 1)
по номеру открытого файла возвращает число, указывающее режим открытия: 1 - Input, 2 - Output, 4 - Random, 8 - Append, 32 - Binary. Второй аргумент достался функции FileAttr в наследство от 16-разрядного режима, где при его значении 2, она возвращала системный указатель на файл.
Мы написали простую процедуру PrintAttr, которая по номеру файла определяет его атрибуты и выводит сообщение в окно отладки. Приведем результаты работы процедуры OpenTwoFiles:
файл # 1 Открыт для Random Длина Записи: 42 файл # 2 Открыт для Binary
Для закрытия файла служит оператор Close, вызываемый:
Close [список-номеров-файлов]
Параметр список-номеров-файлов может содержать один или несколько номеров ранее открытых файлов, которые должны быть закрыты. Как обычно, номера в списке разделяются запятыми. Если параметр опущен, то закрываются все открытые в данный момент файлы. После выполнения оператора Close номера закрытых файлов снова можно использовать при открытии в операторе Open.
Закрыть все открытые оператором Open файлы может также Close без параметров, он освободит все номера открытых файлов и перенесет содержимое их буферов на диск.
При записи и чтении данных в
Print#, представляющий данные в формате, подготовленном для отображения на экране дисплея. По существу, мы уже хорошо знакомы с этим оператором, поскольку ни одна наша программа не обходилась без вывода данных в окно отладки, а применяемый для этих целей метод Print объекта Debug по синтаксису и по действию подобен оператору Print#.Write#. Заметим, в каком - то смысле это более универсальный оператор, чем Print, он, например, не зависит от локализации.Оператор Print# записывает в файл форматированные данные. Его вызов имеет вид:
Print #номер-файла, [список-вывода]
Здесь номер-файла - номер файла, открытого в режиме последовательного доступа для записи ( Append или Output ), список-вывода - одно или несколько выражений, разделенных пробелами или точками с запятой, определяющих выводимые данные. Каждое выражение в этом списке может быть снабжено дополнительной информацией о расположении данных, делающее их восприятие более удобным для пользователя. Общий вид такого выражения:
[{Spc(n) | Tab[(n)]}] [выражение] [симв-поз]
Spc (n) задает количество вставляемых пробелов.Tab(n) указывает абсолютный номер n столбца, в котором будет начинаться текст, определяемый параметром выражение. Если не указывать аргумент у Tab, то позицией вставки является следующая зона печати. (Можно полагать, что при выводе экран разделен на зоны, и табуляция позволяет переходить от одной зоны к другой).выражение задает выражение, значение которого после редактирования и преобразования его в текстовую строку будет записано в файл. На выражения не накладывается особых ограничений, Это могут быть как строковые, так и числовые выражения, они могут содержать данные разных типов. Даты записываются в файл в кратком формате, значения булевых переменных - как ключевые слова Ложь ( False ) или Истина ( True ), пустое значение Empty - как пустое слово, нулевое значение Null выводится как "Null". Данные об ошибке выводятся в виде кода ошибки. Числовые данные выводятся в формате, зависящем от локализации. При выводе можно вызвать для форматирования данных функцию Format.симв-поз задает положение следующего выражения. Если он равен ";" или пробелу, вставка следующего выражения будет происходить сразу за последним выведенным символом, Tab(n) указывает номер n столбца, начиная с которого будет выводиться выражение. Tab без аргумента или "," - переход в следующую позицию табуляции (зону печати).Создадим теперь открытый ранее файл read.me, записывая в него выражения, позволяющие продемонстрировать все возможности оператора Print:
Public Sub WritingWithPrint() Dim MyBool As Boolean, MyDate As Date Dim MyNull As Variant, MyFloat As Double, MyErr As Variant Print #1, "Первая строка файла" ' запись текстовой строки. 'Первая строка файла Print #1, "Зона 1"; Tab; "Зона 2" ' запись в двух позициях табуляции. 'Зона 1 Зона 2 Print #1, "Зона 1", "Зона 2" ' запятая разделяет как табуляции. 'Зона 1 Зона 2 Print #1, "Привет,"; "старик!" '; склеивает выражения. 'Привет,старик! Print #1, "Привет,"; "старик!" ' пробел действует аналогично. 'Привет,старик! Print #1, Spc(5); "5 пробелов " ' вставка 5 пробелов слева. ' 5 пробелов Print #1, Tab(20); "Старик,"; Tab(10); "привет!" ' печать в соответствующих столбцах. ' Старик ' привет! Print #1, "one"; " "; "two" 'разделение пробелом 'one two MyBool = False ' булева переменная Print #1, MyBool 'False MyDate = #6/14/1999# ' значение даты Print #1, MyDate ' печать в кратком формате '14.06.99 MyNull = Null ' нулевое значение Print #1, MyNull 'Null MyFloat = 3.1416 'вещественное значение ' использование функции Format: Print #1, MyFloat, Format(MyFloat, "#.00"), Format(MyFloat, "00.000") '3,1416 3,14 03,142 On Error Resume Next Err.Raise 6 MyErr = Err Print #1, MyErr MyErr = CVErr(2000) Print #1, MyErr End Sub
Здесь в комментариях показан ожидаемый вид показа строки при ее чтении и печати. Чтобы не томить ожиданием, давайте сразу приведем процедуру, которая читает и выводит в окно отладки записи этого файла:
Public Sub ReadingWithLine() Dim MyStr As String Close #1 Open Path "read.me" For Input As #1 Do While Not EOF(1) Line Input #1, MyStr Debug.Print MyStr Loop End Sub
Текст в окне отладки выглядит в полном соответствии с нашими ожиданиями. Вот он:
Первая строка файла Зона 1 Зона 2 Зона 1 Зона 2 Привет,старик! Привет,старик! 5 пробелов Старик, привет! one two False 14.06.99 Null 3,1416 3,14 03,142 6 Error 2000
Обратите внимание, ошибки, возбуждаемые методом Raise и функцией CVErr, редактируются по-разному, в первом случае записывается только код ошибки, во втором ѕ код сопровождается ключевым словом Error, не переводимым в локализованных версиях.
Заметьте, точно такой же текст можно получить, если в процедуре WritingWithPrint заменить оператор Print на Debug.Print, поскольку этот метод и этот оператор порождают одинаковый результат, разница лишь в том, что один из них помещает результаты в файл, а другой ѕ в окно отладки.
При выводе данных можно управлять шириной выводимых строк. Ширину выводимых строк для открытого файла устанавливает оператор:
Width #номер-файла, ширина
Параметр ширина принимает значения от 0 до 255. Если при печати в файл оператором Print добавление значения очередного выражения в строку приведет к превышению установленной границы, это значение будет печататься с новой строки. При этом значение одного выражения не переносится, даже если его длина превышает установленную границу. Например, если для файла с номером 1 ограничить ширину строки 5 символами:
Width #1,5
а затем напечатать строку:
Str = "раму мылом" Print #1, "мама", "мыла", Str
то в файле окажутся строки:
мама мыла раму мылом
Обращаем внимание и на то, что переход к новой строке может происходить и при встрече с разделителем Tab(n), если n задает позицию меньшую, чем текущая позиция. Пример такой ситуации встречался в нашей процедуре.
Как было показано, оператор Print вводит в файл данные разных типов, редактируя их в момент ввода и преобразуя в текстовый формат. Попутно он занимается и форматированием текста, приводя его к форме, удобной для отображения на дисплее. Во многих ситуациях форматирование, ориентированное на выдачу на экран дисплея, не является необходимым. В этих случаях для создания Write#. Его синтаксис:
Write #номер-файла, [список-вывода ]
Параметр номер-файла - номер открытого файла с последовательным доступом, список-вывода - одно или несколько разделенных запятыми числовых или строковых выражений, значения которых записываются в файл. Разделителями выражений могут быть пробелы и точка с запятой.
Также как и оператор Print, оператор Write производит редактирование данных в момент записи. При этом приняты следующие соглашения:
#TRUE# и #FALSE#.#ERROR код-ошибки#.Empty ) ничего не записываетсяNull записывается как #Null#.В отличие от оператора Print, оператор Write# вставляет при записи запятые между соседними выражениями и заключает строковые значения в кавычки. После записи последнего выражения вставляется перевод на новую строку (Chr(13) + Chr(10)).
Следующая процедура создает файл с последовательным доступом "readme.txt" и записывает в него данные с помощью оператора Write#. При записи мы старались повторить содержимое предыдущего файла, чтобы можно было сравнить работу, Print и Write. Вот текст этой процедуры:
Public Sub WritingWithWrite() Dim MyStr As String, MyBool As Boolean, MyDate As Date Dim MyNull As Variant, MyFloat As Double, MyErr As Variant 'Открытие файла readme.txt Open Path "readme.txt" For Output As #7 'Создание файла Write #7, "Первая строка файла" ' запись текстовой строки. Write #7, "Зона 1", "Зона 2" ' запись двух строк. Write #7, "Привет,", "старик!" 'еще две строки MyStr = "раму мылом. " Write #7, "Мама ", "мыла ", MyStr MyBool = False ' булева переменная Write #7, MyBool MyDate = #6/14/1999# ' значение даты Write #7, MyDate ' запись даты MyNull = Null ' нулевое значение Write #7, MyNull MyFloat = 3.1416 'вещественное значение ' использование функции Format: Write #7, MyFloat, Format(MyFloat, "0.00"), Format(MyFloat, "00.000") On Error Resume Next Err.Raise 6 MyErr = Err Write #7, MyErr MyErr = CVErr(2000) Write #7, MyErr End Sub
Результат выполнения этой процедуры - файл "readme.txt" - в текстовом редакторе выглядит так:
"Первая строка файла" "Зона 1","Зона 2" "Привет,","старик!" "Мама ","мыла ","раму мылом. " #FALSE# #1999-06-14# #NULL# 3.1416,"3,14","03,142" 6 #ERROR 2000#
Сравните эти строки с теми, что выдаются оператором Print.
Для чтения данных из файлов последовательного доступа используются операторы:
Input# ѕ данные в файле записаны оператором Write#LineInput# ѕ данные в файле записаны оператором Print#.Оператор Input# вызывается так:
Input #номер-файла, список-переменных
номер-файла - номер открытого файла,список-переменных - одно или несколько разделенных запятыми имен переменных, в которые будут считываться данные из файла. Переменные могут быть разных типов, естественное требование их тип должен быть согласован с типом записи в момент ее создания. Переменные не должны быть массивами или объектами, но могут быть элементами массивов или полями пользовательских записей.При чтении автоматически выполняются преобразования данных, обратные тем, что использовались при записи оператором Write#. В частности, кавычки вокруг строк, разделяющие запятые и пустые строки, игнорируются. Слово #NULL# дает значение Null, #TRUE# и #FALSE# переводятся в булевы значения True и False, универсальные даты вида #yyyy-mm-dd hh:mm:ss# переводятся в формат соответствующих переменных типа дата/время, #ERROR номер-ошибки# при чтении передает в переменную типа Variant номер ошибки. Если для числовой переменной соответствующие по порядку в файле данные будут не числовыми, ей будет присвоено значение 0.
По достижении конца файла, следующая попытка чтения из него приведет к ошибке. Во избежание этого для проверки на конец файла используется функцию:
EOF(номер-файла)
Она возвращает булево значение True, когда при чтении достигается конец файла, открытого в режиме последовательного доступа Input или произвольного доступа Random.
Приведем процедуру чтения записей ранее созданного файла "readme.txt". Заметьте, мы поступаем корректно, читаемые переменные имеют тип, согласованный с типом читаемой записи.
Public Sub ReadingWithInput() Dim MyStr As String, MyBool As Boolean, MyDate As Date Dim MyNull As Variant, MyFloat As Double, MyErr As Variant Dim i As Integer 'Открытие файла readme.txt Open Path "readme.txt" For Input As #7 'Чтение файла 'Первые 8 строк For i = 1 To 8 Input #7, MyStr Debug.Print MyStr Next i 'Данные разных типов Input #7, MyBool, MyDate, MyNull, MyFloat Debug.Print MyBool, MyDate, MyNull, MyFloat 'читаем отформатированные числа Input #7, MyStr Debug.Print MyStr Input #7, MyStr Debug.Print MyStr 'дважды читаем данные типа Error Input #7, MyErr Debug.Print MyErr Input #7, MyErr Debug.Print MyErr 'чтение до конца файла Do While Not EOF(7) Input #7, MyStr Debug.Print MyStr Loop End Sub
В результате будет напечатано:
Первая строка файла Зона 1 Зона 2 Привет, старик! Мама мыла раму мылом. False 14.06.99 Null 3,1416 3,14 03,142 6 Error 2000
Обратите внимание, процедура завершается циклом, типичным при чтении
Для построчного ввода данных из файлов с последовательным доступом, созданных оператором Print, вызывается оператор Line Input#:
Line Input #номер-файла, переменная
Здесь номер-файла имеет тот же смысл, что и для оператора Input, а переменная - имя строковой переменной или переменной типа Variant, в которую будет прочитана очередная строка файла. Оператор Line Input# считывает данные посимвольно, пока не обнаружит признак конца строки - символ возврата каретки ( Chr(13) ) или пару "возврат каретки - перевод строки" ( Chr(13) + Chr(10) ). Эти признаки в переменную не записываются. Никаких преобразований данных при построчном вводе не производится. Мы уже приводили пример чтения файла "read.me", созданного оператором Print#.
Следует заметить, что любой файл может быть открыт для чтения и прочитан оператором Line Input#. Другое дело, что, чаще всего, это не даст желаемого результата. Если, например, прочитать файл "readme.txt" с использованием оператора Line Input#, то будут получены следующие результаты:
"Первая строка файла" "Зона 1","Зона 2" "Привет,","старик!" "Мама ","мыла ","раму мылом. " #FALSE# #1999-06-14# #NULL# 3.1416,"3,14","03,142" 6 #ERROR 2000#
Несмотря на успех чтения, результаты не приемлемы, поскольку не выполняется нужного редактирования. Сохраняются кавычки, запятые, ограничители, в одной строке содержатся данные разных типов. Поэтому оператор Line Input # следует применять, как правило, для чтения строк из файлов, записанных с помощью оператора Print#.
Оператор Line Input # позволяет читать файл строка за строкой. Наряду с этим в языке VBA есть возможность чтения из файла произвольного числа символов или произвольного числа байтов. Для чтения символов Input или Binary файлов используется функция Input, имеющая следующий синтаксис:
Input(размер, [#]номер-файла)
где размер - количество считываемых символов, номер-файла - номер открытого файла (последовательного или бинарного). Данные, считываемые этой функцией, обычно должны быть записаны в файл операторами Print# или Put.
Еще один вариант этой функции:
InputB(размер, [#]номер-файла)
позволяет считывать из файла заданное количество байтов (не обязательно соответствующих символам). Заметьте, эта функция читает все символы, хранимые в файле, в том числе запятые, ограничители, символы конца строки и другие.
Для Get, операция записи ѕ оператором Put. Эти операции позволяют читать и записывать записи в произвольном порядке. Установить нужную позицию записи или чтения можно, вызвав оператор :
Seek [#]номер-файла, позиция
Параметр номер-файла - номер открытого в режиме Random или Binary файла; позиция - число от 1 до 2 147 483 647, определяющее место в файле, куда будут помещены данные следующим оператором Put или откуда они будут считаны следующим оператором Get. Для файлов произвольного доступа позиция задает номер записи в файле, для бинарных и может быть использована для того, чтобы определить текущую позицию:
Seek(номер-файла)
Она возвращает число в диапазоне от 1 до 2 147 483 647. Для файлов произвольного доступа - это номер следующей считываемой или записываемой записи, для файлов, открытых в остальных режимах, - позиция байта, в которой будет выполняться следующая операция
Операторы Put и Get позволяют сами установить позицию, переопределив, тем самым, позицию, установленную оператором . Эти операторы имеют одинаковый, простой и ясный синтаксис:
Put [#]номер-файла, [номер-записи], переменная Get [#]номер-файла, [номер-записи], переменная
Параметры этих операторов имеют следующий смысл:
номер-файла - номер открытого в режиме Random или Binary файла;номер-записи - числовое выражение, определяющее позицию в файле, куда будут помещены вводимые данные или откуда данные будут прочитаны. Для файлов произвольного доступа - это номер записи в файле, для бинарных файлов - порядковый номер байта в файле. Нумерация записей (байтов) в файле начинается с 1. Если параметр номер-записи опустить, данные будут записаны (прочитаны) в текущую позицию. Уже говорилось, что установить текущую позицию можно оператором Seek . Отметим также, что после выполнения операторов Get и Put текущей становится позиция очередной записи (байта), следующей за только что прочитанной записью (байтом). Заметьте, даже если этот параметр опущен, разделяющая запятая все же остается в операторе вызова.переменная - имя переменной, значение которой должно быть записано в файл (в которую будет прочитана запись).Вот простой пример записи и чтения в файл произвольного доступа:
Public Sub Test1() Dim N As Integer Open Path "test.1" For Random Access Read Write As #1 Len = 2 N = 1 Put 1, 1, N Get 1, 1, N Debug.Print N, LOF(1) N = 2 Put 1, 2, N Get 1, 2, N Debug.Print N, LOF(1) N = 3 Put 1, 1, N Get 1, 1, N Debug.Print N, LOF(1) Close #1 End Sub
Используемая здесь функция возвращает длину файла в байтах. Заметьте, будет создан файл из двух записей, поскольку третий по счету оператор Put изменяет значение первой записи, так что перед закрытием функция возвратит значение 4.
Но давайте рассмотрим чуть более серьезный пример. Рассмотрим работу с файлом, который назовем "Товары.9". Этот файл будет содержать информацию о товарах. Код товара будет служить ключом записи. Зная код товара, можно будет найти запись в файле. На практике, когда формальным ключом записи, как в нашем случае, является ее порядковый номер, всегда задается некоторая функция, которая реальный ключ, возможно заданный несколькими параметрами, преобразует в порядковый номер. В нашем примере, чтобы не вводить таких преобразований, будем полагать, что код товара изменяется в пределах от 1 до N и, следовательно, он может служить формальным ключом записи. Вот как задается пользовательский тип, описывающий товар:
Type Товар КодТовара As Integer Наименование As String * 10 Цена As Integer End Type
Создадим файл, хранящий записи этого типа. Чтобы можно было проследить за деталями, наш файл будет содержать не более 10 записей. Мы используем случайные числа для генерирования записей этого файла. Вот три процедуры, решающие эту задачу:
Public Sub CreateRec(Rec As Товар) 'Создает запись типа Товар Randomize Rec.КодТовара = Int(Rnd * 9 + 1) 'Код от 1 до 9 Rec.Наименование = "Товар" Rec.КодТовара Rec.Цена = Int(Rnd * 99 + 1) * Rec.КодТовара End Sub Public Sub PrintRec(Rec As Товар) 'Печать записи о товаре Debug.Print "Код Товара:", Rec.КодТовара, "Цена:", Rec.Цена End Sub Public Sub CreateRandomFile() Dim i As Integer, NewRec As Товар Open Path "Товары.9" For Random Access Write As #1 Len = Len(NewRec) 'Создаем 10 записей For i = 1 To 10 Call CreateRec(NewRec) Call PrintRec(NewRec) Put #1, NewRec.КодТовара, NewRec Next i Debug.Print "Файл Товары.9 успешно создан" Debug.Print "Размер файла", LOF(1) Close #1 End Sub
Приведем результаты отладочной печати:
Код Товара: 7 Цена: 294 Код Товара: 3 Цена: 141 Код Товара: 3 Цена: 84 Код Товара: 1 Цена: 18 Код Товара: 6 Цена: 246 Код Товара: 8 Цена: 472 Код Товара: 8 Цена: 376 Код Товара: 7 Цена: 483 Код Товара: 2 Цена: 168 Код Товара: 8 Цена: 224 Файл Товары.9 успешно создан Размер файла 112
Прокомментируем эти процедуры и полученные результаты. Первая процедура генерирует запись, используя датчик случайных чисел ѕ функцию Rnd. Ранее в своих примерах мы уже использовали эту функцию и подробно говорили о том, как она работает. Вторая процедура позволяет вывести запись в окно отладки. Основной является процедура CreateRandomFile, в которой открывается файл "Товары.9" как файл с произвольным доступом, открытый для записи. При открытии, что очень важно, мы не забыли указать длину записи. Поскольку файл предназначен для хранения значений переменных типа "Товар", то этот тип и определяет длину записи, ѕ она равна 14 байтов (10 байтов занимает строка постоянной длины и 4 байта требуется для двух полей типа Integer).В процедуре 10 раз генерируется новая запись и 10 раз выполняется оператор Put, производящий запись в файл. Но как показывают результаты отладочной печати, некоторые коды повторяются, поэтому в таких случаях происходит обновление содержимого записи. В данном примере только 6 записей имеют уникальный код, и 4 раза происходило обновление уже созданных записей. Важно обратить внимание на длину созданного файла. Анализируя эту характеристику, можно понять, что файл содержит 8 записей, а не 10 и не 6. Дело в том, что длина файла произвольного доступа определяется записью с максимальным номером. Пусть в файле есть запись с максимальным номером M. И пусть добавляется новая запись с номером N, большим M. В этот момент в файле будут созданы новые записи с номерами от M+1 до N включительно. Запись с номером N будет действительно записана, а остальные получат значения по умолчанию, в соответствии с соглашениями, принятыми в VBA. Так что, если в файл пишется одна единственная запись с ключом 10000, то это означает, что в файле автоматически появляется 10000 записей и таков будет его размер. В нашем примере максимальный код равен 8,ѕ отсюда и размер файла равен 112 = 8*14
Рассмотрим теперь работу с этим файлом. Мы приведем две процедуры. Первая из них ведет последовательную обработку всех записей файла, читая их друг за другом. Вторая ѕ случайным образом генерирует запрос на получение записи файла со случайным ключом. Вот эти процедуры:
Public Sub PrintFile() Dim N As Integer, i As Integer, Code As Integer Dim MyRec As Товар 'Открыть файл Товаров для обработки Open Path "Товары.9" For Random Access Read As #1 Len = Len(MyRec) N = LOF(1) \ Len(MyRec) 'Число записей Debug.Print "Число записей файла-- ", N For i = 1 To N 'Установить позицию Seek 1, i 'Получить запись с заданным кодом Get #1,, MyRec Call PrintRec(MyRec) Next i Close #1 End Sub
Вот на какие моменты следует обратить внимание. Файл открывается теперь для чтения и при открытии корректно указывается длина записи. Для подсчета числа записей файла используется упоминавшаяся функция . Заметьте, мы используем оператор для установки позиции, но это скорее для проформы, поскольку в данной ситуации не изменяет уже установленной позиции. Вот результаты работы этой процедуры:
Число записей файла-- 8 Код Товара: 1 Цена: 18 Код Товара: 2 Цена: 168 Код Товара: 3 Цена: 84 Код Товара: 0 Цена: 0 Код Товара: 0 Цена: 0 Код Товара: 6 Цена: 246 Код Товара: 7 Цена: 483 Код Товара: 8 Цена: 224
Мы уже пояснили, почему в файле оказалось 8 записей и почему часть из них имеет нулевые значения.
Следующая процедура работы с этим файлом демонстрирует возможность работы с ним, когда доступ к записям осуществляется в произвольном порядке, что, впрочем, демонстрировалось и при создании этого файла:
Public Sub WorkWithFile() Dim N As Integer, i As Integer, Code As Integer Dim MyRec As Товар Randomize 'Открыть файл Товаров для обработки Open Path "Товары.9" For Random Access Read As #1 Len = Len(MyRec) N = Int(Rnd * 9 + 1) 'Число обрабатываемых записей Debug.Print "Число обрабатываемых записей -- ", N For i = 1 To N Code = Int(Rnd * 9 + 1) 'Установить позицию Seek 1, Code 'Получить запись с заданным кодом Get #1,, MyRec If MyRec.КодТовара = 0 Then Debug.Print "В файле нет записи с кодом:", Code Else: Call PrintRec(MyRec) End If Next i Close #1 End Sub
Из новых деталей отметим следующее. Здесь оператор работает по существу, хотя, конечно, и без него можно было бы обойтись, используя возможности оператора Get. Однако, более важно, обратить внимание на то, что при попытке читать запись с ключом, который не задавался при создании файла (по существу, читать несуществующую запись) не возникает ошибки. Причину этого мы уже объяснили. Поэтому приходится организовывать собственную проверку, как это и сделано в нашей программе. В заключение приведем результаты одного из экспериментов с этой процедурой:
Число обрабатываемых записей -- 2 Код Товара: 8 Цена: 224 В файле нет записи с кодом: 5
До сих пор мы усиленно подчеркивали, что при работе с файлом произвольного доступа размер записи фиксирован и это предполагает хранение в таких файлов данных, размер которых фиксирован в момент их объявления. На самом деле, это не так и в таких файлах можно хранить и строки переменной длины и данные типа Variant. В этих случаях просто следует быть более осторожным, поскольку из-за сложности правил хранения таких данных повышается возможность ошибки. Вот некоторые правила, согласно которым оператор Put размещает данные в файле, а оператор Get их читает:
Len при открытии файла. Если при этом длина данных, записываемых в файл, меньше размера записи, заданного параметром Len, оператор Put заполнит остающееся до длины записи место "случайными" данными из буфера файла, а новые данные поместит, начиная со следующей границы. При попытке записать данные, длина которых больше Len, появится сообщение об ошибке. Таким образом, параметр Len задает максимальный размер записи, внутри которого может храниться значение переменного размера.Variant, динамических массивов VBA помещает туда же дополнительную информацию для их расшифровки.Put помещает непосредственно перед ней 2 байта с ее длиной.Variant. Их содержимое идентифицирует тип значения (как в функции VarType ). Например, если оно равно 3, это соответствует типу Long, и запись такой переменной займет 6 байтов (2 - для типа и 4 - для самого числа). Значение VarType = 8 соответствует строковому типу ( String ). В этом случае Put запишет 2 байта с VarType, затем 2 байта с длиной строки и затем саму строку.Put записывает перед его элементами описатель с длиной 2 + 8* (число измерений массива).Len при записи роли не играет. Put записывает значения переменных подряд без пропусков и не помещает длины динамических строк и Get выполняет преобразования, обратные тем, что при записи выполнял оператор Put. Например, при чтении в строку переменной длины Get определяет по 2-байтовому описателю длину строки, а затем читает ее содержимое в переменную. Аналогичная расшифровка происходит и при чтении в переменные типа Variant и в динамические массивы.Get читает данные подряд. В переменные фиксированного размера считывается соответствующее их размеру число байтов. При чтении в строку переменной длины в нее считывается число символов, равное длине ее текущего значения.Приведем пример работы с файлом произвольного доступа, записи которого содержат данные не фиксированного размера:
Public Sub Test2() 'Эта процедура работает с файлом, записи которого 'содержат строки переменной длины, не превосхоящей максимума, 'а также данные типа Variant Dim Fam As String Dim Other As Variant 'Открытие файла произвольного доступа для чтения и записи Open Path "Strings.var" For Random Access Read Write As #5 Len = 20 Fam = "Степанов" Put 5, 1, Fam Get 5, 1, Fam Debug.Print Fam, LOF(5) Fam = "Архангельский" Put 5, 2, Fam Get 5, 2, Fam Debug.Print Fam, LOF(5) Fam = "Куц" Put 5, 1, Fam Get 5, 1, Fam Debug.Print Fam, LOF(5) 'Запись типа Variant Other = "Петров" Put 5, 3, Other Get 5, 3, Other Debug.Print Other, LOF(5) Other = 125.25 Put 5, 4, Other Get 5, 4, Other Debug.Print Other, LOF(5) Other = 125 Put 5, 5, Other Get 5, 5, Other Debug.Print Other, LOF(5) Close #5 End Sub
Вот результаты ее работы:
Степанов 10 Архангельский 35 Куц 35 Петров 50 125,25 70 125 84
Приведем комментарии:
Put и Get позволяют записать и корректно прочитать данные переменного размера, не превосходящего максимальный размер, заданный при открытии файла.Get корректно прочитать эту строку.Variant. Теперь при записи строки используются два описателя, занимающие 4 байта.Double (8 байтов) и Integer (2 байта) с их описателями.Заканчивая тему работы с файлами произвольного доступа, хотим еще раз обратить внимание на некоторые опасные моменты, возникающие в процессе работы с ними и не контролируемые VBA:
Len ѕ максимальный размер записи. Если этого не сделать, то программа будет работать, но, к сожалению, результаты могут быть не предсказуемыми.1 до N.Бинарные файлы пишутся и читаются порциями, состоящими из произвольного числа байтов. Доступ возможен к любому байту этого файла в произвольном порядке. Никакого редактирования при операциях с этим файлом не производится и вся ответственность за сохранением структуры информации ложится на программиста. Операции над этим файлом выполняются операторами Get и Put, но можно читать этот файл побайтно, используя и уже упоминавшуюся функцию Input. Мы не будем строить специальный пример для работы с этим файлом, а ограничимся примером чтения ранее созданного текстового файла "readme.txt", который откроем, как Binary файл. В этом примере мы прочитаем последовательно этот файл порциями по 20 байтов. Заодно покажем, как организуется чтение "хвоста" файла ѕ его последней порции. Возможно, с содержательной точки зрения пример не очень хорош, поскольку будет нарушена реальная структура читаемых данных, нов этом то и вся суть дела при работе с такими файлами. Программист сам должен восстановить структуру данных, чем мы, в данном случае, заниматься не будем. Но вот сам пример:
Public Sub Test3() 'Чтение Binary файла Dim MyLocation As Long, MyLoc As Long, MyLine As String Open Path "readme.txt" For Binary As #1 MyLocation = 0 Do While MyLocation + 20 < LOF(1) ' Читаем допустимую порцию MyLine = Input(20, #1) MyLocation = Loc(1): MyLoc = Seek(1) ' 2 способа определения текущей позиции Debug.Print MyLine; Tab; MyLocation; Tab; MyLoc Loop 'Читаем последнюю порцию MyLoc = LOF(1) - MyLocation If MyLoc > 0 Then MyLine = Input(MyLoc, #1) MyLocation = Loc(1): MyLoc = Seek(1) Debug.Print MyLine, MyLocation, MyLoc, LOF(1) End If Close #1 End Sub
Перед комментариями приведем результаты работы:
"Первая строка файла 20 21 " "Зона 1","Зона 2" 40 41 "Привет,","старик! 60 61 " "Мама ","мыла "," 80 81 раму мылом. " #FALS 100 101 E# #1999-06-14# #N 120 121 ULL# 3.1416,"3,14", 140 141 "03,142" 6 #ERROR 160 161 2000# 167 168 167
Как видите, файл читается со всеми, входящими в него символами, в том числе и непечатаемыми, задающими конец строки. Заметим, что восстановить исходную структуру строк достаточно просто, было бы только желание. Обращаем внимание на новую функцию, которую мы ввели в этой программе - функцию . Она вычисляет текущую позицию файла и возвращает значение на единицу меньшее, чем функция , которая возвращает позицию следующего байта.
И последний совет в этой лекции, средств VBA для работы с файлами произвольного доступа достаточно для выполнения основных операций: создания, записи, чтения и модификации данных. Но средства поиска данных в файле (оператор ) весьма примитивны. С их помощью трудно решать задачи упорядочения файлов, поиска записей по значениям отдельных полей и другие задачи, характерные для работы с базами данных. Для небольших файлов можно создавать индексные массивы, в которых номера записей упорядочены в соответствии с требуемым критерием, и поддерживать этот порядок при всех изменениях файла. Для больших - заводить свои индексные файлы и т. п. Но владельцам Office 2000 не следует заниматься программированием собственных СУБД. В их распоряжении - возможность переписать содержимое любого файла произвольного доступа в ячейки рабочего листа Excel или в базу данных (таблицу) Access и использовать для работы с его данными всю мощь этих инструментов.
Программный код большинства примеров данной лекции можно найти в проектах, доступных для просмотра: DocOne14.
Как правило, приложения Office имеют дело с файлами, хранящими документы этих приложений. Это файлы с документами Word (с расширениями.doc), рабочими книгами Excel (с расширениями.xls), базами данных Access (с расширениями.mdb). Операции по созданию, открытию и записи этих файлов выполняются с помощью соответствующих методов приложений, которые запускаются командами меню File. Но офисные системы должны уметь работать и с файлами других форматов. Например, часто требуется считывать данные из обычных текстовых файлов и заносить информацию, в них хранящуюся, в таблицы Excel или базы данных Access. Возможна и обратная ситуация, когда, используя данные из приложений Office, требуется сохранять полученную информацию в файлах. В общем, есть некоторые ситуации, когда предпочтительнее работать с собственными файлами, а не со стандартными базами данных. По этой причине в VBA включены средства для работы с внешними файлами, позволяющие их создавать, открывать для работы и осуществлять ввод-вывод данных. Файлы VBA делятся на три группы.
Input ), записи ( Output ), или присоединения ( Append ). Специальных средств поиска для них нет, и чтобы получить данные из такого файла, его нужно открыть и прочесть последовательно все данные до нужного места. Изменения (запись данных) в таком файле всегда происходят в его конце. Существенной особенностью этого файла является редактирование данных в процессе записи и чтения. В файл можно записывать данные разных типов, При вводе они будут отредактированы и преобразованы в строку. Таким образом, Можно заметить, что среди допустимых моделей файлов VBA нет наиболее общей, когда разрешен прямой доступ к элементам файла, когда элементы могут иметь произвольную длину, когда ключ может быть произвольным и не обязательно должен задавать номер записи. Отсутствие такого типа файлов действительно представляет серьезное ограничение, но этому есть и не менее серьезное оправдание. В таких сложных ситуациях рекомендуется переходить на работу с базой данных. Это тем более оправдано тем, что база данных Access доступна из любого приложения Office 2000.
Типичный цикл работы программы с файлом любого типа начинается с открытия или создания файла оператором Open. При этом задается
Вот какие операторы обычно применяются для записи и чтения данных из файлов в зависимости от режима доступа.
| Тип доступа | Запись данных | Чтение данных |
|---|---|---|
| Последовательный | Print#, Write# |
Input#, Line Input# |
| Произвольный | Put |
Get |
| Бинарный | Put |
Get |
Файлы перечисленных видов открываются и могут создаваться оператором Open, который вызывается так:
Open имя-файла For режим [Access доступ] [блокировка] As [#]номерФайла [Len=длина-записи]
имя-файла - строковое выражение, задающее имя открываемого (создаваемого) файла, в него может входить также путь ѕ имя диска и имена каталогов (папок) на пути к файлу.режим - ключевое слово, которое для файлов последовательного доступа может принимать одно из значений: Input (ввод), Output (вывод), Append (присоединение). Для файлов с произвольным доступом в качестве режима нужно указать Random, а для бинарных файлов - Binary.доступ необязателен, он ограничивает набор операций, которые разрешено выполнять над открываемым файлом; возможные значения: Read (только чтение), Write (только запись), Read Write (и то и другое).блокировка позволяет ограничить набор операций над открываемым файлом, выполняемых другими процессами; возможные значения: Shared (разделяемый, другим процессам разрешается работать с файлом без всяких ограничений), Lock Read (запретить чтение), Lock Write (запретить запись), Lock Read Write (запретить чтение и запись). Здесь требуется некоторые пояснения. С одним и тем же физическим файлом одновременно могут работать разные приложения. Возможна и ситуация, когда одно приложение одновременно работает с несколькими экземплярами файла, присваивая каждому экземпляру свой уникальный номер. Параметр блокировка позволяет регулировать отношения между клиентами, получающими доступ к файлу.номерФайла - целое число в интервале от 1 до 511, идентифицирующее файл для других операций. Для каждой операции открытия файла номер-файла должен отличаться от номеров всех открытых в текущий момент файлов. Узнать очередной свободный номер позволяет функция FreeFile.длина-записи - число не более 32767 - обязателен для файлов произвольного доступа и задает для них длину одной записи файла в байтах. Для Если Output ), существует, его содержимое будет утеряно, и данные будут записываться в его начало. Если его нет, создается новый файл. Точно так же будет создан новый файл, если файл, определяемый параметром имя-файла, не существует, а режим открытия задан как Append, Random или Binary. При попытке открыть несуществующий файл в режиме Input будет получено сообщение об ошибке.
Последовательные файлы в режиме чтения, файлы произвольного доступа и бинарные можно открывать по несколько раз с различными номерами. С каждым из номеров будет связан свой буфер, что может повысить эффективность работы с файлом. Файл, открытый для чтения, должен быть закрыт перед его открытием в режимах записи Output или Append.
Рассмотрим детали открытия и создания файлов. В первом примере открывается файл последовательного доступа "read.me" для чтения:
Open "read.me" For Input As #1
Имя можно передавать и через переменную или более сложное строковое выражение:
Const Path = "e:\O2000\CD2000\Ch14\" Public Sub Openfile() Dim NewFile As String, NewNum As Integer NewFile = "read.me" NewNum = FreeFile ' очередной незанятый номер файла Open Path NewFile For Output As NewNum End Sub
Заметьте, при первом запуске этой программы был создан новый пустой файл с заданным именем, поскольку файл не существовал в момент открытия. Поскольку при открытии указан полный путь, то файл был создан в указанном каталоге, в противном случае он был бы создан в том же каталоге, где находится документ, инициировавший его создание.
Файл с последовательным доступом особенно удобен, когда при работе с ним требуется последовательно прочесть и обработать все записи файла. Если же данные из файла обрабатываются в произвольном порядке, или файл открывается для того, чтобы получить доступ к небольшому числу его записей, крайне желательно иметь возможность произвольного (прямого) доступа к нужному элементу файла. Такую возможность и обеспечивают файлы VBA с произвольным доступом, хотя у них есть серьезное ограничение, все записи должны иметь одинаковый размер. Это крайне неудобно в наиболее типичном случае, когда элементами файла с произвольным доступом являются записи, длины которых, естественно, могут быть не постоянны из-за, например, наличия в них строк переменной длины. В таких ситуациях одним из выходов является переход к строкам постоянной длины. Радикальное решение, как мы уже говорили, состоит в том, чтобы в подобных ситуациях переходить на работу с базами данных. Рассмотрим пример открытия двух файлов - файла с произвольным доступом для хранения записей пользовательского типа и бинарного файла для хранения картинки. Вначале дадим объявление пользовательского типа:
Type Person ' пользовательский тип записи. Возраст As Integer Фамилия As String * 20 Имя As String * 20 End Type
Обратили внимание на то, что фамилия и имя заданы, как строки постоянной длины. В данном случае это принципиально, поскольку предполагается возможность хранения переменных этого типа в файле с произвольным доступом. Вот процедура открытия двух файлов:
Public Sub OpenTwoFiles() Dim attr As Integer, Num As Integer Dim MyFriend As Person ' объявление переменной ' Открываем файл с произвольным доступом для записи: Num = FreeFile Open "Friends" For Random Access Read Write As Num Len = Len(MyFriend) PrintAttr (Num) Debug.Print "Длина Записи:", Len(MyFriend) ' Открываем двоичный файл Num = FreeFile Open "pict.bmp" For Binary Access Read Lock Read As Num PrintAttr (Num) End Sub Public Sub PrintAttr(ByVal Num As Integer) 'Эта процедура по номеру файла определяет его атрибуты 'и выводит соответствующее сообщение в окно отладки Dim Msg As String, attr As Integer attr = FileAttr(Num, 1) Debug.Print "файл # ", Num Select Case attr Case 1: Msg = "Input" Case 2: Msg = "Output" Case 4: Msg = "Random" Case 8: Msg = "Append" Case 32: Msg = "Binary" Case Else: Msg = "Таких файлов не бывает" End Select Debug.Print "Открыт для ", Msg End Sub
В этом примере для определения свободного номера файла использовалась функция FreeFile, а для определения длины записи ѕ функция Len. Файл произвольного доступа открыт как для чтения, так и для записи, а бинарный файл только для чтения. Заметьте, что бинарный файл закрыт для чтения другими процессами. Поэтому если повторно запустить процедуру открытия, то файл произвольного доступа будет повторно открыт с другим номером, но возникнет ошибка при повторном открытии бинарного файла. Возможность одновременного открытия под разными номерами нескольких логических экземпляров одного и того же физического файла бывает крайне полезной в ряде ситуаций.
Функция
FileAttr(номер-файла, 1)
по номеру открытого файла возвращает число, указывающее режим открытия: 1 - Input, 2 - Output, 4 - Random, 8 - Append, 32 - Binary. Второй аргумент достался функции FileAttr в наследство от 16-разрядного режима, где при его значении 2, она возвращала системный указатель на файл.
Мы написали простую процедуру PrintAttr, которая по номеру файла определяет его атрибуты и выводит сообщение в окно отладки. Приведем результаты работы процедуры OpenTwoFiles:
файл # 1 Открыт для Random Длина Записи: 42 файл # 2 Открыт для Binary
Для закрытия файла служит оператор Close, вызываемый:
Close [список-номеров-файлов]
Параметр список-номеров-файлов может содержать один или несколько номеров ранее открытых файлов, которые должны быть закрыты. Как обычно, номера в списке разделяются запятыми. Если параметр опущен, то закрываются все открытые в данный момент файлы. После выполнения оператора Close номера закрытых файлов снова можно использовать при открытии в операторе Open.
Закрыть все открытые оператором Open файлы может также Close без параметров, он освободит все номера открытых файлов и перенесет содержимое их буферов на диск.
При записи и чтении данных в
Print#, представляющий данные в формате, подготовленном для отображения на экране дисплея. По существу, мы уже хорошо знакомы с этим оператором, поскольку ни одна наша программа не обходилась без вывода данных в окно отладки, а применяемый для этих целей метод Print объекта Debug по синтаксису и по действию подобен оператору Print#.Write#. Заметим, в каком - то смысле это более универсальный оператор, чем Print, он, например, не зависит от локализации.Оператор Print# записывает в файл форматированные данные. Его вызов имеет вид:
Print #номер-файла, [список-вывода]
Здесь номер-файла - номер файла, открытого в режиме последовательного доступа для записи ( Append или Output ), список-вывода - одно или несколько выражений, разделенных пробелами или точками с запятой, определяющих выводимые данные. Каждое выражение в этом списке может быть снабжено дополнительной информацией о расположении данных, делающее их восприятие более удобным для пользователя. Общий вид такого выражения:
[{Spc(n) | Tab[(n)]}] [выражение] [симв-поз]
Spc (n) задает количество вставляемых пробелов.Tab(n) указывает абсолютный номер n столбца, в котором будет начинаться текст, определяемый параметром выражение. Если не указывать аргумент у Tab, то позицией вставки является следующая зона печати. (Можно полагать, что при выводе экран разделен на зоны, и табуляция позволяет переходить от одной зоны к другой).выражение задает выражение, значение которого после редактирования и преобразования его в текстовую строку будет записано в файл. На выражения не накладывается особых ограничений, Это могут быть как строковые, так и числовые выражения, они могут содержать данные разных типов. Даты записываются в файл в кратком формате, значения булевых переменных - как ключевые слова Ложь ( False ) или Истина ( True ), пустое значение Empty - как пустое слово, нулевое значение Null выводится как "Null". Данные об ошибке выводятся в виде кода ошибки. Числовые данные выводятся в формате, зависящем от локализации. При выводе можно вызвать для форматирования данных функцию Format.симв-поз задает положение следующего выражения. Если он равен ";" или пробелу, вставка следующего выражения будет происходить сразу за последним выведенным символом, Tab(n) указывает номер n столбца, начиная с которого будет выводиться выражение. Tab без аргумента или "," - переход в следующую позицию табуляции (зону печати).Создадим теперь открытый ранее файл read.me, записывая в него выражения, позволяющие продемонстрировать все возможности оператора Print:
Public Sub WritingWithPrint() Dim MyBool As Boolean, MyDate As Date Dim MyNull As Variant, MyFloat As Double, MyErr As Variant Print #1, "Первая строка файла" ' запись текстовой строки. 'Первая строка файла Print #1, "Зона 1"; Tab; "Зона 2" ' запись в двух позициях табуляции. 'Зона 1 Зона 2 Print #1, "Зона 1", "Зона 2" ' запятая разделяет как табуляции. 'Зона 1 Зона 2 Print #1, "Привет,"; "старик!" '; склеивает выражения. 'Привет,старик! Print #1, "Привет,"; "старик!" ' пробел действует аналогично. 'Привет,старик! Print #1, Spc(5); "5 пробелов " ' вставка 5 пробелов слева. ' 5 пробелов Print #1, Tab(20); "Старик,"; Tab(10); "привет!" ' печать в соответствующих столбцах. ' Старик ' привет! Print #1, "one"; " "; "two" 'разделение пробелом 'one two MyBool = False ' булева переменная Print #1, MyBool 'False MyDate = #6/14/1999# ' значение даты Print #1, MyDate ' печать в кратком формате '14.06.99 MyNull = Null ' нулевое значение Print #1, MyNull 'Null MyFloat = 3.1416 'вещественное значение ' использование функции Format: Print #1, MyFloat, Format(MyFloat, "#.00"), Format(MyFloat, "00.000") '3,1416 3,14 03,142 On Error Resume Next Err.Raise 6 MyErr = Err Print #1, MyErr MyErr = CVErr(2000) Print #1, MyErr End Sub
Здесь в комментариях показан ожидаемый вид показа строки при ее чтении и печати. Чтобы не томить ожиданием, давайте сразу приведем процедуру, которая читает и выводит в окно отладки записи этого файла:
Public Sub ReadingWithLine() Dim MyStr As String Close #1 Open Path "read.me" For Input As #1 Do While Not EOF(1) Line Input #1, MyStr Debug.Print MyStr Loop End Sub
Текст в окне отладки выглядит в полном соответствии с нашими ожиданиями. Вот он:
Первая строка файла Зона 1 Зона 2 Зона 1 Зона 2 Привет,старик! Привет,старик! 5 пробелов Старик, привет! one two False 14.06.99 Null 3,1416 3,14 03,142 6 Error 2000
Обратите внимание, ошибки, возбуждаемые методом Raise и функцией CVErr, редактируются по-разному, в первом случае записывается только код ошибки, во втором ѕ код сопровождается ключевым словом Error, не переводимым в локализованных версиях.
Заметьте, точно такой же текст можно получить, если в процедуре WritingWithPrint заменить оператор Print на Debug.Print, поскольку этот метод и этот оператор порождают одинаковый результат, разница лишь в том, что один из них помещает результаты в файл, а другой ѕ в окно отладки.
При выводе данных можно управлять шириной выводимых строк. Ширину выводимых строк для открытого файла устанавливает оператор:
Width #номер-файла, ширина
Параметр ширина принимает значения от 0 до 255. Если при печати в файл оператором Print добавление значения очередного выражения в строку приведет к превышению установленной границы, это значение будет печататься с новой строки. При этом значение одного выражения не переносится, даже если его длина превышает установленную границу. Например, если для файла с номером 1 ограничить ширину строки 5 символами:
Width #1,5
а затем напечатать строку:
Str = "раму мылом" Print #1, "мама", "мыла", Str
то в файле окажутся строки:
мама мыла раму мылом
Обращаем внимание и на то, что переход к новой строке может происходить и при встрече с разделителем Tab(n), если n задает позицию меньшую, чем текущая позиция. Пример такой ситуации встречался в нашей процедуре.
Как было показано, оператор Print вводит в файл данные разных типов, редактируя их в момент ввода и преобразуя в текстовый формат. Попутно он занимается и форматированием текста, приводя его к форме, удобной для отображения на дисплее. Во многих ситуациях форматирование, ориентированное на выдачу на экран дисплея, не является необходимым. В этих случаях для создания Write#. Его синтаксис:
Write #номер-файла, [список-вывода ]
Параметр номер-файла - номер открытого файла с последовательным доступом, список-вывода - одно или несколько разделенных запятыми числовых или строковых выражений, значения которых записываются в файл. Разделителями выражений могут быть пробелы и точка с запятой.
Также как и оператор Print, оператор Write производит редактирование данных в момент записи. При этом приняты следующие соглашения:
#TRUE# и #FALSE#.#ERROR код-ошибки#.Empty ) ничего не записываетсяNull записывается как #Null#.В отличие от оператора Print, оператор Write# вставляет при записи запятые между соседними выражениями и заключает строковые значения в кавычки. После записи последнего выражения вставляется перевод на новую строку (Chr(13) + Chr(10)).
Следующая процедура создает файл с последовательным доступом "readme.txt" и записывает в него данные с помощью оператора Write#. При записи мы старались повторить содержимое предыдущего файла, чтобы можно было сравнить работу, Print и Write. Вот текст этой процедуры:
Public Sub WritingWithWrite() Dim MyStr As String, MyBool As Boolean, MyDate As Date Dim MyNull As Variant, MyFloat As Double, MyErr As Variant 'Открытие файла readme.txt Open Path "readme.txt" For Output As #7 'Создание файла Write #7, "Первая строка файла" ' запись текстовой строки. Write #7, "Зона 1", "Зона 2" ' запись двух строк. Write #7, "Привет,", "старик!" 'еще две строки MyStr = "раму мылом. " Write #7, "Мама ", "мыла ", MyStr MyBool = False ' булева переменная Write #7, MyBool MyDate = #6/14/1999# ' значение даты Write #7, MyDate ' запись даты MyNull = Null ' нулевое значение Write #7, MyNull MyFloat = 3.1416 'вещественное значение ' использование функции Format: Write #7, MyFloat, Format(MyFloat, "0.00"), Format(MyFloat, "00.000") On Error Resume Next Err.Raise 6 MyErr = Err Write #7, MyErr MyErr = CVErr(2000) Write #7, MyErr End Sub
Результат выполнения этой процедуры - файл "readme.txt" - в текстовом редакторе выглядит так:
"Первая строка файла" "Зона 1","Зона 2" "Привет,","старик!" "Мама ","мыла ","раму мылом. " #FALSE# #1999-06-14# #NULL# 3.1416,"3,14","03,142" 6 #ERROR 2000#
Сравните эти строки с теми, что выдаются оператором Print.
Для чтения данных из файлов последовательного доступа используются операторы:
Input# ѕ данные в файле записаны оператором Write#LineInput# ѕ данные в файле записаны оператором Print#.Оператор Input# вызывается так:
Input #номер-файла, список-переменных
номер-файла - номер открытого файла,список-переменных - одно или несколько разделенных запятыми имен переменных, в которые будут считываться данные из файла. Переменные могут быть разных типов, естественное требование их тип должен быть согласован с типом записи в момент ее создания. Переменные не должны быть массивами или объектами, но могут быть элементами массивов или полями пользовательских записей.При чтении автоматически выполняются преобразования данных, обратные тем, что использовались при записи оператором Write#. В частности, кавычки вокруг строк, разделяющие запятые и пустые строки, игнорируются. Слово #NULL# дает значение Null, #TRUE# и #FALSE# переводятся в булевы значения True и False, универсальные даты вида #yyyy-mm-dd hh:mm:ss# переводятся в формат соответствующих переменных типа дата/время, #ERROR номер-ошибки# при чтении передает в переменную типа Variant номер ошибки. Если для числовой переменной соответствующие по порядку в файле данные будут не числовыми, ей будет присвоено значение 0.
По достижении конца файла, следующая попытка чтения из него приведет к ошибке. Во избежание этого для проверки на конец файла используется функцию:
EOF(номер-файла)
Она возвращает булево значение True, когда при чтении достигается конец файла, открытого в режиме последовательного доступа Input или произвольного доступа Random.
Приведем процедуру чтения записей ранее созданного файла "readme.txt". Заметьте, мы поступаем корректно, читаемые переменные имеют тип, согласованный с типом читаемой записи.
Public Sub ReadingWithInput() Dim MyStr As String, MyBool As Boolean, MyDate As Date Dim MyNull As Variant, MyFloat As Double, MyErr As Variant Dim i As Integer 'Открытие файла readme.txt Open Path "readme.txt" For Input As #7 'Чтение файла 'Первые 8 строк For i = 1 To 8 Input #7, MyStr Debug.Print MyStr Next i 'Данные разных типов Input #7, MyBool, MyDate, MyNull, MyFloat Debug.Print MyBool, MyDate, MyNull, MyFloat 'читаем отформатированные числа Input #7, MyStr Debug.Print MyStr Input #7, MyStr Debug.Print MyStr 'дважды читаем данные типа Error Input #7, MyErr Debug.Print MyErr Input #7, MyErr Debug.Print MyErr 'чтение до конца файла Do While Not EOF(7) Input #7, MyStr Debug.Print MyStr Loop End Sub
В результате будет напечатано:
Первая строка файла Зона 1 Зона 2 Привет, старик! Мама мыла раму мылом. False 14.06.99 Null 3,1416 3,14 03,142 6 Error 2000
Обратите внимание, процедура завершается циклом, типичным при чтении
Для построчного ввода данных из файлов с последовательным доступом, созданных оператором Print, вызывается оператор Line Input#:
Line Input #номер-файла, переменная
Здесь номер-файла имеет тот же смысл, что и для оператора Input, а переменная - имя строковой переменной или переменной типа Variant, в которую будет прочитана очередная строка файла. Оператор Line Input# считывает данные посимвольно, пока не обнаружит признак конца строки - символ возврата каретки ( Chr(13) ) или пару "возврат каретки - перевод строки" ( Chr(13) + Chr(10) ). Эти признаки в переменную не записываются. Никаких преобразований данных при построчном вводе не производится. Мы уже приводили пример чтения файла "read.me", созданного оператором Print#.
Следует заметить, что любой файл может быть открыт для чтения и прочитан оператором Line Input#. Другое дело, что, чаще всего, это не даст желаемого результата. Если, например, прочитать файл "readme.txt" с использованием оператора Line Input#, то будут получены следующие результаты:
"Первая строка файла" "Зона 1","Зона 2" "Привет,","старик!" "Мама ","мыла ","раму мылом. " #FALSE# #1999-06-14# #NULL# 3.1416,"3,14","03,142" 6 #ERROR 2000#
Несмотря на успех чтения, результаты не приемлемы, поскольку не выполняется нужного редактирования. Сохраняются кавычки, запятые, ограничители, в одной строке содержатся данные разных типов. Поэтому оператор Line Input # следует применять, как правило, для чтения строк из файлов, записанных с помощью оператора Print#.
Оператор Line Input # позволяет читать файл строка за строкой. Наряду с этим в языке VBA есть возможность чтения из файла произвольного числа символов или произвольного числа байтов. Для чтения символов Input или Binary файлов используется функция Input, имеющая следующий синтаксис:
Input(размер, [#]номер-файла)
где размер - количество считываемых символов, номер-файла - номер открытого файла (последовательного или бинарного). Данные, считываемые этой функцией, обычно должны быть записаны в файл операторами Print# или Put.
Еще один вариант этой функции:
InputB(размер, [#]номер-файла)
позволяет считывать из файла заданное количество байтов (не обязательно соответствующих символам). Заметьте, эта функция читает все символы, хранимые в файле, в том числе запятые, ограничители, символы конца строки и другие.
Для Get, операция записи ѕ оператором Put. Эти операции позволяют читать и записывать записи в произвольном порядке. Установить нужную позицию записи или чтения можно, вызвав оператор :
Seek [#]номер-файла, позиция
Параметр номер-файла - номер открытого в режиме Random или Binary файла; позиция - число от 1 до 2 147 483 647, определяющее место в файле, куда будут помещены данные следующим оператором Put или откуда они будут считаны следующим оператором Get. Для файлов произвольного доступа позиция задает номер записи в файле, для бинарных и может быть использована для того, чтобы определить текущую позицию:
Seek(номер-файла)
Она возвращает число в диапазоне от 1 до 2 147 483 647. Для файлов произвольного доступа - это номер следующей считываемой или записываемой записи, для файлов, открытых в остальных режимах, - позиция байта, в которой будет выполняться следующая операция
Операторы Put и Get позволяют сами установить позицию, переопределив, тем самым, позицию, установленную оператором . Эти операторы имеют одинаковый, простой и ясный синтаксис:
Put [#]номер-файла, [номер-записи], переменная Get [#]номер-файла, [номер-записи], переменная
Параметры этих операторов имеют следующий смысл:
номер-файла - номер открытого в режиме Random или Binary файла;номер-записи - числовое выражение, определяющее позицию в файле, куда будут помещены вводимые данные или откуда данные будут прочитаны. Для файлов произвольного доступа - это номер записи в файле, для бинарных файлов - порядковый номер байта в файле. Нумерация записей (байтов) в файле начинается с 1. Если параметр номер-записи опустить, данные будут записаны (прочитаны) в текущую позицию. Уже говорилось, что установить текущую позицию можно оператором Seek . Отметим также, что после выполнения операторов Get и Put текущей становится позиция очередной записи (байта), следующей за только что прочитанной записью (байтом). Заметьте, даже если этот параметр опущен, разделяющая запятая все же остается в операторе вызова.переменная - имя переменной, значение которой должно быть записано в файл (в которую будет прочитана запись).Вот простой пример записи и чтения в файл произвольного доступа:
Public Sub Test1() Dim N As Integer Open Path "test.1" For Random Access Read Write As #1 Len = 2 N = 1 Put 1, 1, N Get 1, 1, N Debug.Print N, LOF(1) N = 2 Put 1, 2, N Get 1, 2, N Debug.Print N, LOF(1) N = 3 Put 1, 1, N Get 1, 1, N Debug.Print N, LOF(1) Close #1 End Sub
Используемая здесь функция возвращает длину файла в байтах. Заметьте, будет создан файл из двух записей, поскольку третий по счету оператор Put изменяет значение первой записи, так что перед закрытием функция возвратит значение 4.
Но давайте рассмотрим чуть более серьезный пример. Рассмотрим работу с файлом, который назовем "Товары.9". Этот файл будет содержать информацию о товарах. Код товара будет служить ключом записи. Зная код товара, можно будет найти запись в файле. На практике, когда формальным ключом записи, как в нашем случае, является ее порядковый номер, всегда задается некоторая функция, которая реальный ключ, возможно заданный несколькими параметрами, преобразует в порядковый номер. В нашем примере, чтобы не вводить таких преобразований, будем полагать, что код товара изменяется в пределах от 1 до N и, следовательно, он может служить формальным ключом записи. Вот как задается пользовательский тип, описывающий товар:
Type Товар КодТовара As Integer Наименование As String * 10 Цена As Integer End Type
Создадим файл, хранящий записи этого типа. Чтобы можно было проследить за деталями, наш файл будет содержать не более 10 записей. Мы используем случайные числа для генерирования записей этого файла. Вот три процедуры, решающие эту задачу:
Public Sub CreateRec(Rec As Товар) 'Создает запись типа Товар Randomize Rec.КодТовара = Int(Rnd * 9 + 1) 'Код от 1 до 9 Rec.Наименование = "Товар" Rec.КодТовара Rec.Цена = Int(Rnd * 99 + 1) * Rec.КодТовара End Sub Public Sub PrintRec(Rec As Товар) 'Печать записи о товаре Debug.Print "Код Товара:", Rec.КодТовара, "Цена:", Rec.Цена End Sub Public Sub CreateRandomFile() Dim i As Integer, NewRec As Товар Open Path "Товары.9" For Random Access Write As #1 Len = Len(NewRec) 'Создаем 10 записей For i = 1 To 10 Call CreateRec(NewRec) Call PrintRec(NewRec) Put #1, NewRec.КодТовара, NewRec Next i Debug.Print "Файл Товары.9 успешно создан" Debug.Print "Размер файла", LOF(1) Close #1 End Sub
Приведем результаты отладочной печати:
Код Товара: 7 Цена: 294 Код Товара: 3 Цена: 141 Код Товара: 3 Цена: 84 Код Товара: 1 Цена: 18 Код Товара: 6 Цена: 246 Код Товара: 8 Цена: 472 Код Товара: 8 Цена: 376 Код Товара: 7 Цена: 483 Код Товара: 2 Цена: 168 Код Товара: 8 Цена: 224 Файл Товары.9 успешно создан Размер файла 112
Прокомментируем эти процедуры и полученные результаты. Первая процедура генерирует запись, используя датчик случайных чисел ѕ функцию Rnd. Ранее в своих примерах мы уже использовали эту функцию и подробно говорили о том, как она работает. Вторая процедура позволяет вывести запись в окно отладки. Основной является процедура CreateRandomFile, в которой открывается файл "Товары.9" как файл с произвольным доступом, открытый для записи. При открытии, что очень важно, мы не забыли указать длину записи. Поскольку файл предназначен для хранения значений переменных типа "Товар", то этот тип и определяет длину записи, ѕ она равна 14 байтов (10 байтов занимает строка постоянной длины и 4 байта требуется для двух полей типа Integer).В процедуре 10 раз генерируется новая запись и 10 раз выполняется оператор Put, производящий запись в файл. Но как показывают результаты отладочной печати, некоторые коды повторяются, поэтому в таких случаях происходит обновление содержимого записи. В данном примере только 6 записей имеют уникальный код, и 4 раза происходило обновление уже созданных записей. Важно обратить внимание на длину созданного файла. Анализируя эту характеристику, можно понять, что файл содержит 8 записей, а не 10 и не 6. Дело в том, что длина файла произвольного доступа определяется записью с максимальным номером. Пусть в файле есть запись с максимальным номером M. И пусть добавляется новая запись с номером N, большим M. В этот момент в файле будут созданы новые записи с номерами от M+1 до N включительно. Запись с номером N будет действительно записана, а остальные получат значения по умолчанию, в соответствии с соглашениями, принятыми в VBA. Так что, если в файл пишется одна единственная запись с ключом 10000, то это означает, что в файле автоматически появляется 10000 записей и таков будет его размер. В нашем примере максимальный код равен 8,ѕ отсюда и размер файла равен 112 = 8*14
Рассмотрим теперь работу с этим файлом. Мы приведем две процедуры. Первая из них ведет последовательную обработку всех записей файла, читая их друг за другом. Вторая ѕ случайным образом генерирует запрос на получение записи файла со случайным ключом. Вот эти процедуры:
Public Sub PrintFile() Dim N As Integer, i As Integer, Code As Integer Dim MyRec As Товар 'Открыть файл Товаров для обработки Open Path "Товары.9" For Random Access Read As #1 Len = Len(MyRec) N = LOF(1) \ Len(MyRec) 'Число записей Debug.Print "Число записей файла-- ", N For i = 1 To N 'Установить позицию Seek 1, i 'Получить запись с заданным кодом Get #1,, MyRec Call PrintRec(MyRec) Next i Close #1 End Sub
Вот на какие моменты следует обратить внимание. Файл открывается теперь для чтения и при открытии корректно указывается длина записи. Для подсчета числа записей файла используется упоминавшаяся функция . Заметьте, мы используем оператор для установки позиции, но это скорее для проформы, поскольку в данной ситуации не изменяет уже установленной позиции. Вот результаты работы этой процедуры:
Число записей файла-- 8 Код Товара: 1 Цена: 18 Код Товара: 2 Цена: 168 Код Товара: 3 Цена: 84 Код Товара: 0 Цена: 0 Код Товара: 0 Цена: 0 Код Товара: 6 Цена: 246 Код Товара: 7 Цена: 483 Код Товара: 8 Цена: 224
Мы уже пояснили, почему в файле оказалось 8 записей и почему часть из них имеет нулевые значения.
Следующая процедура работы с этим файлом демонстрирует возможность работы с ним, когда доступ к записям осуществляется в произвольном порядке, что, впрочем, демонстрировалось и при создании этого файла:
Public Sub WorkWithFile() Dim N As Integer, i As Integer, Code As Integer Dim MyRec As Товар Randomize 'Открыть файл Товаров для обработки Open Path "Товары.9" For Random Access Read As #1 Len = Len(MyRec) N = Int(Rnd * 9 + 1) 'Число обрабатываемых записей Debug.Print "Число обрабатываемых записей -- ", N For i = 1 To N Code = Int(Rnd * 9 + 1) 'Установить позицию Seek 1, Code 'Получить запись с заданным кодом Get #1,, MyRec If MyRec.КодТовара = 0 Then Debug.Print "В файле нет записи с кодом:", Code Else: Call PrintRec(MyRec) End If Next i Close #1 End Sub
Из новых деталей отметим следующее. Здесь оператор работает по существу, хотя, конечно, и без него можно было бы обойтись, используя возможности оператора Get. Однако, более важно, обратить внимание на то, что при попытке читать запись с ключом, который не задавался при создании файла (по существу, читать несуществующую запись) не возникает ошибки. Причину этого мы уже объяснили. Поэтому приходится организовывать собственную проверку, как это и сделано в нашей программе. В заключение приведем результаты одного из экспериментов с этой процедурой:
Число обрабатываемых записей -- 2 Код Товара: 8 Цена: 224 В файле нет записи с кодом: 5
До сих пор мы усиленно подчеркивали, что при работе с файлом произвольного доступа размер записи фиксирован и это предполагает хранение в таких файлов данных, размер которых фиксирован в момент их объявления. На самом деле, это не так и в таких файлах можно хранить и строки переменной длины и данные типа Variant. В этих случаях просто следует быть более осторожным, поскольку из-за сложности правил хранения таких данных повышается возможность ошибки. Вот некоторые правила, согласно которым оператор Put размещает данные в файле, а оператор Get их читает:
Len при открытии файла. Если при этом длина данных, записываемых в файл, меньше размера записи, заданного параметром Len, оператор Put заполнит остающееся до длины записи место "случайными" данными из буфера файла, а новые данные поместит, начиная со следующей границы. При попытке записать данные, длина которых больше Len, появится сообщение об ошибке. Таким образом, параметр Len задает максимальный размер записи, внутри которого может храниться значение переменного размера.Variant, динамических массивов VBA помещает туда же дополнительную информацию для их расшифровки.Put помещает непосредственно перед ней 2 байта с ее длиной.Variant. Их содержимое идентифицирует тип значения (как в функции VarType ). Например, если оно равно 3, это соответствует типу Long, и запись такой переменной займет 6 байтов (2 - для типа и 4 - для самого числа). Значение VarType = 8 соответствует строковому типу ( String ). В этом случае Put запишет 2 байта с VarType, затем 2 байта с длиной строки и затем саму строку.Put записывает перед его элементами описатель с длиной 2 + 8* (число измерений массива).Len при записи роли не играет. Put записывает значения переменных подряд без пропусков и не помещает длины динамических строк и Get выполняет преобразования, обратные тем, что при записи выполнял оператор Put. Например, при чтении в строку переменной длины Get определяет по 2-байтовому описателю длину строки, а затем читает ее содержимое в переменную. Аналогичная расшифровка происходит и при чтении в переменные типа Variant и в динамические массивы.Get читает данные подряд. В переменные фиксированного размера считывается соответствующее их размеру число байтов. При чтении в строку переменной длины в нее считывается число символов, равное длине ее текущего значения.Приведем пример работы с файлом произвольного доступа, записи которого содержат данные не фиксированного размера:
Public Sub Test2() 'Эта процедура работает с файлом, записи которого 'содержат строки переменной длины, не превосхоящей максимума, 'а также данные типа Variant Dim Fam As String Dim Other As Variant 'Открытие файла произвольного доступа для чтения и записи Open Path "Strings.var" For Random Access Read Write As #5 Len = 20 Fam = "Степанов" Put 5, 1, Fam Get 5, 1, Fam Debug.Print Fam, LOF(5) Fam = "Архангельский" Put 5, 2, Fam Get 5, 2, Fam Debug.Print Fam, LOF(5) Fam = "Куц" Put 5, 1, Fam Get 5, 1, Fam Debug.Print Fam, LOF(5) 'Запись типа Variant Other = "Петров" Put 5, 3, Other Get 5, 3, Other Debug.Print Other, LOF(5) Other = 125.25 Put 5, 4, Other Get 5, 4, Other Debug.Print Other, LOF(5) Other = 125 Put 5, 5, Other Get 5, 5, Other Debug.Print Other, LOF(5) Close #5 End Sub
Вот результаты ее работы:
Степанов 10 Архангельский 35 Куц 35 Петров 50 125,25 70 125 84
Приведем комментарии:
Put и Get позволяют записать и корректно прочитать данные переменного размера, не превосходящего максимальный размер, заданный при открытии файла.Get корректно прочитать эту строку.Variant. Теперь при записи строки используются два описателя, занимающие 4 байта.Double (8 байтов) и Integer (2 байта) с их описателями.Заканчивая тему работы с файлами произвольного доступа, хотим еще раз обратить внимание на некоторые опасные моменты, возникающие в процессе работы с ними и не контролируемые VBA:
Len ѕ максимальный размер записи. Если этого не сделать, то программа будет работать, но, к сожалению, результаты могут быть не предсказуемыми.1 до N.Бинарные файлы пишутся и читаются порциями, состоящими из произвольного числа байтов. Доступ возможен к любому байту этого файла в произвольном порядке. Никакого редактирования при операциях с этим файлом не производится и вся ответственность за сохранением структуры информации ложится на программиста. Операции над этим файлом выполняются операторами Get и Put, но можно читать этот файл побайтно, используя и уже упоминавшуюся функцию Input. Мы не будем строить специальный пример для работы с этим файлом, а ограничимся примером чтения ранее созданного текстового файла "readme.txt", который откроем, как Binary файл. В этом примере мы прочитаем последовательно этот файл порциями по 20 байтов. Заодно покажем, как организуется чтение "хвоста" файла ѕ его последней порции. Возможно, с содержательной точки зрения пример не очень хорош, поскольку будет нарушена реальная структура читаемых данных, нов этом то и вся суть дела при работе с такими файлами. Программист сам должен восстановить структуру данных, чем мы, в данном случае, заниматься не будем. Но вот сам пример:
Public Sub Test3() 'Чтение Binary файла Dim MyLocation As Long, MyLoc As Long, MyLine As String Open Path "readme.txt" For Binary As #1 MyLocation = 0 Do While MyLocation + 20 < LOF(1) ' Читаем допустимую порцию MyLine = Input(20, #1) MyLocation = Loc(1): MyLoc = Seek(1) ' 2 способа определения текущей позиции Debug.Print MyLine; Tab; MyLocation; Tab; MyLoc Loop 'Читаем последнюю порцию MyLoc = LOF(1) - MyLocation If MyLoc > 0 Then MyLine = Input(MyLoc, #1) MyLocation = Loc(1): MyLoc = Seek(1) Debug.Print MyLine, MyLocation, MyLoc, LOF(1) End If Close #1 End Sub
Перед комментариями приведем результаты работы:
"Первая строка файла 20 21 " "Зона 1","Зона 2" 40 41 "Привет,","старик! 60 61 " "Мама ","мыла "," 80 81 раму мылом. " #FALS 100 101 E# #1999-06-14# #N 120 121 ULL# 3.1416,"3,14", 140 141 "03,142" 6 #ERROR 160 161 2000# 167 168 167
Как видите, файл читается со всеми, входящими в него символами, в том числе и непечатаемыми, задающими конец строки. Заметим, что восстановить исходную структуру строк достаточно просто, было бы только желание. Обращаем внимание на новую функцию, которую мы ввели в этой программе - функцию . Она вычисляет текущую позицию файла и возвращает значение на единицу меньшее, чем функция , которая возвращает позицию следующего байта.
И последний совет в этой лекции, средств VBA для работы с файлами произвольного доступа достаточно для выполнения основных операций: создания, записи, чтения и модификации данных. Но средства поиска данных в файле (оператор ) весьма примитивны. С их помощью трудно решать задачи упорядочения файлов, поиска записей по значениям отдельных полей и другие задачи, характерные для работы с базами данных. Для небольших файлов можно создавать индексные массивы, в которых номера записей упорядочены в соответствии с требуемым критерием, и поддерживать этот порядок при всех изменениях файла. Для больших - заводить свои индексные файлы и т. п. Но владельцам Office 2000 не следует заниматься программированием собственных СУБД. В их распоряжении - возможность переписать содержимое любого файла произвольного доступа в ячейки рабочего листа Excel или в базу данных (таблицу) Access и использовать для работы с его данными всю мощь этих инструментов.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.