假设有一个文件太大而无法放入内存,那么我该如何从中获取一行随机数据?谢谢。
更新: 我希望各行被选中的概率相等。
如果您只想获取一行内容,读取整个文件似乎有点过度了。以下方法应该更有效:
这是拒绝抽样的一种变体。
行长度包括行终止字符,因此MIN_LINE_LENGTH >= 1。(如果您知道行长度的更紧密限制,那就更好了)。
值得注意的是,这个算法的运行时间不取决于文件大小,只取决于行长度,即它比读取整个文件的效率要高出很多。
这里有一个解决方案。看一下choose()方法,它执行了真正的操作(main()方法反复运行choose()方法,以显示分布确实非常均匀)。
思路很简单:当你读第一行时,它被选为结果的概率为100%。当你读第二行时,它有50%的几率替换第一行成为结果。当你读第三行时,它有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;
}
}
看了Itay的答案,似乎在抽取一行代码后,读取文件的次数达到了一千次之多,而真正的蓄水池采样应该只需要遍历“带子”一次。我已经设计出了一些代码,使用真正的蓄水池采样方法,只需要一次遍历即可。这个方法基于这个链接和网上的各种描述。
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;
}
}
使用 RandomAccessFile:
使用这种方法,我已经可以在几秒钟内从随机选择的文件中随机取样 Brown Corpus 中的行,并轻松检索 1000 个随机样本。如果我尝试通过逐行阅读每个文件来完成相同的操作,那么需要更长的时间。
选择列表中的随机元素时可以使用相同的原理。与其逐行阅读列表并在随机位置停止,不如生成介于 0 和列表长度之间的随机数,然后直接索引列表。
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;
}
List<Integer>
,然后可以通过Collections.shuffle()
进行随机化。 - trashgod