Программа солиднее, когда она умеет сохранять или читать что-то из файлов. Пусть это даже не будет база данных. Так давайте научимся создавать, удалять, читать, записывать файлы (бонус: папки).
Про файлы в целом
Как вы знаете, файлы хранятся на носителях. Не будем вдаваться в подробности электрических зарядов или магнитных полей, а также всяких нулей и единичек. Мы – на вершине абстракции. Но некоторые вещи про файлы знать нужно для корректной работы с ними.
Например, файлы не могут иметь любое имя, какое вы захотите. Есть запрещённые символы: \/:*?”<>|. Также длина названия может быть не любой. Зависит от местоположения файла и ОС: например, в Windows максимальная длина пути обычно составляет 260 символов, включая имя файла и путь к нему. В некоторых Unix-подобных системах ограничения могут быть менее строгими.
Также в разных ОС может учитываться или не учитываться регистр в названиях файлов. Например, в Windows “file.txt” и “File.txt” – разные файлы, когда в Unix это – одинаковое название.
И напоследок про 2 вида пути к файлу или папке:
- Абсолютный путь указывает полное местоположение файла или папки в файловой системе, начиная от корневого каталога. Точное расположение файла, от самого диска, как здесь: “C:\Program Files\Adobe\Adobe Illustrator 2023“.
- Относительный путь указывает местоположение файла или папки относительно текущего каталога. Это удобно для работы с файлами внутри проекта. Например: “Images\img1.png“, то есть от текущего расположения перейти в папку Images и найти там файл img1.png.
Так как в коде символ обратного слеша (\) зарезервирован для экранирования escape-последовательностей, то нужно ставить в строке пути вместо одного такого слеша два (\\).
FileReader и FileWriter
Эти два класса – два брата-акробата, которые удобно использовать, если вы хотите читать и писать символы (то есть текстовую информацию) в файлах. К ним присоединяются ещё два приятеля – BufferedReader и BufferedWriter. Давайте обо всём по порядку.
Создание файла:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import java.io.File; import java.io.IOException; public class Main { public static void main(String[] args) { try { File file = new File("file.txt"); if (file.createNewFile()) { System.out.println("Файл был успешно создан"); } else { System.out.println("Файл не был создан, так как он уже существует"); } } catch (IOException e) { System.out.println("При создании файла возникла ошибка"); } } } |
Если что-то идёт не так при создании файла, будет выброшено исключение IOException. И вы обязаны его обработать или дописать throws в шапке метода, иначе программа не скомпилируется.
Удаление файла:
1 2 3 4 5 6 7 8 9 10 11 12 |
import java.io.File; public class Main { public static void main(String[] args) { File file = new File("file.txt"); if (file.delete()) { System.out.println("Файл уничтожен"); } else { System.out.println("Файл не был уничтожен, так как не существует"); } } } |
Чтение из файла:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class Main { public static void main(String[] args) { try { FileReader fileReader = new FileReader("file.txt"); BufferedReader bufferedReader = new BufferedReader(fileReader); while (true) { String lineFromFile = bufferedReader.readLine(); if (lineFromFile == null) { break; } System.out.println(lineFromFile); } bufferedReader.close(); } catch (IOException e) { System.out.println("При чтении файла возникла ошибка"); } } } |
Запись в файл:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; public class Main { public static void main(String[] args) { try { FileWriter fileWriter = new FileWriter("file.txt"); BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); bufferedWriter.write("День №129"); bufferedWriter.newLine(); bufferedWriter.write("Дорогой дневник..."); bufferedWriter.close(); } catch (IOException e) { System.out.println("При записи в файл произошла ошибка"); } } } |
Дозапись в конец файла без стирания предыдущего содержимого – нужно только добавить true в конструктор FileWriter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; public class Main { public static void main(String[] args) { try { FileWriter fileWriter = new FileWriter("file.txt", true); BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); bufferedWriter.write("День №129"); bufferedWriter.newLine(); bufferedWriter.write("Дорогой дневник..."); bufferedWriter.close(); } catch (IOException e) { System.out.println("При записи в файл произошла ошибка"); } } } |
Создание папки:
1 2 3 4 5 6 7 8 9 10 11 12 |
import java.io.File; public class Main { public static void main(String[] args) { File folder = new File("ya_papka"); if (folder.mkdir()) { System.out.println("Папка была создана"); } else { System.out.println("Не удалось создать папку =("); } } } |
А удаление папки происходит ровно таким же методом, как и файла:
1 2 3 4 5 6 7 8 9 10 11 12 |
import java.io.File; public class Main { public static void main(String[] args) { File folder = new File("ya_papka"); if (folder.delete()) { System.out.println("Папка уничтожена"); } else { System.out.println("Не удалось уничтожить папку =("); } } } |
FileInputStream и FileOutputStream
А уж эти братья-акробатья нужны для работы с файлами, когда вас интересуют не символы, а байты. Если вам нужно читать или записывать байтовые данные, например, изображения или звуковые файлы, используйте их.
Чтение из файла:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import java.io.FileInputStream; import java.io.IOException; public class Main { public static void main(String[] args) { try { FileInputStream inputStream = new FileInputStream("file.txt"); while (true) { int content = inputStream.read(); if (content == -1) { break; } System.out.print(content + " "); } inputStream.close(); } catch (IOException e) { System.out.println("При чтении файла произошла ошибка :("); } } } |
В данном случае переменная content имеет тип int, а не byte, чтобы иметь возможность считать не только диапазон 0-255, но и -1, который будет сигнализировать о конце файла.
Запись в файл:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import java.io.FileOutputStream; import java.io.IOException; public class Main { public static void main(String[] args) { try { FileOutputStream outputStream = new FileOutputStream("file.txt"); String content = "В тридевятом царстве, в тридесятом государстве..."; outputStream.write(content.getBytes()); outputStream.close(); } catch (IOException e) { System.out.println("При записи в файл произошла ошибка :("); } } } |
Как и для FileWriter, здесь для FileOutputStream нужно указать вторым параметром в конструкторе true, чтобы дозаписать в конец файла информацию:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import java.io.FileOutputStream; import java.io.IOException; public class Main { public static void main(String[] args) { try { FileOutputStream outputStream = new FileOutputStream("file.txt", true); String content = "В тридевятом царстве, в тридесятом государстве..."; outputStream.write(content.getBytes()); outputStream.close(); } catch (IOException e) { System.out.println("При записи в файл произошла ошибка :("); } } } |
java.nio.file.Files
Есть один прикольный класс, Files из пакета java.nio.file, который предоставляет возможности делать с файлами и папками всё. Буквально, целая куча различных операций. Методов очень много, поэтому мы кратко пробежимся по их объявлениям, параметрам и возвращаемым типам.
Перед этим нужно познакомиться с классом Path, который участвует практически во всех операциях класса Files. Он отражает путь до файла или папки, с которыми будем работать. Его объект можно так:
1 2 |
Path absolutePath = Path.of("c:\\testprojects\\fileproject\\file.txt"); Path relativePath = Path.of("directory\\files.txt"); |
Итак, краткий обзор самых используемых методов из класса Files (они все статические):
- Path createFile(Path path) – создание нового файла по пути path
- Path createDirectory(Path path) – создание новой папки по пути path
- Path createDirectories(Path path) – создание папок и подпапок, если требуется, по пути path
- void delete(Path path) – удаление либо файла, либо папки (причём папка обязана быть пустой)
- Path copy(Path src, Path dest) – копирование файла в другое место
- Path move(Path src, Path dest) – перемещение файла в другое место
- boolean isDirectory(Path path) – проверка, является ли объект папкой, а не файлом
- boolean isRegularFile(Path path) – проверка наоборот, является ли объект файлом, а не папкой
- boolean exists(Path path) – проверка, существует ли объект по пути path
- byte[] readAllBytes(Path path) – чтение всех байт из файла
- String readString(Path path) – чтение всего файла и возврат содержимого как одной строки
- List<String> readAllLines(Path path) – чтение всего файла и возврат содержимого как список строк
- Path write(Path path, byte[]) – запись в файл массива байт
- Path writeString(Path path, String str) – запись в файл строки
- DirectoryStream<Path> newDirectoryStream(Path dir) – получение содержимого папки (её внутренние файлы и папки)
Давайте протестируем самый причудливый метод – последний. Он позволяет узнать, что находится в папке. Выведем в консоль список файлов внутри папки target -> classes внутри проекта:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import java.io.IOException; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; public class Main { public static void main(String[] args) { Path path = Path.of("target\\classes"); try (DirectoryStream<Path> files = Files.newDirectoryStream(path)) { for (Path subPath : files) { System.out.print(Files.isDirectory(subPath) ? "Папка" : "Файл"); System.out.print(" "); System.out.println(subPath.getFileName()); } } catch (IOException e) { System.out.println("При чтении объектов в папке произошла ошибка :("); } } } |
Программы, которые умеют оперировать с данными во время выполнения – это, конечно хорошо. Но данные нужны и после перезапуска программы. Например, сохранение настроек приложения, аккаунта пользователя или его записей. Также запись и чтение файлов может быть применимо в более широких операциях – прослушивание песен, просмотр видео и т.д.