Паскаль: буферизація введення-виведення
1. Ідея буферизації
Дівчині Галі треба кілька склянок води, щоб полити квіти на вікні. Але вона чомусь іде до колодязя з двома здоровенними відрами. Шлях туди не зовсім короткий, відра важкі, але принісши їх, Галя не піде до колодязя за наступними склянками, аж поки не вичерпає з відер усю воду.
Такі самі міркування лежать в основі організації обміну даних між фізичними файлами та змінними програми. Фізичний файл можна порівняти з колодязєм. Добування води з нього вимагає багато часу, і вигідно це робити не надто часто та не зовсім малими дозами. У ролі відра під час виконання програми виступає буфер – спеціальна ділянка пам’ яті програми, яка надається кожній файловій змінній при її зв’ язуванні.
Спочатку розглянемо фізичні файли на диску. Дискова пам’ ять розбивається на блоки – ділянки фіксованого розміру (найчастіше, по 512 байтів). Пристрої обміну (дисководи) створено так, що саме блоками дані копіюються на диск або з диску.
Блок є одиницею фізичного обміну між диском та оперативною пам’ яттю. Коли виконується виклик підпрограми читання, в буфер копіюється цілий блок або кілька блоків даних із файла, і до змінних значення потрапляють не з файла, а з буфера. За наступних читань жодних переміщень даних між диском та оперативною пам’ яттю немає, а значення беруться з буфера.
Розмір буфера визначається операційною системою. Як правило, це 2, 8, 16 або навіть більше блоків.
З кожним буфером зв’ язана додаткова змінна, яка вказує на його поточний елемент. Саме його значення копіюється при читанні, а поточним стає наступний за ним. Можна вважати, що поточний елемент в буфері виступає представником доступного елемента файла. Якщо весь буфер прочитано, то за чергової спроби читання наступні кілька блоків файла копіюються в буфер.
Підкреслимо, що буфер, як і вказівник поточного елемента, у Паскаль-програмі явно не означається і не використовується.
Буфер можна розглядати як своєрідне вікно, крізь яке з програми видно файл. Якщо фізичний файл менший буфера за розміром, тобто весь файл "уміщається у вікні", то всі читання з нього вимагають лише одного звернення до файла.
При записі у файл дані записуються в буфер до його заповнення, і фізичне копіювання на диск, або скидання буфера, виконується лише за спроби додати дані в заповнений буфер. Після скидання буфер заповнюється з початку. Незаповнений буфер скидається у файл по закінченні виконання програми.
Зауважимо, що організація буферів текстів у Турбо Паскалі має додаткові особливості, які розглядаються далі. Наприклад, про тексти не можна стверджувати, що незаповнений буфер скидається по закінченні програми. Але буфери файлів усіх типів скидаються при виконанні процедури закриття close. Ось чому варто роботу з файлом описувати в "дужках", утворених викликами процедур відкривання та закривання.
Використання буфера дозволяє "вбити двох зайців". По-перше, враховуються особливості зовнішніх пристроїв, які вимагають обмінювати дані великими порціями (по кілька блоків). По-друге, за більшості викликів підпрограм читання та запису відбувається лише переміщення значень між різними ділянками оперативної пам’ яті, а це набагато швидше від обмінів із диском. Отже, застосування буферів, або буферизація, як правило, зменшує кількість фізичних переміщень даних між зовнішніми носіями та оперативною пам’ яттю і прискорює введення-виведення.
2. Буферизація текстів
З текстом зв’ язано не один, а два буфери. Перший, зовнішній, обробляється згідно написаного в попередньому підрозділі. Робота з другим, внутрішнім, ведеться інакше. При читанні дані копіюються з тексту в зовнішній буфер, а звідти частина їх копіюється у внутрішній. Яка саме частина, залежить від розміру внутрішнього буфера. При читанні символи тексту насправді беруться із внутрішнього буфера, а коли він вичерпується, то в нього копіюється наступна частина зовнішнього буфера (можливо, зі зверненням до фізичного файла), і читання продовжується.
Розмір внутрішнього буфера текстів установлюється в системі програмування у 128 байтів. Програміст має можливість змінити його в межах від 1 до 65536 байтів викликом процедури SETTEXTBUF вигляду
settextbuf(f, Buf, Bufsize)
або
settextbuf(f, Buf),
де f – ім’ я файлової змінної типу text, Buf – ім’ я змінної, тип якої несуттєвий, а Bufsize – вираз цілого типу зі значенням у межах 1..65535. Такий виклик слід записувати після зв’ язування файла перед установленням його в початковий стан (читання чи запису).
Змінна Buf використовується як внутрішній буфер, тому доцільно, щоб її довжина була кратною довжині блоку. Якщо розмір буфера (у байтах) Bufsize у виклику не вказано, то він визначається довжиною змінної Buf. Якщо Bufsize указано і менше довжини змінної Buf, то воно задає довжину буфера в межах змінної Buf. Але якщо Bufsize більше довжини Buf, то змінні, розташовані в пам’ яті безпосередньо за Buf, використовуються під буфер, і це може призвести до непередбачених наслідків.
Приклад 15.1. Розглянемо програму
program GreatBufferManager;
var f : text; Hugebuf : array[1..2]of char;
x, y : char; s : string[4];
begin
assign(f, 'huge.dat'); settextbuf(f, hugebuf, 4);
x:='x'; y:='y';
reset(f); readln(f, s);
writeln('x=', x, '; y=', y);
readln;
end.
Якщо першим рядком тексту в файлі huge.dat є 'qwer', то за виконання цієї програми на екрані з’ явиться зовсім не очікуване
x=x; y=y,
а на перший погляд досить дивне
x=e; y=r.
Справа в тім, що змінні x і y фізично розташовані безпосередньо за масивом Hugebuf, і читання чотирьох символів рядка файла в цей буфер призводить до заповнення не тільки масиву, а й змінних за ним. Якщо зробити рядок у файлі трошки довшим, то, запевняємо читача,