Определение характеристик файла, таких как размер
или дата последнего изменения
Получив экземпляр
File
, представляющий файл, можно читать и записывать
данные в него или определять его характеристики, такие как размер или дата
изменения:
Java
File file = new File(context.getFilesDir(), "path/to/file.ext");
long sizeInBytes = file.length();
long lastModifiedTimestamp = file.lastModified();
Kotlin
val file = File(context.filesDir, "path/to/file.ext")
val sizeInBytes = file.length()
val lastModifiedTimestamp = file.lastModified()
Android
113
Чтение и запись данных в файлы
Первое, что многим приходит на ум при мысли о записи данных в файл, – запись
значения типа
String
в экземпляр
File
. Но на самом деле это не самая частая
операция в Java и Android – вспомните о двоичных файлах с изображениями,
аудио и видео, и сжатыми данными в формате ZIP и TAR. Конечно, существуют
API для чтения и записи строк
String
в экземпляры
File
, такие как
FileReader
и
FileWriter
, предлагающие упрощенный способ чтения/записи строк, но мы
сосредоточимся на базовом шаблоне чтения/записи данных вообще: потоках
байтов. Преимущество работы с данными на уровне байтов состоит в том, что
одну и ту же операцию можно использовать для чего угодно: записи текстовых
файлов, потоковой передачи мультимедиа, загрузки изображений и т. д.
Но не пугайтесь! Все данные в наших программах уже представлены бай
-
тами на некотором уровне, и мы довольно легко можем получить эту инфор
-
мацию с помощью существующих API в стандартной библиотеке. Например,
получить байты из
String
можно простым вызовом метода
getBytes()
соответ
-
ствующего экземпляра.
Кроме того, возможно, вы удивитесь, узнав, что класс
java.io.File
не имеет
специальных методов записи или чтения, и на самом деле для записи и чтения
необходимо использовать еще один вспомогательный класс-посредник: поток
(
InputStream
,
OutputStream
или их подклассы, в зависимости от ситуации). Под по
-
током в Java подразумевается просто часть данных, которые можно читать по
-
следовательно, от начала до конца. Преимущество такого подхода заключается
в отсутствии необходимости хранить весь объем информации одновременно.
В действительности объект потока может даже не иметь видимого конца! На
-
пример, в приложении может понадобиться воспроизвести видео из внешнего
источника; используя поток, мы просто читаем байты по мере поступления
и буферизируем или отображаем их в соответствии с нашей программой.
Но вернемся к нашему практическому примеру. Для начала попробуем за
-
писать данные в файл. Для этого нам понадобится экземпляр
FileOutputStream
.
Получить экземпляр
FileOutputStream
можно вызовом конструктора с готовым
экземпляром
File
:
OutputStream
outputStream
=
new
FileOutputStr
eam(file);
. В классе
FileOutputStream
имеется несколько перегруженных версий метода
write
, но мы
пока остановимся на одной из них:
write(byte[]
bytes)
. Просто передайте этому
методу массив байтов в единственном аргументе, и эти байты будут записаны
в экземпляр
File
, на который ссылается
FileOutputStream
.
То есть вот как можно записать некоторый текст в файл:
Java
// outputStream – это действительный экземпляр FileOutputStream
String message = "Hello world!";
outputStream.write(message.getBytes());
Kotlin
// outputStream – это действительный экземпляр FileOutputStream
val message = "Hello world!"
outputStream.write(message.toByteArray())
114
Файлы
Вот и все! Мы только что записали текст «Hello world!» в файл! Конечно,
в Java и Android приходится предусматривать некоторые защитные меры, ко
-
торые делают реализацию этой простой задачи немного более громоздкой.
Например, конструктор
FileOutputStream
может сгенерировать исключение
FileNotFoundException
, а метод
write
– исключение
IOException
. Кроме того, мы
должны закрыть поток, закончив работу с ним, что также может вызвать ис
-
ключение
IOException
. Полный код, реализующий все эти меры предосторож
-
ности, выглядит довольно сложно, но ничто не мешает нам заключить его
в метод с контролируемым исключением и тем самым немного уменьшить
количество строк:
Java
public class Files {
public static void writeToFile(File file, byte[] data)
throws IOException {
OutputStream outputStream = null;
try {
if (!file.exists()) {
// сначала нужно создать все промежуточные каталоги
file.getParentFile().mkdirs();
// затем создать пустой файл
file.createNewFile();
}
// создать поток вывода для записи данных
outputStream = new FileOutputStream(file);
// и записать в него данные
outputStream.write(data);
} finally {
if (outputStream != null) {
outputStream.close();
}
}
}
}
Kotlin
@Throws(IOException::class)
fun writeToFile(file: File, data: ByteArray) {
var outputStream: OutputStream? = null
try {
if (!file.exists()) {
// сначала нужно создать все промежуточные каталоги
file.parentFile.mkdirs()
// затем создать пустой файл
file.createNewFile()
}
// создать поток вывода для записи данных
outputStream = FileOutputStream(file)
// и записать в него данные
outputStream.write(data)
Android
115
} finally {
outputStream?.close()
}
}
И затем использовать его в программе:
Java
File file = new File(context.getFilesDir(), "path/to/file.ext");
byte[] data = "hello world!".toBytes();
Files.writeToFile(file, data);
Kotlin
val file = File(context.getFilesDir(), "path/to/file.ext")
writeToFile(file, "hello world!".toByteArray())
Чтение данных из файлов реализуется аналогично, с той лишь разницей, как
вы наверняка догадались, что вместо
FileOutputStream
используется
FileInput
Stream
.
Поскольку мы рассуждаем в терминах двоичных данных, нам нужно сде
-
лать еще кое-что, чтобы преобразовать содержимое файла в удобочитаемый
текст. Если в приложении часто нужно читать и записывать текст в файлы, для
этого лучше использовать более простые альтернативы, такие как
FileWriter
и
FileReader
, но, как уже говорилось выше, использование потоков байтов яв
-
ляется универсальным решением, и преобразование байтов в строку выполня
-
ется тривиально просто.
Конструктор
FileInputStream
, подобно конструктору
FileOutputStream
, также
принимает параметр
File
:
InputStream
inputStream
=
new
F
ileInputStream(file);
.
Пос ле создания потока ввода из него можно извлекать отдельные байты, вызы
-
вая метод
read()
без параметров, или большие блоки, передавая в вызов буфер
в виде массива байтов. Сейчас мы поговорим о простом методе
read()
, возвра
-
щающем один байт, но имейте в виду, что версия, принимающая буфер, обычно
действует более эффективно, особенно при работе с большими файлами.
Метод
read
возвращает целое число, представляющее количество прочи
-
танных байтов, при этом значение –1 обозначает конец файла (и потока). Это
число можно привести к типу
char
, чтобы создать строковое представление со
-
держимого файла:
// inputStream -- это действительный экземпляр FileInputStream
StringBuilder builder = new StringBuilder();
int byte = inputStream.read();
while (byte != -1) {
builder.append((char) byte);
byte = inputStream.read();
}
String message = builder.toString();
И снова некоторые методы могут сгенерировать контролируемые исключе
-
ния, и точно так же нужно закрыть поток, закончив работу с ним, поэтому бу
-
дет полезно заключить всю эту низкоуровневую логику в единственный метод:
116
Файлы
Java
public class Files {
public static String readStringFromFile(File file)
throws IOException {
InputStream inputStream = null;
try {
// создать поток ввода для чтения из файла
inputStream = new FileInputStream(file);
// StringBuilder обеспечивает эффективный способ сборки строк из символов
StringBuilder builder = new StringBuilder();
// Для удобочитаемости я разместил эту операцию в отдельной строке,
// но часто она выполняется внутри условного выражения цикла
int b = inputStream.read();
while (b != -1) {
builder.append((char) b);
b = inputStream.read();
}
// мы можем вставить оператор return сюда, потому что блок finally
// выполняется всегда!
return builder.toString();
} finally {
if (inputStream != null) {
inputStream.close();
}
}
}
}
Kotlin
@Throws(IOException::class)
fun readStringFromFile(file: File): String {
var inputStream: InputStream? = null
try {
// создать поток ввода для чтения из файла
inputStream = FileInputStream(file)
// StringBuilder обеспечивает эффективный способ сборки строк из символов
val builder = StringBuilder()
// Для удобочитаемости я разместил эту операцию в отдельной строке,
// но часто она выполняется внутри условного выражения цикла
var b = inputStream.read()
while (b != -1) {
builder.append(b.toChar())
b = inputStream.read()
}
// мы можем вставить оператор return сюда, потому что блок finally
// выполняется всегда!
return builder.toString()
} finally {
inputStream?.close()
}
}
Android
117
Помимо
FileInputStream
, есть и другие классы потоков ввода, поэтому иногда
желательно абстрагировать метод преобразования потока в строку, например:
Java
public static String readStringFromFile(File file)
throws IOException {
InputStream stream = new FileInputStream(file);
return getStringFromStream(stream);
}
public static String getStringFromStream(InputStream stream) throws IOException {
try {
// StringBuilder обеспечивает эффективный способ сборки строк из символов
StringBuilder builder = new StringBuilder();
// Для удобочитаемости я разместил эту операцию в отдельной строке,
// но часто она выполняется внутри условного выражения цикла
int b = stream.read();
while (b != -1) {
builder.append((char) b);
b = stream.read();
}
// мы можем вставить оператор return сюда, потому что блок finally
// выполняется всегда!
return builder.toString();
} finally {
if (stream != null) {
stream.close();
}
}
}
Kotlin
object Files {
@Throws(IOException::class)
fun readStringFromFile(file: File): String {
val stream = FileInputStream(file)
return getStringFromStream(stream)
}
@Throws(IOException::class)
fun getStringFromStream(stream: InputStream): String {
stream.use { s >
val builder = StringBuilder()
var b = s.read()
while (b != -1) {
builder.append(b.toChar())
b = s.read()
}
return builder.toString()
}
}
}
Do'stlaringiz bilan baham: |