Deno предоставляет несколько API для чтения файлов. Вы можете прочитать весь файл, используя Deno.readAll и Deno.readTextFile. Однако построчное чтение по-прежнему недоступно в std библиотеке. Здесь, в этом уроке, я объясню, как вы можете прочитать весь файл построчно (поток).

Прежде чем перейти к реальному коду, давайте сначала разберемся со стандартной библиотекой на примерах.

Deno предоставляет Deno.open API для открытия файла. Это асинхронный API. Это означает, что вам нужно await. Взамен вы получите File, который содержит rid.

Образец: открыть файл #

// examples/06_readfile_chunk.ts
async function main(name?: string) {
  if (name) {
    const file = await Deno.open(name);
    console.log(file);
  }
}
const [fileName] = Deno.args;
main(fileName);

[Бегать]

$ deno  run --allow-read  examples/06_readfile_chunk.ts examples/tom.json
## Output:
# File { rid: 3 }

Вы можете увидеть rid в ответ. Давайте воспользуемся этим rid, чтобы получить порцию данных. Чтение чанка требует API Deno.read

// examples/06_readfile_chunk.ts
async function main(name?: string) {
  if (name) {
    const file = await Deno.open(name);
    const decoder = new TextDecoder();
    let buf = new Uint8Array(100);
    const numOfByteRead = await Deno.read(file?.rid, buf);
    console.log(numOfByteRead);
    console.log(decoder.decode(buf));
  }
}
const [fileName] = Deno.args;
main(fileName);

[Выполнить]

$ deno  run --allow-read  examples/06_readfile_chunk.ts examples/tom.json
# Output
# 100
{
   "id": 1,
   "version": "1.0.1",
   "contributors": [
     "deepak",
     "gary"
   ],
   "actor": {

Здесь, как вы можете видеть, каждый раз, когда вы вызываете Deno.read, он возвращает количество прочитанных байтов. Если numOfByteRead равно null, т.е. это конец файла [EOF].

new Uint8Array(100);Uint8Array будет заполнен при вызове read. Размер буфера может быть любым. Читатель будет читать байты до размера буфера.

Если вы заметили, прочитанный файл не является полным файлом. Вам нужно увеличить размер баффа, чтобы прочитать все файлы.

// examples/06_readfile_chunk.ts
async function main(name?: string) {
  if (name) {
    const file = await Deno.open(name);
    const decoder = new TextDecoder();
    let buf = new Uint8Array(1000); // 353
    const numOfByteRead = await Deno.read(file?.rid, buf);
    console.log(numOfByteRead);
    console.log(decoder.decode(buf));
  }
}
const [fileName] = Deno.args;
main(fileName);

[Бегать]

$ deno  run --allow-read  examples/06_readfile_chunk.ts examples/tom.json
# Output
# 353
## JSON here..

Здесь, в этом примере, я увеличил размер буфера до 1000, что больше, чем 353. Таким образом, я могу прочитать весь файл JSON.

[ПРИМЕЧАНИЕ]: следует избегать больших размеров буфера. Чтение большого файла может создать проблемы с памятью. и в то же время будет трудно предсказать реальный размер.

Чтобы прочитать весь файл по частям, мы можем использовать рекурсию на then способном API.

// examples/06_readfile_chunk.ts
async function main(name?: string) {
  if (name) {
    const file = await Deno.open(name);
    const decoder = new TextDecoder();
    let buf = new Uint8Array(100);
    let chunk = new Uint8Array(0);
    Deno.read(file?.rid, buf).then(function readByte(numOfByteRead) {
      if (numOfByteRead) {
        chunk = _append(chunk, buf, numOfByteRead);
        Deno.read(file?.rid, buf).then(readByte);
      } else {
        console.log(decoder.decode(chunk));
      }
    });
  }
}
const [fileName] = Deno.args;
main(fileName);

[Бегать]

$ deno  run --allow-read  examples/06_readfile_chunk.ts examples/tom.json
# Output
{
  "id": 1,
  "version": "1.0.1",
  "contributors": [
    "deepak",
    "gary"
  ],
  "actor": {
    "name": "Tom Cruise",
    "age": 56,
    "Born At": "Syracuse, NY",
    "Birthdate": "July 3 1962",
    "movies": [
      "Top Gun",
      "Mission: Impossible",
      "Oblivion"
    ],
    "photo": "https://jsonformatter.org/img/tom-cruise.jpg"
  }
}

[Авария]

Вот в этом коде, когда я вызываю Deno.read(file?.rid, buf).then. Это вызовет именованную функцию function readByte(numOfByteRead). Это будет внутренне проверять numOfByteRead каждый раз. Вы можете добавить текст, возвращаемый после декодирования, с помощью decoder.decode. Я добавляю как Uint8Array. К добавленным массивам Uint8Array я нашел хороший образец на StackOverflow.

[_append]

function _append(a: Uint8Array, b: Uint8Array, numOfByteRead:number) {
  var c = new Uint8Array(a.length + numOfByteRead);
  c.set(a, 0);
  c.set(b.slice(0, numOfByteRead), a.length);
  return c;
}

[ПРИМЕЧАНИЕ]: если вы не пройдете numOfByteRead, вы можете удалить значение, прочитанное в последний раз.

Красиво 🙂, вроде все нормально. Тем не менее, мы все еще далеки от чтения построчно. Для этого мы будем использовать асинхронный итератор.

Базовый пример для асинхронного итератора #

let range = {
  from: 1,
  to: 5,
  [Symbol.asyncIterator]() {
    return {
      current: this.from,
      last: this.to,
      async next() {
        const value = await new Promise<number>((resolve) =>
          setTimeout(() => {
            resolve(this.current++);
          }, 1000)
        );
        if (value <= this.last) {
          return { done: false, value };
        } else {
          return { done: true };
        }
      },
    };
  },
};
(async () => {
  for await (let value of range) {
    console.log(value); // 1,2,3,4,5
  }
})();

[выход]: `1,2,3,4,5`

Как и Symbol.iterator, мы можем использовать Symbol.asyncIterator для создания асинхронного итератора. Поскольку typescript поддерживает асинхронный итератор из коробки. Мы можем использовать этот API. Чтобы понять больше, вы можете прочитать асинхронные итераторы-генераторы.

Для построчного чтения я создал два служебных метода _readTillDone и readLine.

const _readTillDone = async (
  rid: number,
  text: string = ""
): Promise<[string, string, boolean]> => {
  let buf = new Uint8Array(100);
  let indexOfLine = text.indexOf("\n");
  if (indexOfLine === -1) {
    const num = await Deno.read(rid, buf);
    if (num) {
      text = text + decoder.decode(buf.slice(0, num));
      return _readTillDone(rid, text);
    } else {
      return [text, "", true];
    }
  } else {
    return [text.slice(0, indexOfLine), text.slice(indexOfLine + 1), false];
  }
};
const readLine = async (fileName: string) => {
  const file = await Deno.open(fileName);
  let text = "";
  let done = false;
  return {
    [Symbol.asyncIterator]() {
      return {
        async next() {
          const [t, rest, d] = await _readTillDone(file?.rid, text);
          if (done) {
            return { done: true, value: t };
          } else {
            text = rest;
            done = d;
            return { done: false, value: t };
          }
        },
      };
    },
  };
};

[Авария]

readLine очень просто. При каждом вызове асинхронного итератора он будет вызывать _readTillDone и возвращать строку. Однако _readTillDone немного сложен. Я использую file.rid для отслеживания прочитанного файла.

Всякий раз, когда я звоню _readTillDone с file?.rid, text. Он пытается разделить текст с помощью newLine. Я не смог найти newLine. Он пытается прочитать больше строк до конца. _readTillDone возвращает три параметра [t, rest, d]. Здесь t текст читается построчно, rest является буферным текстом, а d возвращает результат выполнения.

Давайте завершим обучение. Когда у нас есть эти утилиты, реализация становится очень простой.

Пример - Конечный код #

// examples/06_readfile_chunk.ts
import { readLine } from "https://raw.githubusercontent.com/deepakshrma/deno-by-example/master/examples/file_reader.ts";
async function main(name?: string) {
  if (name) {
    // Example 6
    const reader = await readLine(name);
    for await (let value of reader) {
      console.log(value);
    }
  }
}
const [fileName] = Deno.args;
main(fileName);

[Бегать]

$ deno  run --allow-read  examples/06_readfile_chunk.ts examples/tom.json
# Output
{
  "id": 1,
  "version": "1.0.1",
  "contributors": [
    "deepak",
    "gary"
  ],
  "actor": {
    "name": "Tom Cruise",
    "age": 56,
    "Born At": "Syracuse, NY",
    "Birthdate": "July 3 1962",
    "movies": [
      "Top Gun",
      "Mission: Impossible",
      "Oblivion"
    ],
    "photo": "https://jsonformatter.org/img/tom-cruise.jpg"
  }
}

ТаДа! 👏👏 Теперь вы можете прочитать весь файл построчно.

Надеюсь, вам понравится этот урок. дайте мне знать ваш отзыв в комментарии. Пожалуйста, поддержите (🙏🙏) подпиской и аплодисментами.

Все рабочие примеры можно найти у меня на Github: https://github.com/deepakshrma/deno-by-example/tree/master/examples

Первоначально опубликовано на https://deepakshrma.github.io.

Примечание на простом английском языке

А вы знали, что у нас четыре публикации и канал на YouTube? Вы можете найти все это на нашей домашней странице plainenglish.io — проявите свою любовь, подписавшись на наши публикации и подписавшись на наш канал YouTube!