1. Обзор
В этом руководстве мы рассмотрим различные способы чтения из файла в Java .
Во-первых, мы узнаем, как загрузить файл из пути к классам, URL-адреса или из файла JAR, используя стандартные классы Java.
Во-вторых, мы увидим, как читать содержимое с помощью BufferedReader
, Scanner
, StreamTokenizer
, DataInputStream
, SequenceInputStream
и FileChannel
. Мы также обсудим, как читать файл в кодировке UTF-8.
Наконец, мы рассмотрим новые методы загрузки и чтения файла в Java 7 и Java 8.
Эта статья является частью серии «Java — Back to Basic» на ForEach.
2. Настройка
2.1. Входной файл
В большинстве примеров в этой статье мы будем читать текстовый файл с именем файла fileTest.txt
, который содержит одну строку:
Hello, world!
Для нескольких примеров мы будем использовать другой файл; в этих случаях мы будем явно указывать файл и его содержимое.
2.2. Вспомогательный метод
Мы будем использовать набор тестовых примеров только с основными классами Java, а в тестах мы будем использовать утверждения с сопоставителями Hamcrest .
Тесты будут использовать общий метод readFromInputStream
, который преобразует InputStream
в String
для упрощения утверждения результатов:
private String readFromInputStream(InputStream inputStream)
throws IOException {
StringBuilder resultStringBuilder = new StringBuilder();
try (BufferedReader br
= new BufferedReader(new InputStreamReader(inputStream))) {
String line;
while ((line = br.readLine()) != null) {
resultStringBuilder.append(line).append("\n");
}
}
return resultStringBuilder.toString();
}
Обратите внимание, что есть и другие способы достижения того же результата. Мы можем обратиться к этой статье для некоторых альтернатив.
3. Чтение файла из пути к классам
3.1. Использование стандартной Java
В этом разделе объясняется, как читать файл, доступный в пути к классам. Мы прочитаем « fileTest.txt
», доступный в src/main/resources
:
@Test
public void givenFileNameAsAbsolutePath_whenUsingClasspath_thenFileData() {
String expectedData = "Hello, world!";
Class clazz = FileOperationsTest.class;
InputStream inputStream = clazz.getResourceAsStream("/fileTest.txt");
String data = readFromInputStream(inputStream);
Assert.assertThat(data, containsString(expectedData));
}
В приведенном выше фрагменте кода мы использовали текущий класс для загрузки файла с помощью метода getResourceAsStream
и передали абсолютный путь к файлу для загрузки.
Тот же метод доступен и для экземпляра ClassLoader
:
ClassLoader classLoader = getClass().getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream("fileTest.txt");
String data = readFromInputStream(inputStream);
Мы получаем classLoader
текущего класса, используя getClass().getClassLoader()
.
Основное отличие состоит в том, что при использовании getResourceAsStream
в экземпляре ClassLoader
путь рассматривается как абсолютный, начиная с корня пути к классам.
При использовании для экземпляра Class
путь
может быть относительным для пакета или абсолютным путем, на который намекает косая черта в начале. ``
Конечно, обратите внимание, что на практике открытые потоки всегда должны быть закрыты , как, например, InputStream
в нашем примере:
InputStream inputStream = null;
try {
File file = new File(classLoader.getResource("fileTest.txt").getFile());
inputStream = new FileInputStream(file);
//...
}
finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.2. Использование библиотеки commons-io
Другой распространенный вариант — использование класса FileUtils пакета
commons-io
:
@Test
public void givenFileName_whenUsingFileUtils_thenFileData() {
String expectedData = "Hello, world!";
ClassLoader classLoader = getClass().getClassLoader();
File file = new File(classLoader.getResource("fileTest.txt").getFile());
String data = FileUtils.readFileToString(file, "UTF-8");
assertEquals(expectedData, data.trim());
}
Здесь мы передаем объект File
в метод readFileToString()
класса FileUtils
. Этот служебный класс позволяет загружать содержимое без необходимости написания какого-либо шаблонного кода для создания экземпляра InputStream
и чтения данных.
Эта же библиотека также предлагает класс IOUtils
`` :
@Test
public void givenFileName_whenUsingIOUtils_thenFileData() {
String expectedData = "Hello, world!";
FileInputStream fis = new FileInputStream("src/test/resources/fileTest.txt");
String data = IOUtils.toString(fis, "UTF-8");
assertEquals(expectedData, data.trim());
}
Здесь мы передаем объект FileInputStream
в метод toString()
класса IOUtils
. Этот служебный класс действует так же, как и предыдущий, для создания экземпляра InputStream
и чтения данных.
4. Чтение с помощью BufferedReader
Теперь давайте сосредоточимся на различных способах разбора содержимого файла.
Мы начнем с простого способа чтения из файла с помощью BufferedReader:
@Test
public void whenReadWithBufferedReader_thenCorrect()
throws IOException {
String expected_value = "Hello, world!";
String file ="src/test/resources/fileTest.txt";
BufferedReader reader = new BufferedReader(new FileReader(file));
String currentLine = reader.readLine();
reader.close();
assertEquals(expected_value, currentLine);
}
Обратите внимание, что readLine()
вернет null
, когда будет достигнут конец файла.
5. Чтение из файла с помощью Java NIO
В JDK7 значительно обновлен пакет NIO.
Давайте рассмотрим пример с использованием класса Files
и метода readAllLines
. Метод readAllLines
принимает Path.
Класс Path
можно рассматривать как обновление java.io.File
с некоторыми дополнительными операциями.
5.1. Чтение небольшого файла
В следующем коде показано, как прочитать небольшой файл с помощью нового класса Files :
@Test
public void whenReadSmallFileJava7_thenCorrect()
throws IOException {
String expected_value = "Hello, world!";
Path path = Paths.get("src/test/resources/fileTest.txt");
String read = Files.readAllLines(path).get(0);
assertEquals(expected_value, read);
}
Обратите внимание, что мы также можем использовать метод readAllBytes()
, если нам нужны двоичные данные.
5.2. Чтение большого файла
Если мы хотим прочитать большой файл с помощью класса Files
, мы можем использовать метод BufferedReader.
Следующий код читает файл, используя новый класс Files и
BufferedReader
:
@Test
public void whenReadLargeFileJava7_thenCorrect()
throws IOException {
String expected_value = "Hello, world!";
Path path = Paths.get("src/test/resources/fileTest.txt");
BufferedReader reader = Files.newBufferedReader(path);
String line = reader.readLine();
assertEquals(expected_value, line);
}
5.3. Чтение файла с помощью Files.lines()
JDK8 предлагает метод lines()
внутри класса Files .
Он возвращает поток
элементов String.
Давайте рассмотрим пример того, как читать данные в байты и декодировать их с помощью кодировки UTF-8.
Следующий код читает файл с помощью new Files.lines()
:
@Test
public void givenFilePath_whenUsingFilesLines_thenFileData() {
String expectedData = "Hello, world!";
Path path = Paths.get(getClass().getClassLoader()
.getResource("fileTest.txt").toURI());
Stream<String> lines = Files.lines(path);
String data = lines.collect(Collectors.joining("\n"));
lines.close();
Assert.assertEquals(expectedData, data.trim());
}
Используя Stream с каналами ввода-вывода, такими как файловые операции, нам нужно явно закрыть поток, используя метод close()
.
Как мы видим, Files
API предлагает еще один простой способ чтения содержимого файла в строку.
В следующих разделах мы рассмотрим другие менее распространенные методы чтения файла, которые могут быть уместны в некоторых ситуациях.
6. Чтение со сканером
Далее давайте использовать сканер
для чтения из файла. Здесь мы будем использовать пробел в качестве разделителя:
@Test
public void whenReadWithScanner_thenCorrect()
throws IOException {
String file = "src/test/resources/fileTest.txt";
Scanner scanner = new Scanner(new File(file));
scanner.useDelimiter(" ");
assertTrue(scanner.hasNext());
assertEquals("Hello,", scanner.next());
assertEquals("world!", scanner.next());
scanner.close();
}
Обратите внимание, что разделителем по умолчанию является пробел, но со Scanner
можно использовать несколько разделителей .
Класс Scanner полезен при чтении содержимого из консоли или когда содержимое содержит примитивные значения с известным разделителем (например, список целых чисел, разделенных пробелом).
7. Чтение с помощью StreamTokenizer
Теперь давайте прочитаем текстовый файл в токены, используя StreamTokenizer
.
Токенизатор работает, сначала выясняя, что будет следующим токеном, строкой или числом. Мы делаем это, просматривая поле tokenizer.ttype
.
Затем мы прочитаем фактический токен на основе этого типа:
tokenizer.nval
— если тип был числомtokenizer.sval
— если тип был String
В этом примере мы будем использовать другой входной файл, который просто содержит:
Hello 1
Следующий код считывает из файла как строку, так и число:
@Test
public void whenReadWithStreamTokenizer_thenCorrectTokens()
throws IOException {
String file = "src/test/resources/fileTestTokenizer.txt";
FileReader reader = new FileReader(file);
StreamTokenizer tokenizer = new StreamTokenizer(reader);
// token 1
tokenizer.nextToken();
assertEquals(StreamTokenizer.TT_WORD, tokenizer.ttype);
assertEquals("Hello", tokenizer.sval);
// token 2
tokenizer.nextToken();
assertEquals(StreamTokenizer.TT_NUMBER, tokenizer.ttype);
assertEquals(1, tokenizer.nval, 0.0000001);
// token 3
tokenizer.nextToken();
assertEquals(StreamTokenizer.TT_EOF, tokenizer.ttype);
reader.close();
}
Обратите внимание, как токен конца файла используется в конце.
Этот подход полезен для разбора входного потока на токены.
8. Чтение с DataInputStream
Мы можем использовать DataInputStream
для чтения двоичных или примитивных типов данных из файла.
Следующий тест считывает файл с помощью DataInputStream
:
@Test
public void whenReadWithDataInputStream_thenCorrect() throws IOException {
String expectedValue = "Hello, world!";
String file ="src/test/resources/fileTest.txt";
String result = null;
DataInputStream reader = new DataInputStream(new FileInputStream(file));
int nBytesToRead = reader.available();
if(nBytesToRead > 0) {
byte[] bytes = new byte[nBytesToRead];
reader.read(bytes);
result = new String(bytes);
}
assertEquals(expectedValue, result);
}
9. Чтение с FileChannel
Если мы читаем большой файл, FileChannel
может быть быстрее, чем стандартный ввод-вывод.
Следующий код считывает байты данных из файла, используя FileChannel
и RandomAccessFile
:
@Test
public void whenReadWithFileChannel_thenCorrect()
throws IOException {
String expected_value = "Hello, world!";
String file = "src/test/resources/fileTest.txt";
RandomAccessFile reader = new RandomAccessFile(file, "r");
FileChannel channel = reader.getChannel();
int bufferSize = 1024;
if (bufferSize > channel.size()) {
bufferSize = (int) channel.size();
}
ByteBuffer buff = ByteBuffer.allocate(bufferSize);
channel.read(buff);
buff.flip();
assertEquals(expected_value, new String(buff.array()));
channel.close();
reader.close();
}
10. Чтение файла в кодировке UTF-8
Теперь давайте посмотрим, как прочитать файл в кодировке UTF-8 с помощью BufferedReader.
В этом примере мы прочитаем файл, содержащий китайские символы:
@Test
public void whenReadUTFEncodedFile_thenCorrect()
throws IOException {
String expected_value = "青空";
String file = "src/test/resources/fileTestUtf8.txt";
BufferedReader reader = new BufferedReader
(new InputStreamReader(new FileInputStream(file), "UTF-8"));
String currentLine = reader.readLine();
reader.close();
assertEquals(expected_value, currentLine);
}
11. Чтение контента из URL
Чтобы прочитать содержимое с URL-адреса, мы будем использовать « /
» URL-адрес в нашем примере:
@Test
public void givenURLName_whenUsingURL_thenFileData() {
String expectedData = "ForEach";
URL urlObject = new URL("/");
URLConnection urlConnection = urlObject.openConnection();
InputStream inputStream = urlConnection.getInputStream();
String data = readFromInputStream(inputStream);
Assert.assertThat(data, containsString(expectedData));
}
Существуют также альтернативные способы подключения к URL-адресу. Здесь мы использовали классы URL
и URLConnection
, доступные в стандартном SDK.
12. Чтение файла из JAR
Чтобы прочитать файл, который находится внутри файла JAR, нам понадобится JAR с файлом внутри него. Для нашего примера мы прочитаем « LICENSE.txt
» из файла « hamcrest-library-1.3.jar
»:
@Test
public void givenFileName_whenUsingJarFile_thenFileData() {
String expectedData = "BSD License";
Class clazz = Matchers.class;
InputStream inputStream = clazz.getResourceAsStream("/LICENSE.txt");
String data = readFromInputStream(inputStream);
Assert.assertThat(data, containsString(expectedData));
}
Здесь мы хотим загрузить LICENSE.txt
, который находится в библиотеке Hamcrest, поэтому мы будем использовать класс Matcher,
который помогает получить ресурс. Тот же файл можно загрузить и с помощью загрузчика классов.
13. Заключение
Как мы видим, есть много возможностей для загрузки файла и чтения из него данных с помощью простой Java.
Мы можем загрузить файл из разных мест, таких как classpath, URL или jar-файлы.
Затем мы можем использовать BufferedReader
для чтения построчно, Scanner
для чтения с использованием разных разделителей, StreamTokenizer
для чтения файла в токены, DataInputStream
для чтения двоичных данных и примитивных типов данных, SequenceInput Stream
для связывания нескольких файлов в один поток, FileChannel
для более быстрого чтения . из больших файлов и т.д.
Исходный код этой статьи можно найти в следующем репозитории GitHub .