Прочитать файл в массив java

Обновлено: 05.07.2024

У меня есть текстовый файл с набором массивов с конкретным номером, который я должен найти в массиве. Текстовый файл выглядит так:

Где число в скобках - это число, которое я должен найти с помощью бинарного поиска. а остальное это фактический массив. Я не знаю, как я мог бы получить этот массив из текстового файла и быть в состоянии прочитать его как фактический массив. [Это вопрос о предыдущем конкурсе по программированию, который я принимал, и собираюсь решить проблемы]

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

И использовал цикл while, чтобы иметь возможность перебирать файл и читать его.

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

3 ответа

Вы можете просто проанализировать каждую строку (номер для поиска и массив) следующим образом:

Если вам не нравится replaceAll, вы можете заменить первую строку в цикле на две ниже:

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

Ниже регулярное выражение должно соответствовать линии

Затем группа (1) выдаст цифру в скобках (в виде строки), а группа (2) - строку в фигурных скобках, которые можно разделить с помощью пробела, и пробел (если каждая запятая следует за пробелом) и получит массив чисел (снова как строки)

Надеюсь это поможет!

Чтобы прочитать файл, вы можете использовать Files.readAllLines()

Чтобы фактически разобрать каждую строку, вы можете использовать что-то вроде этого.

Во-первых, чтобы сделать вещи проще, удалите все пробелы из строки.

Затем используйте регулярное выражение для проверки строки. Если линия не совпадает, дальнейшие действия не требуются.

  • \(8*\) относится к (8) (пример выше)
  • \ относится к

Если вы не понимаете выражения, перейдите здесь.

Наконец, мы можем разобрать строку на две составляющие: число для поиска и int[] с фактическими значениями.

Разобрав оба этих компонента, вы можете выполнить бинарный поиск самостоятельно.

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

Если, например, наш файл содержит длинный список, который мы хотим отсортировать, нам придется прочитать его в адекватную структуру данных, выполнить операции и затем снова сохранить его - в данном случае ArrayList .

Этого можно достичь несколькими разными подходами:

  • Files.readAllLines()
  • FileReader
  • Scanner
  • BufferedReader
  • ObjectInputStream
  • Java Streams API

Files.readAllLines ()

Начиная с Java 7, можно очень просто ArrayList

Мы также можем указать charset для обработки различных форматов текста, если необходимо:

Files.readAllLines() автоматически открывает и закрывает необходимые ресурсы.

Сканер

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

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

Простой пример того, когда мы предпочли бы использовать Scanner был бы, если бы в нашем файле была только одна строка, и данные нужно было бы проанализировать во что-то пригодное для использования.

Разделитель - это последовательность символов, которую Scanner использует для разделения значений. По умолчанию он использует серию пробелов / табуляций в качестве разделителя (пробелы между значениями), но мы можем объявить наш собственный разделитель и использовать его для анализа данных.

Давайте посмотрим на пример файла:

В таком случае легко заметить, что все значения имеют общий разделитель. Мы можем просто объявить, что наш разделитель - "-", окруженный любым количеством пробелов.

Запуск этого фрагмента кода даст нам список ArrayList со следующими элементами:

С другой стороны, если бы мы использовали только разделитель по умолчанию (пробел), ArrayList выглядел бы так:

Scanner имеет несколько полезных функций для анализа данных, таких как nextInt() , nextDouble() и т. Д.

Важно : вызов .nextInt() НЕ вернет следующее int которое можно найти в файле! Он вернет int только в том случае, если следующие элементы, которые "сканирует" Scanner int , в противном случае будет выдано исключение. Простой способ убедиться, что исключение не возникает, - это выполнить соответствующую проверку «имеет» - например, .hasNextInt() перед фактическим использованием .nextInt() .

Несмотря на то, что мы не видим, что когда мы вызываем такие функции, как scanner.nextInt() или scanner.hasNextDouble() , Scanner использует регулярные выражения в фоновом режиме.

Очень важно: чрезвычайно распространенная ошибка при использовании Scanner возникает при работе с файлами, состоящими из нескольких строк, и использовании .nextLine() вместе с .nextInt() , nextDouble() и т. Д.

Взглянем на другой файл:

Часто новые разработчики, использующие Scanner , пишут такой код:

Этот код кажется логически правильным - мы читаем целое число из файла, затем следующую строку, затем второе целое число. Если вы попытаетесь запустить этот код, InputMismatchException будет выброшено без очевидной причины.

Если вы начнете отладку и распечатать то, что вы отсканировали, вы увидите, что int a загружен, но этот String s пуст.

Это почему? Первое, что следует отметить, это то, что как только Scanner что-то читает из файла, он продолжает сканирование файла с первого символа после данных, которые он ранее отсканировал.

Например, если у нас есть «12 13 14» в файле и .nextInt() , сканер впоследствии будет делать вид, будто в файле только «13 14». Обратите внимание, что пробел между «12» и «13» все еще присутствует.

Второе важное замечание: первая строка в нашем example.txt содержит не только число 12 , но и то, что она называет «символом новой строки», и на самом деле это 12\n а не просто 12 .

Наш файл на самом деле выглядит так:

Когда мы впервые вызываем .nextInt() , Scanner считывает только число 12 и оставляет первое \n непрочитанным.

.nextLine() считывает все символы, которые сканер еще не прочитал, пока не достигнет первого \n , который он пропускает, а затем возвращает прочитанные символы. В этом и заключается проблема в нашем случае - у нас остался \n после чтения 12 .

Поэтому, когда мы вызываем .nextLine() мы получаем в результате пустую строку, поскольку Scanner не добавляет \n к возвращаемой строке.

Теперь Scanner находится в начале второй строки в нашем файле, и когда мы пытаемся вызвать .nextInt() , Scanner обнаруживает что-то, что не может быть проанализировано до int и выдает вышеупомянутое InputMismatchException .

Решения

  • Поскольку мы знаем, что именно не так в этом коде, мы можем жестко закодировать обходной путь. Мы просто «потребляем» символ новой строки между .nextInt() и .nextLine() :
  • Учитывая, что мы знаем, как example.txt мы можем прочитать весь файл построчно и проанализировать необходимые строки с помощью Integer.parseInt() :

BufferedReader

BufferedReader читает текст из потока ввода символов, но делает это путем буферизации символов для обеспечения эффективных .read() . Поскольку доступ к жесткому диску - это очень трудоемкая операция, BufferedReader собирает больше данных, чем мы запрашиваем, и сохраняет их в буфере.

Идея состоит в том, что когда мы вызываем .read() (или аналогичную операцию), мы, скорее всего, вскоре снова будем читать из того же блока данных, из которого мы только что прочитали, и поэтому «окружающие» данные сохраняются в буфере. Если бы мы захотели его прочитать, мы бы прочитали его прямо из буфера, а не с диска, что намного эффективнее.

Это подводит нас к тому, чем BufferedReader - чтению больших файлов. BufferedReader имеет значительно большую буферную память, чем Scanner (8192 символа по умолчанию против 1024 символа по умолчанию соответственно).

BufferedReader используется как оболочка для других устройств чтения , поэтому конструкторы для BufferedReader принимают объект Reader в качестве параметра, например FileReader .

Мы используем try-with-resources, поэтому нам не нужно закрывать программу чтения вручную:

Рекомендуется заключить FileReader в BufferedReader именно из-за повышения производительности.

ObjectInputStream

ObjectInputStream следует использовать только вместе с ObjectOutputStream . Эти два класса помогают нам сохранять объект (или массив объектов) в файл, а затем легко читать из этого файла.

Это можно сделать только с классами, реализующими интерфейс Serializable Интерфейс Serializable не имеет методов или полей и служит только для определения семантики сериализации:

Java Streams API

Начиная с Java 8, еще одним быстрым и простым способом загрузки содержимого файла в ArrayList было бы использование Java Streams API :

Однако имейте в виду, что этот подход, как и Files.readAllLines() будет работать только в том случае, если данные хранятся в строках.

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

Мы можем легко отсортировать / отфильтровать / сопоставить данные перед сохранением их в ArrayList :

Примеры реализации операций, которые модифицируют текстовые файлы. Классы FileReader , FileOutputStream , PrintStream

Содержание

  • 1. Функция CountLinesInFile() . Вычислить количество строк в символьном файле
  • 2. Функция GetLinesFromFile() . Получить строки файла в виде массива строк типа String[]
  • 3. Функция WriteLinesToFile() . Записать массив типа String[] в файл
  • 4. Функция ReplaceStringInFile() . Заменить указанною строку в текстовом файле
  • 5. Функция SortLinesInFile() . Сортировка строк в файле методом вставки
  • 6. Запись/чтение массива целых чисел в текстовый файл

Поиск на других ресурсах:

1. Функция CountLinesInFile() . Вычислить количество строк в символьном файле

Для вычисления количества строк в символьном файле можно применить следующую функцию

Использование функции в другом программном коде может быть, например, таким

Далее приведен пример возможного использования функции:

3. Функция WriteLinesToFile() . Записать массив типа String[] в файл

При работе с файлами полезной может быть функция записи массива строк типа String[] в файл. Функция использует возможности классов FileOutputStream и PrintStream .

4. Функция ReplaceStringInFile() . Заменить указанною строку в текстовом файле

Чтобы заменить указанную строку в файле нужно указать его позицию. Замена строки осуществляется по следующему алгоритму:

  • получить строки файла в виде массива String[] ;
  • заменить строку в массиве;
  • записать массив обратно в файл.

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

Текст функции ReplaceStringInFile() следующий.

Использование функции может быть, например, следующим

5. Функция SortLinesInFile() . Сортировка строк в файле методом вставки

Сортировка строк происходит по тому самому принципу, который описан в пункте 4. Сначала строки файла копируются во временный массив типа String[] . Затем происходит сортировка временного массива. На последнем шаге отсортированный массив копируется обратно в файл.

В своей работе функция SortLinesInFile() использует две функции GetLinesFromFile() и WriteLinesToFile() , реализация которых описывается в пунктах 2, 3.

Текст функции следующий.

Использование функции может быть следующим

6. Запись/чтение массива целых чисел в текстовый файл. Пример

В примере приведен программный код, который выполняет следующую работу:

  • записывает в файл массив целых чисел;
  • зчитывает массив целых чисел из файла.

Для выполнения задачи разработан класс ReadWriteArrayFile , в котором реализованы два метода:

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

В Java есть четыре основных абстрактных класса, реализующих потоки ввода-вывода: InputStream, OutputStream, Reader, Writer. Первые два работают с байтами, вторые – с символами.

Для работы с файлами от этих абстрактных классов созданы соответственно классы FileInputStream, FileOutputStream, FileReader, FileWriter. Они являются адаптерами для объектов класса File к "интерфейсам" InputStream, OutputStream, Reader, Writer, т. е. к их методам.

Скажем несколько слов об адаптере как паттерне, или шаблоне, проектирования. Класс-адаптер A наследуется от интерфейса B, к которому приспосабливается объект другого класса – C. Класс-адаптер A имеет поле типа класса объекта C.

Например, объект File адаптируется к потоку ввода InputStream, т. е. все, что мы хотим получить из File, в конечном итоге мы будем получать из InputStream. Фактически мы работаем с InputStream, через адаптер FileInputStream, который с одной стороны наследуется от InputStream, а с другой – имеет поле, которому присваивается объект File.

Адаптер выполняет работу по получению данных из файла и адаптации их к тому виду, который можно передать в методы InputStream. Класс-адаптер, в данном примере – FileInputStream, переопределяет методы InputStream, добавляя в них свой код.

В основной ветке сначала создается объект, для которого требуется адаптер. Затем создается переменная класса, к которому выполняется адаптация. Этой переменной присваивается объект класса-адаптера, в конструктор которого передается адаптируемый объект.

Часто переменную определяют самим классом-адаптером:

В конструктор можно передать строку-адрес. Объект File будет создан внутри адаптера. Пример побайтового копирования файла:

Если используются относительные адреса, они должны начинаться от корня проекта.

В конструктор FileOutputStream можно также передать второй аргумент true. В этом случае, если файл существует, данные в него будут добавляться. Перезаписи файла не произойдет.

Метод available() объекта класса FileInputStream возвращает количество непрочитанных байтов. Метод read() читает один байт и расширяет его до типа int. Кроме этого, есть другой метод read(), читающий массив байт в переменную-аргумент и возвращающий количество реально прочитанных байт. Метод write() также позволяет записывать блоками.

При чтении конца файла блок может содержать меньше прочитанных байт, чем размерность массива. Поэтому write() позволяет указывать срез массива.

У объектов FileOutputStream имеется метод flush(), который принудительно записывает находящиеся в буфере байты на диск. При вызове close() это происходит автоматически.

С помощью класса PrintStream также можно создать поток вывода в файл. PrintStream является наследником FilterOutputStream, который в свою очередь наследник OutputStream как и FileOutputStream.

Функция printf() предназначена для форматированного вывода.

Заметим, переменная System.out является объектом типа PrintStream.

В работе с вводом-выводом также используется другой паттерн проектирования – обертка (wrapper), он же декоратор (decorator). Декоратор расширяет функциональность объекта, а не приспосабливает объект к какому-либо стороннему интерфейсу.

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

В основной ветке создается объект оборачиваемого класса, который передается в конструктор обертки. Внутри класса-обертки есть поле типа декорируемого класса. Этому полю присваивается переданный объект.

BufferedInputStream – класс-обертка для InputStream (наследует через FilterInputStream). В отличие от InputStream класс BufferedInputStream позволяет предварительно читать в буфер порции байт, что уменьшает количество обращений к файлу. Существует также BufferedOutputStream.

Конструктор класса BufferedInputStream принимает объект InputStream или его наследника.

Хотя данные считываются блоками, метод read() извлекает их по одному. Однако в данном случае он будет извлекать их из буфера.

С помощью классов FileReader и FileWriter выполняется ввод-вывод в текстовые файлы.

Метод ready() возвращает истину, если остались непрочитанные символы.

Читать и писать можно блоками. Также методу write() можно передать строку:

Рассматривая ввод данных с клавиатуры, мы уже использовали класс BufferedReader, который наследуется от Reader и позволяет читать отдельные строки методом readLine(). Его также можно использовать для построчного чтения файлов:

Читайте также: