Перейти к содержанию

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

В этом руководстве рассмотрим различные способы чтения из файла в Java.

Во-первых, узнаем, как загрузить файл из classpath, URL-адреса или из файла JAR, используя стандартные классы Java.

Во-вторых, увидим, как читать содержимое с помощью BufferedReader, Scanner, StreamTokenizer, DataInputStream, SequenceInputStream и FileChannel. Также обсудим, как читать файл в кодировке UTF-8.

Наконец, рассмотрим новые методы загрузки и чтения файла в Java 7 и Java 8.

Настройка

Входной файл

В большинстве примеров в этой статье будем читать текстовый файл fileTest.txt, который содержит одну строку:

Hello, world!

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

Вспомогательный метод

Будем использовать набор тестовых примеров только с основными классами Java, а в тестах будем использовать утверждения с matchers 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();
}

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

Чтение файла из Classpath

Использование стандартной Java

В этом разделе объясняется, как читать файл, доступный в Classpath. Прочитаем 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 путь рассматривается как абсолютный, начиная с корня classpath.

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

Обратите внимание, что на практике открытые потоки всегда должны быть закрыты, например, 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();
        }
    }
}

Использование библиотеки 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 и чтения данных.

Чтение с помощью 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, когда будет достигнут конец файла.

Чтение из файла с помощью Java NIO

В JDK7 значительно обновлен пакет NIO.

Рассмотрим пример с использованием класса Files и метода readAllLines. Метод readAllLines принимает Path.

Класс Path можно рассматривать как обновление java.io.File с некоторыми дополнительными операциями.

Чтение небольшого файла

В следующем коде показано, как прочитать небольшой файл с помощью нового класса 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(), если нужны двоичные данные.

Чтение большого файла

Если хотим прочитать большой файл с помощью класса 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);
}

Чтение файла с помощью Files.lines()

JDK8 предлагает метод lines() внутри класса Files. Он возвращает поток элементов String.

Рассмотрим пример того, как читать данные в байты и декодировать их с помощью кодировки UTF-8.

Следующий код считывает файл с помощью новой функции 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 предлагает еще один простой способ чтения содержимого файла в строку.

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

Чтение через Scanner

Далее давайте использовать Scanner для чтения из файла. Здесь будем использовать пробел в качестве разделителя:

@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 полезен при чтении содержимого из консоли или когда содержимое содержит примитивные значения с известным разделителем (например, список целых чисел, разделенных пробелом).

Чтение с помощью StreamTokenizer

Теперь прочитаем текстовый файл в токены с помощью StreamTokenizer.

Tokenizer работает, сначала выясняя, что будет следующим токеном, строкой или числом. Мы делаем это, просматривая поле 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();
}

Обратите внимание, как конец файла токена используется в конце.

Этот подход полезен для парсинга входного потока на токены.

Чтение через 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);
}

Чтение через 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();
}

Чтение файла в кодировке 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);
}

Чтение контента с URL-адреса

Чтобы прочитать содержимое с URL-адреса, в примере будем использовать URL-адрес «/»:

@Test
public void givenURLName_whenUsingURL_thenFileData() {
    String expectedData = "JavaMaster";

    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.

Чтение файла из 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, который помогает получить ресурс. Тот же файл можно загрузить и с помощью загрузчика классов.

Заключение

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

Можно загрузить файл из разных мест, таких как classpath, URL или jar-файлы.

Можно использовать BufferedReader для чтения построчно, Scanner для чтения с использованием разных разделителей, StreamTokenizer для чтения файла в токены, DataInputStream для чтения двоичных данных и примитивных типов данных, SequenceInput Stream для связывания нескольких файлов в один поток, FileChannel для более быстрого чтения из больших файлов и т.д.

Исходный код этой статьи можно найти на GitHub.

Оригинал