Философия Java3 - Брюс Эккель
Шрифт:
Интервал:
Закладка:
//: io/UsingRandomAccessFile.java import java.io.*,
public class UsingRandomAccessFile { static String file = "rtest.dat". static void displayО throws IOException {
RandomAccessFile rf = new RandomAccessFile(file, "r"). for(int i =0. i <7, i++) System.out println(
"Значение " + i + " " + rf .readDoubleO); System.out.pri ntln(rf.readUTF()); rf.closeO;
}
public static void main(String[] args) throws IOException {
RandomAccessFile rf = new RandomAccessFile(file, "rw"). for(int i = 0; i < 7; i++)
rf writeDouble(i*1.414); rf writeUTFCThe end of the file"), rf closeO. displayO,
rf = new RandomAccessFile(file, "rw"); rf.seek(5*8); rf writeDouble(47.0001), rf.closeO; displayO;
}
} /* Output; Значение 0; 0.0 Значение 1. 1.414 Значение 2; 2.828 Значение 3- 4 242 Значение 4; 5.656 Значение 5; 7.069999999999999 Значение 6. 8.484 The end of the file Значение 0; 0.0 Значение 1; 1.414 Значение 2; 2 828 Значение 3; 4.242 Значение 4; 5.656 Значение 5; 47.0001 Значение 6; 8.484 The end of the file *///;-
Метод display() открывает файл и выводит семь значений в формате double. Метод main() создает файл, открывает и модифицирует его. Поскольку значение double всегда занимает 8 байт, для перехода к пятому числу методу seek() следует передать смещение 5*8.
Как упоминалось ранее, класс RandomAccessFile отделен от остальных классов иерархии ввода/вывода, если не считать того факта, что он реализует интерфейсы Datalnput и DataOutput. Приходится предполагать, что для этого RandomAccessFile правильно организована буферизация, потому что включить ее в программе не удастся.
Некоторая свобода выбора предоставляется только со вторым аргументом конструктора: RandomAccessFile может открываться в режиме чтения ("г") или чтения/записи ("rw").
Также стоит рассмотреть возможность употребления вместо класса RandomAccessFile механизма отображаемых в память файлов.
Каналы
В этой главе были коротко упомянуты классы каналов PipedlnputStream, Piped-OutputStream, PipedReader и PipedWriter. Это не значит, что они редко используются или не слишком полезны, просто их смысл и действие нельзя донести до понимания до тех пор, пока не объяснена многозадачность: каналы предназначены для связи между отдельными потоками программы. Они будут описаны позднее.
Средства чтения и записи файлов
Очень часто в программировании производится такая цепочка действий: файл считывается в память, там он изменяется, а потом снова записывается на диск. Одна из проблем при работе с библиотекой ввода/вывода Java состоит в том, что для выполнения таких достаточно типичных операций вам придется написать некоторое количество кода — не существует вспомогательных функций, на которые можно переложить такую деятельность. Что еще хуже, с надстройками вообще трудно запомнить, как открываются файлы. Поэтому имеет смысл добавить в вашу библиотеку вспомогательные классы, которые легко сделают нужное за вас. В Java SE5 у PrintWriter появился вспомогательный конструктор, позволяющий легко открыть текстовый файл для чтения. Тем не менее существует много других типичных задач, часто выполняемых в повседневной работе, и было бы разумно избавиться от лишнего кода, связанного с их выполнением.
Ниже показан такой класс Text File с набором статических методов, построчно считывающих и записывающих текстовые файлы. Вдобавок можно создать экземпляр класса Text File, который будет хранить содержимое файла в списке ArrayList (и функциональность списка ArrayList станет доступной при работе, с содержимым файла):
//• net/mi ndvi ew/uti1/TextFi1e java
// Статические функции для построчного считывания и записи
// текстовых файлов,- а также манипуляции файлом как списком ArrayList
package net mindview util;
import java io.*.
import java util *.
public class TextFile extends ArrayList<String> { // Чтение файла как одной строки; public static String read(String fileName) {
StringBuilder sb = new StringBuilderO; try {
BufferedReader in= new BufferedReader(new FileReader(
продолжение &
new Fi1е(fi 1eName).getAbsoluteFi1e()));
try {
String s,
while((s = in readLineO) != null) { sb append(s); sb.append("n");
}
} finally {
in.closeO;
}
} catch(IOException e) {
throw new RuntimeException(e);
}
return sb toStringO;
}
// Запись файла за один вызов метода: public static void write(String fileName, String text) { try {
PrintWriter out = new PrintWriter(
new FileCfileName).getAbsoluteFileO);
try {
out.print(text); } finally {
out. closeO;
}
} catch(IOException e) {
throw new RuntimeException(e);
}
}
// Чтение файла с разбиением по регулярному выражению: public TextFi1e(String fileName. String splitter) {
super(Arrays.asLi st(readCfi1eName).splitCsplitter))); // Вызов splitO часто оставляет пустой объект // String в начальной позиции: if(get(0) equalsC")) remove(O);
}
// Обычное построчное чтение: public TextFi1e(String fileName) { this(fileName, "n");
}
public void writeCString fileName) { try {
PrintWriter out = new PrintWriterC
new FileCfil eName). getAbsol uteFi 1 eO);
try {
forCString item : this)
out.println(item);
} finally {
out.closeO;
}
} catch(IOException e) {
throw new RuntimeException(e);
}
}
// Простая проверка :
public static void main(String[] args) {
String file = read("TextFile.java"); writeCtest.txt", file);
TextFile text = new TextFileCtest.txt"); text.wri te("test2.txt");
// Разбиение на уникальный отсортированный список слов: TreeSet<String> words = new TreeSet<String>(
new TextFileC"TextFile.java". "WW+")); // Вывод слов, начинающихся с прописной буквы-System.out.pri ntln(words.headSet("a"));
}
} /* Output:
[0. ArrayList. Arrays. Break. BufferedReader. BufferedWriter. Clean. Display. File.
FileReader, FileWriter, IOException. Normally. Output. PrintWriter. Read. Regular.
RuntimeException. Simple. Static. String. StringBuilder. System, TextFile, Tools,
TreeSet. W. Write]
*///:-
Метод read() присоединяет каждую строку к StringBuilder, а за ней присоединяется перевод строки, удаленный при чтении. Затем возвращается объект String, содержащий весь файл. Метод write() открывает файл и записывает в него текст.
Обратите внимание: к каждой операции открытия файла добавляется парный вызов close() в секции finally. Тем самым обеспечивается гарантированное закрытие файла после завершения работы.
Конструктор использует метод read() для превращения файла в String, после чего он вызывает метод String.split(), чтобы разбить результат на строки. В качестве разделителя используются символы новой строки (если вы будете часто использовать этот класс, то, возможно, захотите переписать этот конструктор, чтобы он работал эффективнее). К сожалению, аналогичного метода для соединения строк нет, так что для записи строк придется обойтись нестатическим методом write().
Так как класс должен упростить процесс чтения и записи файлов, все исключения IOException преобразуются в RuntimeException, чтобы пользователю не пришлось создавать блоки try/catch. Возможно, вы предпочтете создать другую версию, которая возвращает IOException вызывающей стороне.
В методе main() выполняется небольшой тест, позволяющий удостовериться в правильной работе методов. Несмотря на то что кода в этом классе немного, его применение позволит сэкономить вам уйму времени и сделать вашу жизнь проще, в чем вы еще будете иметь возможность убедиться чуть позже.
Стандартный ввод/вывод
Термин «стандартный ввод/вывод» возник еще в эпоху UNIX (и в некоторой форме имеется и в Windows, и во многих других операционных системах). Он означает единственный поток информации, используемый программой. Вся информация программы приходит из стандартного ввода (standard input), все данные записываются в стандартный вывод (standard output), а все ошибки программы передаются в стандартный поток для ошибок (standard error). Значение стандартного ввода/вывода состоит в том, что программы легко соединять в цепочку, где стандартный вывод одной программы становится стандартным вводом другой программы. Это мощный инструмент.
Чтение из стандартного потока ввода
Следуя модели стандартного ввода/вывода, Java определяет необходимые потоки для стандартного ввода, вывода и ошибок: System.in, System.out и System.err. На многих страницах книги вы не раз могли наблюдать процесс записи в стандартный вывод System.out, для которого уже надстроен класс форматирования данных PrintStream. Поток для ошибок System.err схож со стандартным выводом, а стандартный ввод System.in представляет собой «низкоуровневый» поток InputStream без дополнительных надстроек. Это значит, что потоки System, out и System.err можно использовать напрямую, в то время как стандартный ввод System.in желательно надстраивать.
Обычно чтение осуществляется построчно, методом readLine(), поэтому имеет смысл буферизовать стандартный ввод System.in посредством BufferedReader. Чтобы сделать это, предварительно следует конвертировать поток System.in в считывающее устройство Reader посредством класса-преобразователя InputStreamReader. Следующий пример просто отображает на экране последнюю строку, введенную пользователем (эхо-вывод):
//• io/Echo java
// Чтение из стандартного ввода
// {RunByHand}
import java io.*;
public class Echo {
public static void main(String[] args) throws IOException {
BufferedReader stdin = new BufferedReaderC new InputStreamReader(System in)),
String s,
while((s = stdin readLineO) != null && s.lengthO! = 0)
System.out.printin(s); // Пустая строка или нажатие Ctrl+Z завершает программу
}
} ///-
Присутствие спецификации исключений объясняется тем, что метод readLineO может возбуждать исключение IOException. Снова обратите внимание, что поток System.in обычно буферизуется, впрочем, как и большинство потоков.
Замена System.in на PrintWriter
Стандартный вывод System.out является объектом PrintStream, который, в свою очередь, наследует от базового класса OutputStream. В классе PrintWriter имеется конструктор, который принимает в качестве аргумента выходной поток Output-Stream. Таким образом, вы можете преобразовать поток стандартного вывода System.out в символьно-ориентированный поток PrintWriter: