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

Как прочитать файл в Java

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

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 .