在Java中,将CLOB转换为字符串和字符串转换为CLOB的最有效解决方案是什么?

58
我有一个大的CLOB(超过32KB),我想使用StringBuilder将其读取到字符串中。最有效的方法是什么?由于我的CLOB长度超过“int”,需要“long”值,因此无法使用StringBuilder的“int length”构造函数。
我对Java I/O类不太熟悉,希望能得到一些指导。 编辑 - 我已经尝试了这个clobToString()代码:
private String clobToString(Clob data) {
    StringBuilder sb = new StringBuilder();
    try {
        Reader reader = data.getCharacterStream();
        BufferedReader br = new BufferedReader(reader);

        String line;
        while(null != (line = br.readLine())) {
            sb.append(line);
        }
        br.close();
    } catch (SQLException e) {
        // handle this exception
    } catch (IOException e) {
        // handle this exception
    }
    return sb.toString();
}

你是指数据库中的 CLOB,还是仅仅指“大字符串”? - skaffman
是的,它是来自DB2数据库的CLOB。 - Jonas
2
我想知道Java NIO中是否有任何有用的类可以实现这个功能。 - Jonas
不,还没有,但我有一种感觉,知道可能会遇到什么问题,所以我会进行更多的测试。在这里得到了很好的建议,谢谢。 - Jonas
超过32KB - 你是指32位吗? - Stefan Reich
显示剩余4条评论
12个回答

54

好的,我会假设这是一个普遍的用法。首先,您需要下载apache commons,在那里您将找到一个名为IOUtils的实用程序类,其中有一个名为copy()的方法;

现在的解决方案是:使用getAsciiStream()获取CLOB对象的输入流,并将其传递给copy()方法。

InputStream in = clobObject.getAsciiStream();
StringWriter w = new StringWriter();
IOUtils.copy(in, w);
String clobAsString = w.toString();

1
谢谢,看起来很不错。但我想要让这个问题再开放一点,因为我更喜欢只使用标准库的解决方案。 - Jonas
我已经加载了Apache Commons库,所以这是完美的解决方案。谢谢! - John Strickler
16
如果您使用Unicode(或任何ASCII字符范围之外的字符),使用getAsciiStream可能会让您头疼。 - TJ Ellis
24
为了避免编码问题,我将InputStream更改为Reader,并将clobObject.getAsciiStream()更改为clobObject.getCharacterStream() - Dormouse
2
IOUtils.copy(in, w)已过时,请改用IOUtils.copy(in, w, StandardCharsets.UTF_8) - Shephard

37

有什么问题:

clob.getSubString(1, (int) clob.length());

例如,Oracle的oracle.sql.CLOB在内部对定义在oracle.jdbc.driver.T4CConnection中的char[]执行getSubString(),仅使用System.arraycopy()并包装到String中...您永远不会得到比System.arraycopy()更快的读取速度。

更新:获取驱动程序ojdbc6.jar,反编译CLOB实现,并基于内部知识研究哪种情况可能更快。


在字符串中留下了很多换行符。 - Gervase
2
@Gervase 在 XML 中,换行符可能是重要的。无论如何,在将其存储到数据库之前,您应该修剪掉无用的空格和换行符。 - Florian F
一些需要澄清的点:如果 clob.length() 大于 Integer.MAX_VALUE 会发生什么?包含 oracle.sql.CLOB 的 jar 是什么? - Stephan
2
@Stephan 我研究了 ojdbc6.jarInteger.MAX_VALUEJDK Platform 2 数组长度的限制,而 String 则是以数组形式存储字符。因此,如果要处理大于 2 GiB 的 CLOBs,你需要尝试流式处理方式,因为使用纯 Java 内存模型无法存储这些数据(除非你使用一些本地扩展和具有足够系统内存的 64 位平台)。 - gavenkoa
什么问题?https://dev59.com/s3HYa4cB1Zd3GeqPL3cB... 当SQL关闭到CLOB数据的连接时,我在生产中遇到了同样的问题。 - Marek Bernád
1
@MarekBernád 好的。我相信你遇到了问题,因为你跨越了事务/连接边界。这是繁琐框架的问题,它隐藏了资源管理。如果你在托管EE环境中访问@Transactional内部的getter,如果你关心效率,Hibernate并不是一个好的框架 - gavenkoa

23

我的答案只是同样方法的一种变通而已。但是我用序列化压缩内容来测试过,它可以正常工作。因此,我可以相信这个解决方案,而不像先前提供的那个解决方案(使用readLine)会忽略换行符并损坏输入。

/*********************************************************************************************
 * From CLOB to String
 * @return string representation of clob
 *********************************************************************************************/
private String clobToString(java.sql.Clob data)
{
    final StringBuilder sb = new StringBuilder();

    try
    {
        final Reader         reader = data.getCharacterStream();
        final BufferedReader br     = new BufferedReader(reader);

        int b;
        while(-1 != (b = br.read()))
        {
            sb.append((char)b);
        }

        br.close();
    }
    catch (SQLException e)
    {
        log.error("SQL. Could not convert CLOB to string",e);
        return e.toString();
    }
    catch (IOException e)
    {
        log.error("IO. Could not convert CLOB to string",e);
        return e.toString();
    }

    return sb.toString();
}

干得好,谢谢。 - Bruno L.

21

我的 CLOB 长度超过了 int 的限制,需要使用 long 值,因此无法使用 StringBuilder 的 "int length" 构造函数。

如果 CLOB 长度大于 int 可以容纳的范围,那么该 CLOB 数据也无法适应 String 类型。您将需要使用流式处理来处理大量的 XML 数据。

如果 CLOB 的实际长度小于 Integer.MAX_VALUE,则只需在前面加上 (int),将 long 强制转换为 int


9
确实,如果 CLOB 大小大于 2^32 字节,那么你会遇到很大的问题。 - skaffman
如果他需要整个 CLOB 进行处理,我建议将其写入文件。 - Khaled.K

4
如果使用Mule,请按照以下步骤操作:
1. 在连接器中启用流式传输,即progressiveStreaming=2。
2. 将DB2返回的CLOB类型转换为java.sql.Clob类型(IBM支持此类型转换)。
3. 将其转换为字符流(ASCII流有时可能不支持某些特殊字符)。因此,您可能需要使用getCharacterStream()。
4. 这将返回一个“reader”对象,可以使用common-io(IOUtils)将其转换为“String”。
5. 简而言之,使用groovy组件并添加以下代码。
clobTest = (java.sql.Clob)payload.field1 
bodyText = clobTest.getCharacterStream() 
targetString = org.apache.commons.io.IOUtils.toString(bodyText)
payload.PAYLOADHEADERS=targetString return payload

注意:我在这里假设"payload.field1"存储了clob数据。

就这样!

祝好,Naveen


4
如果您确实必须仅使用标准库,那么您只需要稍微扩展Omar的解决方案。(Apache的IOUtils基本上只是一组方便的方法,可以节省大量编码时间)
您已经能够通过获取输入流。
您只需“手动传输”字符到StringWriter即可:
InputStream in = clobObject.getAsciiStream();
Reader read = new InputStreamReader(in);
StringWriter write = new StringWriter();

int c = -1;
while ((c = read.read()) != -1)
{
    write.write(c);
}
write.flush();
String s = write.toString();

请记住

  1. 如果您的clob包含比字符串容量更多的字符,此方法将无效。
  2. 为了获得更好的性能,请使用BufferedReader和BufferedWriter分别对InputStreamReader和StringWriter进行包装。

这看起来与我在问题中提供的代码相似,它们之间有任何关键差异我没有注意到吗?例如在性能方面? - Jonas
哎呀,我错过了你的代码片段!它有点类似,但请记住,仅仅使用 BufferedReader.readLine() 会忽略掉换行符。 - Edwin Lee
1
第二行需要进行小修正,应该是:Reader read = new InputStreamReader(in); - Vivek
2
不, 不, 不. getAsciiStream() 强制使用 ASCII 编码并破坏所有非 ASCII 字符。你正在从字符源获取一个输入流 (字节),然后立即在 InputStreamReader 上使用随机的 (平台默认) 编码将它们转换回字符。这是一个多余的操作,除了它破坏非 ASCII 数据之外。直接从 getCharacterStream() Reader 中读取并写入 StringWriter - Christoffer Hammarström

2
使用 Apache Commons.io 的友好助手方法。
Reader reader = clob.getCharacterStream();
StringWriter writer = new StringWriter();
IOUtils.copy(reader, writer);
String clobContent = writer.toString();

1
public static final String tryClob2String(final Object value)
{
    final Clob clobValue = (Clob) value;
    String result = null;

    try
    {
        final long clobLength = clobValue.length();

        if (clobLength < Integer.MIN_VALUE || clobLength > Integer.MAX_VALUE)
        {
            log.debug("CLOB size too big for String!");
        }
        else
        {
            result = clobValue.getSubString(1, (int) clobValue.length());
        }
    }
    catch (SQLException e)
    {
        log.error("tryClob2String ERROR: {}", e);
    }
    finally
    {
        if (clobValue != null)
        {
            try
            {
                clobValue.free();
            }
            catch (SQLException e)
            {
                log.error("CLOB FREE ERROR: {}", e);
            }
        }
    }

    return result;
}

0
public static String readClob(Clob clob) throws SQLException, IOException {
    StringBuilder sb = new StringBuilder((int) clob.length());
    Reader r = clob.getCharacterStream();
    char[] cbuf = new char[2048];
    int n;
    while ((n = r.read(cbuf, 0, cbuf.length)) != -1) {
        sb.append(cbuf, 0, n);
    }
    return sb.toString();
}

上述方法也非常高效。


0
private String convertToString(java.sql.Clob data)
{
    final StringBuilder builder= new StringBuilder();

    try
    {
        final Reader         reader = data.getCharacterStream();
        final BufferedReader br     = new BufferedReader(reader);

        int b;
        while(-1 != (b = br.read()))
        {
            builder.append((char)b);
        }

        br.close();
    }
    catch (SQLException e)
    {
        log.error("Within SQLException, Could not convert CLOB to string",e);
        return e.toString();
    }
    catch (IOException e)
    {
        log.error("Within IOException, Could not convert CLOB to string",e);
        return e.toString();
    }
    //enter code here
    return builder.toString();
}

2
通常最好解释一下解决方案,而不仅仅是发布一些匿名代码行。您可以阅读《如何撰写良好的答案》和《解释完全基于代码的答案》。 - Anh Pham

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接