当前位置: 首页 > 知识库问答 >
问题:

用Java创建大型csv文件变得非常慢

冯嘉荣
2023-03-14

当尝试从另一个csv文件开始创建csv文件时,我遇到了一个性能问题。原始文件的外观如下:

country,state,co,olt,olu,splitter,ont,cpe,cpe.latitude,cpe.longitude,cpe.customer_class,cpe.phone,cpe.ip,cpe.subscriber_id
COUNTRY-0001,STATE-0001,CO-0001,OLT-0001,OLU0001,SPLITTER-0001,ONT-0001,CPE-0001,28.21487,77.451775,ALL,SIP:+674100002743@IMS.COMCAST.NET,SIP:E28EDADA06B2@IMS.COMCAST.NET,CPE_SUBSCRIBER_ID-QHLHW4
COUNTRY-0001,STATE-0002,CO-0002,OLT-0002,OLU0002,SPLITTER-0002,ONT-0002,CPE-0002,28.294018,77.068924,ALL,SIP:+796107443092@IMS.COMCAST.NET,SIP:58DD999D6466@IMS.COMCAST.NET,CPE_SUBSCRIBER_ID-AH8NJQ

可能会有数百万行这样的代码,我已经发现了1.280.000行的问题。

这是算法:

File csvInputFile = new File(csv_path);
int blockSize = 409600;
brCsvInputFile = new BufferedReader(frCsvInputFile, blockSize);

String line = null;
StringBuilder sbIntermediate = new StringBuilder();
skipFirstLine(brCsvInputFile);
while ((line = brCsvInputFile.readLine()) != null) {
    createIntermediateStringBuffer(sbIntermediate, line.split(REGEX_COMMA));
}


private static void skipFirstLine(BufferedReader br) throws IOException {
    String line = br.readLine();
    String[] splitLine = line.split(REGEX_COMMA);
    LOGGER.debug("First line detected! ");
    createIndex(splitLine);
    createIntermediateIndex(splitLine);
}

private static void createIndex(String[] splitLine) {
    LOGGER.debug("START method createIndex.");
    for (int i = 0; i < splitLine.length; i++)
        headerIndex.put(splitLine[i], i);
    printMap(headerIndex);
    LOGGER.debug("COMPLETED method createIndex.");
}

    private static void createIntermediateIndex(String[] splitLine) {

    LOGGER.debug("START method createIntermediateIndex.");
    com.tekcomms.c2d.xml.model.v2.Metadata_element[] metadata_element = null;
    String[] servicePath = newTopology.getElement().getEntity().getService_path().getLevel();

    if (newTopology.getElement().getMetadata() != null)
        metadata_element = newTopology.getElement().getMetadata().getMetadata_element();

    LOGGER.debug(servicePath.toString());
    LOGGER.debug(metadata_element.toString());

    headerIntermediateIndex.clear();
    int indexIntermediateId = 0;
    for (int i = 0; i < servicePath.length; i++) {
        String level = servicePath[i];
        LOGGER.debug("level is: " + level);
        headerIntermediateIndex.put(level, indexIntermediateId);
        indexIntermediateId++;
        // its identificator is going to be located to the next one
        headerIntermediateIndex.put(level + "ID", indexIntermediateId);
        indexIntermediateId++;
    }
    // adding cpe.latitude,cpe.longitude,cpe.customer_class, it could be
    // better if it would be metadata as well.
    String labelLatitude = newTopology.getElement().getEntity().getLatitude();
    // indexIntermediateId++;
    headerIntermediateIndex.put(labelLatitude, indexIntermediateId);
    String labelLongitude = newTopology.getElement().getEntity().getLongitude();
    indexIntermediateId++;
    headerIntermediateIndex.put(labelLongitude, indexIntermediateId);
    String labelCustomerClass = newTopology.getElement().getCustomer_class();
    indexIntermediateId++;
    headerIntermediateIndex.put(labelCustomerClass, indexIntermediateId);

    // adding metadata
    // cpe.phone,cpe.ip,cpe.subscriber_id,cpe.vendor,cpe.model,cpe.customer_status,cpe.contact_telephone,cpe.address,
    // cpe.city,cpe.state,cpe.zip,cpe.bootfile,cpe.software_version,cpe.hardware_version
    // now i need to iterate over each Metadata_element belonging to
    // topology.element.metadata
    // are there any metadata?
    if (metadata_element != null && metadata_element.length != 0)
        for (int j = 0; j < metadata_element.length; j++) {
            String label = metadata_element[j].getLabel();
            label = label.toLowerCase();
            LOGGER.debug(" ==label: " + label + " index_pos: " + j);
            indexIntermediateId++;
            headerIntermediateIndex.put(label, indexIntermediateId);
        }

    printMap(headerIntermediateIndex);
    LOGGER.debug("COMPLETED method createIntermediateIndex.");
}

读取整个数据集,1.280.000行需要800毫秒!所以问题出在这个方法上

    private static void createIntermediateStringBuffer(StringBuilder sbIntermediate, String[] splitLine) throws ClassCastException,
        NullPointerException {

    LOGGER.debug("START method createIntermediateStringBuffer.");
    long start, end;
    start = System.currentTimeMillis();
    ArrayList<String> hashes = new ArrayList<String>();
    com.tekcomms.c2d.xml.model.v2.Metadata_element[] metadata_element = null;

    String[] servicePath = newTopology.getElement().getEntity().getService_path().getLevel();
    LOGGER.debug(servicePath.toString());

    if (newTopology.getElement().getMetadata() != null) {
        metadata_element = newTopology.getElement().getMetadata().getMetadata_element();
        LOGGER.debug(metadata_element.toString());
    }

    for (int i = 0; i < servicePath.length; i++) {
        String level = servicePath[i];
        LOGGER.debug("level is: " + level);
        if (splitLine.length > getPositionFromIndex(level)) {
            String name = splitLine[getPositionFromIndex(level)];
            sbIntermediate.append(name);
            hashes.add(name);
            sbIntermediate.append(REGEX_COMMA).append(HashUtils.calculateHash(hashes)).append(REGEX_COMMA);
            LOGGER.debug(" ==sbIntermediate: " + sbIntermediate.toString());
        }
    }

    //      end=System.currentTimeMillis();
    //      LOGGER.info("COMPLETED adding name hash. " + (end - start) + " ms. " + (end - start) / 1000 + " seg.");
    // adding cpe.latitude,cpe.longitude,cpe.customer_class, it should be
    // better if it would be metadata as well.
    String labelLatitude = newTopology.getElement().getEntity().getLatitude();
    if (splitLine.length > getPositionFromIndex(labelLatitude)) {
        String lat = splitLine[getPositionFromIndex(labelLatitude)];
        sbIntermediate.append(lat).append(REGEX_COMMA);
    }

    String labelLongitude = newTopology.getElement().getEntity().getLongitude();
    if (splitLine.length > getPositionFromIndex(labelLongitude)) {
        String lon = splitLine[getPositionFromIndex(labelLongitude)];
        sbIntermediate.append(lon).append(REGEX_COMMA);
    }
    String labelCustomerClass = newTopology.getElement().getCustomer_class();
    if (splitLine.length > getPositionFromIndex(labelCustomerClass)) {
        String customerClass = splitLine[getPositionFromIndex(labelCustomerClass)];
        sbIntermediate.append(customerClass).append(REGEX_COMMA);
    }
    //      end=System.currentTimeMillis();
    //      LOGGER.info("COMPLETED adding lat,lon,customer. " + (end - start) + " ms. " + (end - start) / 1000 + " seg.");
    // watch out metadata are optional, it can appear as a void chain!
    if (metadata_element != null && metadata_element.length != 0)
        for (int j = 0; j < metadata_element.length; j++) {
            String label = metadata_element[j].getLabel();
            LOGGER.debug(" ==label: " + label + " index_pos: " + j);
            if (splitLine.length > getPositionFromIndex(label)) {
                String actualValue = splitLine[getPositionFromIndex(label)];
                if (!"".equals(actualValue))
                    sbIntermediate.append(actualValue).append(REGEX_COMMA);
                else
                    sbIntermediate.append("").append(REGEX_COMMA);
            } else
                sbIntermediate.append("").append(REGEX_COMMA);
            LOGGER.debug(" ==sbIntermediate: " + sbIntermediate.toString());
        }//for
    sbIntermediate.append("\n");
    end = System.currentTimeMillis();
    LOGGER.info("COMPLETED method createIntermediateStringBuffer. " + (end - start) + " ms. ");
}

正如您所看到的,这个方法将一个预先计算的行添加到StringBuffer中,从输入csv文件中读取每一行,从这些行中计算新的数据,最后将生成的行添加至StringBuff中,这样我就可以使用该缓冲区创建文件了。

我运行了jconsole,可以看到没有内存泄漏,我可以看到代表对象创建的锯齿和gc回忆garbaje。它从不转移内存堆阈值。

我注意到的一件事是,向StringBuffer添加新行所需的时间在很少的毫秒范围内完成,(5,6,10),但随着时间的推移,增加到(100-200)毫秒,我怀疑在不久的将来会有更多,所以这可能是一匹战马。

我试图分析代码,我知道有3个for循环,但它们非常短,第一个循环只迭代8个元素

for (int i = 0; i < servicePath.length; i++) {
        String level = servicePath[i];
        LOGGER.debug("level is: " + level);
        if (splitLine.length > getPositionFromIndex(level)) {
            String name = splitLine[getPositionFromIndex(level)];
            sbIntermediate.append(name);
            hashes.add(name);
            sbIntermediate.append(REGEX_COMMA).append(HashUtils.calculateHash(hashes)).append(REGEX_COMMA);
            LOGGER.debug(" ==sbIntermediate: " + sbIntermediate.toString());
        }
    }

我已经保证了从分割线获取名称所需的时间,它是毫无价值的,0 毫秒,与计算哈希方法相同,0 毫秒。

另一个循环,实际上是相同的,在0到n上迭代,其中n是一个非常小的整数,例如3到10,所以我不明白为什么要花更多的时间来完成这个方法,我唯一发现的是向缓冲区添加一个新行会减慢这个过程

我在考虑一个生产者消费者多线程策略,一个读取器线程读取每一行并将其放入循环缓冲区,另一个线程一个接一个地获取,处理它们并向StringBuffer添加一个预先计算好的行,这是线程安全的,当文件被完全读取时,读取器线程向另一个线程发送消息,告诉它们停止。最后,我必须将这个缓冲区保存到一个文件中。你怎么想呢?这是个好主意吗?

共有2个答案

解阳荣
2023-03-14

我在项目中使用了superCSV库来处理大量行。它比手动读取行相对较快。参考

简宏义
2023-03-14

我正在考虑一个生产者消费者多线程策略,一个读取器线程读取每一行并将它们放入循环缓冲区,另一个线程一个接一个地获取它,处理它们并将预先计算的行添加到StringBuffer,这是线程安全的,当文件被完全读取时,读取器线程向另一个线程发送消息,告诉它们停止。最后我必须将这个缓冲区保存到文件中。你觉得呢?这是个好主意吗?

也许吧,但是工作量很大,我会先尝试简单一点的。

line.split(REGEX_COMMA)

您的REGEX_COMMA是一个字符串,它被编译为正则表达式一百万次。这很简单,但我会尝试使用Pattern

你分裂产生了很多垃圾。也许您应该通过手动将输入拆分为重用的 ArrayList 来避免它

如果您所需要的只是将结果写入文件,最好避免构建一个巨大的字符串。也许是一个列表

您似乎只使用ASCII。您的编码依赖于平台,这可能意味着您使用的是UTF-8,这可能很慢。切换到更简单的编码可能会有所帮助。

使用byte[]而不是String很可能会有所帮助。字节是字符的一半大,读取文件时不需要转换。您所做的所有操作都可以用字节同样容易地完成。

我注意到的一件事是,向StringBuffer添加新行所需的时间在很少的毫秒范围内完成,(5,6,10),但随着时间的推移,增加到(100-200)毫秒,我怀疑在不久的将来会有更多,所以这可能是一匹战马。

这就是调整大小,可以通过使用建议的< code>ArrayList来加速

我已经保证了从分割线获取名称所需的时间,它是毫无价值的,0 毫秒,与计算哈希方法相同,0 毫秒。

切勿为此使用< code>currentTimeMillis,因为< code>nanoTime更好。使用侧写器。分析器的问题在于它改变了它应该测量的内容。作为一个穷人的剖析器,您可以计算在可疑方法中花费的所有时间的总和,并将其与总时间进行比较。

CPU负载是什么,GC在运行程序时做什么?

 类似资料:
  • 问题内容: 我想创建一个csv文件,但是当我运行代码时,它返回一个空白页,没有csv文件。我使用PHP5。我使用以下代码: 谢谢! 问题答案: 其空白,因为您正在写信。您应该写到using 代替,还应该发送标头信息以表明它是csv。 例

  • 问题内容: 我正在使用Java为某些文件生成MD5哈希。我需要为多个文件生成一个MD5,总大小约为1 GB。这是我的代码: } 这似乎永远存在。如何提高效率? 问题答案: 您可能要使用Fast MD5 库。它比Java内置的MD5提供程序快得多,并且获取哈希的过程非常简单: 请注意,速度较慢也可能是由于文件I / O速度较慢所致。

  • 问题内容: 我正在寻找一个允许我将.csv内容映射到对象的库。 就像是: 然后说像这样: 从给定的CSV: 有谁知道这样的API,或者做类似的事情。我不希望它必须使用注释,我只是希望能够用一行代码和一个预定义的类来加载文件。 问题答案: JSefa允许您注释可在序列化和反序列化过程中使用的Java类。本教程演示了如何与CsvIOFactory类一起使用。 (来自教程)对bean进行注释就像在值列表

  • 我使用Dask读取2.5GB csv文件,Python给了我错误。这是我写的代码: 以下是我从Python得到的错误: dask_df=df1.read_csv('3SPACK_N150_7Ah_压力分布研究_Data_Matrix.csv')) 文件“C:\ProgramData\Anaconda3\lib\site packages\dask\dataframe\io\csv.py”,第645

  • 我正在使用Java开发一个web应用程序,在这里我有一个方法可以读取。使用apache poi的xlsx文件: 该方法工作正常,但是该方法处理具有数千行记录的文件的可能性有多大,例如,大约2530万行。当处理一个大文件时,我采取以下异常: 我需要知道如何避免这种错误。例如,如果有,请读取并处理该文件。xlsx 1000至1000线,或其他解决方案。