我读取文件并从中创建一个Object并存储到postgresql数据库。我的文件有100,000个文件,我从一个文件中读取并拆分并最终存储到数据库。
我无法创建List<>
并将所有文档存储在List<>
中,因为我的内存很少。我读写数据库的代码如下。但是我的JVM堆填充并且无法继续存储更多文档。如何有效地读取文件和存储到数据库。
public void readFile() {
StringBuilder wholeDocument = new StringBuilder();
try {
bufferedReader = new BufferedReader(new FileReader(files));
String line;
int count = 0;
while ((line = bufferedReader.readLine()) != null) {
if (line.contains("<page>")) {
wholeDocument.append(line);
while ((line = bufferedReader.readLine()) != null) {
wholeDocument = wholeDocument.append("\n" + line);
if (line.contains("</page>")) {
System.out.println(count++);
addBodyToDatabase(wholeDocument.toString());
wholeDocument.setLength(0);
break;
}
}
}
}
wikiParser.commit();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void addBodyToDatabase(String wholeContent) {
Page page = new Page(new Timestamp(System.currentTimeMillis()),
wholeContent);
database.addPageToDatabase(page);
}
public static int counter = 1;
public void addPageToDatabase(Page page) {
session.save(page);
if (counter % 3000 == 0) {
commit();
}
counter++;
}
答案 0 :(得分:10)
首先,您应该在此处应用fork-join方法。
主要任务解析文件并将最多100个项目的批次发送到ExecutorService。 ExecutorService
应该有许多工作线程,它们等于可用数据库连接的数量。如果你有4个CPU内核,那么假设数据库可以采用8个并发连接,而无需进行太多的上下文切换。
然后,您应配置connection pooling DataSource
并使minSize等于maxSize且等于8.尝试使用HikariCP或ViburDBCP进行连接池。
然后您需要配置JDBC batching。如果你使用MySQL,IDENTITY生成器将禁用沐浴。如果您使用的是支持序列的数据库,请确保您还使用增强的标识符生成器(它们是Hibernate 5.x中的默认选项)。
这样,实体插入过程被并行化并解耦为主解析线程。主线程应该等待ExecutorService
在关闭之前完成所有任务的处理。
答案 1 :(得分:2)
实际上,如果不进行真正的分析并找出使代码变慢或效率低的原因,很难向您建议。
但是,我们可以从您的代码中看到一些内容
您使用StringBuilder效率低下
wholeDocument.append("\n" + line);
应该写成wholeDocument.append("\n").append(line);
而不是
因为您原来写的内容将由编译器翻译成
whileDocument.append(new StringBuilder("\n").append(line).toString())
。您可以看到您创建了多少不必要的StringBuilder
:)
使用Hibernate时的注意事项
我不确定您是如何管理session
或如何实施commit()
的,我认为您做得对,还有更多事情需要考虑:
您是否在Hibernate中正确设置了批量大小? (hibernate.jdbc.batch_size
)默认情况下,JDBC批处理大小约为5.您可能希望确保将其设置为更大的大小(以便内部Hibernate将以更大的批量发送插入)。
鉴于您不需要第一级缓存中的实体供以后使用,您可能希望间歇性会话flush()
+ clear()
到
远离Hibernate以获取此功能。
Hibernate很酷但它并不是万能的灵丹妙药。鉴于此功能,您只需根据文本文件内容将记录保存到DB中。你既不需要任何实体行为,也不需要使用第一级缓存进行后续处理,在这里没有太多理由使用Hibernate给出额外的处理和空间开销。简单地使用手动批量处理来完成JDBC将为您节省很多麻烦。
答案 2 :(得分:1)
我使用@RookieGuy回答。 stackoverflow.com/questions/14581865/hibernate-commit-and-flush
我用
session.flush();
session.clear();
最后阅读完所有文件并将其存储到数据库中
tx.commit();
session.close();
并更改
wholeDocument = wholeDocument.append("\n" + line);
到
wholeDocument.append("\n" + line);
答案 3 :(得分:0)
我不太确定你的数据文件的结构。如果你能提供你的文件样本,那将很容易理解。
内存消耗的根本原因是读取/迭代文件的方式。一旦读完了某些内容,就会留在内存中。您应该使用java.io.FileInputStream
或org.apache.commons.io.FileUtils
。
以下是使用java.io.FileInputStream
try (
FileInputStream inputStream = new FileInputStream("/tmp/sample.txt");
Scanner sc = new Scanner(inputStream, "UTF-8")
) {
while (sc.hasNextLine()) {
String line = sc.nextLine();
addBodyToDatabase(line);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
以下是使用org.apache.commons.io.FileUtils
File file = new File("/tmp/sample.txt");
LineIterator it = FileUtils.lineIterator(file, "UTF-8");
try {
while (it.hasNext()) {
String line = it.nextLine();
addBodyToDatabase(line);
}
} finally {
LineIterator.closeQuietly(it);
}
答案 4 :(得分:0)
您应该开始一个事务,执行保存操作并提交事务。 (保存后不要开始交易!)。您可以尝试使用StatelessSession来排除缓存的内存消耗。
在此代码
中使用更少的值,例如20if (counter % 20 == 0)
您可以尝试尽可能地将StringBuilder
作为方法的参数传递。