2010-08-26 204 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; 
}