2010-08-26 206 views
8

我创建以下代码以将java中的字符串截断为具有给定字节数的新字符串。按字节截断字符串

 String truncatedValue = ""; 
     String currentValue = string; 
     int pivotIndex = (int) Math.round(((double) string.length())/2); 
     while(!truncatedValue.equals(currentValue)){ 
      currentValue = string.substring(0,pivotIndex); 
      byte[] bytes = null; 
      bytes = currentValue.getBytes(encoding); 
      if(bytes==null){ 
       return string; 
      } 
      int byteLength = bytes.length; 
      int newIndex = (int) Math.round(((double) pivotIndex)/2); 
      if(byteLength > maxBytesLength){ 
       pivotIndex = newIndex; 
      } else if(byteLength < maxBytesLength){ 
       pivotIndex = pivotIndex + 1; 
      } else { 
       truncatedValue = currentValue; 
      } 
     } 
     return truncatedValue; 

这是我首先想到的,我知道我可以改进它。我看到另一篇文章提出了类似的问题,但他们使用字节而不是String.substring截断了字符串。我想我宁愿在我的情况下使用String.substring。

编辑:我只是删除了UTF8的参考,因为我宁愿能够为不同的存储类型做到这一点。

+0

我会修改你的问题。您正试图将字符串放入不能超过maxUTF8BytesLength的字节数组中。你想使用UTF-8编码。你想复制尽可能多的字符。正确? – gawi 2010-08-26 15:51:01

+0

对,我会说这是正确的。我也想有效地做到这一点。 – stevebot 2010-08-26 16:04:02

+0

我刚刚编辑的问题不参考UTF-8。对不起,这是误导。 – stevebot 2010-08-26 16:09:33

回答

11

为什么不转换为字节并向前走 - 遵循UTF8字符边界 - 直到获得最大数字,然后将这些字节转换回字符串?

或者你可以只切原字符串,如果你跟踪应该出现的地方切割的:

// Assuming that Java will always produce valid UTF8 from a string, so no error checking! 
// (Is this always true, I wonder?) 
public class UTF8Cutter { 
    public static String cut(String s, int n) { 
    byte[] utf8 = s.getBytes(); 
    if (utf8.length < n) n = utf8.length; 
    int n16 = 0; 
    int advance = 1; 
    int i = 0; 
    while (i < n) { 
     advance = 1; 
     if ((utf8[i] & 0x80) == 0) i += 1; 
     else if ((utf8[i] & 0xE0) == 0xC0) i += 2; 
     else if ((utf8[i] & 0xF0) == 0xE0) i += 3; 
     else { i += 4; advance = 2; } 
     if (i <= n) n16 += advance; 
    } 
    return s.substring(0,n16); 
    } 
} 

注:编辑以修复bug的2014年8月25日

+1

我绝对可以做到这一点。有什么理由为什么使用String.substring更糟?看起来,按照你描述的方式来做这件事必须考虑所有的代码点,这并不是很有趣。 (取决于你的乐趣定义:))。 – stevebot 2010-08-26 16:04:53

+0

@stevebot - 为了高效率,您需要利用已知的数据结构。如果您不关心效率并希望它很简单,或者您想要支持每种可能的Java编码而无需知道它是什么,那么您的方法似乎足够合理。 – 2010-08-26 16:22:44

1

你可以将字符串转换为字节并将这些字节转换回字符串。

public static String substring(String text, int maxBytes) { 
    StringBuilder ret = new StringBuilder(); 
    for(int i = 0;i < text.length(); i++) { 
     // works out how many bytes a character takes, 
     // and removes these from the total allowed. 
     if((maxBytes -= text.substring(i, i+1).getBytes().length) < 0) break; 
     ret.append(text.charAt(i)); 
    } 
    return ret.toString(); 
} 
+0

检查一个字符可能不是很好的性能 – NguyenDat 2010-12-17 11:34:34

+2

@nguyendat,有很多原因,这是不是很高性能。主要的是为substring()和getBytes()创建对象。然而,你会惊奇地发现你可以在毫秒内完成多少,而这通常就足够了。 – 2010-12-17 11:46:52

+1

该方法不能正确处理代理对,例如子字符串(“\ uD800 \ uDF30 \ uD800 \ uDF30”,4).getBytes(“UTF-8”)。length将返回8,而不是4.代理对的一半表示为单字节“?”通过String.getBytes(“UTF-8”)。 – 2013-02-17 00:14:43

3

使用UTF-8 CharsetEncoder和编码直到输出字节缓冲区包含的字节数,你愿意承担,通过寻找CoderResult.OVERFLOW。这里

2

如前所述,彼得Lawrey解决方案具有重大的性能劣势(〜3,500msc 10,000次),雷克斯科尔就好多了(〜500msc 10,000次),但结果不是准确的 - 它减少了超过需要的数量(而不是剩余的4000字节,例如重新指定3500)。这里附上我的解决方案(〜250msc 10,000次)假设以字节为单位UTF-8最大长度的字符为4(感谢维基百科):

public static String cutWord (String word, int dbLimit) throws UnsupportedEncodingException{ 
    double MAX_UTF8_CHAR_LENGTH = 4.0; 
    if(word.length()>dbLimit){ 
     word = word.substring(0, dbLimit); 
    } 
    if(word.length() > dbLimit/MAX_UTF8_CHAR_LENGTH){ 
     int residual=word.getBytes("UTF-8").length-dbLimit; 
     if(residual>0){ 
      int tempResidual = residual,start, end = word.length(); 
      while(tempResidual > 0){ 
       start = end-((int) Math.ceil((double)tempResidual/MAX_UTF8_CHAR_LENGTH)); 
       tempResidual = tempResidual - word.substring(start,end).getBytes("UTF-8").length; 
       end=start; 
      } 
      word = word.substring(0, end); 
     } 
    } 
    return word; 
} 
+0

看起来不像这个解决方案可以防止后代一半的代理对吗?其次,如果getBytes()。length会碰巧被单独应用于代理对的两个部分(对我来说不是很明显,它永远也不会),但它也会低估该对的UTF-8表示的大小作为一个整体,假设“替换字节数组”是单个字节。第三,4字节的UTF-8代码点在Java中都需要一个双字符替代对,所以有效的最大值仅为每个Java字符3个字节。 – 2013-02-16 23:33:51

0

s = new String(s.getBytes("UTF-8"), 0, MAX_LENGTH - 2, "UTF-8");

5

我认为雷克斯·科尔的解决方案有2个错误。

  • 首先,如果非ASCII字符恰好在极限之前,它将截断以限制+ 1。截断“123456789á1”将产生“123456789á”,用UTF-8中的11个字符表示。
  • 其次,我认为他误解了UTF标准。 https://en.wikipedia.org/wiki/UTF-8#Description显示在UTF序列开始处的110xxxxx告诉我们该表示是2个字符长(而不是3)。这就是他的实施通常不会耗尽所有可用空间的原因(如Nissim Avitan指出的)。

请在下面找到我的修正版本:

public String cut(String s, int charLimit) throws UnsupportedEncodingException { 
    byte[] utf8 = s.getBytes("UTF-8"); 
    if (utf8.length <= charLimit) { 
     return s; 
    } 
    int n16 = 0; 
    boolean extraLong = false; 
    int i = 0; 
    while (i < charLimit) { 
     // Unicode characters above U+FFFF need 2 words in utf16 
     extraLong = ((utf8[i] & 0xF0) == 0xF0); 
     if ((utf8[i] & 0x80) == 0) { 
      i += 1; 
     } else { 
      int b = utf8[i]; 
      while ((b & 0x80) > 0) { 
       ++i; 
       b = b << 1; 
      } 
     } 
     if (i <= charLimit) { 
      n16 += (extraLong) ? 2 : 1; 
     } 
    } 
    return s.substring(0, n16); 
} 

我仍然认为这是远远有效。所以,如果你并不真正需要的结果的串表示和字节数组会做,你可以使用这个:

private byte[] cutToBytes(String s, int charLimit) throws UnsupportedEncodingException { 
    byte[] utf8 = s.getBytes("UTF-8"); 
    if (utf8.length <= charLimit) { 
     return utf8; 
    } 
    if ((utf8[charLimit] & 0x80) == 0) { 
     // the limit doesn't cut an UTF-8 sequence 
     return Arrays.copyOf(utf8, charLimit); 
    } 
    int i = 0; 
    while ((utf8[charLimit-i-1] & 0x80) > 0 && (utf8[charLimit-i-1] & 0x40) == 0) { 
     ++i; 
    } 
    if ((utf8[charLimit-i-1] & 0x80) > 0) { 
     // we have to skip the starter UTF-8 byte 
     return Arrays.copyOf(utf8, charLimit-i-1); 
    } else { 
     // we passed all UTF-8 bytes 
     return Arrays.copyOf(utf8, charLimit-i); 
    } 
} 

有趣的是,与现实20-500字节限制他们的表现几乎是相同IF您再次从字节数组中创建一个字符串。

请注意,这两种方法假定使用Java的getBytes()函数后,有效的UTF-8输入是有效的假设。

+0

您还应该在s.getBytes(“UTF-8”)处捕获UnsupportedEncodingException – asalamon74 2015-05-19 10:04:28

+0

我没有看到getBytes抛出任何东西。 尽管http://docs.oracle.com/javase/7/docs/api/java/lang/String.html#getBytes%28java.lang.String%29说:“当这个字符串不能被编码时这种方法的行为在给定的字符集中没有指定。“ – 2015-08-29 00:25:21

+1

您链接的页面显示它抛出UnsupportedEncodingException:“public byte [] getBytes(String charsetName) throws UnsupportedEncodingException” – asalamon74 2015-08-29 18:45:32

0

这是我的:

private static final int FIELD_MAX = 2000; 
private static final Charset CHARSET = Charset.forName("UTF-8"); 

public String trancStatus(String status) { 

    if (status != null && (status.getBytes(CHARSET).length > FIELD_MAX)) { 
     int maxLength = FIELD_MAX; 

     int left = 0, right = status.length(); 
     int index = 0, bytes = 0, sizeNextChar = 0; 

     while (bytes != maxLength && (bytes > maxLength || (bytes + sizeNextChar < maxLength))) { 

      index = left + (right - left)/2; 

      bytes = status.substring(0, index).getBytes(CHARSET).length; 
      sizeNextChar = String.valueOf(status.charAt(index + 1)).getBytes(CHARSET).length; 

      if (bytes < maxLength) { 
       left = index - 1; 
      } else { 
       right = index + 1; 
      } 
     } 

     return status.substring(0, index); 

    } else { 
     return status; 
    } 
} 
0

通过使用下面的正则表达式,你也可以去掉开头和结尾的双字节字符的空格。

stringtoConvert = stringtoConvert.replaceAll("^[\\s ]*", "").replaceAll("[\\s ]*$", ""); 
0

这一个不能更有效的解决方案,但工程

public static String substring(String s, int byteLimit) { 
    if (s.getBytes().length <= byteLimit) { 
     return s; 
    } 

    int n = Math.min(byteLimit-1, s.length()-1); 
    do { 
     s = s.substring(0, n--); 
    } while (s.getBytes().length > byteLimit); 

    return s; 
} 
5

更理智的解决方案是使用解码器:

final Charset CHARSET = Charset.forName("UTF-8"); // or any other charset 
final byte[] bytes = inputString.getBytes(CHARSET); 
final CharsetDecoder decoder = CHARSET.newDecoder(); 
decoder.onMalformedInput(CodingErrorAction.IGNORE); 
decoder.reset(); 
final CharBuffer decoded = decoder.decode(ByteBuffer.wrap(bytes, 0, limit)); 
final String outputString = decoded.toString(); 
0

我在彼得Lawrey的解决方案,以改善准确处理代理对。此外,我优化的基于这样的事实,每char字节的UTF-8编码的最大数量为3

public static String substring(String text, int maxBytes) { 
    for (int i = 0, len = text.length(); (len - i) * 3 > maxBytes;) { 
     int j = text.offsetByCodePoints(i, 1); 
     if ((maxBytes -= text.substring(i, j).getBytes(StandardCharsets.UTF_8).length) < 0) 
      return text.substring(0, i); 
     i = j; 
    } 
    return text; 
}