【问题标题】:How to do substring for UTF8 string in java?如何在java中为UTF8字符串做子字符串?
【发布时间】:2023-03-23 18:04:02
【问题描述】:

假设我有以下字符串:Rückruf ins Ausland 我需要将它插入到最大大小为 10 的数据库中。 我在 java 中做了一个普通的子字符串,它提取了这个字符串 Rückruf in,它是 10 个字符。当它尝试插入此列时,我收到以下 oracle 错误:

java.sql.SQLException: ORA-12899: value too large for column "WAEL"."TESTTBL"."DESC"(实际:11,最大:10) 原因是数据库有一个 AL32UTF8 字符集,因此 ü 需要 2 个字符。

我需要在 java 中编写一个函数来执行此子字符串,但考虑到 ü 占用 2 个字节,因此在这种情况下返回的子字符串应该是 Rückruf i(9 个字符)。有什么建议吗?

【问题讨论】:

  • 也许可以选择使用字符长度语义来定义列长度。

标签: java oracle substring


【解决方案1】:

我认为在这种情况下最好的选择是在数据库级别使用 Oracle SUBSTR 函数直接在 SQL QUERY 上进行子字符串化。

例如:

INSERT INTO ttable (colname) VALUES (SUBSTR( ?, 1, 10 ))

其中感叹号代表通过 JDBC 发送的 SQL 参数。

【讨论】:

  • 如果你有一个 2 字节字符在截断之前开始,这个截断不会在 2 字节字符中间然后存储数据不正确吗?或者在那种情况下会发生什么?
【解决方案2】:

您可以在将字符串转换为字节数组的java中计算String的正确长度。

以下代码为例:

System.out.println("Rückruf i".length()); // prints 9 
System.out.println("Rückruf i".getBytes().length); // prints 10 

如果当前字符集不是 UTF-8,请将代码替换为:

System.out.println("Rückruf i".length()); // prints 9 
System.out.println("Rückruf i".getBytes("UTF-8").length); // prints 10 

如果需要,您可以将 UTF-8 替换为您想要测试该字符集中字符串长度的字符集。

【讨论】:

  • 是的,但是如果底层数据库字符集更改为其他字符怎么办?
  • 字节数可能会根据使用的编码而改变。所以这不是普遍的
  • 这是java中使用的字节数。如果一个字符存在于 UTF8 中,则表示为 1 个字节,如果它是 UTF16 的字符,则不存在于 UTF8 中,则表示为 2 个字节。
  • 我明白你所说的它在 Java 中是一致的,但我的意思是,它不一定总是与数据库字节匹配。它也取决于数据库中的编码。但在这种特定情况下,它匹配。
  • @Singh 也许您的默认字符集不是 UTF-8?如果是,则必须替换为 System.out.println("Rückruf i".getBytes("UTF-8").length);我添加了非 UTF-8 字符集的解决方案
【解决方案3】:

您需要使数据库中的编码与 java 字符串的编码相匹配。或者,您可以使用this 之类的方式转换字符串,并获取与数据库中的编码匹配的长度。这将为您提供准确的字节数。否则,您仍然只是希望编码匹配。

    String string = "Rückruf ins Ausland";

    int curByteCount = 0;
    String nextChar;
    for(int index = 0; curByteCount +  
         (nextChar = string.substr(index,index + 1)).getBytes("UTF-8").length < trimmedBytes.length;  index++){
        curByteCount += nextChar.getBytes("UTF-8").length;

    }
    byte[] subStringBytes = new byte[10];
    System.arraycopy(string.getBytes("UTF-8"), 0, subStringBytes, 0, curByteCount);
    String trimed = new String(subStringBytes, "UTF-8");

应该这样做。它还不应在此过程中截断多字节字符。这里的假设是数据库是 UTF-8 编码的。另一个假设是字符串实际上需要修剪。

【讨论】:

    【解决方案4】:

    如果你想在 Java 中修剪数据,你必须编写一个函数,使用所使用的 db 字符集修剪字符串,类似于这个测试用例:

    package test;
    
    import java.io.UnsupportedEncodingException;
    
    public class TrimField {
    
        public static void main(String[] args) {
            //UTF-8 is the db charset
            System.out.println(trim("Rückruf ins Ausland",10,"UTF-8"));
            System.out.println(trim("Rüückruf ins Ausland",10,"UTF-8"));
        }
    
        public static String trim(String value, int numBytes, String charset) {
            do {
                byte[] valueInBytes = null;
                try {
                    valueInBytes = value.getBytes(charset);
                } catch (UnsupportedEncodingException e) {
                    throw new RuntimeException(e.getMessage(), e);
                }
                if (valueInBytes.length > numBytes) {
                    value = value.substring(0, value.length() - 1);
                } else {
                    return value;
                }
            } while (value.length() > 0);
            return "";
    
        }
    
    }
    

    【讨论】:

      【解决方案5】:

      嘿,所有的 ASCII 字符都小于 128。你可以使用下面的代码。

      public class Test {
          public static void main(String[] args) {
              String s= "Rückruf ins Ausland";
              int length =10;
              for(int i=0;i<s.length();i++){
                  if(!(((int)s.charAt(i))<128)){
                      length--;                   
                  }
              }
              System.out.println(s.substring(0,length));
          }
      }
      

      您可以复制粘贴并检查它是否满足您的需要或在任何地方中断。

      【讨论】:

      • 不应该length=9;length--; 并且没有休息吗?如果字符串中有两个"üü" 怎么办?
      • 是的,对..我的错..让我编辑它,我也打破了循环。
      • 此解决方案仅适用于最多包含一个“两个字节”字符的 10 个字符的字符串。任何其他字符串总是返回 9 或 10 或抛出 indexOutOfBoundsException
      • 是的,我根据上述评论更改了解决方案。你现在可以检查吗?现在它可以为任何解决方案动态工作。
      【解决方案6】:

      如果必须是 Java,您可以将字符串解析为字节并修剪数组的长度。

              String s = "Rückruf ins Ausland";
              byte[] bytes = s.getBytes("UTF-8");
              byte[] bytes2 = new byte[10];
              System.arraycopy(bytes, 0, bytes2, 0, 10);
              String trim = new String(bytes2, "UTF-8");
      

      【讨论】:

      • 这行得通,它的好处是它没有任何循环。这是直截了当的
      • 我很确定如果它们位于修剪的边界,它将截断多字节字符。我的解决方案以此为基础,但我循环并检查新角色是否不会越界。
      • 是的,你是对的,我尝试了一个像 123456789ü 这样的例子,修剪后的字符串是 123456789?有一个?最后
      • @CarlosBribiescas 你说得对,没想到!
      【解决方案7】:

      以下可怕的情况是通过完整的 Unicode 代码点遍历整个字符串,字符对(代理代码点)也是如此。

      public String trim(String s, int length) {
          byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
          if (bytes.length <= length) {
              return s;
          }
          int totalByteCount = 0;
          for (int i = 0; i < s.length(); ) {
              int cp = s.codePointAt(i);
              int n = Character.charCount(cp);
              int byteCount = s.substring(i, i + n)
                      .getBytes(StandardCharsets.UTF_8).length;
              if (totalByteCount + byteCount) > length) {
                  break;
              }
              totalByteCount += byteCount;
              i += n;
          }
          return new String(bytes, 0, totalByteCount);
      }
      

      还是可以稍微优化一下的。

      【讨论】:

        猜你喜欢
        • 2016-09-04
        • 2011-01-21
        • 1970-01-01
        • 2014-01-29
        • 1970-01-01
        • 1970-01-01
        • 2014-09-26
        • 2012-11-06
        • 1970-01-01
        相关资源
        最近更新 更多