7. Основы Kotlin. Файловые операции

За занавесом: чтение из файла

Пакет java.io позволяет работать с файлами на трёх разных уровнях:

  1. Уровень отдельных байт. В этом случае файл воспринимается как массив или, точнее, как поток байт. Поток, в отличие от массива, можно только перебирать, с сильно ограниченными возможностями по возвращению назад. Для этой цели имеется тип java.io.InputStream.
  2. Уровень символов. В этом случае файл воспринимается уже как поток символов типа Char, то есть каждые несколько байт файла превращаются в определённый символ — с учётом заданной кодировки файла. Для этой цели имеется тип java.io.InputStreamReader, который внутри себя использует InputStream для чтения байт.
  3. Уровень строк. На этом уровне файл воспринимается как набор строк String, составленных из символов по определённым правилам — чаще всего используется разделение по отдельным строкам файла. Эту роль выполняет тип java.io.BufferedReader, использующий внутри себя InputStreamReader для чтения символов.

При программировании на Java каждый из этих объектов приходится создавать отдельно — вначале InputStream, потом InputStreamReader и, наконец, BufferedReader. Библиотека Котлина позволяет создать любой из этих объектов сразу, используя файл-получатель:

  1. file.inputStream() создаёт байтовый поток.
  2. file.reader() создаёт читатель символов, используя кодировку по умолчанию. file.reader(Charset.forName("CP1251")) создаёт писатель с заданной кодировкой (в данном случае CP1251).
  3. Наконец, file.bufferedReader() создаёт буферизованный читатель строк. Опять-таки, может быть задана нужная кодировка, иначе используется кодировка по умолчанию.

Набор функций у данных трёх объектов различается. У всех у них есть функция close(), закрывающая исходный файл в конце работы с потоком. Также, у них имеется функция высшего порядка use { …​ }, выполняющая описанные в лямбде действия и закрывающая файл в конце своей работы автоматически. Скажем, исходный пример можно было бы переписать с помощью use так:

fun alignFile(inputName: String, lineLength: Int, outputName: String) {
    File(outputName).bufferedWriter().use {
        var currentLineLength = 0
        for (line in File(inputName).readLines()) {
            if (line.isEmpty()) {
                it.newLine()
                if (currentLineLength > 0) {
                    it.newLine()
                    currentLineLength = 0
                }
                continue
            }
            for (word in line.split(" ")) {
                if (currentLineLength > 0) {
                    if (word.length + currentLineLength >= lineLength) {
                        it.newLine()
                        currentLineLength = 0
                    } else {
                        it.write(" ")
                        currentLineLength++
                    }
                }
                it.write(word)
                currentLineLength += word.length
            }
        }
    }
}

Здесь исходный BufferedWriter в лямбде становится параметром it. Заметим, что при использовании use исходный файл будет закрыт как при корректном завершении функции, так и при возникновении исключения.

Кроме этого, каждый объект обладает своими методами для чтения информации:

  1. inputStream.read() читает из InputStream очередной байт, возвращая его в виде результата типа Int. Если файл закончен, результат этой функции будет -1. inputStream.read(byteArray) читает сразу несколько байт, записывая их в массив байт (число прочитанных байт равно размеру массива). inputStream.read(byteArray, offset, length)записывает в byteArray length байт, начиная с индекса offset.
  2. reader.read() читает из InputStreamReader очередной символ, возвращая его в виде результата типа Int. Здесь используется именно Int, а не Char, так как, во-первых, символ в общем случае может не поместиться в двухбайтовые тип и, во-вторых, чтобы вернуть -1 в случае неудачи. Есть аналогичные методы для чтения символьного массива (НЕ строки) с возможным указанием смещения и числа символов — см. выше про байтовый массив.
  3. bufferedReader.readLine() читает из BufferedReader очередную строку (до перевода строки). bufferedReader.readLines() читает сразу же все строки. Есть ряд других методов для работы со строками по отдельности.

Следует отметить, что все функции чтения информации могут бросить исключение IOException в том случае, если чтение по какой-либо причине невозможно (например, если файл не существует или недоступен).

В примере, мы вообще не создавали bufferedReader, а использовали функцию file.readLines(). Она создаёт bufferedReader внутри себя и обращается к его функции readLines(). После чтения последней строки файл закрывается.

Добавить комментарий