我正在Servlet中读取一个77MB的文件,将来这将是150GB。此文件不是使用任何类型的nio
包编写的,而是使用BufferedWriter
编写的。
现在这就是我需要做的。
逐行读取文件。每行是文本的“哈希码”。将它分成3个字符(3个字符代表1个字)可能很长,可能很短,我不知道。
读完该行后,将其转换为真实的单词。我们有一个单词和哈希的地图,所以我们可以找到单词。
到目前为止,我使用BufferedReader
来读取文件。这对于像150GB这样的巨大文件来说很慢而且不好。即使对于这个77MB的文件,也需要数小时才能完成整个过程。因为我们无法让用户等待几个小时,所以应该在几秒钟之内。所以,我们决定将文件加载到内存中。首先我们考虑将每一行加载到LinkedList中,因此内存coulkd将其保存。但是你知道,记忆不能保存这么大的数量。在大搜索之后,我决定将文件映射到内存就是答案。内存比磁盘快,所以我们也可以超快速地读取文件。
public class MapRead {
public MapRead()
{
try {
File file = new File("E:/Amazon HashFile/Hash.txt");
FileChannel c = new RandomAccessFile(file,"r").getChannel();
MappedByteBuffer buffer = c.map(FileChannel.MapMode.READ_ONLY, 0,c.size()).load();
for(int i=0;i<buffer.limit();i++)
{
System.out.println((char)buffer.get());
}
System.out.println(buffer.isLoaded());
System.out.println(buffer.capacity());
} catch (IOException ex) {
Logger.getLogger(MapRead.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
但我看不出任何“超级快”的东西。我需要一行一行。我有几个问题要问。
您阅读我的描述并知道我需要做什么。我已经完成了第一步,那是正确的吗?
我的地图方式是否正确?我的意思是,这与以正常方式阅读它没有区别。那么这首先将“整个”文件保存在内存中吗? (假设使用一种名为Mapping
的技术)然后我们必须编写另一个代码来访问该内存?
如何在超级“快速”中逐行阅读? (如果我必须先将整个文件加载/映射到内存中几个小时,然后在几秒钟内以超高速访问它,我也完全没问题了)
在Servlet中读取文件好吗? (因为它被人数访问,并且一次只能打开一个IO流。在这种情况下,这个servlet将被数千个访问一次)
更新
这是我使用SO用户 Luiggi Mendoza 的答案更新代码时的样子。
public class BigFileProcessor implements Runnable {
private final BlockingQueue<String> linesToProcess;
public BigFileProcessor (BlockingQueue<String> linesToProcess) {
this.linesToProcess = linesToProcess;
}
@Override
public void run() {
String line = "";
try {
while ( (line = linesToProcess.take()) != null) {
System.out.println(line); //This is not happening
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class BigFileReader implements Runnable {
private final String fileName;
int a = 0;
private final BlockingQueue<String> linesRead;
public BigFileReader(String fileName, BlockingQueue<String> linesRead) {
this.fileName = fileName;
this.linesRead = linesRead;
}
@Override
public void run() {
try {
//Scanner do not work. I had to use BufferedReader
BufferedReader br = new BufferedReader(new FileReader(new File("E:/Amazon HashFile/Hash.txt")));
String str = "";
while((str=br.readLine())!=null)
{
// System.out.println(a);
a++;
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
public class BigFileWholeProcessor {
private static final int NUMBER_OF_THREADS = 2;
public void processFile(String fileName) {
BlockingQueue<String> fileContent = new LinkedBlockingQueue<String>();
BigFileReader bigFileReader = new BigFileReader(fileName, fileContent);
BigFileProcessor bigFileProcessor = new BigFileProcessor(fileContent);
ExecutorService es = Executors.newFixedThreadPool(NUMBER_OF_THREADS);
es.execute(bigFileReader);
es.execute(bigFileProcessor);
es.shutdown();
}
}
public class Main {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
BigFileWholeProcessor b = new BigFileWholeProcessor ();
b.processFile("E:/Amazon HashFile/Hash.txt");
}
}
我正在尝试在BigFileProcessor
中打印文件。我理解的是这个;
用户输入文件名
BigFileReader
逐行读取该文件
在每个行之后,BigFileProcessor
被调用。这意味着,假设BigFileReader
读取第一行。现在调用BigFileProcessor
。现在BigFileProcessor
完成该行的处理后,现在BigFileReader
读取第2行。然后再次调用该行BigFileProcessor
,依此类推。
可能是我对此代码的理解不正确。我该如何处理这条线?
答案 0 :(得分:6)
我建议在这里使用多线程:
BlockingQueue
以便进行处理。take
来自此队列的元素并处理它们。要实现这个多线程工作,最好使用ExecutorService
接口并传递Runnable
个实例,每个实例都应该实现。记住只有一个任务来读取文件。
如果队列具有特定大小,您还可以管理停止阅读的方法,例如如果队列有10000个元素,那么等到它的大小降到8000,然后继续读取并填充队列。
在Servlet中读取文件很好吗?
我建议永远不要在servlet中做繁重的工作。相反,触发异步任务,例如通过JMS调用,然后在这个外部代理中,您将处理您的文件。
上述解释问题的简要示例:
public class BigFileReader implements Runnable {
private final String fileName;
private final BlockingQueue<String> linesRead;
public BigFileReader(String fileName, BlockingQueue<String> linesRead) {
this.fileName = fileName;
this.linesRead = linesRead;
}
@Override
public void run() {
//since it is a sample, I avoid the manage of how many lines you have read
//and that stuff, but it should not be complicated to accomplish
Scanner scanner = new Scanner(new File(fileName));
while (scanner.hasNext()) {
try {
linesRead.put(scanner.nextLine());
} catch (InterruptedException ie) {
//handle the exception...
ie.printStackTrace();
}
}
scanner.close();
}
}
public class BigFileProcessor implements Runnable {
private final BlockingQueue<String> linesToProcess;
public BigFileProcessor (BlockingQueue<String> linesToProcess) {
this.linesToProcess = linesToProcess;
}
@Override
public void run() {
String line = "";
try {
while ( (line = linesToProcess.take()) != null) {
//do what you want/need to process this line...
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class BigFileWholeProcessor {
private static final int NUMBER_OF_THREADS = 2;
public void processFile(String fileName) {
BlockingQueue<String> fileContent = new LinkedBlockingQueue<String>();
BigFileReader bigFileReader = new BigFileReader(fileName, fileContent);
BigFileProcessor bigFileProcessor = new BigFileProcessor(fileContent);
ExecutorService es = Executors.newFixedThreadPool(NUMBER_OF_THREADS);
es.execute(bigFileReader);
es.execute(bigFileProcessor);
es.shutdown();
}
}
答案 1 :(得分:2)
NIO不会在这里帮到你。 BufferedReader并不慢。如果您受I / O约束,则您受I / O限制 - 获得更快的I / O.
将文件映射到内存可能会有所帮助,但前提是您实际使用内存,而不是仅仅将所有数据复制到您返回的大字节数组中。映射文件的主要优点是它可以将数据保存在Java堆之外,并远离垃圾收集器。
您的最佳表现将来自处理数据,如果可以的话,不会将其复制到堆中。
您的某些性能可能会受到对象创建的影响。例如,如果您尝试将数据加载到LinkedList中,那么您为List本身创建(可能)数百万个节点,加上数据周围的对象(即使它们只是字符串)
基于内存映射数组创建字符串可能非常有效,因为String将简单地包装数据,而不是复制数据。但是,如果您正在使用除ASCII之外的其他内容(因为字节不是Java中的字符),您必须能够识别UTF。
此外,如果您正在加载包含大量对象的大型内容,请确保堆中有可用空间。而通过自由空间,我的意思是实际的空间。你可以有一个500MB的堆,由-Xmx指定,但是ACTUAL堆最初不会那么大,它会增长到那个限制。
假设你有足够的内存,你可以通过-Xms来做到这一点,它会将堆预先分配到所需的大小,或者你可以简单地快速byte[] buf = new byte[400 * 1024 * 1024]
来制作一个巨大的内存分配,强制GC,并拉伸堆。
您不想做的事情是分配一百万个对象,并且随着它的增长,每隔10000左右就有一个VM GC。预先分配其他数据结构也很有用(特别是ArrayLists,LinkedLists不是那么多)。
答案 2 :(得分:0)
将文件分成更小的部分。为此,您需要访问seekable
读取权限,以便快进到文件的其他部分。
对于每个部分,生成多个工作线程,每个线程都有自己的哈希查找表副本。让已完成的线程join
成为一个收集器线程,它将按顺序写入已完成的块并表示处理完成。
最好是流文件块而不是将所有内容加载到内存中。