RandomAccessFile(String name, String mode);
RandomAccessFile(File file, String mode);
Параметр mode равен "r" для чтения или "rw" для чтения и записи.
/* пример # 4 : запись и чтение из потока: RandomFiles.java */
package chapt09;
import java.io.*;
public class RandomFiles {
public static void main(String[] args) {
double data[] = { 1, 10, 50, 200, 5000 };
try {
RandomAccessFile rf =
new RandomAccessFile("temp.txt", "rw");
for (double d : data)
rf.writeDouble(d); // запись в файл
/* чтение в обратном порядке */
for (int i = data.length - 1; i >= 0; i--) {
rf.seek(i * 8);
// длина каждой переменной типа double равна 8-и байтам
System.out.println(rf.readDouble());
}
rf.close();
} catch (IOException e) {
System.err.println(e);
}
}
}
В результате будет выведено:
5000.0
200.0
50.0
10.0
1.0
Предопределенные потоки
Система ввода/вывода языка Java содержит стандартные потоки ввода, вывода и вывода ошибок. Класс Systemпакетаjava.langсодержит поле in, которое является ссылкой на объект классаInputStream, иполя out,err– ссылки на объекты классаPrintStream, объявленные со спецификаторами public static и являющиеся стандартными потоками ввода, вывода и вывода ошибок соответственно. Эти потоки связаны с консолью, но могут быть переназначены на другое устройство.
Для назначения вывода текстовой информации в произвольный поток следует использовать класс PrintWriter, являющийся подклассом абстрактного класса Writer.
При наиболее удобного вывода информации в файл (или в любой другой поток) следует организовать следующую последовательность инициализации потоков с помощью класса PrintWriter:
New PrintWriter(new BufferedWriter(
new FileWriter(new File("file.txt"))));
В итоге класс BufferedWriter выступает классом-оберткой для класса FileWriter, так же как и классBufferedReader для FileReader.
Приведенный ниже пример демонстрирует вывод в файл строк и чисел с плавающей точкой.
// пример # 5 : вывод в файл: DemoWriter.java
package chapt09;
import java.io.*;
public class DemoWriter {
public static void main(String[] args) {
File f = new File("res.txt");
FileWriter fw = null;
try {
fw = new FileWriter(f, true);
} catch (IOException e) {
System.err.println("ошибка открытия потока " + e);
System.exit(1);
}
BufferedWriter bw = new BufferedWriter(fw);
PrintWriter pw = new PrintWriter(bw);
double[] v = { 1.10, 1.2, 1.401, 5.01 };
for (double version : v)
pw.printf("Java %.2g%n", version);
pw.close();
}
}
В итоге в файл res.txt будет помещена следующая информация:
Java 1.1
Java 1.2
Java 1.4
Java 5.0
Для вывода данных в файл в текстовом формате использовался фильтрованный поток вывода PrintWriter и метод printf(). После соединения этого потока с дисковым файлом посредством символьного потока BufferedWriter и удобного средства записи в файл FileWriter становится возможной запись текстовой информации с помощью обычных методов println(), print(), printf(), format(), write(), append().
В отличие от Java 1.1 в языке Java 1.2 для консольного ввода используется не байтовый, а символьный поток. В этой ситуации для ввода используется подкласс BufferedReaderабстрактного класса Readerи методы read() и readLine()для чтениясимвола и строки соответственно. Этот поток для организации чтения из файла лучше всего инициализировать объектом класса FileReader в виде:
new BufferedReader(new FileReader(new File("f.txt")));
Чтение из созданного в предыдущем примере файла с использованием удобной технологии можно произвести следующим образом:
// пример # 6 : чтение из файла: DemoReader.java
package chapt09;
import java.io.*;
public class DemoReader {
public static void main(String[] args) {
try {
BufferedReader br =
new BufferedReader(new FileReader("res.txt"));
String tmp = "";
while ((tmp = br.readLine()) != null) {
//пробел использовать как разделитель
String[] s = tmp.split("\\s");
//вывод полученных строк
for (String res : s)
System.out.println(res);
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
В консоль будет выведено:
Java
1.1
Java
1.2
Java
1.4
Java
5.0
Сериализация объектов
Кроме данных базовых типов, в поток можно отправлять объекты классов.
Процесс преобразования объектов в потоки байтов для хранения называется сериализацией. Процесс извлечения объекта из потока байтов называется десериализацией. Существует два способа сделать объект сериализуемым.
Для того чтобы объекты класса могли быть подвергнуты процессу сериализации, этот класс должен расширять интерфейс Serializable. Все подклассы такого класса также будут сериализованы. Многие стандартные классы реализуют этот интерфейс. Этот процесс заключается в сериализации каждого поля объекта, но только в том случае, если это поле не имеет спецификатора staticили transient. Спецификаторы transientиstatic означают, что поля, помеченные ими, не могут быть предметом сериализации, но существует различие в десериализации. Так, поле со спецификатором transientпосле десериализацииполучает значение по умолчанию, соответствующее его типу (объектный тип всегда инициализируется по умолчанию значением null), а поле со спецификатором staticполучает значение по умолчанию в случае отсутствия в области видимости объектов своего типа, а при их наличии получает значение, которое определено для существующего объекта.
ИнтерфейсSerializable не имеет методов, которые необходимо реализовать, поэтому его использование ограничивается упоминанием при объявлении класса. Все действия в дальнейшем производятся по умолчанию. Для записи объектов в поток необходимо использовать класс ObjectOutputStream. После этого достаточно вызвать метод writeObject(Object ob) этого класса для сериализации объекта ob и пересылки его в выходной поток данных. Для чтения используется соответственно класс ObjectInputStream и его метод readObject(), возвращающий ссылку на класс Object. После чего следует преобразовать полученный объект к нужному типу.
Необходимо знать, что при использовании Serializable десериализация происходит следующим образом: под объект выделяется память, после чего его поля заполняются значениями из потока. Конструктор объекта при этом не вызывается.
/* пример # 7 : запись сериализованного объекта в файл и его десериализация : Student.java : DemoSerialization.java */
package chapt09;
importjava.io.*;
classStudentimplementsSerializable{
protected staticString faculty;
privateString name;
private int id;
private transientString password;
private static final long serialVersionUID = 1L;
/*значение этого поля для класса будет дано далее*/
publicStudent(String nameOfFaculty, String name,
intid, String password){
faculty = nameOfFaculty;
this.name = name;
this.id = id;
this.password = password;
}
publicString toString(){
return"\nfaculty " + faculty + "\nname " + name
+ "\nID " + id + "\npassword " + password;
}
}
public classDemoSerialization {
public static voidmain(String[] args) {
// создание и запись объекта
Student goncharenko =
newStudent("MMF", "Goncharenko", 1, "G017s9");
System.out.println(goncharenko);
File fw =newFile("demo.dat");
try{
ObjectOutputStream ostream =
newObjectOutputStream(
newFileOutputStream(fw));
ostream.writeObject(goncharenko);
ostream.close();
}catch(IOException e) {
System.err.println(e);
}
Student.faculty="GEO";//изменение значения static-поля
// чтение и вывод объекта
File fr =newFile("demo.dat");
try{
ObjectInputStream istream =
new ObjectInputStream(
new FileInputStream(fr));
Student unknown =
(Student)istream.readObject();
istream.close();
System.out.println(unknown);
}catch(ClassNotFoundException ce) {
System.err.println(ce);
System.err.println("Класс не существует");
}catch(FileNotFoundException fe) {
System.err.println(fe);
System.err.println("Файл не найден");
} catch(IOException ioe) {
System.err.println(ioe);
System.err.println("Ошибка доступа");
}
}
}
В результате выполнения данного кода в консоль будет выведено:
Faculty MMF
Name Goncharenko
ID 1
Password G017s9
Faculty GEO
Name Goncharenko
ID 1
Password null
В итоге поля nameи id нового объекта unknown сохранили значения, которые им были присвоены до записи в файл. Полe passwоrdсо спецификатором transientполучило значение по умолчанию, соответствующее типу (объектный тип всегда инициализируется по умолчанию значением null). Поле faculty, помеченное как статическое, получает то значение, которое имеет это поле на текущий момент, то есть при создании объекта goncharenko поле получило значение MMF, а затем значение статического поля было изменено на GEO. Если же объекта данного типа нет в области видимости, то статическое поле также получает значение по умолчанию.
Если поля класса являются объектами другого класса, то необходимо, чтобы тот класс тоже реализовал интерфейс Serializable.
При сериализации объекта класса, реализующего интерфейс Serializable, учитывается порядок объявления полей в классе. Поэтому при изменении порядка десериализация пройдет некорректно. Это обусловлено тем, что в каждый класс, реализующий интерфейс Serializable, на стадии компиляции добавляется поле private static final long serialVersionUID. Это поле содержит уникальный идентификатор версии сериализованного класса. Оно вычисляется по содержимому класса – полям, их порядку объявления, методам, их порядку объявления.
Это поле записывается в поток при сериализации класса. Это единственный случай, когда static-поле сериализуется.
При десериализации значение этого поля сравнивается с имеющимся у класса в виртуальной машине. Если значения не совпадают, инициируется исключение java.io.InvalidClassException. Соответственно, при любом изменении в классе это поле поменяет свое значение.
Если набор полей класса и их порядок жестко определены, методы класса могут меняться. В этом случае сериализации ничего не угрожает, однако стандартный механизм не даст десериализовать данные. В таких случаях можно вручную
в классе определить поле private static final long serialVersionUID.
Вместо реализации интерфейса Serializable можно реализовать Externalizable, который содержит два метода: