有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

用于虚拟拆分文本文件的java解决方案

我需要阅读&;处理一个巨大的文本文件。为了缩短数据处理时间,我考虑使用多个读卡器同时读取数据。这个想法是通过记下开始和结束指针来虚拟地分割文件。这是由主线程在程序开始时完成的。实际上,我的意思是,不创建物理拆分文件

稍后,当读取和处理由并发读卡器完成时,每个线程都可以调用bufferedReader。跳过(长)并跟踪读取的字符数,以便它们不会跨越结束指针边界

问题是单个线程的文件读取是使用BufferedReader完成的,因此要跳过,我需要知道字符数,而主线程无法确定。要计算开始和结束指针,主线程仅有的数据是以字节为单位的文件长度

如何根据字符确定起始和结束指针,以便读者可以跳过这些字符

注-

  1. 输入文本文件可以采用不同的字符编码,例如ASCII、EBCDIC、UTF-8、UTF-16等
  2. 逐行读取输入文件以确定起始指针和结束指针不是一个选项,因为这会破坏拆分文本文件的目的

更新

注意我被限制使用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];
        }
    }

共 (5) 个答案

  1. # 1 楼答案

    你的建议是不可能的。磁盘上的所有I/O操作本质上都是串行的。想想普通硬盘是什么样子。该文件存储在一个带有一个读取头的盘片上。您不会从java创建更多的标题标题-因此,即使您创建了多个阅读器,它们最终也会等待对方完成阅读

    此外,所有读取都从文件开始。不能开始在中间读取文件。如果要向前查找读数,可以使用skip()方法,但该方法读取那么多字符,而不需要对数据做任何处理

    编辑:您可以将读取线程与处理线程分开。创建一个读取线程,从头到尾读取文件。每次读取完文件的适当部分后,它都会启动一个新线程来处理读取的数据。同时,读取线程将读取一个新的文件块,启动该块的线程,等等。。。当读取线程到达文件的末尾时,它终止,启动了几个新线程,这些新线程现在同时处理文件中各自的部分

  2. # 2 楼答案

    问题是:UTF-8字符可以有不同的长度。因此,仅以文件长度作为提示,不可能确定x%字符的结尾

  3. # 3 楼答案

    请阅读hadoop和HDFS。它们也被设计成这样。有许多教程可用于net。请更清楚地说明您要进行哪种处理

  4. # 4 楼答案

    你的问题中有两点需要回答:

    To improve the data processing time, I thought of reading it concurrently by having multiple readers.

    如果您的处理是I/O绑定的,那么尝试读取包含多个流的单个文件不太可能给您带来任何速度。这可能会让事情变得更糟。然而,很难给出一个明确的答案,因为这取决于操作系统如何处理预读、内存文件系统缓冲、RAID和其他因素

    另一方面,如果处理是CPU受限的,可以进行并行化,并且您有多个可用的内核,那么多个流可能是有效的

    How do I determine the start and end pointers in terms of characters so that the reader can skip those many characters?

    计算出大致的分区大小和大致的边界。然后你需要做一些工作来找到确切的边界

    • 如果要从行或字的开头开始每个段。选择一个点,一次读取一个字节,直到到达相关边界

    • 如果要从下一个有效字符的开头开始,请执行以下操作:

      • 对于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

    显然,一旦知道分区的开始,就可以知道上一个分区的结束

    如您所见,您需要知道文件的字符编码是什么。但是,如果您知道这一点,您可以快速可靠地找到一个合适的位置来设置分区边界


    The only problem comes is when there are text qualifiers in the data i.e. the configured record delimiters could also be a part of the data.

    这可能很难:

    • 如果分隔符仅在开始处或接近开始处设置一次,则只需从开始处读取,直到找出分隔符是什么。然后进行分区

    • 如果可以在文件中的任何位置更改分隔符,那么使用单个线程读取可能是唯一的选择。(也许您可以在将输入分解为分隔的记录、行或其他内容后,并行化处理。)

    • 最后一个选项是线程分区并处理假设一个分隔符,但也要查找嵌入的“更改分隔符”指令。如果它们确实检测到实际的更改,则告诉后续分区的线程重新启动。这有点复杂

  5. # 5 楼答案

    我认为最好的方法是让一个读卡器负责对数据进行分区,当读卡器到达每个分区边界时,它将分区提交给一个处理队列。然后,您可以拥有一个从队列中读取数据的处理器池。这样,如果处理一个分区比读取一个分区慢,那么您将获得并行处理分区的好处