【问题标题】:How to get a random line of a text file in Java?如何在Java中获取文本文件的随机行?
【发布时间】:2010-02-07 19:35:57
【问题描述】:

假设有一个文件太大而无法存储。我怎样才能从中得到一条随机线?谢谢。

更新: 我希望每条线的概率相等。

【问题讨论】:

    标签: java file random


    【解决方案1】:

    如果只想要一行,则读取整个文件似乎有点过分。以下应该更有效:

    1. 使用RandomAccessFile 查找文件中的随机字节位置。
    2. 左右查找下一行终止符。让 L 在它们之间划线。
    3. 有概率 (MIN_LINE_LENGTH / L.length) 返回 L。否则,从第 1 步重新开始。

    这是rejection sampling 的变体。

    行长包括行终止符,因此 MIN_LINE_LENGTH >= 1。(如果您知道更严格的行长限制,那就更好了)。

    值得注意的是,该算法的运行时间不依赖于文件大小,只依赖于行长,即它的扩展性比读取整个文件要好得多。

    【讨论】:

    • 太棒了!如果文件将被重复采样,则使用单次传递来收集List<Integer> 的偏移量,然后可以通过Collections.shuffle() 随机化。
    • 这应该是最好的答案了。
    【解决方案2】:

    这里有一个解决方案。看看做真实事情的choose()方法(main()方法反复练习choose(),可见分布确实很均匀)。

    这个想法很简单:当您阅读第一行时,它有 100% 的机会被选为结果。当您阅读第二行时,它有 50% 的机会替换第一行作为结果。当您阅读第 3 行时,它有 33% 的机会成为结果。第四行有25%,以此类推....

    import java.io.*;
    import java.util.*;
    
    public class B {
    
      public static void main(String[] args) throws FileNotFoundException {
         Map<String,Integer> map = new HashMap<String,Integer>();
         for(int i = 0; i < 1000; ++i)
         {
            String s = choose(new File("g:/temp/a.txt"));
            if(!map.containsKey(s))
               map.put(s, 0);
            map.put(s, map.get(s) + 1);
         }
    
         System.out.println(map);
      }
    
      public static String choose(File f) throws FileNotFoundException
      {
         String result = null;
         Random rand = new Random();
         int n = 0;
         for(Scanner sc = new Scanner(f); sc.hasNext(); )
         {
            ++n;
            String line = sc.nextLine();
            if(rand.nextInt(n) == 0)
               result = line;         
         }
    
         return result;      
      }
    }
    

    【讨论】:

    • 水库采样的实现
    • 太棒了。从未听说过水库采样。如果我的文件是 MB 怎么办?有性能问题吗?如果是,是否有其他方法可以避免完整文件扫描?
    • 我是对的吗,假设这是一个固定的 n=1,其中 n 是“样本”的数量?有没有办法一次选择多个?就目前而言,您不止一次“循环播放磁带”,或者至少尝试似乎效率低下。
    【解决方案3】:

    无论是你

    1. 两次读取文件 - 一次计算行数,第二次提取随机行,或者

    2. 使用reservoir sampling

    【讨论】:

      【解决方案4】:

      查看 Itay 的答案,它看起来好像在对一行代码进行采样后读取文件一千次,而真正的水库采样应该只遍历“磁带”一次。根据this 和网络上的各种描述,我已经设计了一些代码来通过真实的水库采样检查代码。

      import java.io.FileNotFoundException;
      import java.io.IOException;
      import java.util.List;
      
      public class reservoirSampling {
      
          public static void main(String[] args) throws FileNotFoundException, IOException{
              Sampler mySampler = new Sampler();
              List<String> myList = mySampler.sampler(10);
              for(int index = 0;index<myList.size();index++){
                  System.out.println(myList.get(index));
              }
          }
      }
      
      import java.io.File;
      import java.io.FileNotFoundException;
      import java.io.IOException;
      import java.util.ArrayList;
      import java.util.List;
      import java.util.Random;
      import java.util.Scanner;
      
      public class Sampler {
      
          public Sampler(){}
          public List<String> sampler (int reservoirSize) throws FileNotFoundException, IOException
          {
              String currentLine=null;
              //reservoirList is where our selected lines stored
              List <String> reservoirList= new ArrayList<String>(reservoirSize); 
              // we will use this counter to count the current line number while iterating
              int count=0; 
      
              Random ra = new Random();
              int randomNumber = 0;
              Scanner sc = new Scanner(new File("Open_source.html")).useDelimiter("\n");
              while (sc.hasNext())
              {
                  currentLine = sc.next();
                  count ++;
                  if (count<=reservoirSize)
                  {
                      reservoirList.add(currentLine);
                  }
                  else if ((randomNumber = (int) ra.nextInt(count))<reservoirSize)
                  {
                      reservoirList.set(randomNumber, currentLine);
                  }
              }
              return reservoirList;
          }
      }
      

      基本前提是你填满水库,然后回到那里,以 1/ReservoirSize 的机会填满随机线。我希望这提供更有效的代码。如果这对你不起作用,请告诉我,因为我在半小时内就搞定了。

      【讨论】:

      • 我已经为review提供了这个。
      【解决方案5】:

      使用RandomAccessFile

      1. 构造一个RandomAccessFile文件
      2. 通过调用 file.length() 获取该文件的长度 filelen
      3. 生成一个随机数,pos,介于 0 和 filelen 之间
      4. 调用file.seek(pos)寻找随机位置
      5. 调用 file.readLine() 到达当前行的末尾
      6. 再次调用 file.readLine() 读取下一行

      使用这种方法,我一直在从布朗语料库中随机采样行,并且可以在几秒钟内轻松地从随机选择的文件中检索 1000 个随机样本。如果我尝试通过逐行阅读每个文件来做同样的事情,那将花费我更长的时间。

      同样的原则可以用于从列表中选择随机元素。如果您生成一个介于 0 和列表长度之间的随机数,那么您可以直接对列表进行索引,而不是通读列表并在随机位置停止。

      【讨论】:

        【解决方案6】:

        从java文件中读取随机行:

        public String getRandomLineFromTheFile(String filePathWithFileName) throws Exception {
        
                File file = new File(filePathWithFileName); 
                final RandomAccessFile f = new RandomAccessFile(file, "r");
                final long randomLocation = (long) (Math.random() * f.length());
                f.seek(randomLocation);
                f.readLine();
                String randomLine = f.readLine();
                f.close();
                return randomLine;
            }
        

        【讨论】:

          【解决方案7】:

          使用 BufferedReader 并逐行读取。使用 java.util.Random 对象随机停止;)

          【讨论】:

          • 当我想停止时,如何确保文件没有结束? IE。如何知道文件的行数?
          • 另外,我希望让每一行的概率相等。
          • @Dinuk,所以如果文件比其他文件小,我会经常得到最后一行,如果文件更大 - 我会很少得到它
          • 那么你必须读取文件两次,或者如果所有行的长度相等,你可以从文件大小计算行数
          猜你喜欢
          • 2015-01-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-10-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多