我必须读取仅包含文本的BLOB
列。之前它的工作效率非常高(在 3 分钟内读取 100k blob),但在不同的环境中,尽管使用相同的硬件,却花费了大量的时间。
这是我的代码:-
while (rs.next()) {
is = rs.getBinaryStream(3);
while ((len = is.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
is.close();
blobByte = baos.toByteArray();
baos.close();
String blob = new String(blobByte);
String msisdn = rs.getString(2);
blobData = blob.split("\\|");
//some operations
}
我每隔 5 秒获取一次 jstack,发现应用程序总是在这一行:-
blobData = blob.split("\\|");
有时是:-
new String(blobByte);
我的java选项:-
-ms10g -mx12g -XX:NewSize=1g -XX:MaxNewSize=1g
我的代码的某些部分是否未优化?或者有没有一种非常有效的方法来读取BLOB
?
最佳答案
您将获得 InputStream
BLOB 能够避免将整个 BLOB 数据存储在内存中。但随后,你却做了完全相反的事情
- 您使用
ByteArrayOutputStream
将整个数据传输到byte[]
大批。请注意,数据甚至在内存中存在两次,一次位于ByteArrayOutputStream
内。自己的缓冲区,然后在baos.toByteArray()
创建并返回的副本中 - 然后,将整个数组转换为可能巨大的
String
通过new String(blobByte)
,承担整个数据的第3次复制(包括字符集转换)。 -
split("\\|")
将运行整个String
,为分隔符之间的每个序列创建子字符串,这意味着将整个数据再次复制到子字符串中(减去分隔符字符),届时,内存中将拥有整个数据的四个副本,具体取决于源的缓冲,它可能是五次。此外,还会创建并填充一个包含对所有这些子字符串的引用的数组
并非所有复制操作都可以避免。但我们可以避免将整个数据存储在内存中:
try(Scanner s = new Scanner(is).useDelimiter("\\|")) {
while(s.hasNext()) {
String next = s.next();
System.out.println(next);// replace with actual processing
}
}
当您能够单独处理项目而不保留对前一个项目的引用时,这些字符串可能会被垃圾收集,在最好的情况下会进行少量收集。
即使 String[]
处理时需要包含所有元素的数组,这使得整个数据的一份副本(以单个字符串的形式)不可避免,您可以避免所有其他副本:
try(Scanner s = new Scanner(is).useDelimiter("\\|")) {
List<String> list = new ArrayList<>();
while(s.hasNext()) list.add(s.next());
System.out.println(list);// replace with actual processing as List
String[] array = list.toArray(new String[0]); // when an array really is required
}
从 Java 9 开始,您可以使用
try(Scanner s = new Scanner(is).useDelimiter("\\|")) {
List<String> list = s.tokens().collect(Collectors.toList());
System.out.println(list); // replace with actual processing as List
}
或
try(Scanner s = new Scanner(is).useDelimiter("\\|")) {
String[] array = s.tokens().toArray(String[]::new);
System.out.println(Arrays.toString(array)); // replace with actual processing
}
但是单独处理元素,而不将所有元素保存在内存中,是首选方法。
<小时/>另一种可能的优化是避免多个(内部)Pattern.compile("\\|")
通过自己执行一次并传递准备好的 Pattern
来调用而不是 "\\|"
字符串到 useDelimiter
方法。
请注意,所有这些示例都使用系统的默认字符集编码,就像您的原始代码一样。由于运行代码的环境的默认字符集不一定与数据库相同,因此您应该明确,即使用 new Scanner(is, charset)
,就像您应该使用 new String(blobByte, charset)
一样在你的原始代码中,而不是 new String(blobByte)
.
或者您首先使用 CLOB。
关于java - 对 BLOB 进行操作需要太多时间,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57408541/