首页 > 解决方案 > 对 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

标签: javajvmblob

解决方案


你得到InputStream一个 BLOB 来避免将整个 BLOB 数据放在内存中。但是,你做的完全相反

  • 您使用 aByteArrayOutputStream将整个数据传输到byte[]数组中。请注意,数据甚至在内存中存在两次,一次在ByteArrayOutputStream自己的缓冲区中,然后在由创建并返回的副本中baos.toByteArray()
  • 然后,您将整个数组转换为一个潜在的巨大Stringvia 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。


推荐阅读