我试图从一个可能很大的文件中读取行。
为了获得更好的性能,我尝试使用映射文件。但是当我比较性能时,我发现映射文件的方式比我从BufferedReader
读取的文件慢一点
public long chunkMappedFile(String filePath, int trunkSize) throws IOException {
long begin = System.currentTimeMillis();
logger.info("Processing imei file, mapped file [{}], trunk size = {} ", filePath, trunkSize);
//Create file object
File file = new File(filePath);
//Get file channel in readonly mode
FileChannel fileChannel = new RandomAccessFile(file, "r").getChannel();
long positionStart = 0;
StringBuilder line = new StringBuilder();
long lineCnt = 0;
while(positionStart < fileChannel.size()) {
long mapSize = positionStart + trunkSize < fileChannel.size() ? trunkSize : fileChannel.size() - positionStart ;
MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, positionStart, mapSize);//mapped read
for (int i = 0; i < buffer.limit(); i++) {
char c = (char) buffer.get();
//System.out.print(c); //Print the content of file
if ('\n' != c) {
line.append(c);
} else {// line ends
processor.processLine(line.toString());
if (++lineCnt % 100000 ==0) {
try {
logger.info("mappedfile processed {} lines already, sleep 1ms", lineCnt);
Thread.sleep(1);
} catch (InterruptedException e) {}
}
line = new StringBuilder();
}
}
closeDirectBuffer(buffer);
positionStart = positionStart + buffer.limit();
}
long end = System.currentTimeMillis();
logger.info("chunkMappedFile {} , trunkSize: {}, cost : {} " ,filePath, trunkSize, end - begin);
return lineCnt;
}
public long normalFileRead(String filePath) throws IOException {
long begin = System.currentTimeMillis();
logger.info("Processing imei file, Normal read file [{}] ", filePath);
long lineCnt = 0;
try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
String line;
while ((line = br.readLine()) != null) {
processor.processLine(line.toString());
if (++lineCnt % 100000 ==0) {
try {
logger.info("file processed {} lines already, sleep 1ms", lineCnt);
Thread.sleep(1);
} catch (InterruptedException e) {}
} }
}
long end = System.currentTimeMillis();
logger.info("normalFileRead {} , cost : {} " ,filePath, end - begin);
return lineCnt;
}
在Linux中测试结果,读取大小为537MB的文件:
MappedBuffer方式:
2017-09-28 14:33:19.277 [main] INFO com.oppo.push.ts.dispatcher.imei2device.ImeiTransformerOfflineImpl - process imei file ends:/push/file/imei2device-local/20170928/imei2device-13 , lines :12758858 , cost :14804 , lines per seconds: 861852.0670089165
BufferedReader方式:
2017-09-28 14:27:03.374 [main] INFO com.oppo.push.ts.dispatcher.imei2device.ImeiTransformerOfflineImpl - process imei file ends:/push/file/imei2device-local/20170928/imei2device-13 , lines :12758858 , cost :13001 , lines per seconds: 981375.1249903854
答案 0 :(得分:2)
事情就是这样:文件IO并不简单直接。
您必须记住,您的操作系统会对将要发生的事情产生巨大的影响。从这个意义上说:没有可靠的规则适用于所有平台上的所有JVM实现。
如果您真的不得不担心最后一点性能,那么在目标平台上进行深入分析是主要的解决方案。
除此之外,你的“性能”方面也是错误的。含义:内存映射IO不会神奇地提高一次读取应用程序中单个文件的性能。它的主要优势在于:
如果你有多个进程从同一个文件以只读方式访问数据,那么mmap很棒,这在我编写的服务器系统类型中很常见。 mmap允许所有这些进程共享相同的物理内存页面,从而节省了大量内存。
(引用此answer关于使用C mmap()
系统调用)
换句话说:您的示例是关于读取文件内容。最后,操作系统仍然必须转向驱动器以从那里读取所有字节。含义:它读取光盘内容并将其放入内存中。当你这样做第一次的时候......你做一些“特殊”的事情并不重要。相反 - 当您执行“特殊”操作时,内存映射方法可能会更慢 - 因为与“普通”读取相比,开销。
回到我的第一条记录:即使你有5个进程读取同一个文件,内存映射方法也不一定更快。正如Linux可能认为的那样:我已经将该文件读入内存,并且没有改变 - 所以即使没有明确的“内存映射”,Linux内核也可能会缓存信息。
答案 1 :(得分:2)
内存映射实际上没有任何优势,因为即使您将文件批量加载到内存中,您仍然只能一次处理一个字节。如果以适当大小的byte[]
块处理缓冲区,您可能会看到性能提升。即使这样,BufferedReader
版本可能表现得更好或至少几乎相同。
您的任务的性质是按顺序处理文件。 BufferedReader
已经做得非常好而且代码很简单,所以如果我必须选择,我会选择最简单的选项。
另请注意,除单字节编码外,缓冲区代码不起作用。一旦你为每个角色获得多个字节,它就会失败。
答案 2 :(得分:2)
GhostCat是正确的。除了您的操作系统选择,还有其他可能影响性能的因素。
映射文件会对物理内存产生更大的需求。如果物理内存“紧”,可能导致分页活动,并且性能受到影响。
如果您使用read
系统调用来读取文件而不是将其映射到内存中,则操作系统可以使用不同的预读策略。预读(进入缓冲区缓存)可以使文件读取速度更快。
BufferedReader
的默认缓冲区大小和操作系统内存页面大小可能不同。这可能导致磁盘读取请求的大小不同。 (较大的读取通常会导致更高的吞吐量I / O.至少在某一点上。)
您的基准测试方式也可能导致“人工制品”。例如:
read
时间会缩短。