从文件中随机抽取一行

17

我不知道该怎么做。我应该从哪里开始?我已经在谷歌上搜索了,但是没有查到如何从文本文件中提取随机行的结果。

我唯一找到的是https://github.com/chrisinajar/node-rand-line,但它无法使用。我该如何读取文本文件中的随机行?


1
这个文件有多大?一个简单的方法是读取整个文件,然后选择一行随机的数据。但是,这需要与文件大小至少相同的内存。 - Brad
2MB?只需将其读入内存即可。 - Dmitry
5个回答

13

你可能想要查看node.js标准库函数读取文件的相关内容,fs.readFile,最终得到以下类似代码:

const fs = require("fs");
// note this will be async
function getRandomLine(filename, callback){
  fs.readFile(filename, "utf-8", function(err, data){
    if(err) {
        throw err;
    }

    // note: this assumes `data` is a string - you may need
    //       to coerce it - see the comments for an approach
    var lines = data.split('\n');
    
    // choose one of the lines...
    var line = lines[Math.floor(Math.random()*lines.length)]

    // invoke the callback with our line
    callback(line);
 })
}

如果无法读取整个文件并进行分割,则可以参考这个Stack Overflow上的建议。


2
这对我来说并没有立即起作用,我收到了错误信息:data.split is not a function。根据这个问题的答案,我添加了 data+='',然后它就可以工作了。 - Teleporting Goat
请注意,如果文件包含 foo\nbar\n,该函数将返回其中之一:'foo''bar'''。通过更改 data.split('\n')data.replace(/\n$/, '').split('\n') 来修复此问题。 - tuomassalo
1
你应该尝试返回行而不是在函数中执行某些操作。 - SP73

5

我有一个类似的需求,需要从一个超过100MB的文件中随机选择一行。
因此我想避免将整个文件内容存储在内存中。
最终我采用了两次迭代所有行的方法:第一次获取行数,第二次获取目标行内容。
以下是代码示例:

const readline = require('readline');
const fs = require('fs');
const FILE_PATH = 'data.ndjson';

module.exports = async () =>
{
    const linesCount = await getLinesCount();
    const randomLineIndex = Math.floor(Math.random() * linesCount);
    const content = await getLineContent(randomLineIndex);
    return content;
};

//
// HELPERS
//

function getLineReader()
{
    return readline.createInterface({
        input: fs.createReadStream(FILE_PATH)
    });
}

async function getLinesCount()
{
    return new Promise(resolve =>
    {
        let counter = 0;
        getLineReader()
        .on('line', function (line)
        {
            counter++;
        })
        .on('close', () =>
        {
            resolve(counter);
        });
    });
}

async function getLineContent(index)
{
    return new Promise(resolve =>
    {
        let counter = 0;
        getLineReader().on('line', function (line)
        {
            if (counter === index)
            {
                resolve(line);
            }
            counter++;
        });
    });
}

3

我没有Node来测试代码,所以无法给您确切的代码,但我会做如下处理:

  1. 获取文件大小(以字节为单位),选择一个随机的字节偏移量
  2. 以流的形式打开文件
  3. 使用此片段代码发出行(或readline,但上次我使用时它有一个严重的错误,基本上不起作用)
  4. 阅读时跟踪文件中的位置。当您通过所选偏移量时,选择该行并返回它。

请注意,这不是完全随机的。较长的行将更加重要,但这是在不读取整个文件以获取行数计数的情况下完成的唯一方法。

此方法允许您获取“随机”行而无需将整个文件保存在内存中。


应该指出的是,node.js操作系统特定的EOL标记可以通过os.EOL访问。 - saintedlama

0

我可以给你一个建议,因为我没有任何演示代码

  1. 使用 buffered reader 逐行读取文件
  2. 将每一行存储在字符串数组中
  3. 创建一个方法 int returnRandom(arraySize)
  4. 将数组大小传入函数中
  5. 计算一个介于 0arraySize 之间的随机数
  6. 返回这个随机数
  7. 打印出你的字符串数组中给定索引的值

0
我是这样做的
const path = require('path')
const fs = require('fs/promises')

const FILE_NAME = path.resolve(__dirname, '../bigfile.txt')
const DELIMITER = '\n'

const READ_BUFFER_SIZE = 1000 // Must be greater than the record size

/*
 * Reading a random line from a very large (does not fit in RAM) file
 *
 * Note that you will never get the first or last line in the file,
 * but who cares when the file contains millions of lines.
 */
async function main() {
    const stats = await fs.stat(FILE_NAME)
    const handle = await fs.open(FILE_NAME, 'r')

    for (;;) {
        const randomPos = Math.floor(Math.random() * stats.size)

        const buffer = Buffer.alloc(READ_BUFFER_SIZE)
        await handle.read(buffer, 0, READ_BUFFER_SIZE, randomPos)

        const xs = buffer.toString().split(DELIMITER)
        if (xs[2] !== undefined) {
            console.log('Random line:', xs[1])
        }
    }
}
main().catch(console.log)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接