2010-08-26 46 views
8

Tôi tạo đoạn mã sau để cắt ngắn chuỗi trong java thành chuỗi mới với số byte nhất định.Cắt ngắn chuỗi bằng Bytes

 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; 

Đây là điều đầu tiên tôi nghĩ đến và tôi biết mình có thể cải thiện nó. Tôi thấy một bài đăng khác đã hỏi một câu hỏi tương tự ở đó, nhưng họ đã cắt xén chuỗi bằng cách sử dụng các byte thay vì String.substring. Tôi nghĩ rằng tôi thà sử dụng String.substring trong trường hợp của tôi.

EDIT: Tôi vừa xóa tham chiếu UTF8 vì tôi muốn có thể thực hiện việc này cho các loại lưu trữ khác nhau.

+0

Tôi sẽ nói lại vấn đề của bạn. Bạn đang cố gắng để phù hợp với một chuỗi thành một mảng byte mà không thể lớn hơn maxUTF8BytesLength. Bạn muốn sử dụng UTF-8 cho mã hóa. Bạn muốn sao chép nhiều ký tự nhất có thể. Chính xác? – gawi

+0

đúng, tôi sẽ nói điều đó là chính xác. Tôi cũng muốn làm điều đó một cách hiệu quả. – stevebot

+0

Tôi vừa chỉnh sửa câu hỏi để không tham khảo UTF-8. Xin lỗi về điều đó, nó đã gây hiểu nhầm. – stevebot

Trả lời

11

Tại sao không chuyển đổi thành byte và đi tiếp - tuân thủ ranh giới ký tự UTF8 như bạn làm - cho đến khi bạn có số tối đa, sau đó chuyển đổi các byte đó thành chuỗi?

Hoặc bạn chỉ có thể cắt chuỗi gốc nếu bạn theo dõi các nơi cắt nên xảy ra:

// 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); 
    } 
} 

Lưu ý: chỉnh sửa để sửa lỗi trên 2014-08-25

+1

Tôi chắc chắn có thể làm điều đó. Có bất kỳ lý do tại sao sử dụng String.substring là bất kỳ tồi tệ hơn? Nó có vẻ như làm nó theo cách bạn mô tả sẽ phải tính đến tất cả các điểm mã, mà không phải là một toàn bộ rất nhiều niềm vui. (tùy thuộc vào định nghĩa của bạn về niềm vui :)). – stevebot

+0

@stevebot - Để có hiệu quả, bạn cần tận dụng lợi thế của cấu trúc dữ liệu đã biết. Nếu bạn không quan tâm về hiệu quả và muốn nó trở nên dễ dàng, hoặc bạn muốn hỗ trợ mọi mã hóa Java có thể mà không cần phải biết nó là gì, phương pháp của bạn có vẻ hợp lý đủ. –

1

bạn có thể chuyển đổi chuỗi thành byte và chỉ chuyển đổi các byte đó thành chuỗi.

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

Kiểm tra từng nhân vật có thể không tốt cho hiệu suất – NguyenDat

+2

@nguyendat, có rất nhiều lý do khiến điều này không thực sự hiệu quả. Một trong những chính sẽ là tạo đối tượng cho các chuỗi con() và getBytes() Tuy nhiên, bạn sẽ ngạc nhiên bao nhiêu bạn có thể làm trong một phần nghìn giây và đó là thường là đủ. –

+1

Phương pháp đó không xử lý các cặp thay thế đúng cách, ví dụ: chuỗi con ("\ uD800 \ uDF30 \ uD800 \ uDF30", 4) .getBytes ("UTF-8"). chiều dài sẽ trả về 8, chứ không phải 4. Một nửa cặp thay thế được biểu diễn dưới dạng một byte "?" bởi String.getBytes ("UTF-8"). –

3

Sử dụng UTF-8 CharsetEncoder, và mã hóa cho đến khi sản lượng ByteBuffer chứa nhiều byte như bạn đang sẵn sàng chấp nhận, bằng cách tìm kiếm CoderResult.OVERFLOW.

2

Như đã đề cập, giải pháp Peter Lawrey có bất lợi lớn hiệu suất (~ 3,500msc cho 10.000 lần), Rex Kerr đã tốt hơn nhiều (~ 500msc cho 10.000 lần) nhưng kết quả không là chính xác - nó cắt giảm nhiều hơn mức cần thiết (thay vì còn lại 4000 byte nó còn lại 3500 cho một số ví dụ). kèm theo đây giải pháp của tôi (~ 250msc cho 10.000 lần) giả định rằng UTF-8 max char chiều dài tính bằng byte là 4 (nhờ WikiPedia):

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

Không giống như giải pháp này ngăn cản một cặp thay thế nửa thay thế? Thứ hai, trong trường hợp getBytes() chiều dài sẽ xảy ra được áp dụng cho cả hai nửa của một cặp thay thế riêng lẻ (không ngay lập tức rõ ràng với tôi nó sẽ không bao giờ), nó cũng sẽ đánh giá thấp kích thước của biểu diễn UTF-8 của cặp nói chung, giả sử "mảng byte thay thế" là một byte đơn. Thứ ba, các điểm mã UTF-8 4 byte yêu cầu một cặp thay thế hai char trong Java, vì vậy hiệu quả tối đa chỉ là 3 byte cho mỗi ký tự Java. –

0

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

5

Tôi nghĩ rằng giải pháp Rex Kerr có 2 lỗi.

  • Đầu tiên, nó sẽ cắt bớt giới hạn + 1 nếu ký tự không phải ASCII ngay trước giới hạn. Cắt ngắn "123456789á1" sẽ dẫn đến "123456789á" được biểu thị bằng 11 ký tự trong UTF-8.
  • Thứ hai, tôi nghĩ rằng ông đã giải thích sai tiêu chuẩn UTF. https://en.wikipedia.org/wiki/UTF-8#Description cho thấy rằng một 110xxxxx ở đầu của một chuỗi UTF cho chúng ta biết rằng các đại diện là 2 ký tự dài (như trái ngược với 3). Đó là lý do tại sao việc thực hiện của anh ta thường không sử dụng hết tất cả không gian có sẵn (như Nissim Avitan lưu ý).

hãy tìm phiên bản chỉnh sửa của tôi dưới đây:

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); 
} 

tôi vẫn nghĩ đây là xa hiệu quả.Vì vậy, nếu bạn không thực sự cần biểu diễn chuỗi của kết quả và mảng byte sẽ làm, bạn có thể sử dụng điều này:

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); 
    } 
} 

Điều thú vị là với giới hạn 20-500 byte thực tế chúng thực hiện khá nhiều tương tự IF bạn tạo một chuỗi từ mảng byte một lần nữa.

Xin lưu ý rằng cả hai phương thức đều cho rằng đầu vào utf-8 hợp lệ là một giả định hợp lệ sau khi sử dụng hàm getBytes() của Java.

+0

Bạn cũng nên bắt UnsupportedEncodingException tại s.getBytes ("UTF-8") – asalamon74

+0

Tôi không thấy getBytes ném bất cứ thứ gì. Mặc dù http://docs.oracle.com/javase/7/docs/api/java/lang/String.html#getBytes%28java.lang.String%29 nói "Hành vi của phương thức này khi chuỗi này không thể được mã hóa trong bộ ký tự đã cho không được chỉ định. " –

+1

Trang bạn đã liên kết cho biết rằng trang này ném UnsupportedEncodingException: "byte công cộng [] getBytes (String charsetName) ném UnsupportedEncodingException" – asalamon74

0

Đây là của tôi:

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

Bằng cách sử dụng dưới đây Regular Expression bạn cũng có thể loại bỏ đầu và đuôi không gian màu trắng của nhân vật byte kép.

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

Cái này không thể là giải pháp hiệu quả hơn nhưng làm việc

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

Các giải pháp lành mạnh hơn là sử dụng bộ giải mã:

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

tôi đã cải tiến giải pháp Peter Lawrey để xử lý chính xác các cặp thay thế. Ngoài ra, tôi đã tối ưu hóa dựa trên thực tế là số byte tối đa trên mỗi mã char trong mã UTF-8 là 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; 
} 
Các vấn đề liên quan