За занавесом: чтение из файла
Пакет java.io
позволяет работать с файлами на трёх разных уровнях:
- Уровень отдельных байт. В этом случае файл воспринимается как массив или, точнее, как поток байт. Поток, в отличие от массива, можно только перебирать, с сильно ограниченными возможностями по возвращению назад. Для этой цели имеется тип
java.io.InputStream
. - Уровень символов. В этом случае файл воспринимается уже как поток символов типа
Char
, то есть каждые несколько байт файла превращаются в определённый символ — с учётом заданной кодировки файла. Для этой цели имеется типjava.io.InputStreamReader
, который внутри себя используетInputStream
для чтения байт. - Уровень строк. На этом уровне файл воспринимается как набор строк
String
, составленных из символов по определённым правилам — чаще всего используется разделение по отдельным строкам файла. Эту роль выполняет типjava.io.BufferedReader
, использующий внутри себяInputStreamReader
для чтения символов.
При программировании на Java каждый из этих объектов приходится создавать отдельно — вначале InputStream
, потом InputStreamReader
и, наконец, BufferedReader
. Библиотека Котлина позволяет создать любой из этих объектов сразу, используя файл-получатель:
file.inputStream()
создаёт байтовый поток.file.reader()
создаёт читатель символов, используя кодировку по умолчанию.file.reader(Charset.forName("CP1251"))
создаёт писатель с заданной кодировкой (в данном случае CP1251).- Наконец,
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
исходный файл будет закрыт как при корректном завершении функции, так и при возникновении исключения.
Кроме этого, каждый объект обладает своими методами для чтения информации:
inputStream.read()
читает изInputStream
очередной байт, возвращая его в виде результата типаInt
. Если файл закончен, результат этой функции будет -1.inputStream.read(byteArray)
читает сразу несколько байт, записывая их в массив байт (число прочитанных байт равно размеру массива).inputStream.read(byteArray, offset, length)
записывает вbyteArray
length
байт, начиная с индексаoffset
.reader.read()
читает изInputStreamReader
очередной символ, возвращая его в виде результата типаInt
. Здесь используется именноInt
, а неChar
, так как, во-первых, символ в общем случае может не поместиться в двухбайтовые тип и, во-вторых, чтобы вернуть -1 в случае неудачи. Есть аналогичные методы для чтения символьного массива (НЕ строки) с возможным указанием смещения и числа символов — см. выше про байтовый массив.bufferedReader.readLine()
читает изBufferedReader
очередную строку (до перевода строки).bufferedReader.readLines()
читает сразу же все строки. Есть ряд других методов для работы со строками по отдельности.
Следует отметить, что все функции чтения информации могут бросить исключение IOException
в том случае, если чтение по какой-либо причине невозможно (например, если файл не существует или недоступен).
В примере, мы вообще не создавали bufferedReader
, а использовали функцию file.readLines()
. Она создаёт bufferedReader
внутри себя и обращается к его функции readLines()
. После чтения последней строки файл закрывается.