Чтение и запись файлов в Node.js
Операции с файлами языку JavaScript не в новинку — в JScript, встроенном в Windows, доступен полный набор функций для работы с диском. Node, в силу своей асинхронной природы, несколько усложняет эти в общем то тривиальные задачи.
Сразу хочу предупредить об одной возможной ошибке. Если Вы, как и я, запускаете Node в виртуальной машине из общей папки, помните — VM в эту папку писать не может. Попытки создать или дополнить файлы в ней закончатся только
Error: Permission denied
Открытие файла
Обращение к файлам на диске — операция небыстрая. Она в среднем в десятки тысяч раз дольше чем обращение к оперативной памяти. Поэтому, большинство операций с файлами асинхронные. Все операции с файловой системой собраны во встроенном модуле fs, стало быть начнём с его подключения.
var fs = require("fs"), sys = require("sys");
Модуль sys нам нужен для вывода информации в консоль. В последующих примерах я эти строки буду опускать, чтобы не повторяться.
Открытие файла делается так:
fs.open(<путь>, <флаги>, <режим доступа>, <функция-обработчик>)
- Путь к файлу. Относительно запущенного скрипта либо абсолютный.
- Флаг — режим доступа к файлу. Может принимать следующие значения:
r
— только чтение, указатель в начале файлаr+
— чтение и запись, указатель в начале файлаw
— только запись, указатель в начале файлаw+
— запись и чтение, указатель в начале файлаa
— запись, указатель в конце файлаa+
— запись и чтение, указатель в конце файла
- Режим доступа используется если открываемый файл не существует. В таком случае будет создан новый пустой файл с заданным режимом. Нотация стандартная для UNIX — например 0664
- Обработчик — функция, которая будет выполнена при открытии/создании файла. В качестве аргументов передаются флаг ошибки и дескриптор файла
Например:
fs.open("readme.txt", "r+", 0644, function(err, file_handle) { if (!err) { // Операции с открытым файлом } else { // Обработка ошибок } });
Запись в файл
Для записи в файл используется метод fs.write
:
fs.write(<дескриптор>, <данные>, <позиция>, <кодировка>, <обработчик>)
- Дескриптор файла, полученный в
fs.open
. - Данные, которые мы записываем. Объекты здесь будут приведены к строковому типу.
- Позиция, с которой начинается запись.
Null
означает запись с текущей позиции. - Кодировка, в которой будут записаны данные. Может быть «ascii«, «utf8» и «raw«
- Обработчик — функция, которая будет выполнена после записи. Аргументы — флаг ошибки и количество записанных байт
Расширим предыдущий пример записью строки 🙂
fs.open("readme.txt", "a", 0644, function(err, file_handle) { if (!err) { // Записываем в конец файла readme.txt фразу "Copyrighted by Me" // при открытии в режиме "a" указатель уже в конце файла, и мы передаём null // в качестве позиции fs.write(file_handle, 'Copyrighted by Me', null, 'ascii', function(err, written) { if (!err) { // Всё прошло хорошо } else { // Произошла ошибка при записи } }); } else { // Обработка ошибок при открытии } });
Чтение из файла
Чтение делается так:
fs.read(<дескриптор>, <длина>, <позиция>, <кодировка>, <обработчик>)
Здесь всё почти так же, как в fs.write
.
- Дескриптор файла, полученный в
fs.open
- Длина данных, которые мы планируем прочитать
- Позиция, с которой начинаем читать.
Null
— с текущей позиции - Кодировка, в которой читаются данные. Может быть «ascii«, «utf8» и «raw«. Здесь лучше не ошибаться )
- Обработчик — функция, которая будет выполнена после чтения. Аргументы — флаг ошибки ,данные, количество прочитанных байт
Чтение из файла — совсем несложный процесс:
fs.open("readme.txt", "r", 0644, function(err, file_handle) { if (!err) { // Читаем 10 килобайт с начала файла, в ascii fs.read(file_handle, 10000, null, 'ascii', function(err, data) { if (!err) { // Всё прошло хорошо, выводим прочитанное в консоль sys.puts(data); } else { // Произошла ошибка при чтении } }); } else { // Обработка ошибок при открытии файла } });
После того как чтение/запись завершены, файл надо закрыть. Node может обслуживать одновременно очень много клиентов, поэтому ресурсы лучше освобождать сразу когда они становятся не нужны.
fs.open("readme.txt", "r", 0644, function(err, file_handle) { if (!err) { // Читаем 10 килобайт с начала файла, в ascii fs.read(file_handle, 10000, null, 'ascii', function(err, data) { if (!err) { // Всё прошло хорошо, выводим прочитанное в консоль sys.puts(data); fs.close(file_handle); } else { // Произошла ошибка при чтении } }); } else { // Обработка ошибок при открытии файла } });
Вторым аргументом fs.close
может принимать функцию-callback, которой передаётся исключение в случае ошибки.
У всех перечисленных функций есть синхронные варианты. К их названию добавлено Sync
и они не принимают последним аргументом функцию-обработчик, а просто возвращают соответствующее значение (или бросают исключение). Обратите внимание, readSync возвращает массив из данных и количества прочитанных байт.
var file_handle = fs.openSync("readme.txt", "r", 0644); var data = fs.readSync(file_handle, 10000, null, 'ascii'); sys.puts(data[0]); fs.closeSync(file_handle);
Поимел недавно очень серьезную проблему именно с этой подсистемой. Подробнее об этом по этому адресу http://github.com/ry/node/issues#issue/112.
Если пытаться читать файл размером больше чем 4096 байта в UTF-8, то периодически начинают появляться парные некорректные символы. Все из-за того, что перевод в UTF-8 осуществляется частями.
Ценное замечание, спасибо ) Я пока не сталкивался, может быть потому что всегда читаю/пишу файл целиком (в основном в целях дебага).
Вы не можете попытаться воспроизвести этот баг? Судя по комментариям Райана — ему не удалось, и он на него элементарно забил. Вот мне и интересно — сломан ли у меня мозг?
Баг проявляется нерегулярно, и, если честно, у меня волосы начинают шевелиться везде при мысли — сколько же кровушки он может попортить в будущем. И не только с файлами.
Думаю, могу попробовать заняться этим завтра.
Огромное спасибо.
Здравствуйте, подскажите пожалуйста как можно организовать следущую функцию.
Пример:
Мне нужно из mp3 вырезать припев, с 30 секунды по 45 получить новый файл, как узнать с какого килобайта начинаться 30 секунда по 45.
Не надо резать побайтово. FFMPEG например умеет вырезать кусок по указанному времени. Вызываете его, ждете окончания работы, забираете файл вывода.
Полезная статья мне лично пригодилась.Спасибо.