java - 对 BLOB 进行操作需要花费太多时间
问题描述
我必须阅读BLOB
仅包含文本的列。之前它工作得非常有效(在 3 分钟内读取 100kblob
秒),但是在不同的环境中虽然使用相同的硬件,但它需要花费大量的时间。
这是我的代码:-
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 数据放在内存中。但是,你做的完全相反
- 您使用 a
ByteArrayOutputStream
将整个数据传输到byte[]
数组中。请注意,数据甚至在内存中存在两次,一次在ByteArrayOutputStream
自己的缓冲区中,然后在由创建并返回的副本中baos.toByteArray()
- 然后,您将整个数组转换为一个潜在的巨大
String
vianew 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。
推荐阅读
- android-jetpack - 在 Jetpack Compose 中按下后退按钮时滚动位置丢失
- ios - 如何在 SwiftUI 中为具有属性的 VStack 创建扩展?
- react-native - 视图不会环绕文本
- server - change-master-password no funciona en payara 5
- vue.js - 如何在 Vue.js 中突出显示组件和取消突出显示其他组件
- android - 单击子元素时,布局上的监听器上的android ontouch不会触发
- c# - 从 xamarin 发布到 restful API
- node.js - 可以将数字签名和签名者的公钥存储在 json 文件中吗?
- python - Kivy Scrollview 自动滚动到新文本和行消失
- remote-access - RDWEB 链接在外部打开,但远程应用程序未打开