Прочитать файл в массив java
У меня есть текстовый файл с набором массивов с конкретным номером, который я должен найти в массиве. Текстовый файл выглядит так:
Где число в скобках - это число, которое я должен найти с помощью бинарного поиска. а остальное это фактический массив. Я не знаю, как я мог бы получить этот массив из текстового файла и быть в состоянии прочитать его как фактический массив. [Это вопрос о предыдущем конкурсе по программированию, который я принимал, и собираюсь решить проблемы]
У меня уже есть метод для выполнения бинарного поиска, и я использовал сканер для чтения файла следующим образом:
И использовал цикл 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(). Его также можно использовать для построчного чтения файлов:
Читайте также: