Перейти к основному содержимому

Java — запись в файл

· 6 мин. чтения

Задача: Медиана двух отсортированных массивов

Даны два отсортированных массива размерами n и m. Найдите медиану слияния этих двух массивов.
Временная сложность решения должна быть O(log(m + n)) ...

ANDROMEDA

1. Обзор

В этом руководстве мы рассмотрим различные способы записи в файл с помощью Java. Мы будем использовать BufferedWriter , PrintWriter , FileOutputStream , DataOutputStream , RandomAccessFile , FileChannel и служебный класс файлов Java 7 .

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

Этот учебник является частью серии «Назад к основам Java» здесь, на ForEach.

2. Пишите с помощью BufferedWriter

Давайте начнем с простого и воспользуемся BufferedWriter для записи строки в новый файл :

public void whenWriteStringUsingBufferedWritter_thenCorrect() 
throws IOException {
String str = "Hello";
BufferedWriter writer = new BufferedWriter(new FileWriter(fileName));
writer.write(str);

writer.close();
}

Вывод в файле будет:

Hello

Затем мы можем добавить строку к существующему файлу :

@Test
public void whenAppendStringUsingBufferedWritter_thenOldContentShouldExistToo()
throws IOException {
String str = "World";
BufferedWriter writer = new BufferedWriter(new FileWriter(fileName, true));
writer.append(' ');
writer.append(str);

writer.close();
}

Тогда файл будет:

Hello World

3. Пишите с помощью PrintWriter

Далее давайте посмотрим, как мы можем использовать PrintWriter для записи форматированного текста в файл :

@Test
public void givenWritingStringToFile_whenUsingPrintWriter_thenCorrect()
throws IOException {
FileWriter fileWriter = new FileWriter(fileName);
PrintWriter printWriter = new PrintWriter(fileWriter);
printWriter.print("Some String");
printWriter.printf("Product name is %s and its price is %d $", "iPhone", 1000);
printWriter.close();
}

Полученный файл будет содержать:

Some String
Product name is iPhone and its price is 1000$

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

Мы можем создать модуль записи, используя FileWriter , BufferedWriter или даже System.out .

4. Пишите с помощью FileOutputStream

Давайте теперь посмотрим, как мы можем использовать FileOutputStream для записи двоичных данных в файл.

Следующий код преобразует String в байты и записывает байты в файл с помощью FileOutputStream :

@Test
public void givenWritingStringToFile_whenUsingFileOutputStream_thenCorrect()
throws IOException {
String str = "Hello";
FileOutputStream outputStream = new FileOutputStream(fileName);
byte[] strToBytes = str.getBytes();
outputStream.write(strToBytes);

outputStream.close();
}

Вывод в файле, конечно, будет:

Hello

5. Пишите с помощью DataOutputStream

Далее давайте посмотрим, как мы можем использовать DataOutputStream для записи строки в файл:

@Test
public void givenWritingToFile_whenUsingDataOutputStream_thenCorrect()
throws IOException {
String value = "Hello";
FileOutputStream fos = new FileOutputStream(fileName);
DataOutputStream outStream = new DataOutputStream(new BufferedOutputStream(fos));
outStream.writeUTF(value);
outStream.close();

// verify the results
String result;
FileInputStream fis = new FileInputStream(fileName);
DataInputStream reader = new DataInputStream(fis);
result = reader.readUTF();
reader.close();

assertEquals(value, result);
}

6. Пишите с RandomAccessFile

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

RandomAccessFile позволяет нам записывать в определенную позицию в файле с заданным смещением — от начала файла — в байтах.

Этот код записывает целочисленное значение со смещением, заданным от начала файла:

private void writeToPosition(String filename, int data, long position) 
throws IOException {
RandomAccessFile writer = new RandomAccessFile(filename, "rw");
writer.seek(position);
writer.writeInt(data);
writer.close();
}

Если мы хотим прочитать int , хранящийся в определенном месте , мы можем использовать этот метод:

private int readFromPosition(String filename, long position) 
throws IOException {
int result = 0;
RandomAccessFile reader = new RandomAccessFile(filename, "r");
reader.seek(position);
result = reader.readInt();
reader.close();
return result;
}

Чтобы протестировать наши функции, давайте запишем целое число, отредактируем его и, наконец, прочитаем обратно:

@Test
public void whenWritingToSpecificPositionInFile_thenCorrect()
throws IOException {
int data1 = 2014;
int data2 = 1500;

writeToPosition(fileName, data1, 4);
assertEquals(data1, readFromPosition(fileName, 4));

writeToPosition(fileName2, data2, 4);
assertEquals(data2, readFromPosition(fileName, 4));
}

7. Пишите с помощью FileChannel

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

@Test
public void givenWritingToFile_whenUsingFileChannel_thenCorrect()
throws IOException {
RandomAccessFile stream = new RandomAccessFile(fileName, "rw");
FileChannel channel = stream.getChannel();
String value = "Hello";
byte[] strBytes = value.getBytes();
ByteBuffer buffer = ByteBuffer.allocate(strBytes.length);
buffer.put(strBytes);
buffer.flip();
channel.write(buffer);
stream.close();
channel.close();

// verify
RandomAccessFile reader = new RandomAccessFile(fileName, "r");
assertEquals(value, reader.readLine());
reader.close();
}

8. Пишите с помощью файлов

В Java 7 представлен новый способ работы с файловой системой, а также новый служебный класс: Files .

Используя класс Files , мы можем создавать, перемещать, копировать и удалять файлы и каталоги. Его также можно использовать для чтения и записи в файл:

@Test
public void givenUsingJava7_whenWritingToFile_thenCorrect()
throws IOException {
String str = "Hello";

Path path = Paths.get(fileName);
byte[] strToBytes = str.getBytes();

Files.write(path, strToBytes);

String read = Files.readAllLines(path).get(0);
assertEquals(str, read);
}

9. Запись во временный файл

Теперь попробуем записать во временный файл. Следующий код создает временный файл и записывает в него строку :

@Test
public void whenWriteToTmpFile_thenCorrect() throws IOException {
String toWrite = "Hello";
File tmpFile = File.createTempFile("test", ".tmp");
FileWriter writer = new FileWriter(tmpFile);
writer.write(toWrite);
writer.close();

BufferedReader reader = new BufferedReader(new FileReader(tmpFile));
assertEquals(toWrite, reader.readLine());
reader.close();
}

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

10. Заблокируйте файл перед записью

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

Давайте воспользуемся FileChannel , чтобы попытаться заблокировать файл перед записью в него:

@Test
public void whenTryToLockFile_thenItShouldBeLocked()
throws IOException {
RandomAccessFile stream = new RandomAccessFile(fileName, "rw");
FileChannel channel = stream.getChannel();

FileLock lock = null;
try {
lock = channel.tryLock();
} catch (final OverlappingFileLockException e) {
stream.close();
channel.close();
}
stream.writeChars("test lock");
lock.release();

stream.close();
channel.close();
}

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

11. Примечания

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

  • Если мы попытаемся прочитать из несуществующего файла, будет выброшено исключение FileNotFoundException .
  • Если мы попытаемся записать в файл, который не существует, файл будет создан первым, и никаких исключений не будет.
  • Очень важно закрыть поток после его использования, так как он не закрывается неявно, чтобы освободить любые связанные с ним ресурсы.
  • В выходном потоке метод close() вызывает flush() перед освобождением ресурсов, что заставляет любые буферизованные байты записываться в поток.

Глядя на общие практики использования, мы видим, например, что PrintWriter используется для записи форматированного текста, FileOutputStream для записи двоичных данных, DataOutputStream для записи примитивных типов данных, RandomAccessFile для записи в определенную позицию и FileChannel для более быстрой записи в файлы большего размера. Некоторые из API этих классов позволяют больше, но это хорошее место для начала.

12. Заключение

В этой статье показано множество вариантов записи данных в файл с помощью Java.

Реализацию всех этих примеров и фрагментов кода можно найти на GitHub .