Spiral group

_________________________________________________________________________________

 

< Раздел “Язык Java > < Раздел “Технологии Java > < Основная страница >

 

Произвольный доступ, дописывание и клонирование файлов с помощью RandomAccessFile. Java JDK 1.5.

         

 

Статья опубликована: 12.05.2006

Последнее обновление: 18.03.2007

 

 

Класс java.io.RandomAccessFile обеспечивает чтение и запись данных в любом месте файла. Известно, что потоки данных из сетевых соединений позволяют получить данные последовательным путем. В отличие от сетевых соединений, файлы, записанные на дисках, обладают произвольным доступом. Данный способ чтения и записи необходим в тех случаях, когда требуется получить доступ к данным, не читая данные с начала файла. Кроме того, файлы с произвольным доступом могут открываться одновременно по чтению и записи, а также могут открываться только по чтению.

В файле с произвольным доступом присутствует указатель файла (file pointer), который всегда указывает на положение новой записи. Метод seek позволяет произвольно устанавливать указатель на любой байт внутри файла. Аргумент метода seek имеет тип long и принимает значение от 0 до максимальной длины файла, выраженной в байтах. А с помощью метода getFilePointer можно получить текущую позицию указателя файла.

Наиболее употребляемые конструкторы и методы класса:

 

RandomAccessFile(String name, String mode)

name – имя файла, зависящее от системы.

mode – режим открытия файла, может принимать значения r”,”rw”,”rws”,”rwd.

 

RandomAccessFile(File file, String mode)

file – объект класса File, инкапсулирующий системно-зависимое имя файла.

mode – режим открытия файла, может принимать значения r”,”rw”,”rws”,”rwd.

 

Значения режимов открытия файла:

 

"r"

Открывает файл только по чтению. Запуск любых методов записи данных приведет к выбросу исключения IOException.

"rw"

Открывает файл по чтению и записи. Если файл еще не создан, то осуществляется попытка создать его.

"rws"

Открывает файл по чтению и записи подобно "rw", и также требует системе при каждом изменении содержимого файла или метаданных синхронно записывать эти изменения на основной носитель.

"rwd"  

Открывает файл по чтению и записи подобно "rws", но принуждает систему синхронно записывать изменения на основной носитель только при каждом изменении содержимого файла. Если изменяются метаданные, синхронная запись не осуществляется.

 

long getFilePointer()

Возвращает текущую позицию указателя файла.

 

void seek(long pos)

Перемещает указатель файла на pos байтов от начала файла. Начиная с этой позиции, будет производиться следующая операция чтения или записи. Смещение может быть установлено за пределы файла. В этом случае длина файла не изменится. Длина файла изменится только в случае записи, при которой смещение установлено за пределы файла.

 

int skipBytes(int n)

Осуществляет попытку пропустить n байт. Этот метод может пропустить гораздо меньше байт, возможно даже 0 байт. Подобная ситуация может возникнуть в случае достижения конца файла до того как n байт будет пропущено – это одна из возможностей. Этот метод никогда не выбрасывает EOFException. Возвратится действительное количество пропущенных байтов. Если n отрицательно, то байты не будут пропускаться.

 

long length()

Возвращает длину файла, выраженную в байтах.

 

void setLength(long newLength)

Устанавливает длину заданного файла.

Если текущая длина файла, возвращаемая методом length больше чем аргумент newLength, то файл будет обрезан. В этом случае, если смещение, которое возвращается методом getFilePointer больше чем аргумент newLength, то после этого метода смещение будет равно newLength. Если текущая длина файла, возвращаемая методом length меньше чем аргумент newLength, то файл будет расширен. В этом случае содержимое расширяемой порции файла не определено, хотя часто заполняется символом с кодом 0.

 

Метод для работы с уникальным файловым каналом, ассоциированным с заданным файлом:

FileChannel getChannel();

 

Методы для выполнения обычного и форматированного ввода из файла:

int read();
int read(byte b[]);
int read(byte b[],int off,int len);
final boolean readBoolean();
final byte readByte();
final char readChar();
final double readDouble();
final float readFloat();
final void readFully(byte b[]);
final void readFully(byte b[], int off, int len); 
final int readInt();
final String readLine();
final long readLong();
final short readShort();
final int readUnsignedBytee();
final int readUnsignedShort();
final String readUTF();
 

Существуют также методы для обычной или форматированной записи в файл с прямым доступом:

void write(byte b[]);
void write(byte b[],int off,int len);
void write(int b); 
final void writeBoolean(boolean v); 
final void writeBytee(int v); 
final void writeBytes(String s); 
final void writeChar(int v); 
final void writeChars(String s); 
final void writeDouble(double v); 
final void writeFloat(float v); 
final void writeInt(int v); 
final void writeLong(long v); 
final void writeShort(int v); 

final void writeUTF(String str);

 

Попытка открыть несуществующий файл только на чтение приведет к исключению FileNotFoundException. При открытии на чтение и запись он будет создан сразу (или же будет выброшено исключение FileNotFoundException, если это невозможно осуществить).

"rws" и "rwd" режимы гарантируют при запуске методов, что все изменения файла будут сразу же записываться на носитель. Этот метод пригоден для уверенности, что критически важная информация не будет потеряна во время сбоя системы. Рассмотрим подробнее процессы, происходящие при использовании данных режимов.

Дисковые операции связаны с механическими перемещениями деталей дисковода для позиционирования по диску. На подобные перемещения требуется определенное время, которое замедляет процесс записи. Чем больше операций чтения записи производится на диске, тем больше время отнимается на позиционирование. Кроме того, когда позиционирование произошло, удобней записать один большой блок данных одной операцией, чем производить запись нескольких маленьких блоков. Обычно, операционная система буферизует операции записи в основной памяти и, при накоплении определенного количества данных, соразмерных с большими блоками записи, производит физическую запись данных на диск. Таким образом, данные не посылаются прямо в дисковое устройство. Вместо этого, результаты операций записи фиксируются в стабильной памяти и подтверждаются как завершенные. Это намного быстрее, чем ожидание окончания механической операции записи данных на диск. Спустя некоторое время данные фиксируются на диске.

Данный принцип выгоден в том случае, если требуется высокая скорость операций чтения/записи. Однако, в случае сбоя системы возникает риск потери блоков файла, которые еще физически не переписались из памяти на диск. Если необходимо по максимуму избегать подобных ситуаций, то используется синхронный метод записи. В этом случае любая операция, приводящая к изменению содержимого файла или его метаданных, будет фиксироваться сразу на физический носитель. Синхронный принцип операций записи будет выполняться гораздо медленнее, в основном из-за операций позиционирования на физическом носителе. Синхронный принцип оправдан в случаях работы с базами данных, серверами, системами с транзакционной схемой обработки данных, а также системами, где потеря даже нескольких байт данных приводит к глобальным ошибкам.

При использовании синхронного принципа с сохранением не только содержимого файла, но и его метаданных необходимо понимать значение самих метаданных. Метаданные – это информация, описывающая непосредственно файловую систему. Метаданные файловой системы могут содержать информацию о каталоге файла, дату последней модификации, индексные дескрипторы и еще ряд специфических элементов. Например, в файловой системе NTFS метаданные файловой системы являются частью файла. При этом следует иметь ввиду, что не всякая файловая система может поддерживать метаданные и, следовательно, сохранение метаданных может быть не нужно вообще.

На практике синхронный и асинхронный методы записи имеют место, и разработчикам приходится самостоятельно делать выбор между эффективностью и надежностью.

По окончании работы с файлом его следует закрыть, вызвав метод close().

 

Создание файлов.

 

Если при создании экземпляра класса  RandomAccessFile в конструкторе указать имя файла, который отсутствует в файловой системе, то он будет автоматически создан с нулевой длиной. Автоматическое создание файла гарантировано в том случае, если пользователь имеет права на создание файлов в заданном каталоге. Если такие права отсутствуют, то будет выброшено исключение. Перед тем как оно будет выброшено, следует закрыть поток, освободив системные ресурсы, связанные с этим потоком. В подобных случаях для гарантированного закрытия файловых потоков часто применяется блок finally после соответствующих try и catch блоков. В нижеприведенной программе, код в блоке finally гарантирует закрытие файлового потока непосредственно перед выбросом исключения. Внешние try catch блоки в данном случае присутствуют из-за того, что использование метода close() в блоке finally требует либо внесения в список исключений, которые будут возбуждаться методом с помощью ключевого слова throws, либо перехвата исключения. В некоторых случаях лучше перехватывать исключения с помощью try catch блоков, чем вносить в список исключений метода, поскольку дальнейшее использование подобного метода внутри других методов также требует  перехвата или внесения в список исключений. Кроме того, блоками try catch удобно управлять выводом информации о перехваченном исключении и обхода исключений для дальнейшего выполнения программы.

 

Файл CreateFileDemo.java

import java.io.RandomAccessFile;

import java.io.IOException;

public class CreateFileDemo

{

 public static void main(String[] args)

 {

  RandomAccessFile raf = null;

  try

  {

   try

   {

    // Create a new file

    raf = new RandomAccessFile("output.dat", "rw");

    raf.close();

   }

   catch(IOException e)

   {

    e.printStackTrace();

   }

   finally

   {

    if (raf != null)

    {

     raf.close();

    }

   }

  }

  catch(IOException e)

  {

   e.printStackTrace();

  }

 }

}

 

При создании потоков могут возникать исключения FileNotFoundException, SecurityException, IOException. Исключение FileNotFoundException возникает при попытке открыть входной поток данных для несуществующего файла, то есть когда файл не найден. Исключение SecurityException возникает при попытке открыть файл, для которого запрещен доступ. Например, если файл можно только читать, а он открывается для записи, возникнет исключение SecurityException. Если файл не может быть открыт для записи по каким-либо другим причинам, то возникает исключение IOException.

Чтение и запись файлов.

После того как файл открыт, вы можете использовать любые методы readXXX() и writeXXX() для ввода и ввода. В момент создания объекта класса RandomAccessFile файловый указатель устанавливается в начало файла и имеет значение 0. Вызовы методов readXXX() и writeXXX() обновляют позиции файлового указателя, сдвигая его на количество прочитанных (записанных) байтов. Для произвольного сдвига файлового указателя на некоторое количество байтов можно применить метод skipBytes(), или же установить файловый указатель в определенное место файла вызовом метода seek(). Для того, чтобы узнать текущую позицию, в которой находится файловый указатель, нужно вызвать метод getFilePointer().

RandomAccessFile одновременно реализует интерфейсы DataInput и DataOutput. Поэтому класс может производить запись и чтение всех примитивных типов Java. При этом их можно производить поочередно, произвольным образом перемещаясь по файлу.

Чтение и запись примитивов не означает, что вы можете их считывать и записывать их как есть. Например, записать числа в десятичной системе в файл и считать их как есть, вы не сможете. Однако если есть такая необходимость, то можно воспользоваться классом java.util.Scanner. Чтение и запись в приведенной программе проходит по нескольку байт в зависимости от того, сколько отводится байт для примитивного типа данных.

 

Файл writeIntDemo.java

/**

 * Программа демонстрирует запись чисел типа int в файл.

 * Запись производится четырьмя байтами, старший байт первый.

 * Запись начинается с текуей позиции указателя файла.

 * Перед запуском программы необходимо удалить файл int.txt

 * если он существует.

 */

import java.io.RandomAccessFile;

import java.io.IOException;

public class writeIntDemo

{

 public static void main(String args[])

 {

  int numbers[] = {1, 8, 16, 1024, 311111111};

  try

  {

   RandomAccessFile raf = new RandomAccessFile("int.txt","rw");

   for (int i = 0; i<numbers.length; i++)

   {

    raf.writeInt(numbers[i]);

   }

   raf.close();

  }

  catch (IOException e)

  {

   System.err.println(e.getMessage());

  }

 }

}

 

В результате выполнения программы можно ничего не увидеть в текстовом редакторе. Реально записанные данные можно просмотреть в Hex-редакторе.

 

 

Следующий пример демонстрирует запись строки в указанное место в файле. В отличие от предыдущего примера в данном случае в файл записывается строка, которую можно просмотреть в текстовом редакторе.

 

Файл seekDemo.java

/**

 * Программа демонстрирует позиционирование для

 * записи в указанное место файла.

 * Позиционирование осуществляется с помощью метода seek.

 * Запись затирает данные, которые находились в месте записи.

 * Перед запуском программы необходимо удалить файл seek.txt

 * если он существует.

 */

import java.io.RandomAccessFile;

import java.io.IOException;

public class seekDemo

{

 public static void main(String[] args)

 {

  RandomAccessFile raf = null;

  try

  {

   try

   {

    // Create a new file

    raf = new RandomAccessFile("seek.txt", "rw");

    raf.writeBytes("It is a string");

    raf.seek(8);

    raf.writeBytes("surprise!");

    raf.close();

   }

   catch(IOException e)

   {

    e.printStackTrace();

   }

   finally

   {

    if (raf != null)

    {

     raf.close();

    }

   }

  }

  catch(IOException e)

  {

   e.printStackTrace();

  }

 }

}

 

В результирующем файле будет содержаться строка "It is a surprise!". Нужно постоянно иметь ввиду, что запись данных в указанное место в файле фактически не сдвигает последующее содержимое, а затирает его.

 

Клонирование файлов.

 

RandomAccessFile можно использовать также для копирования содержимого файла в новый файл с другим именем (так называемого клонирования). Наиболее быстрым и удобным является способ копирования с помощью каналов java.nio.channels.FileChannel. Этот пакет введен в JDK 1.4 и хорошо зарекомендовал себя для перемещения содержимого между файлами больших размеров.  Копирование с помощью каналов особенно подходит в тех случаях, когда файл имеет размер выходящий за пределы нескольких десятков мегабайт. Для небольших файлов можно воспользоваться обычным способом: прочитать в массив байт, а затем записать их в файл под другим именем. Метод клонирования с помощью каналов предоставляет возможность последовательно читать файл небольшими блоками и записывать их в другой файл, что существенно упрощает запись файлов большого объема. Кроме того, количество строк кода минимально. Ниже приведен пример для такого метода. Перед запуском программы следует создать исходный файл 1.txt.

 

Файл CloneFileDemo.java

import java.io.RandomAccessFile;
import java.io.IOException;
import java.nio.channels.FileChannel;
 
public class CloneFileDemo
{
 public static void main(String[] args)
 {
  FileChannel srcChannel = null;
  FileChannel dstChannel = null;
  try
  {
   try
   {
    // Create a read-only memory-mapped file
    srcChannel = new RandomAccessFile("1.txt", "r").getChannel();
    // Create a read-write memory-mapped file
    dstChannel = new RandomAccessFile("2.txt", "rw").getChannel();
    // Copy file contents from source to destination
    dstChannel.transferFrom(srcChannel, 0, srcChannel.size());
    // Close the channels
    srcChannel.close();
    dstChannel.close();
   }
   catch(IOException e)
   {
    e.printStackTrace();
   }
   finally
   {
    if (srcChannel != null)
    {
     srcChannel.close();
    }
    if (dstChannel != null)
    {
     dstChannel.close();
    }
   }
  }
  catch(IOException e)
  {
   e.printStackTrace();
  }
 }
}

 

Клонирование файлов с помощью каналов можно осуществить и с помощью потоков на базе классов FileInputStream FileOutputStream, у которых также есть метод getChannel(). Использование каналов позволяет уже в самом начале копирования операционной системе сообщить об ошибке, если окажется, что места на дисковом носителе недостаточно для размещения полного файла. Ранее для этих целей можно было воспользоваться методом setLength() класса RandomAccessFile, которого нет у класса FileOutputStream, что ограничивало универсальное применение потоков на базе последнего класса. Метод setLength() мог также, как и в случае использования каналов вызвать исключение о нехватке места на диске, не начиная в данной ситуации бесполезного процесса копирования. Например:

 

java.io.IOException: Недостаточно места на диске

        at java.io.RandomAccessFile.setLength(Native Method)

        ...

 

В случае каких-либо ошибок результирующий файл может получиться либо полной (правильной) длины, но с неверным содержимым, либо длины 0. Несмотря на то, что ошибки при копировании файлов происходят редко, следует иметь в виду, что использование try catch finally блоков в подобных ситуациях делает более стабильной работу программы с файловой системой.

 

Дописывание в файл.

 

Чаще всего для добавления данных в конец файла используется java.io.RandomAccessFile. Однако для добавления небольших блоков данных можно воспользоваться альтернативным способом с помощью класса FileOutputStream. Этот класс имеет конструктор, который считывает булевый параметр. Установка этого параметра в значение true приведет к записи в конец файла, то есть, к добавлению в конец файла. Ниже приведена одна и та же программа дописывания в конец файла с помощью разных классов.

 

Файл FileAppendDemo.java

import java.io.PrintStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
 
public class FileAppendDemo
{
 public static void main(String[] args)
 {
  PrintStream out = null;
  try
  {
   // Create a new file
   out = new PrintStream(
   new BufferedOutputStream(
   new FileOutputStream("output.txt")));
 
   // Write some test data to it
   out.println("Original data");
 
   // Close the file
   out.close();
 
   // Open the file with append mode
   out = new PrintStream(
   new BufferedOutputStream(
   new FileOutputStream("output.txt", true)));
 
   // Append data
   out.println("Appended data");
  }
  catch(IOException e)
  {
   e.printStackTrace();
  }
  finally
  {
   if (out != null)
   {
    out.close();
   }
  }
 }
}
 
 

Файл FileAppendDemo.java

import java.io.RandomAccessFile;

import java.io.IOException;

public class FileAppendDemo

{

 public static void main(String[] args)

 {

  long l;

  RandomAccessFile raf = null;

  try

  {

   try

   {

    // Create a new file

    // Open the file with append mode

    raf = new RandomAccessFile("output.txt", "rw");

    raf.writeBytes("Original data \r\n");

    raf.close();

    raf = new RandomAccessFile("output.txt", "rw");

    l = raf.length();

    raf.seek(l);

    // Append data

    raf.writeBytes("Appended data");

    raf.close();

   }

   catch(IOException e)

   {

    e.printStackTrace();

   }

   finally

   {

    if (raf != null)

    {

     raf.close();

    }

   }

  }

  catch(IOException e)

  {

   e.printStackTrace();

  }

 }

}

 

Наиболее типичной ошибкой при дописывании является следующая последовательность действий: запись, закрытие потока, открытие потока и дописывание без установки указателя. Если открыть поток и не установить указатель в файле, то он по умолчанию будет указывать на начало файла, и любые операции записи будут затирать уже имеющиеся данные файла.

 

Заключение

 

Класс java.io.RandomAccessFile является гибким и мощным средством для чтения, записи, дописывания и клонирования файлов. Кроме того, обладает несколькими режимами записи файлов, что позволяет сохранить данные в случае системных сбоев. В отличие от других подобных классов данный класс имеет возможность записи и позиционирования в любом месте файла, а также позволяет устанавливать длину файла. Дополнительное преимущество класса состоит в том, что можно производить запись в файл данные примитивных типов и типа String. В сочетании с классом java.nio.channels.FileChannel позволяет быстро клонировать файл и является альтернативой клонирования при сочетании других классов.

 

 

Ссылки:

 

1. Хорстман К.С., Корнелл Г. Библиотека профессионала. Java 2. Том 1. Основы. — М.: Издательский дом Вильямс, 2003. — 848 с

2. Сайт современных технологий программирования. Начинаем программировать на языке Java. часть 3.

http://www.javable.com/tutorials/begin/ch3

3. Информационно методический сервер кафедры компьютерных технологий и систем Владивостокского госуниверситета экономики и сервиса. Произвольный доступ к файлам.

http://www.sun.vvsu.ru/docs/c-java/java_f/ch12.html

4. Интернет университет информационных технологий. Лекция: пакет java.io.

http://www.intuit.ru/department/pl/javapl/15/4.html

5. JavaTips - Добавление в конец файла.

http://openschool.ru

6. Package java.io. JavaTM 2 Platform Standard Edition 5.0 API Specification.

http://java.sun.com

 

 

 

 

Автор:

 

 

Загребин Виктор Александрович

 

 

< Раздел “Язык Java > < Раздел “Технологии Java > < Основная страница >

 

 

 

Hosted by uCoz