用于虚拟拆分文本文件的java解决方案
我需要阅读&;处理一个巨大的文本文件。为了缩短数据处理时间,我考虑使用多个读卡器同时读取数据。这个想法是通过记下开始和结束指针来虚拟地分割文件。这是由主线程在程序开始时完成的。实际上,我的意思是,不创建物理拆分文件
稍后,当读取和处理由并发读卡器完成时,每个线程都可以调用bufferedReader。跳过(长)并跟踪读取的字符数,以便它们不会跨越结束指针边界
问题是单个线程的文件读取是使用BufferedReader完成的,因此要跳过,我需要知道字符数,而主线程无法确定。要计算开始和结束指针,主线程仅有的数据是以字节为单位的文件长度
如何根据字符确定起始和结束指针,以便读者可以跳过这些字符
注-
- 输入文本文件可以采用不同的字符编码,例如ASCII、EBCDIC、UTF-8、UTF-16等
- 逐行读取输入文件以确定起始指针和结束指针不是一个选项,因为这会破坏拆分文本文件的目的李>
更新
注意我被限制使用java文件API,而不是像Hadoop这样的框架。这是一个应用程序架构限制
更新
下面是通过跳过计算出的字节数,然后逐字节读取输入文件以确定记录分隔符来读取输入文件的代码。如果您发现代码有问题(特别是考虑到输入文件可能采用不同的字符编码),请用您的想法回答
{
CountingInputStream countingInputStream = new CountingInputStream(new FileInputStream(inputFilePath.toFile()));
long endPointer;
while(true) {
long actualSkipped = countingInputStream.skip(skipCount);
if(actualSkipped == 0) {
logger.info("Nothing to skip");
break; //nothing to skip now.
}
byte[] inputBytes = new byte[recordDelimiterBytes.length];
int noOfBytesRead = countingInputStream.read(inputBytes);
if(noOfBytesRead == -1) {
//end of file already reached!
endPointer = countingInputStream.getCount();
break;
}
while (!(Arrays.equals(recordDelimiterBytes, inputBytes))) {
shiftLeft(inputBytes);
int readByte = countingInputStream.read();
if(readByte != -1) {
inputBytes[inputBytes.length - 1] = (byte) readByte;
} else {
throw new IllegalStateException("EOF reached before getting the delimiter");
}
}
endPointer = countingInputStream.getCount();
}
private void shiftLeft(byte[] inputBytes) {
for(int i=0; i<inputBytes.length - 1; i++) {
inputBytes[i] = inputBytes[i+1];
}
}
# 1 楼答案
你的建议是不可能的。磁盘上的所有I/O操作本质上都是串行的。想想普通硬盘是什么样子。该文件存储在一个带有一个读取头的盘片上。您不会从java创建更多的标题标题-因此,即使您创建了多个阅读器,它们最终也会等待对方完成阅读
此外,所有读取都从文件开始。不能开始在中间读取文件。如果要向前查找读数,可以使用skip()方法,但该方法读取那么多字符,而不需要对数据做任何处理
编辑:您可以将读取线程与处理线程分开。创建一个读取线程,从头到尾读取文件。每次读取完文件的适当部分后,它都会启动一个新线程来处理读取的数据。同时,读取线程将读取一个新的文件块,启动该块的线程,等等。。。当读取线程到达文件的末尾时,它终止,启动了几个新线程,这些新线程现在同时处理文件中各自的部分
# 2 楼答案
问题是:UTF-8字符可以有不同的长度。因此,仅以文件长度作为提示,不可能确定x%字符的结尾
# 3 楼答案
请阅读hadoop和HDFS。它们也被设计成这样。有许多教程可用于net。请更清楚地说明您要进行哪种处理
# 4 楼答案
你的问题中有两点需要回答:
如果您的处理是I/O绑定的,那么尝试读取包含多个流的单个文件不太可能给您带来任何速度。这可能会让事情变得更糟。然而,很难给出一个明确的答案,因为这取决于操作系统如何处理预读、内存文件系统缓冲、RAID和其他因素
另一方面,如果处理是CPU受限的,可以进行并行化,并且您有多个可用的内核,那么多个流可能是有效的
计算出大致的分区大小和大致的边界。然后你需要做一些工作来找到确切的边界
如果要从行或字的开头开始每个段。选择一个点,一次读取一个字节,直到到达相关边界
如果要从下一个有效字符的开头开始,请执行以下操作:
对于ASCII、Latin-1等8位编码来说,这个问题微不足道。
使用UTF-8可以跳到下一个字节,该字节的顶部位为00、01或11,这是代码点的开始。请参阅Wikipedia page on UTF-8上的表格
使用UTF-16,您必须读取字节对。如果您不知道顺序(大端或小端),可以检查前2个字节是否为BOM。在此基础上,不在DC00-DFFF范围内的字节对是代码点的起点。请参阅Wikipedia page on UTF-16
显然,一旦知道分区的开始,就可以知道上一个分区的结束
如您所见,您需要知道文件的字符编码是什么。但是,如果您知道这一点,您可以快速可靠地找到一个合适的位置来设置分区边界
这可能很难:
如果分隔符仅在开始处或接近开始处设置一次,则只需从开始处读取,直到找出分隔符是什么。然后进行分区
如果可以在文件中的任何位置更改分隔符,那么使用单个线程读取可能是唯一的选择。(也许您可以在将输入分解为分隔的记录、行或其他内容后,并行化处理。)
最后一个选项是线程分区并处理假设一个分隔符,但也要查找嵌入的“更改分隔符”指令。如果它们确实检测到实际的更改,则告诉后续分区的线程重新启动。这有点复杂
# 5 楼答案
我认为最好的方法是让一个读卡器负责对数据进行分区,当读卡器到达每个分区边界时,它将分区提交给一个处理队列。然后,您可以拥有一个从队列中读取数据的处理器池。这样,如果处理一个分区比读取一个分区慢,那么您将获得并行处理分区的好处