【问题标题】:Slow BufferedWriter PerformanceBufferedWriter 性能缓慢
【发布时间】:2015-10-20 13:57:03
【问题描述】:

我有一个方法可以将 44 MB 的数据从 ResultSet 写入 CSV 文件。但是,大约需要 3.5 分钟才能完成。对于只有 44 MB 的数据,这似乎很慢。任何人都可以看到任何减慢我的代码的东西吗?:

public static void convertToCSV(final ResultSet rs) throws SQLException, IOException {
    final BufferedWriter fw = new BufferedWriter(new FileWriter(new File("alert.csv")));
    while (rs.next()) {
        fw.write(rs.getString("FIELD1")+",");
        fw.write(rs.getString("FIELD2")+",");
        fw.write(rs.getString("FIELD3")+",");
        final String clobValue = rs.getString("FIELD4");
        if(clobValue==null)
            fw.write("null,");
        else{
            fw.write("\""+clobValue+"\",");
        }
        final Date date = new Date(rs.getLong("FIELD5"));
        final DateFormat format = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
        format.setTimeZone(TimeZone.getTimeZone("Etc/UTC"));
        final String dateTime[] = format.format(date).split(" ");
        fw.write(dateTime[0]+",");
        fw.write(dateTime[1]);

        fw.write("\n");
    }
    fw.close();
}

【问题讨论】:

  • 增加获取 ResultSet 的 Statement 的获取大小可能在一定程度上有所帮助 - 默认情况下,它会返回数据库以每 10 行获取更多数据。权衡是,如果增加 fetch 大小,也会增加内存占用。
  • 真的很慢吗,涉及到很多 JDBC 代码,你正在做很多 String concats 生成很多需要 gc'd 的字符串。尽管日期格式不是线程安全的,但我只会在此方法中构造一次并重用。并且可能使用StringBuilder 创建一个字符串并一次性写入。
  • 不写数据的情况下,你测过时间,把SimpleDateFormat移出循环吗?
  • 您还可以测量写入虚拟数据所需的时间,而无需从数据库中获取数据。

标签: java file csv io bufferedwriter


【解决方案1】:

这样的事情可能会更快

public static void convertToCSV(final ResultSet rs) throws SQLException, IOException {
    final BufferedWriter fw = new BufferedWriter(new FileWriter(new File("alert.csv")));
    final DateFormat format = new SimpleDateFormat("'yyyyMMdd','HH:mm:ss'");
    format.setTimeZone(TimeZone.getTimeZone("Etc/UTC"));

    while (rs.next()) {
      StringBuilder sb = new StringBuilder();
      sb.append(rs.getString("FIELD1")).append(',')
        .append(rs.getString("FIELD2")).append(',')
        .append(rs.getString("FIELD3")).append(',');

        final String clobValue = rs.getString("FIELD4");
        if(clobValue==null)
          sb.append("null,");            
        else{
          sb.append('\"').append(clobValue).append('\"').append(',');
        }
        Date date = new Date(rs.getLong("FIELD5"));
        sb.append(format.format(date)).append('\n');
        fw.write(sb.toString());
    }
    fw.close();
}
  1. DateFormat 的创建只在循环外完成一次。
  2. 使用StringBuilder
  3. 尽可能避免创建临时对象。

您甚至可以重用StringBuilder,方法是将创建移出循环并在编写后执行setLength(0)

【讨论】:

    【解决方案2】:

    这可能取决于 JDBC 驱动程序、您的获取策略、磁盘...

    但你当然可以避免创建这么多临时对象:

    1. DateFormat 移出循环。
    2. 避免创建临时字符串rs.getString("FIELD1")+",",而是对写入器执行两次写入调用。
    3. 避免拆分操作。

    【讨论】:

      【解决方案3】:

      下面的应该会快一点。

      fw.write(rs.getString("FIELD1"));
      fw.write(',');
      

      还在循环之前声明并准备 SimpleDateFormat。

      final DateFormat format = new SimpleDateFormat("yyyyMMdd','HH:mm:ss");
      

      (此处不需要单引号。) 没有split,这很昂贵。

      【讨论】:

      • 您也可以将setTimeZone(...) 语句移到循环之外
      【解决方案4】:

      您可以在产生您的ResultSetStatement 上增加提取大小 - 这将减少它需要返回数据库以提取下一批行的次数(设置为 10默认)。这样做的缺点是会增加ResultSet 的内存占用,因为它将在内存中保存更多数据。

      您的 SimpleDatFormat 对象在循环的每次迭代中都使用相同的数据创建 - 如果您将其移到循环之外,您只需将其实例化一次。您也可以将setTimeZone(...) 语句移到循环之外。

      String 连接使用变量值在内部创建一个新的StringBuilder 对象,每次你这样做 - 你可以通过确保你永远不会在循环中进行连接来获得更多收益。您可以通过手动创建自己的StringBuilder 来执行此操作,将整行的数据放入其中,然后将该行写入BufferedWriter 一次(可能会消耗更多内存),或者您可以放入更多write(...) 调用以避免进行串联。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-10-14
        • 2015-01-27
        • 2021-06-04
        • 2016-11-09
        • 2021-06-21
        • 2012-05-11
        • 2023-04-07
        • 1970-01-01
        相关资源
        最近更新 更多