将大型 ResultSet 写入文件
Posted
技术标签:
【中文标题】将大型 ResultSet 写入文件【英文标题】:Writing a large ResultSet to a File 【发布时间】:2011-08-25 18:01:26 【问题描述】:我正在尝试将一个大的 ResulSet(~1mm 行)写入单个文件。在 Java 1.6 中是否有首选/有效的方法来执行此操作?
【问题讨论】:
~1mm 有多大?只是好奇 @Bozho:该副本的答案没有考虑任何 JDBC 细节。 【参考方案1】:这取决于使用的 JDBC 驱动程序。您需要指示 JDBC 驱动程序不预先将整个 ResultSet
加载到 Java 的内存中,而是在每次 next()
调用时按行加载它。然后,在ResultSet#next()
循环中,您需要立即将数据写入文件,而不是将其保存在List
或其他东西中。
不清楚您使用的是什么 JDBC 驱动程序,但例如,可以指示 mysql JDBC 驱动程序按照MySQL JDBC driver documentation 的以下方式按每行提供结果集:
结果集
默认情况下,ResultSet 被完全检索并存储在内存中。在大多数情况下这是最有效的操作方式,并且由于 MySQL 网络协议的设计更容易实现。如果您正在使用具有大量行或大值的 ResultSet,并且无法在 JVM 中为所需的内存分配堆空间,您可以告诉驱动程序一次将结果流回一行。
要启用此功能,您需要通过以下方式创建一个 Statement 实例:
stmt = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); stmt.setFetchSize(Integer.MIN_VALUE);
这是一个具体的启动示例:
try (
PrintWriter writer = new PrintWriter(new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream("/records.txt")), "UTF-8"));
Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
)
statement.setFetchSize(Integer.MIN_VALUE);
try (ResultSet resultSet = statement.executeQuery("SELECT col1, col2, col3 FROM sometable"))
while (resultSet.next())
writer.append(resultSet.getString("col1")).append(",")
.append(resultSet.getString("col2")).append(",")
.append(resultSet.getString("col3")).println();
顺便说一句,我会先检查数据库是否没有内置的 SQL 支持,这样可以更有效地完成此操作。例如,MySQL 对此有一个SELECT INTO OUTFILE
construct。
SELECT
的SELECT ... INTO OUTFILE 'file_name'
形式将选定的行写入文件。该文件是在服务器主机上创建的,因此您必须具有FILE
权限才能使用此语法。 file_name 不能是现有文件,这样可以防止/etc/passwd
和数据库表等文件被破坏。从 MySQL 5.1.6 开始,character_set_filesystem
系统变量控制文件名的解释。
【讨论】:
@Nathan:这取决于 JDBC 驱动程序。 API 文档字面意思是 “给 JDBC 驱动程序一个提示...” 注意,一个提示。我在回答中引用的 MySQL 文档中提到了正确的值。 是的,您的答案肯定就在这里。我只是想知道为什么 setFetchSize(1) 不是一个足够好的提示。也许我可以按照这些思路提出另一个问题。 @Nathan:同样,这取决于 JDBC 驱动程序。 JDBC API 中没有指定有效/允许值的内容。您需要查阅 JDBC 驱动程序实现特定文档。 @balusc 感谢您的意见。我正在使用 Oracle 11g 驱动程序。我已经尝试过性能改进有限的 setFetchSize()。我认为这个问题是两方面的——一个是加载大的 ResultSet,另一个是写入文件。在我的桌面(3GHz Core 2 Duo、4 GB RAM@800MHz 总线和 7200RPM HDD)上,使用不带缓冲的 FileOutputStream(w/或 w/o setFetchSize()),这需要大约 40 分钟来写入。我想知道您是否有任何使用 NIO 包的示例,由于通过 JVM 进行 DMA 访问,这可能有助于更快地写入磁盘。 相关:webmoli.com/2009/02/01/… 您可能希望将 Oracle 默认提取大小增加到例如100(如果内存允许的话)。你真的应该用BufferedOutputStream
缓冲FileOutputStream
,如我的代码示例所示。这会有很大的不同。如果 Oracle 不支持 SELECT INTO OUTFILE
之类的东西,我已经环顾四周,但似乎你必须为此编写一个 proc。我不是Oracle DB,所以我不能详细说明,对不起。【参考方案2】:
来自 GitHub:https://github.com/OhadR/ohadr.common/blob/master/src/main/java/com/ohadr/common/utils/resultset/ResultSetConverters.java
public static void writeResultSetToWriter(ResultSet resultSet, PrintWriter writer) throws SQLException
ResultSetMetaData metadata = resultSet.getMetaData();
int numColumns = metadata.getColumnCount();
int numRows = 0;
while(resultSet.next()) //iterate rows
++numRows;
JSONObject obj = new JSONObject(); //extends HashMap
for (int i = 1; i <= numColumns; ++i) //iterate columns
String column_name = metadata.getColumnName(i);
obj.put(column_name, resultSet.getObject(column_name));
writer.println(obj.toJSONString());
if(numRows % 1000 == 0)
writer.flush();
【讨论】:
以上是关于将大型 ResultSet 写入文件的主要内容,如果未能解决你的问题,请参考以下文章