Чтение текстового файла в Java
Существует несколько способов чтения и записи данных файлов в Java.
Я хочу прочитать данные в формате ASCII из файла. Какие существуют способы и в чем их различия?
5 ответ(ов)
Самый простой способ - использовать класс Scanner
в Java вместе с объектом FileReader
. Простой пример:
Scanner in = new Scanner(new FileReader("filename.txt"));
У класса Scanner
есть несколько методов для чтения строк, чисел и т.д. Дополнительную информацию можно найти на странице документации по Java.
Например, для чтения всего содержимого файла в строку:
StringBuilder sb = new StringBuilder();
while(in.hasNext()) {
sb.append(in.next());
}
in.close();
outString = sb.toString();
Если вам нужно использовать определенную кодировку, вы можете воспользоваться следующим вариантом вместо FileReader
:
new InputStreamReader(new FileInputStream(fileUtf8), StandardCharsets.UTF_8)
Вот простое решение:
String content = new String(Files.readAllBytes(Paths.get("sample.txt")));
Или, если нужно считать содержимое в виде списка строк:
List<String> content = Files.readAllLines(Paths.get("sample.txt"));
Оба варианта используют класс Files
и позволяют удобно работать с файлами в Java.
Вот еще один способ сделать это без использования внешних библиотек:
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public String readFile(String filename)
{
String content = null;
File file = new File(filename); // Например, foo.txt
FileReader reader = null;
try {
reader = new FileReader(file);
char[] chars = new char[(int) file.length()];
reader.read(chars);
content = new String(chars);
reader.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(reader != null){
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return content;
}
Этот код открывает файл с указанным именем, считывает его содержимое в массив символов, а затем преобразует этот массив в строку. Не забудьте обрабатывать возможные исключения, чтобы избежать утечек ресурсов.
Я провел бенчмаркинг разных способов чтения файлов в Java и поделюсь своими находками. Вкратце, самый быстрый способ - использовать BufferedInputStream
совместно с FileInputStream
. Если нужно читать много файлов, то использование трех потоков позволяет уменьшить общее время выполнения примерно вдвое. Однако добавление большего количества потоков постепенно ухудшает производительность, и с двадцатью потоками время исполнения становится в три раза больше, чем с одним потоком.
Считаем, что необходимо прочитать файл и выполнить что-то осмысленное с его содержимым. В моих примерах я читаю строки из лог-файла и подсчитываю те, которые содержат значения, превышающие определенный порог. Поэтому я предполагаю, что однострочный код Java 8 вида Files.lines(Paths.get("/path/to/file.txt")).map(line -> line.split(";"))
не подходит.
Я тестировал на Java 1.8, Windows 7 и как на SSD, так и на HDD.
Я написал шесть различных реализаций:
rawParse: Использует BufferedInputStream
над FileInputStream
и считывает строки посимвольно. Этот метод превзошел все другие однопоточные подходы, но может быть неудобным для не-ASCII файлов.
lineReaderParse: Использует BufferedReader
над FileReader
, читает построчно и разбивает строки, вызывая String.split()
. Этот метод примерно на 20% медленнее, чем rawParse.
lineReaderParseParallel: То же самое, что и lineReaderParse
, но использует несколько потоков. Это самый быстрый вариант во всех случаях.
nioFilesParse: Использует java.nio.files.Files.lines()
.
nioAsyncParse: Использует AsynchronousFileChannel
с обработчиком завершения и пулом потоков.
nioMemoryMappedParse: Использует файловое отображение в память. Это действительно плохая идея, время выполнения оказывается как минимум в три раза длиннее, чем у любой другой реализации.
Вот средние времена чтения 204 файлов по 4 МБ каждый на четырехядерном процессоре i7 и SSD. Файлы генерировались динамически, чтобы избежать кэширования на диске.
rawParse 11.10 sec
lineReaderParse 13.86 sec
lineReaderParseParallel 6.00 sec
nioFilesParse 13.52 sec
nioAsyncParse 16.06 sec
nioMemoryMappedParse 37.68 sec
Я обнаружил, что разница между работой на SSD и HDD меньше, чем ожидал, SSD оказался примерно на 15% быстрее. Это может быть связано с тем, что файлы генерировались на неконтролируемом HDD и читались последовательно, в результате чего вращающийся диск может выполнять операции почти так же, как и SSD.
Меня удивила низкая производительность реализации nioAsyncParse
. Либо я что-то реализовал неправильно, либо многопоточная реализация с использованием NIO и обработчика завершения работает так же (или даже хуже), чем однопоточная реализация с использованием java.io API. Более того, асинхронный парсер с CompletionHandler
требует гораздо больше строк кода и сложнее для корректной реализации, чем простая реализация на старых потоках.
Теперь представлю шесть реализаций, за которыми следует класс, содержащий их все, плюс параметризуемый метод main(), который позволяет поиграть с количеством файлов, размером файлов и степенью параллелизма. Обратите внимание, что размер файлов варьируется плюс-минус 20%. Это сделано, чтобы избежать эффекта из-за того, что все файлы одинакового размера.
Полная исполнимая реализация всех случаев доступна по ссылке: FileReadBenchmark.java
Вот три проверенных метода для чтения файлов в Java:
1. Используя BufferedReader
package io;
import java.io.*;
public class ReadFromFile2 {
public static void main(String[] args) throws Exception {
File file = new File("C:\\Users\\pankaj\\Desktop\\test.java");
BufferedReader br = new BufferedReader(new FileReader(file));
String st;
while ((st = br.readLine()) != null) {
System.out.println(st);
}
br.close();
}
}
Этот метод подходит для чтения текстовых файлов построчно. Важно закрывать BufferedReader после окончания чтения, чтобы избежать утечки ресурсов.
2. Используя Scanner
package io;
import java.io.File;
import java.util.Scanner;
public class ReadFromFileUsingScanner {
public static void main(String[] args) throws Exception {
File file = new File("C:\\Users\\pankaj\\Desktop\\test.java");
Scanner sc = new Scanner(file);
while (sc.hasNextLine()) {
System.out.println(sc.nextLine());
}
sc.close();
}
}
Scanner удобен для чтения файлов построчно и предоставляет ряд методов для работы с разными форматами данных. Не забудьте закрыть сканер после использования.
3. Используя FileReader
package io;
import java.io.*;
public class ReadingFromFile {
public static void main(String[] args) throws Exception {
FileReader fr = new FileReader("C:\\Users\\pankaj\\Desktop\\test.java");
int i;
while ((i = fr.read()) != -1) {
System.out.print((char) i);
}
fr.close();
}
}
Этот вариант читает файл посимвольно и подходит для работы с текстовыми файлами, когда требуется большая гибкость.
Чтение всего файла без цикла с использованием Scanner
package io;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class ReadingEntireFileWithoutLoop {
public static void main(String[] args) throws FileNotFoundException {
File file = new File("C:\\Users\\pankaj\\Desktop\\test.java");
Scanner sc = new Scanner(file);
sc.useDelimiter("\\Z");
System.out.println(sc.next());
sc.close();
}
}
Этот метод позволяет прочитать весь файл сразу, не используя цикл. Убедитесь, что используете правильный делимитор (в данном случае \\Z
), который указывает на конец файла.
Выбирайте метод в зависимости от ваших требований к чтению файлов.
Как прочитать большой текстовый файл построчно с помощью Java?
Как создать строку Java из содержимого файла?
Вывод строки в текстовый файл
Как установить Java 8 на Mac
Что значит 'synchronized'?